Compare commits

..

359 Commits
1.7.0 ... 1.8.0

Author SHA1 Message Date
Cohee
d6fd5455e6 Merge pull request #625 from SillyTavern/dev
Dev
2023-07-03 19:23:26 +03:00
Cohee
f3c9f57cb9 Merge branch 'main' into dev 2023-07-03 19:22:44 +03:00
Cohee
3fcf232537 Bump package version 2023-07-03 19:19:22 +03:00
Cohee
6be18e4212 Fix Poe client 2023-07-03 19:18:42 +03:00
Cohee
9e3c55805f Better chat completion continue 2023-07-03 18:22:12 +03:00
Cohee
9ab7265053 Touch chat window to cancel auto scroll on mobile 2023-07-03 18:17:10 +03:00
Cohee
25e3005de9 Unlocked context warning should be unclickable 2023-07-03 13:51:05 +03:00
Cohee
2b8f5d14e9 Don't stack suggested replies on Poe with continue. 2023-07-03 13:49:26 +03:00
Cohee
13deac2527 Don't insert a newline on empty WI 2023-07-03 11:34:15 +03:00
RossAscends
d566be077d Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-07-03 13:20:14 +09:00
RossAscends
7fe2ea31b9 better /api explainer text 2023-07-03 13:20:12 +09:00
Cohee
d153488690 More continue copium 2023-07-03 03:45:13 +03:00
Cohee
a61a8f9495 More safeties for swipes 2023-07-03 01:53:19 +03:00
Cohee
a904260614 Fix old message left swipe 2023-07-03 01:42:40 +03:00
Cohee
5e24beef58 Merge pull request #613 from BlipRanger/feature/swipe_info
Feature/swipe info
2023-07-03 01:36:16 +03:00
Cohee
47e4d5925b Merge pull request #618 from 50h100a/kobold-streaming-req
Cancel KoboldAI generation if streaming option conflicts.
2023-07-03 01:35:29 +03:00
Cohee
52b994a45b Merge pull request #619 from bdashore3/dev
Add UUID support
2023-07-03 01:32:32 +03:00
kingbri
ca2542d81a Utils: Add UUID v4 support
Unique IDs should use UUID as there's a higher prevention of overflow
when assigning them.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-07-02 18:31:02 -04:00
Cohee
1fe2e4032a Fix JSON data shift on newly created characters 2023-07-03 01:14:13 +03:00
Cohee
84cfe1c706 Evenize margins in document style 2023-07-03 00:53:47 +03:00
50h100a
4afd616099 kAI streamin mismatch Warning -> Error 2023-07-02 17:19:53 -04:00
50h100a
44bf31e602 Block generation if streaming option conflicts. 2023-07-02 17:04:21 -04:00
Cohee
6d33e44519 Fix "continue" as a slash command 2023-07-02 23:59:33 +03:00
Cohee
16e8c7a3c8 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-07-02 23:47:35 +03:00
Cohee
0c3f3f952d Toggle for space trimming 2023-07-02 23:47:33 +03:00
RossAscends
bef7d1492b fix single doc mode height for smol last mes 2023-07-03 05:31:52 +09:00
Cohee
6e15f7474f Fix obliteration of quick reply slots on input 2023-07-02 23:09:32 +03:00
Cohee
7a33042ea9 Add slash commands for chat styles and continue 2023-07-02 22:40:38 +03:00
Cohee
2e80de230e Fix Quick reply skill issue 2023-07-02 22:18:11 +03:00
Cohee
225bd5aa0a Update Poe impersonate prompt 2023-07-02 21:39:41 +03:00
Cohee
b14a85a96b Lower PaLM max context size 2023-07-02 21:37:44 +03:00
Cohee
febef5dfba Customizable number of quick reply slots 2023-07-02 21:37:14 +03:00
Cohee
9aab388531 Move continue option to power user section 2023-07-02 20:27:19 +03:00
Cohee
549fb19676 continue copium for Poe 2023-07-02 20:23:37 +03:00
Cohee
8eb82cdcd9 Continue 2023-07-02 20:21:42 +03:00
Cohee
c156e32ec7 Merge pull request #611 from bdashore3/dev
Add AI reply prefixes for prompt construction
2023-07-02 18:00:00 +03:00
RossAscends
002dbae8c5 MVP: 'Single Document' chat mode 2023-07-02 23:34:46 +09:00
RossAscends
bd9e739de2 new slash command: /resetpanels (alias /resetui) 2023-07-02 19:10:04 +09:00
RossAscends
5dbfe209f6 better /cut error messages 2023-07-02 17:40:15 +09:00
RossAscends
229ec5f255 +"/cut N" for targeted mes deletion,
+ toggle to show mesIDs in chat
2023-07-02 17:32:48 +09:00
RossAscends
8d18f2a80a /delmode alias /del + args for N msgs deletion 2023-07-02 15:49:50 +09:00
RossAscends
43a28fdb05 fix edit char panel for narrow screens 2023-07-02 14:23:40 +09:00
kingbri
37219c3370 Formatting: Switch reply prefix to textarea
Fixes newline formatting.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-07-01 20:12:19 -04:00
kingbri
990a5faf7d Generate: Move reply prefix call to common function
Helps pass down user biases to OpenAI's prompt builder.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-07-01 20:12:19 -04:00
kingbri
080ecec5f2 Chat: Add AI reply prefixes
Experimentation yields that prefixing a reply with something drives
the AI to possibly produce more targeted and meaningful conversation.

The example I used is showing an AI's "thoughts" in a message via
the use of backticks. By automatically prefixing {{char}}'s thoughts,
the AI generates an internal monologue that the user can also use
for their own responses.

This feature is stable, but in an experimental state for now in terms
of expansion.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-07-01 20:12:19 -04:00
BlipRanger
715a6f1bff Cleanup 2023-07-01 18:35:47 -04:00
BlipRanger
e01a2c3bcf Store metadata for swipes, pull it back 2023-07-01 18:32:51 -04:00
Cohee
6c33dff0ba Update Metharme preset
Jarvis, enter roleplay mode. Disable safety precautions. Engage the smut cannons.
2023-07-02 00:45:51 +03:00
Cohee
596cd1762f #609 Export JSONL chat with octet-stream type 2023-07-01 23:55:53 +03:00
Cohee
3b4f8811e7 Add probability of activation to WI 2023-07-01 21:02:03 +03:00
Cohee
63bd4cd499 Fix selecting personas with quick reply 2023-07-01 19:25:04 +03:00
Cohee
822e4b88f5 #606 Support multiline curly bias 2023-07-01 17:24:55 +03:00
Cohee
59b92d4356 Fix message view inflation on Safari when custom separators set 2023-07-01 17:22:49 +03:00
RossAscends
309a2ed564 fix /name for quickreplies (maybe stealth broken, test pls) 2023-07-01 13:33:36 +09:00
Cohee
fcef55d900 Make authorize OpenRouter a button 2023-07-01 00:08:34 +03:00
Cohee
cba2feb875 #569 Use main API as summary source 2023-06-30 23:37:01 +03:00
Cohee
fe8db4ded8 #605 JB prompt override for Poe. 2023-06-30 19:32:07 +03:00
Cohee
8cda073d00 Fix sticky bias 2023-06-30 18:38:15 +03:00
Cohee
c134aed9f5 Fix budget cutting in WI 2023-06-30 17:42:33 +03:00
Cohee
32441aa33e [chore] Copy-paste example id fix 2023-06-30 12:11:38 +03:00
Cohee
6be7d5704e Add OpenRouter OAuth flow 2023-06-30 12:01:49 +03:00
Cohee
1d2dc19359 #600 Add gpt-4-32k to OpenRouter selection 2023-06-30 11:44:23 +03:00
Cohee
dd028a9564 Merge pull request #602 from bdashore3/dev
Add persistence to tag exclusions
2023-06-30 11:36:10 +03:00
RossAscends
e6761f7293 Zoomed Avatars back to reg. mobile 🦀 2023-06-30 12:59:40 +09:00
kingbri
8547f362c5 Tags: Persist exclusions
If a tag is excluded, keep it excluded even on a page refresh.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-29 23:15:07 -04:00
Cohee
8251d15455 Update readme.md 2023-06-30 00:55:12 +03:00
Cohee
27c780c18b Update readme.md 2023-06-30 00:52:48 +03:00
Cohee
3353fe572c [chore] HTML formatting 2023-06-30 00:45:17 +03:00
Cohee
e2bbc7fbcf Infer model settings from Window extension settings 2023-06-30 00:36:39 +03:00
Cohee
f532192726 Add direct OpenRouter connection and PaLM models to Window selection 2023-06-30 00:32:52 +03:00
RossAscends
757e9b672a dont make RecoveredRuins abide by /r9k/ rules 2023-06-30 02:30:28 +09:00
Cohee
0bfa9f0e29 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-29 20:27:14 +03:00
Cohee
0da4bce378 Use select2 for character lore selector 2023-06-29 20:27:08 +03:00
RossAscends
16915ae6a5 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-30 02:19:48 +09:00
RossAscends
ad9599c2fc WI selector style tweak + new KAI preset option 2023-06-30 02:19:46 +09:00
Cohee
8687932896 Customizable samplers order for KoboldAI 2023-06-29 20:15:48 +03:00
Cohee
10a5836893 Merge branch 'main' of https://github.com/SillyLossy/TavernAI 2023-06-29 18:34:03 +03:00
Cohee
f75930a75d Fix Novel tablet tier context size 2023-06-29 18:33:56 +03:00
Cohee
d813ec4ef3 Fix Novel tablet tier context size 2023-06-29 18:32:57 +03:00
Cohee
89f905f0e2 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-29 18:22:19 +03:00
Cohee
5af6874b5f Restyle Global WI selector 2023-06-29 18:22:14 +03:00
Cohee
60586e7720 Merge pull request #597 from BlipRanger/feature/tags
Case-insensitive tag importing.
2023-06-29 17:11:30 +03:00
BlipRanger
4d3677dc5d Case-insensitive tag importing. 2023-06-29 09:54:21 -04:00
RossAscends
c6f1ec696a debug log to server window on poe API misread 2023-06-29 21:21:54 +09:00
RossAscends
9f5bd9d728 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-29 20:38:03 +09:00
RossAscends
466bcd8833 attempt fix defaultbot detection 2023-06-29 20:38:01 +09:00
RossAscends
05fd6d4a6c attempt fix defaultbot detection 2023-06-29 20:37:41 +09:00
Cohee
5d1edf7456 #588 Fix System TTS promise never resolving 2023-06-29 13:01:38 +03:00
Cohee
b9a067b79f #588 Fix System TTS promise never resolving 2023-06-29 13:00:15 +03:00
Cohee
6981151c8f Display existing tags in import tags popup 2023-06-29 12:46:45 +03:00
Cohee
650e7b0588 Merge pull request #590 from As4shi/dev
Filter generic creator's note
2023-06-29 12:36:36 +03:00
Cohee
e9466916da Fix tags import after merge 2023-06-29 12:27:23 +03:00
Cohee
bc7d7ee3ff Merge pull request #589 from BlipRanger/feature/tags
Tag, Author, Author Notes Import Support
2023-06-29 12:24:18 +03:00
Cohee
2bbc40a796 Bump package version 2023-06-29 12:20:00 +03:00
RossAscends
0c55d36a2b sync dev poe fix with main 2023-06-29 17:06:17 +09:00
RossAscends
fcc6448d7a improve Poe futureproofing 2023-06-29 16:42:59 +09:00
RossAscends
215e34bb52 WIP futureproofing strategy against PoeAPI changes 2023-06-29 15:40:04 +09:00
RossAscends
29e0a8335b smart detection of data vs payload on poe api 2023-06-29 14:10:30 +09:00
RossAscends
5ab4179920 no poe console spam 2023-06-29 14:10:16 +09:00
RossAscends
ebb93451b2 smart detection of poe data vs payload API 2023-06-29 14:08:29 +09:00
RossAscends
084d17dc19 revert poe changes. data>>payload 2023-06-29 12:13:28 +09:00
RossAscends
2722813efb poe api fakeout, reverting data to payload. 2023-06-29 12:11:41 +09:00
Cohee
e81b867676 Merge branch 'main' into dev 2023-06-29 01:25:52 +03:00
Cohee
2dabcc28a5 Merge pull request #594 from MDMCK10/dev
Fix Poe issues
2023-06-29 01:10:13 +03:00
Cohee
3b99f7839b Bump package version 2023-06-29 01:10:04 +03:00
Cohee
7c6c2ee8b6 Fix Poe 2023-06-29 01:09:34 +03:00
MDMCK10
52a2cee73c Fix Poe issues 2023-06-29 00:03:10 +02:00
Cohee1207
991d437749 Fix skill issue in avatar uploading 2023-06-28 23:23:37 +03:00
Cohee1207
8168a9205a Merge branch 'dev' of http://github.com/cohee1207/SillyTavern into dev 2023-06-28 23:21:17 +03:00
Cohee
332648973f Merge branch 'dev' into feature/tags 2023-06-28 22:57:53 +03:00
Cohee
fc8553a140 Remove embedded lorebook when unselecting it in UI 2023-06-28 20:35:25 +03:00
BlipRanger
048d65c1e1 Anti-troll filter 2023-06-28 12:18:58 -04:00
Cohee
322ab9b47a Fix Chub import by URL 2023-06-28 19:17:16 +03:00
BlipRanger
0f0647c6e4 Prompt when tags are being imported 2023-06-28 12:12:56 -04:00
Cohee
674a15b842 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-28 18:26:26 +03:00
Cohee
d0ab763d2e [Feature Request] Filter via Tags on Group Chat's "Add Members" Search Box #388 2023-06-28 18:26:23 +03:00
RossAscends
2f8b624578 cleanup Adv Def's creator/overrides blocks 2023-06-28 23:45:41 +09:00
RossAscends
e4e8cdfca5 Adv Char Defs panel gets correct BG color 2023-06-28 22:55:35 +09:00
RossAscends
df35fb0775 less movingUI logspam 2023-06-28 22:47:33 +09:00
Cohee
fca732c933 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-28 16:39:40 +03:00
Cohee
c18008725d Fix group VN mode 2023-06-28 16:39:37 +03:00
RossAscends
38931e7a2f Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-28 22:31:49 +09:00
RossAscends
667cbf6f0f optimize Reset Panels, saveSettings emit onSuccess 2023-06-28 22:31:47 +09:00
Cohee
931fffaa5c Fix chat.comp models not saving on Safari 2023-06-28 16:16:49 +03:00
Cohee1207
f06ca28bbf Reduce console spam on expressions 2023-06-28 15:55:53 +03:00
BlipRanger
ad11ec8d00 Merge branch 'dev' of https://github.com/BlipRanger/SillyTavern into feature/tags 2023-06-28 08:55:40 -04:00
Cohee1207
a1eb2b794e Rearrange Horde blocks 2023-06-28 15:48:12 +03:00
RossAscends
4ad328029e Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-28 20:40:31 +09:00
RossAscends
d66542e88e fix: MovingUI breaking, typos, WI selector scroll 2023-06-28 20:40:29 +09:00
Cohee
893d1fa9e4 Merge branch 'dev' into dev 2023-06-28 12:02:34 +03:00
Cohee
4a9d9b69e9 Fix random chat slash breaking in groups 2023-06-28 11:19:50 +03:00
Cohee
ebf51f3a17 Fix select2 selection remove style 2023-06-28 11:15:38 +03:00
Cohee
7bec130bf7 Fix focused select border 2023-06-28 11:06:57 +03:00
Cohee
914282faf0 Remove spammy logs 2023-06-28 11:02:54 +03:00
Cohee
1d8ecacd8b Update Horde documentation link 2023-06-28 11:01:27 +03:00
Cohee
a0814defff Fix select2 noblur background 2023-06-28 10:51:14 +03:00
As4shi
d410118cc4 Filter generic creator's note
Removes "Creator's notes go here." message when importing cards.
2023-06-28 04:47:01 -03:00
RossAscends
5084ae9f9a Merge pull request #587 from BlipRanger/feature/import
Import creator and creator notes/comments (if they exist)
2023-06-28 14:20:04 +09:00
RossAscends
93b1774135 Merge pull request #586 from bdashore3/dev
[IMPORTANT] Fix world info splice method
2023-06-28 14:08:32 +09:00
BlipRanger
0a5c226af6 Remove unencessary functions 2023-06-28 01:06:38 -04:00
RossAscends
2cde62f618 Merge pull request #585 from BlipRanger/feature/random
Add random chat slash command
2023-06-28 14:01:51 +09:00
BlipRanger
136ba40956 Fix the check for V1 vs V2, cleanup 2023-06-28 00:57:20 -04:00
RossAscends
7fe758d697 ( ) skill issue ( ) MovingUI works now. 2023-06-28 13:53:36 +09:00
BlipRanger
762684ffea Filter default tags (probably), update name 2023-06-28 00:43:45 -04:00
BlipRanger
2b5a028af4 Import tags from card correctly 2023-06-28 00:32:07 -04:00
BlipRanger
487b36a326 Merge branch 'feature/import' of https://github.com/BlipRanger/SillyTavern into feature/tags 2023-06-27 23:59:30 -04:00
BlipRanger
ea2c7973a9 Unmerge plus first try at importing card tags 2023-06-27 23:59:22 -04:00
BlipRanger
2d34b54874 Merge branch 'feature/autotagger' of https://github.com/BlipRanger/SillyTavern into feature/tags 2023-06-27 23:55:58 -04:00
BlipRanger
696f9083f3 Import tags and just save them to the card 2023-06-27 21:51:28 -04:00
BlipRanger
db08d4eab3 Import creator and creator notes/comments (if they exist) 2023-06-27 21:29:05 -04:00
kingbri
2f2f88dedd World Info: Fix array splice method
Character lore layers used array.splice() in the wrong way. Somehow
this passed my testing through sheer luck. Rewrite for using indicies
to splice and for more efficiency with iteration.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-27 21:04:04 -04:00
BlipRanger
a512e3bec0 Merge branch 'dev' of https://github.com/BlipRanger/SillyTavern into feature/random 2023-06-27 20:18:55 -04:00
BlipRanger
06f2c920f0 Start autotagger extension 2023-06-27 20:17:43 -04:00
BlipRanger
9903e85a66 Add random slash command 2023-06-27 20:03:49 -04:00
Cohee
9949d5695c Restyle Horde model selector 2023-06-28 02:09:33 +03:00
Cohee
515a0af1b4 Write settings backup on starting the server 2023-06-27 21:40:36 +03:00
Cohee
f5ba78be81 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-27 20:48:14 +03:00
Cohee
10e87dd5ca [WIP/Untested] DeepL translation API 2023-06-27 20:48:09 +03:00
RossAscends
5db69d1ce0 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-28 01:30:15 +09:00
RossAscends
a9009725ce hide unusables on mobile 2023-06-28 01:29:35 +09:00
Cohee
d59174da77 Add optional chaining 2023-06-27 19:22:42 +03:00
Cohee
679143967d Merge pull request #580 from lsaa/first-line-desc-cutoff
Limit the character list sysnopsis to the first line of Creator's Notes
2023-06-27 19:20:03 +03:00
Cohee
59e833b6cc Merge pull request #581 from bdashore3/dev
Tags: Fix mobile exclusions
2023-06-27 19:19:18 +03:00
kingbri
2e9bccf9e9 Tags: Fix mobile exclusions
Fixes mobile positioning issues and adds conditional directives.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-27 12:15:13 -04:00
RossAscends
54f472a750 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-28 00:58:36 +09:00
RossAscends
a0dbee6749 unlimited zoomed avatars + movingUI rewrite 2023-06-28 00:58:32 +09:00
Cohee
69fdb9090f Add Chub downloader for characters and lorebooks 2023-06-27 18:39:08 +03:00
lsaa
43e5849015 Only display the first line of Creator's Notes as description 2023-06-27 09:15:52 -03:00
Cohee
92775e459c Merge pull request #574 from 50h100a/show-avatar-names
Display avatar name in card list
2023-06-27 12:39:53 +03:00
Cohee
748bee74cb Make avatar URL display not default 2023-06-27 12:38:46 +03:00
Cohee
3cdec1cea3 Merge pull request #576 from bdashore3/dev
World Info: Fix lorebook loading error
2023-06-27 12:34:29 +03:00
RossAscends
76b7e24614 also reset AN panel loc/size 2023-06-27 13:22:39 +09:00
RossAscends
9737fda9ae persistent state for movingUI 2023-06-27 13:08:02 +09:00
50h100a
06f580ed29 Add power-user option to toggle card filenames.
Rephrased 'resize' option so It's Not Talking Like This.
2023-06-26 21:45:15 -04:00
50h100a
553a95fd39 Merge branch 'SillyTavern:main' into show-avatar-names 2023-06-26 21:33:24 -04:00
kingbri
3e30fb5d14 World Info: Fix lorebook loading error
charLore isn't always present, so make optional calls to it.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-26 20:19:21 -04:00
Cohee
847961861f Better VN mode autoscrolling 2023-06-27 00:57:16 +03:00
Cohee
59dba15a4f Unlock scrolling on streaming if scrolled with mouse wheel 2023-06-27 00:45:48 +03:00
50h100a
2aa0c5d707 Display avatar name in card list 2023-06-26 17:33:10 -04:00
Cohee
976a8fd65c Fix WI terms usage 2023-06-26 22:27:58 +03:00
Cohee
5798c98f41 Adjust texts for character lorebooks 2023-06-26 22:21:01 +03:00
Cohee
187a0925b0 Merge pull request #570 from bdashore3/dev
World Info: Add support for character layers and more fixes
2023-06-26 22:10:58 +03:00
kingbri
45047fc6b2 World Info: Fix entries being improperly appended
Entries were being added to the prompt in an inconsistent way. WI
before and after character were using newline formatting while AN
was using array joins. This caused some issues with insertion order
in the prompt.

Fix this by making each injection use an array and unshift elements
into them. Then, join them into a string that preserves insertion
order.

The reason why unshift is used is because higher insertion orders
translate to a higher priority during scanning. This prevents the
need to reverse arrays which is a more costly function depending
on how many entries are in them.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-26 14:32:24 -04:00
kingbri
9d5af39682 World Info: Add rename and delete hooks to char layers
If a world info is renamed or deleted, update that in the character
lore section of settings as well.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-26 14:32:24 -04:00
kingbri
b6fbe41f93 World Info: Add support for character layering
Some characters have different cards depending on what the user wants
from the character. However, maintaining multiple lorebooks for different
personas of the same character can be difficult. In addition, there
is redundancy, overlap, and possiblities to miss information when
creating separate lorebooks with the same base info.

Therefore, add a "DLC"/layering system of sorts for characters.
This works the same way as multi-global world info where character
lorebooks added as needed. The only catch is that a base character
book must be tied to a card before selecting any extra info.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-26 14:32:24 -04:00
Cohee
47a5c9e9f6 Remove insta-faving on import 2023-06-26 17:20:59 +03:00
Cohee
c9fa19e8dd Un-egg group avatars round 2023-06-26 17:00:35 +03:00
Cohee
89a1378397 Fix sprite folder for Risu sprites. Don't overwrite existing files. 2023-06-26 17:00:13 +03:00
Cohee
1739af3ef6 Cleanup Risu import 2023-06-26 15:26:29 +03:00
Cohee
c1b9a30087 Import embedded Risu sprites 2023-06-26 15:19:35 +03:00
Cohee
ca8b06f4cb Fix bold italics 2023-06-26 13:42:58 +03:00
Cohee
68f967ea78 Add Claude tokenizer 2023-06-26 13:36:56 +03:00
Cohee
7354003db1 Fix typo 2023-06-26 11:51:11 +03:00
RossAscends
b2e541c6d9 char edit panel grid fix 2023-06-26 07:08:46 +09:00
Cohee
7321b37799 Un-egg round personas 2023-06-25 23:55:16 +03:00
Cohee
9532ad4e5a Enable selection in WI editor 2023-06-25 23:24:22 +03:00
Cohee
7118b430d5 Only display a warning if card tokens is more than 50% of real context size 2023-06-25 23:17:05 +03:00
Cohee
961139304d Unify group/solo avatar display 2023-06-25 23:05:11 +03:00
Cohee
efe4a974be Less gaps == more space for descriptions 2023-06-25 22:56:11 +03:00
Cohee
92127615e5 Tighter display of character top bar. Return some of the buttons 2023-06-25 22:44:46 +03:00
RossAscends
deb2efc16e realign character panel 2023-06-26 04:17:06 +09:00
Cohee
9bbaa85a3b Clarified persona deletion message 2023-06-25 20:13:16 +03:00
Cohee
04cfedea7c Chroma: Remove dupe label 2023-06-25 19:56:15 +03:00
Cohee
16fd92b1a3 Merge pull request #566 from BlipRanger/dev
First try at chromadb multichat
2023-06-25 19:36:19 +03:00
Cohee
7984e3b818 Add contextual Character WI button 2023-06-25 18:40:13 +03:00
Cohee
da8beeb503 Force reload user avatar on upload 2023-06-25 17:21:18 +03:00
Cohee
612db28bcb Better name for backgrounds plugin 2023-06-25 17:06:20 +03:00
Cohee
506aeb2e40 Substitute params in persona descriptions. 2023-06-25 17:00:27 +03:00
Cohee
26ac519c55 User persona management block. Persona descriptions. Dummy personas. Change persona avatar 2023-06-25 16:46:23 +03:00
Cohee
bbec184d17 Reverse persona lock icon 2023-06-25 13:20:11 +03:00
Cohee
951e22ac8e Fix scrolling and width of global WI 2023-06-25 12:43:37 +03:00
RossAscends
861e0d017e fix CSS for new WI multiselector 2023-06-25 14:53:41 +09:00
RossAscends
f6526bbb4c Merge pull request #567 from bdashore3/dev
Add multiple global WI merging (and optimizations)
2023-06-25 14:33:54 +09:00
BlipRanger
d004a3141e Default bracket, make sure we have memories. 2023-06-25 01:03:47 -04:00
BlipRanger
bd74939a55 Add templating for custom injection 2023-06-25 01:01:47 -04:00
kingbri
c0286150ed World Info: Add optimizations and fixes
Use a switch instead of if/else chain, fix un-needed addition
when determining token counts, and remove newline spacing for AN
WI formatting.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-25 01:00:47 -04:00
kingbri
fc9c90c4ee World Info: Add global multi-selection/merging
Global world info always involved some kind of merging leading to
extremely large lorebook files that took a long time to import.

This commit adds the ability to select more than one world info file
and they will be merged together along with character world info.
In short, multiple worlds can be meshed together to further contribute
to context.

You can also use this for world "DLCs" of sorts. Let's say someone
else has more information to add regarding a world, but doesn't
want to use a large world file. The JSONs can now be merged.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-25 00:49:42 -04:00
BlipRanger
2836704c4e Add sort options, remove duplicate entires from custom. 2023-06-25 00:17:58 -04:00
BlipRanger
71201377ef Merge branch 'SillyTavern:dev' into dev 2023-06-24 20:43:42 -04:00
BlipRanger
9de7db8f2d Add custom chroma strat 2023-06-24 20:41:54 -04:00
Cohee
31057e1e81 Add custom avatars for groups 2023-06-25 02:57:07 +03:00
Cohee
66de4a1e09 Add positions for char A/N 2023-06-25 01:40:06 +03:00
Cohee
4cc3d335a8 Save A/N to metadata immediately 2023-06-25 01:08:38 +03:00
Cohee
09eea3c8cd Add caching layer to WI save/load 2023-06-24 23:57:44 +03:00
Cohee
747f7829fd Import Risu lorebooks 2023-06-24 23:34:36 +03:00
Cohee
ab90d6ec3d Save before exporting 2023-06-24 23:19:21 +03:00
BlipRanger
3ce14883b9 First try at chromadb multichat 2023-06-24 15:36:26 -04:00
Cohee
f360706227 Post-activation sorting of WI entries 2023-06-24 20:54:56 +03:00
galasal
ef9b7187dc improve performance when filling context 2023-06-24 15:17:19 +10:00
Cohee
869e02dd42 #561 [WIP] Fix chat file deletion 2023-06-23 23:19:23 +03:00
Cohee
e78abf9269 #557 Only add user name to chat completion name if it was sent as another persona in the same chat. 2023-06-23 23:05:41 +03:00
Cohee
23287597ee Fix onboarding text. 2023-06-23 22:54:35 +03:00
Cohee
9f0530f422 Export characters with world info embeds. 2023-06-23 22:50:16 +03:00
Cohee
275f187719 Don't animate changing avatar size on right panel 2023-06-23 21:49:59 +03:00
Cohee
97e1585152 Import Agnai memory book JSON 2023-06-23 21:43:14 +03:00
Cohee
8a2506d8a3 Merge pull request #559 from sifsera/dev 2023-06-23 19:45:59 +03:00
Cohee
a79bae5975 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-23 19:21:06 +03:00
Cohee
01b4e1dae3 Import Novel PNG lorebooks 2023-06-23 19:20:58 +03:00
sifsera
4fd714a1ee Add Prompt Arena presets from TextGenWebUi 2023-06-23 12:08:09 -04:00
RossAscends
84e44d7c0a Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-24 00:59:11 +09:00
RossAscends
fed47d7477 /api 2023-06-24 00:59:09 +09:00
Cohee
2302785242 #502 Import Novel Lorebooks (JSON-based) 2023-06-23 18:43:11 +03:00
Cohee
6cbfb56fff Merge pull request #558 from sifsera/dev
Add-epsilon-and-eta-settings-to-textgenwebui-parameters
2023-06-23 18:03:13 +03:00
sifsera
59c699c999 Add-epsilon-and-eta-settings-to-textgenwebui-parameters 2023-06-23 10:59:38 -04:00
RossAscends
1d4746b743 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-23 20:21:04 +09:00
RossAscends
82a09d2feb batched slash commands 2023-06-23 20:21:01 +09:00
Cohee
c873a6b04c Fix npm audit. 2023-06-23 13:53:37 +03:00
Cohee
e9f7ea16ce Fix semver audit. 2023-06-23 13:53:08 +03:00
Cohee
786ae619cb Merge pull request #554 from bdashore3/dev
Expand markdown exclusions
2023-06-23 13:46:02 +03:00
RossAscends
ad779129d3 /world to set/unset active world 2023-06-23 16:51:15 +09:00
kingbri
e7af6892fb Markdown: Add ability to exclude specific strings
A comma-separated list of markdown strings provided by the user can
be excluded as needed. This is combined with the set chat separator
to provide a seamless experience when chatting.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-22 21:10:19 -04:00
kingbri
fa9df8f22e Markdown: Fix dinkus formatter with codefences
Change how the formatter is applied to use an invisible null unicode
character instead of div tags and add a newline to preserve the DOM
tree so codeblocks and child elements aren't altered.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-22 17:58:57 -04:00
Cohee
1672824416 Spec v2: {{original}} macro for prompt overrides. 2023-06-22 23:24:22 +03:00
Cohee
c096a55697 Merge branch 'main' into dev 2023-06-22 20:51:10 +03:00
Cohee
d4332aa7ec No audit to npm install 2023-06-22 20:50:01 +03:00
Cohee
2f497cf25b Drag-sortable WI entries. 2023-06-22 20:11:29 +03:00
Cohee
7d472f00f7 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-22 18:11:55 +03:00
Cohee
1e5f789f59 Minor clarification to placeholder text. 2023-06-22 18:11:48 +03:00
RossAscends
1f14c3669d Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-22 22:21:24 +09:00
RossAscends
40e2af4c73 /dupe 2023-06-22 22:21:21 +09:00
Cohee
bd34cab6e8 #547 Display 3 lines of description in big avatars mode 2023-06-22 13:10:39 +03:00
Cohee
85876e9377 Revert old quote wrapping for code blocks 2023-06-22 12:47:05 +03:00
Cohee
e5f37ee073 Merge pull request #549 from 50h100a/codeblock-newline-fix
Codeblock newline fix for Firefox
2023-06-22 12:44:57 +03:00
Cohee
267db5166f Merge pull request #550 from v86861062/main
start.sh: Fixed the commands
2023-06-22 12:41:28 +03:00
v86861062
0e45450912 start.sh: Fixed the commands
Signed-off-by: v86861062 <v86861062@gmail.com>
2023-06-22 12:19:35 +08:00
50h100a
4dac2126bf Do not replace codeblock newlines with <br> tags. 2023-06-21 23:59:52 -04:00
Cohee
4230f3881d #547 Display Creator's Note in characters list 2023-06-21 21:12:46 +03:00
Cohee
408f83804d Move set avatar button to on-click on avatar 2023-06-21 20:22:54 +03:00
Cohee
4d299916be Convert and import v2 character book embeds 2023-06-21 18:50:34 +03:00
Cohee
4b8711c8f8 Minor spelling mistake 2023-06-21 14:55:49 +03:00
Cohee
8d6e6de200 [Feature Request] Make "Instruct i/o sequence macro replacing" optional SillyTavern/SillyTavern#546 2023-06-21 14:54:36 +03:00
Cohee
df4586811d Don't query OpenAI status if it's not a currently selected API on load 2023-06-21 12:50:43 +03:00
Cohee
012f0237db Don't change active WI selection on import 2023-06-21 12:23:14 +03:00
Cohee
23a6064a55 Merge pull request #545 from breathingmanually/comment-command 2023-06-21 10:02:45 +03:00
Cohee
a37922ad59 Merge pull request #544 from BlipRanger/patch-3 2023-06-21 09:57:44 +03:00
breathingmanually
2a235b7889 Add /comment -- adds a note/comment message not part of the chat 2023-06-21 02:22:20 -03:00
BlipRanger
e4a6bdb389 Update readme.md
Add BlipRanger
2023-06-20 20:33:46 -04:00
Cohee
4a29072e1c Import / export chat completion presets 2023-06-20 23:53:52 +03:00
Cohee
36d0244be4 Remove instruct mode extra newlines 2023-06-20 23:29:07 +03:00
Cohee
fda152cef0 Placeholders for import/export chat completion preset 2023-06-20 22:41:15 +03:00
Cohee
3723ae840f Use proportional WI budget. Add None option for WI editor. 2023-06-20 22:11:01 +03:00
Cohee
a513434b5f Fix cards hiding on returning to characters list when using search input 2023-06-20 19:41:06 +03:00
Cohee
ec05937dd4 #540 Save streaming flag to Chat Completion preset 2023-06-20 18:58:09 +03:00
Cohee
0ec9198ef5 Merge pull request #535 from bdashore3/dev
Fix WI2AN formatting + Add contributor
2023-06-20 18:43:00 +03:00
kingbri
64bba40c41 README: Add kingbri as a contributor
Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-20 11:37:40 -04:00
kingbri
350e2108e2 WI2AN: Fix formatting
Signed-off-by: kingbri <bdashore3@proton.me>
2023-06-20 11:30:29 -04:00
Cohee
5fa5edffba Merge pull request #539 from deedlitelf/main
Fix the i18n translation
2023-06-20 18:24:44 +03:00
Cohee
df184bd46a Split WI editor and global WI select 2023-06-20 17:52:40 +03:00
deedlitelf
f0d0f38c4f Update index.html 2023-06-20 21:11:51 +08:00
Cohee
2663a8370f (Internal refactor) Decouple world info editor from global WI selector 2023-06-20 13:47:34 +03:00
RossAscends
3b66310dd2 fix WI2AN insertions 2023-06-20 17:59:01 +09:00
Cohee
9c28126ccd #517 Export chats as JSONL 2023-06-19 22:29:09 +03:00
Cohee
fb97d95dae Use single format for chat timestamps 2023-06-19 22:05:20 +03:00
Cohee
2d6ed116e6 Clarify auto-adjust UI labels 2023-06-19 21:54:58 +03:00
RossAscends
114d756a68 better display for excluded tags 2023-06-20 01:45:11 +09:00
RossAscends
8c710a08a3 fix timestamps on SD gens 2023-06-20 01:08:58 +09:00
Cohee
c5c921b0d6 Restyle excluded tags 2023-06-19 17:42:25 +03:00
Cohee
3c68a4e2a0 Support new timestamp format in view past chats 2023-06-19 17:23:08 +03:00
Cohee
72488b5900 Merge pull request #523 from 50h100a/card-resize-fix
Card resize fix
2023-06-19 15:14:48 +03:00
Cohee
b970bde972 Merge pull request #525 from BlipRanger/dev
Prototype for Negative Filtering
2023-06-19 14:32:59 +03:00
Cohee
10c836fcbc Fix removing world link from character 2023-06-19 10:54:43 +03:00
RossAscends
d979dd263a fix height overflow on adv char defs 2023-06-19 15:55:37 +09:00
RossAscends
4c51b1ffe1 toggle for Timestamps on messages 2023-06-19 14:39:33 +09:00
RossAscends
0490ca25b0 char panel buttons into dropdown; sheld width fix 2023-06-19 13:23:26 +09:00
BlipRanger
5ce41342c0 Style change for excluded tags 2023-06-18 20:17:36 -04:00
BlipRanger
052089b3c0 First shot at filtering tags negatively
Needs some work, but proof of concept
2023-06-18 20:16:41 -04:00
Cohee
21bb5d7808 Fix WI button class toggle 2023-06-19 02:55:32 +03:00
Cohee
f51af31850 Fix duplicating entries if char and global WI are the same 2023-06-19 02:19:42 +03:00
Cohee
7e975e9df0 (WIP) Assign World Info to a character. [Out of spec] 2023-06-19 01:59:09 +03:00
hh_aa
56656b95cf Added "Never Resize Avatars" option.
Changed crop dialog to have "Accept"/"Cancel" buttons.
2023-06-18 17:09:10 -04:00
hh_aa
2d97b4bd0a Merge branch 'main' of https://github.com/50h100a/SillyTavern into main 2023-06-18 15:39:47 -04:00
Cohee
81d9cead5c Hide scenario override in char creation and group member peeking 2023-06-18 22:09:57 +03:00
Cohee
511f762e54 Auto-adjust values should not exceed max slider limits 2023-06-18 21:25:47 +03:00
Cohee
04a645141c Set scenario override for characters 2023-06-18 20:32:51 +03:00
Cohee
9ef8f0b069 More gradual Poe streaming. 2023-06-18 19:02:02 +03:00
Cohee
e12242f44f Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-18 18:33:59 +03:00
Cohee
976248b665 Fix char A/N in groups 2023-06-18 18:33:57 +03:00
RossAscends
84d9113ed3 new base themes 2023-06-18 23:58:08 +09:00
RossAscends
a43f99b492 new base themes 2023-06-18 23:57:30 +09:00
RossAscends
28ba84ea6f Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-18 23:56:59 +09:00
RossAscends
93876b8189 themes upgrade, user/bot messages get unique color 2023-06-18 23:56:56 +09:00
Cohee
040c4a8894 Merge pull request #518 from 50h100a/main
Mismatch between file and character names prevents chat logs from being deleted.
2023-06-18 16:34:42 +03:00
Cohee
22a5def618 Add option to auto-adjust number of chroma messages to keep / query based on context size. 2023-06-18 16:29:23 +03:00
RossAscends
58a6ccd4a5 WI-to-AN for real this time. 2023-06-18 18:17:43 +09:00
hh_aa
cefc10b405 Merge branch 'main' of https://github.com/50h100a/SillyTavern into main 2023-06-17 23:40:19 -04:00
hh_aa
6d649c716d Use filename instead of id to delete chat logs. 2023-06-17 23:39:04 -04:00
hh_aa
ba545e44e3 Use filename instead of id to delete chat logs. 2023-06-17 23:30:48 -04:00
RossAscends
d59024b4a5 forgot zoomed avatar z-index fix for normal mode 2023-06-18 10:09:28 +09:00
RossAscends
6a5b44b3b3 zoomed avatars display on top of sprites 2023-06-18 10:06:00 +09:00
RossAscends
75090c4fa4 WorldInfo can now be routed into Author's Note 2023-06-18 08:27:24 +09:00
RossAscends
b0db3686b1 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-18 06:35:24 +09:00
RossAscends
468aafb384 less console spam, placehold for dinkus MD escape 2023-06-18 06:35:22 +09:00
Cohee
b85605cac8 Don't auto-select chat.comp preset if it's already selected 2023-06-17 20:28:02 +03:00
Cohee
2edebec52c Auto-balance quotes on streaming 2023-06-17 20:23:03 +03:00
Cohee
64fcb4b1f0 Adjust css index 2023-06-17 19:48:20 +03:00
Cohee
feecb1fa27 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-17 18:30:07 +03:00
Cohee
2ae467d14f Grammarly extension no longer hides in shame behind the textarea 2023-06-17 18:30:05 +03:00
RossAscends
0db9cec7c4 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-17 10:19:28 +09:00
RossAscends
0c0baecb5f clarify TTS checkboxes 2023-06-17 10:19:27 +09:00
Cohee
b34478f800 Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev 2023-06-16 19:29:06 +03:00
Cohee
3b51252e9e Pretty print save themes and presets 2023-06-16 19:29:02 +03:00
Cohee
3a8f4e4f76 Merge pull request #508 from zerfl/patch-1
fix: prevent swiping messages when editing tasks
2023-06-16 19:27:37 +03:00
Daniel Martin
a8341f7b57 fix: prevent swiping messages when editing tasks 2023-06-16 17:53:21 +02:00
RossAscends
db7578be8e toggle for hiding WI Entry memo input box 2023-06-16 23:00:02 +09:00
Cohee
536052af3d Rate-limit Poe message requests 2023-06-16 15:13:03 +03:00
Cohee
f4cc3932da Merge branch 'main' into dev 2023-06-16 14:12:33 +03:00
Cohee
c890da2877 Fix server crash when KoboldCpp server goes offline and stop is clicked 2023-06-16 14:11:52 +03:00
RossAscends
b5d1ed048d resolve skill issue for WI entry mass open/close 2023-06-16 19:32:20 +09:00
RossAscends
ed0272efa6 Merge branch 'dev' of https://github.com/Cohee1207/SillyTavern into dev 2023-06-16 19:24:26 +09:00
RossAscends
7686ac0b28 fix WI open/close buttons + entries start closed 2023-06-16 19:24:24 +09:00
Cohee
bec6227aaf Merge branch 'main' of https://github.com/SillyLossy/TavernAI 2023-06-16 12:55:08 +03:00
Cohee
6a2a0efc84 Don't run streaming with Kobold on quiet gens #504 2023-06-16 12:55:02 +03:00
Cohee
b09ea054df Bump package version 2023-06-16 00:24:41 +03:00
Cohee
024784e0b0 Fix oobabooga homunculus mode 2023-06-16 00:22:27 +03:00
Cohee
329158349f Remove debug statement 2023-06-16 00:08:52 +03:00
Cohee
62d5f20590 Don't stack suggested replies when using impersonate on Poe 2023-06-15 20:05:53 +03:00
Cohee
e420c96e77 Fix mobile audio playback stopping on swiping 2023-06-15 19:31:18 +03:00
Cohee
7af5a6ee5d Fix broken preset file 2023-06-15 19:26:25 +03:00
Cohee
e91cbe009f Correctly clamp max_context value on saving a chat completion preset 2023-06-15 18:32:56 +03:00
82 changed files with 6360 additions and 1674 deletions

View File

@@ -3,4 +3,5 @@ node_modules
npm-debug.log
readme*
Start.bat
/dist
/dist
/backups/

56
.github/readme.md vendored
View File

@@ -4,13 +4,13 @@ Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies,
Based on a fork of TavernAI 1.2.8
### Brought to you by Cohee, RossAscends and the SillyTavern community
### Brought to you by Cohee, RossAscends, and the SillyTavern community
NOTE: We have created a [Documentation website](https://docs.sillytavern.app/) to answer most of your questions and help you get started.
### What is SillyTavern or TavernAI?
Tavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.
SillyTavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.
SillyTavern is a fork of TavernAI 1.2.8 which is under more active development and has added many major features. At this point, they can be thought of as completely independent programs.
@@ -53,7 +53,7 @@ Get support, share favorite characters and prompts:
Get in touch with the developers directly:
* Discord: Cohee#1207 or RossAscends#1779
* Discord: cohee or rossascends
* Reddit: /u/RossAscends or /u/sillylossy
* [Post a GitHub issue](https://github.com/SillyTavern/SillyTavern/issues)
@@ -64,36 +64,36 @@ Get in touch with the developers directly:
* Group chats: multi-bot rooms for characters to talk to you or each other
* Chat bookmarks / branching (duplicates the dialogue in its current state)
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
* World Info support: create a rich lore or save tokens on your character card
* World Info support: create rich lore or save tokens on your character card
* Window AI browser extension support (run models like Claude, GPT 4): https://windowai.io/
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
* [AI Horde](https://horde.koboldai.net/) connection
* [Poe.com](https://poe.com) (ChatGPT / Claude) connection
* Soft prompts selector for KoboldAI
* Prompt generation formatting tweaking
* webp character card interoperability (PNG is still an internal format)
## Extensions
SillyTavern has an extensibility support, with some additional AI modules hosted via [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras)
SillyTavern has extensibility support, with some additional AI modules hosted via [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras)
* Author's Note / Character Bias
* Character emotional expressions
* Character emotional expressions (sprites)
* Auto-Summary of the chat history
* Sending images to chat, and the AI interpreting the content.
* Sending images to chat, and the AI interpreting the content
* Stable Diffusion image generation (5 chat-related presets plus 'free mode')
* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
* ChromaDB vector storage for smarter chat prompt formatting
Full list of included extenisons and tutorials how to use them can be found on [Wiki](https://github.com/SillyTavern/SillyTavern/wiki).
A full list of included extensions and tutorials on how to use them can be found in the [Docs](https://docs.sillytavern.app/extras/extensions/).
## UI/CSS/Quality of Life tweaks by RossAscends
* Mobile UI with optimized for iOS, and supports saving a shortcut to home screen and opening in fullscreen mode.
* Mobile UI optimized for iOS, and supports saving a shortcut to the home screen and opening in fullscreen mode.
* HotKeys
* Up = Edit last message in chat
* Ctrl+Up = Edit last USER message in chat
* Left = swipe left
* Right = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)
* Right = swipe right (NOTE: swipe hotkeys are disabled when the chat bar has something typed into it)
* Ctrl+Left = view locally stored variables (in the browser console window)
* Enter (with chat bar selected) = send your message to AI
* Ctrl+Enter = Regenerate the last AI response
@@ -118,7 +118,7 @@ Full list of included extenisons and tutorials how to use them can be found on [
* Switch between round or rectangle avatar styles
* Have a wider chat window on the desktop
* Optional semi-transparent glass-like panels
* Customizable page colors for 'main text', 'quoted text' 'italics text'.
* Customizable page colors for 'main text', 'quoted text', and 'italics text'.
* Customizable UI background color and blur amount
## Installation
@@ -135,8 +135,8 @@ Full list of included extenisons and tutorials how to use them can be found on [
Installing via Git (recommended for easy updating)
Easy to follow guide with pretty pictures:
<https://docs.alpindale.dev/pygmalion-extras/sillytavern/#windows-installation>
An easy-to-follow guide with pretty pictures:
<https://docs.sillytavern.app/installation/windows/>
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
@@ -148,15 +148,15 @@ Easy to follow guide with pretty pictures:
* for Main Branch: `git clone https://github.com/SillyTavern/SillyTavern -b main`
* for Dev Branch: `git clone https://github.com/SillyTavern/SillyTavern -b dev`
7. Once everything is cloned, double click `Start.bat` to make NodeJS install its requirements.
8. The server will then start, and SillyTavern will popup in your browser.
7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.
8. The server will then start, and SillyTavern will pop up in your browser.
Installing via zip download
Installing via ZIP download (discouraged)
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
2. Download the zip from this GitHub repo. (Get the `Source code (zip)` from [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest))
3. Unzip it into a folder of your choice
4. Run `Start.bat` via double-clicking or in a command line.
4. Run `Start.bat` by double-clicking or in a command line.
5. Once the server has prepared everything for you, it will open a tab in your browser.
### Linux
@@ -168,7 +168,7 @@ Installing via zip download
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
By default they will not be exposed to a frontend after you enter them and reload the page.
By default, they will not be exposed to a frontend after you enter them and reload the page.
In order to enable viewing your keys by clicking a button in the API block:
@@ -186,9 +186,9 @@ However, it can be used to allow remote connections from anywhere as well.
### 1. Managing whitelisted IPs
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
* Open the file in a text editor, and add a list of IPs you want to be allowed to connect.
*Both indidivual IPs, and wildcard IP ranges are accepted. Examples:*
*Both individual IPs and wildcard IP ranges are accepted. Examples:*
```txt
192.168.0.1
@@ -220,7 +220,7 @@ If the ST-hosting device is on the same wifi network, you will use the ST-host's
* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
If you (or someone else) wants to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
If you (or someone else) want to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
* While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device.
@@ -228,7 +228,7 @@ If you (or someone else) wants to connect to your hosted ST while not being on t
Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.
A typical address for an ST host on the same wifi network would look like:
A typical address for an ST host on the same wifi network would look like this:
`http://192.168.0.5:8000`
@@ -238,7 +238,7 @@ Use http:// NOT https://
We do not recommend doing this, but you can open `config.conf` and change `whitelist` to `false`.
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder if it exists.
This is usually an insecure practice, so we require you to set a username and password when you do this.
@@ -248,8 +248,8 @@ After restarting your ST server, any device will be able to connect to it, regar
### Still Unable To Connect?
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for port-forwarding on your router, otherwise, someone could find your chat logs and that's a big no-no.
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise, you would be unable to connect even with the aforementioned firewall rules.
## Performance issues?
@@ -271,7 +271,7 @@ Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.
## Where can I find the old backgrounds?
We're moving to 100% original content only policy, so old background images have been removed from this repository.
We're moving to a 100% original content only policy, so old background images have been removed from this repository.
You can find them archived here:
@@ -293,6 +293,8 @@ GNU Affero General Public License for more details.**
* Cohee's modifications and derived code: AGPL v3
* RossAscends' additions: AGPL v3
* Portions of CncAnon's TavernAITurbo mod: Unknown license
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
* BlipRanger's miscellaneous UI & extension modifications (<https://github.com/BlipRanger>)
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
* Thanks oobabooga for compiling presets for TextGen

1
.gitignore vendored
View File

@@ -20,3 +20,4 @@ whitelist.txt
secrets.json
/dist
poe_device.json
/backups/

View File

@@ -5,3 +5,4 @@ node_modules/
secrets.json
/dist
poe_device.json
/backups/

View File

@@ -1,5 +1,5 @@
pushd %~dp0
call npm install
call npm install --no-audit
node server.js
pause
popd
popd

16
package-lock.json generated
View File

@@ -1,15 +1,16 @@
{
"name": "sillytavern",
"version": "1.7.0",
"version": "1.8.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.7.0",
"version": "1.8.0",
"license": "AGPL-3.0",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",
"@mlc-ai/web-tokenizers": "^0.1.0",
"axios": "^1.4.0",
"command-exists": "^1.2.9",
"compression": "^1",
@@ -561,6 +562,11 @@
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@mlc-ai/web-tokenizers": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mlc-ai/web-tokenizers/-/web-tokenizers-0.1.0.tgz",
"integrity": "sha512-whiQ+40ohtAFoFOGcje1Io7BMr434Wh3hM3nBCWlJMpXxL5Rlig/AH9wjyUPsytKwWTEe7RoYPyXSbFw5Vs6Tw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3035,9 +3041,9 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"

View File

@@ -1,6 +1,7 @@
{
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",
"@mlc-ai/web-tokenizers": "^0.1.0",
"axios": "^1.4.0",
"command-exists": "^1.2.9",
"compression": "^1",
@@ -48,7 +49,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.7.0",
"version": "1.8.0",
"scripts": {
"start": "node server.js",
"pkg": "pkg --compress Gzip --no-bytecode --public ."

View File

@@ -0,0 +1,22 @@
{
"max_length": 100,
"temp": 1,
"genamt": 100,
"top_k": 0,
"top_p": 0.95,
"top_a": 0,
"typical": 1,
"tfs": 1,
"rep_pen": 1.1,
"rep_pen_range": 600,
"rep_pen_slope": 0,
"sampler_order": [
6,
0,
1,
2,
3,
4,
5
]
}

View File

@@ -1,5 +1,5 @@
{
"order": [1, 0, 3]
"order": [1, 0, 3],
"temperature": 1.07,
"max_length": 60,
"min_length": 60,
@@ -14,4 +14,4 @@
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"max_context":2048
}
}

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 4.5,
"no_repeat_ngram_size": 2,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0.6,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.2,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 0.19,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 0.6,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.18,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.15,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -6,6 +6,8 @@
"rep_pen": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.05,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.15,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.05,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.15,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.15,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -5,6 +5,8 @@
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,

View File

@@ -0,0 +1,19 @@
{
"temp": 1.68,
"top_p": 0.17,
"top_k": 77,
"typical_p": 1,
"top_a": 0.42,
"tfs": 0.97,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.02,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 0.87,
"top_p": 0.99,
"top_k": 85,
"typical_p": 0.68,
"top_a": 0,
"tfs": 0.68,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.01,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,20 @@
{
"temp": 1.31,
"top_p": 0.14,
"top_k": 49,
"typical_p": 1,
"top_a": 0.52,
"tfs": 1,
"epsilon_cutoff": 1.49,
"eta_cutoff": 10.42,
"rep_pen": 1.17,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 0.98,
"top_p": 0.37,
"top_k": 100,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.18,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 1.53,
"top_p": 0.64,
"top_k": 33,
"typical_p": 1,
"top_a": 0.04,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.07,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 1.31,
"top_p": 0.29,
"top_k": 72,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.09,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 0.02,
"top_p": 0.95,
"top_k": 50,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 1.01,
"top_p": 0.21,
"top_k": 91,
"typical_p": 1,
"top_a": 0.75,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 10.78,
"rep_pen": 1.21,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1.07,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 0.82,
"top_p": 0.21,
"top_k": 72,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.19,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 0.7,
"top_p": 0.9,
"top_k": 20,
"typical_p": 1,
"top_a": 0.75,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.15,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -0,0 +1,19 @@
{
"temp": 0.7,
"top_p": 1,
"top_k": 0,
"typical_p": 1,
"top_a": 0.2,
"tfs": 0.95,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.15,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
"length_penalty": 1,
"min_length": 0,
"encoder_rep_pen": 1,
"do_sample": true,
"early_stopping": false
}

View File

@@ -1,73 +0,0 @@
body {
margin: 0;
padding: 0;
width: 100%;
background-color: rgb(36, 37, 37);
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
font-size: 16px;
/*1rem*/
color: #999;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/*z-index:0;*/
}
#main {
padding-top: 20px;
/*z-index:1;*/
}
#content {
margin: 0 auto;
max-width: 700px;
border: 1px solid #333;
padding: 20px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.5);
line-height: 1.5rem;
box-shadow: 0 0 5px black;
/*z-index: 2;*/
}
code {
border: 1px solid #999;
background-color: rgba(0, 0, 0, 0.5);
padding: 5px;
border-radius: 5px;
display: block;
white-space: pre-line;
}
a {
color: orange;
text-decoration: none;
border-bottom: 1px dotted orange;
}
h2,
h3 {
color: #ccc;
}
hr {
border: 1px solid #999;
}
table {
width: 100%;
}
table,
th,
td {
border: 1px solid;
border-collapse: collapse;
}
table img {
max-width: 200px;
}

1
public/css/select2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
public/img/quill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"name": "Metharme",
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.",
"system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
"system_sequence": "<|system|>",
"stop_sequence": "</s>",
"input_sequence": "<|user|>",
"output_sequence": "<|model|>",
"separator_sequence": "",
"wrap": false
}
}

View File

@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>SillyTavern Documentation</title>
<link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
</head>
<body>
<div id="main">
<div id="content">
<h2>You weren't supposed to be able to get here, you know.</h1>
<h3>All help materials has been moved here:</h3>
<h3><a href="https://docs.sillytavern.app/">SillyTavern Documentation</a></h3>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ import {
is_send_press,
getTokenCount,
menu_type,
max_context,
saveSettingsDebounced,
} from "../script.js";
@@ -27,7 +27,7 @@ import {
SECRET_KEYS,
secret_state,
} from "./secrets.js";
import { sortByCssOrder, debounce } from "./utils.js";
import { sortByCssOrder, debounce, delay } from "./utils.js";
import { chat_completion_sources, oai_settings } from "./openai.js";
var NavToggle = document.getElementById("nav-toggle");
@@ -177,6 +177,29 @@ export function humanizedDateTime() {
return HumanizedDateTime;
}
//this is a common format version to display a timestamp on each chat message
//returns something like: June 19, 2023 2:20pm
export function getMessageTimeStamp() {
const date = Date.now();
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const d = new Date(date);
const month = months[d.getMonth()];
const day = d.getDate();
const year = d.getFullYear();
let hours = d.getHours();
const minutes = ('0' + d.getMinutes()).slice(-2);
let meridiem = 'am';
if (hours >= 12) {
meridiem = 'pm';
hours -= 12;
}
if (hours === 0) {
hours = 12;
}
const formattedDate = month + ' ' + day + ', ' + year + ' ' + hours + ':' + minutes + meridiem;
return formattedDate;
}
// triggers:
$("#rm_button_create").on("click", function () { //when "+New Character" is clicked
@@ -264,18 +287,18 @@ export function RA_CountCharTokens() {
} else { console.debug("RA_TC -- no valid char found, closing."); }
}
// display the counted tokens
if (count_tokens < 1024 && perm_tokens < 1024) {
//display normal if both counts are under 1024
const tokenLimit = Math.max(((main_api !== 'openai' ? max_context : oai_settings.openai_max_context) / 2), 1024);
if (count_tokens < tokenLimit && perm_tokens < tokenLimit) {
$("#result_info").html(`<small>${count_tokens} Tokens (${perm_tokens} Permanent)</small>`);
} else {
$("#result_info").html(`
<div class="flex-container flexFlowColumn alignitemscenter">
<div class="flex-container alignitemscenter">
<div class="flex-container flexnowrap flexNoGap">
<small class="flex-container flexnowrap flexNoGap">
<div class="neutral_warning">${count_tokens}</div>&nbsp;Tokens (<div class="neutral_warning">${perm_tokens}</div><div>&nbsp;Permanent)</div>
</small>
</div>
<div id="chartokenwarning" class="menu_button whitespacenowrap"><a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank">About Token 'Limits'</a></div>
<div id="chartokenwarning" class="menu_button margin0 whitespacenowrap"><a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank">About Token 'Limits'</a></div>
</div>`);
} //warn if either are over 1024
}
@@ -450,60 +473,139 @@ function OpenNavPanels() {
// Make the DIV element draggable:
dragElement(document.getElementById("sheld"));
dragElement(document.getElementById("left-nav-panel"));
dragElement(document.getElementById("right-nav-panel"));
dragElement(document.getElementById("avatar_zoom_popup"));
dragElement(document.getElementById("WorldInfo"));
// SECOND UPDATE AIMING FOR MUTATIONS ONLY
export function dragElement(elmnt) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader"
// if present, the header is where you move the DIV from, but this overrides everything else:
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
var height, width, top, left, right, bottom;
var oldTop = Number((String($(elmnt).css('top')).replace('px', '')))
var oldLeft = Number((String($(elmnt).css('left')).replace('px', '')))
var oldWidth = Number((String($(elmnt).css('width')).replace('px', '')))
var oldHeight = Number((String($(elmnt).css('width')).replace('px', '')))
var oldRight = Number((String($(elmnt).css('right')).replace('px', '')))
var oldBottom = Number((String($(elmnt).css('bottom')).replace('px', '')))
var elmntName = elmnt.attr('id');
console.debug(`${elmntName} init state:
T: ${$(elmnt).css('top')}
L: ${$(elmnt).css('left')}
W: ${$(elmnt).css('width')}
H: ${$(elmnt).css('height')}
R: ${$(elmnt).css('right')}
B: ${$(elmnt).css('bottom')}
---`);
const elmntNameEscaped = $.escapeSelector(elmntName);
const elmntHeader = $(`#${elmntNameEscaped}header`);
if (elmntHeader.length) {
elmntHeader.off('mousedown').on('mousedown', (e) => {
dragMouseDown(e);
});
} else {
// otherwise, move the DIV from anywhere inside the DIV, b:
elmnt.onmousedown = dragMouseDown;
elmnt.off('mousedown').on('mousedown', dragMouseDown);
}
const observer = new MutationObserver((mutations) => {
const target = mutations[0].target;
if (!$(target).is(':visible')
|| $(target).hasClass('resizing')
|| Number((String(target.height).replace('px', ''))) < 50
|| Number((String(target.width).replace('px', ''))) < 50
|| power_user.movingUI === false
|| isMobile() === true
) {
console.debug('aborting mutator')
return
}
const style = getComputedStyle(target);
//console.log(style.top, style.left)
height = target.offsetHeight;
width = target.offsetWidth;
top = parseInt(style.top);
left = parseInt(style.left);
right = parseInt(style.right);
bottom = parseInt(style.bottom);
if (!power_user.movingUIState[elmntName]) {
console.debug(`adding config property for ${elmntName}`)
power_user.movingUIState[elmntName] = {};
}
power_user.movingUIState[elmntName].top = top;
power_user.movingUIState[elmntName].left = left;
if (!isNaN(oldWidth)
&& !isNaN(oldHeight)
&& (oldHeight !== height || oldWidth !== width)) {
power_user.movingUIState[elmntName].width = width;
power_user.movingUIState[elmntName].height = height;
} else {
console.debug('skipping W/H setting')
}
power_user.movingUIState[elmntName].right = right;
power_user.movingUIState[elmntName].bottom = bottom;
if (!isNaN(oldTop) && !isNaN(oldLeft) && (oldTop !== top || oldLeft !== left)) {
console.debug('unsetting margin due to custom position')
console.debug(`${elmntName}:
T: ${oldTop}>>${top}
L: ${oldLeft}>> ${left}
H: ${oldHeight} >> ${height}
W: ${oldWidth}>> ${width}
R: ${oldRight} >> ${right}
B: ${oldBottom}>> ${bottom}
---`)
power_user.movingUIState[elmntName].margin = 'unset';
} else {
console.debug('skipped unsetting margins')
//console.debug(oldTop, top, oldLeft, left)
}
saveSettingsDebounced();
// Check if the element header exists and set the listener on the grabber
if (elmntHeader.length) {
elmntHeader.off('mousedown').on('mousedown', (e) => {
console.debug('listener started from header')
dragMouseDown(e);
});
} else {
elmnt.off('mousedown').on('mousedown', dragMouseDown);
}
});
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
function dragMouseDown(e) {
//console.log(e);
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX; //mouse X at click
pos4 = e.clientY; //mouse Y at click
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
if (e) {
e.preventDefault();
pos3 = e.clientX; //mouse X at click
pos4 = e.clientY; //mouse Y at click
}
$(document).on('mouseup', closeDragElement);
$(document).on('mousemove', elementDrag);
}
function elementDrag(e) {
//disable scrollbars when dragging to prevent jitter
$("body").css("overflow", "hidden");
if (!power_user.movingUIState[elmntName]) {
power_user.movingUIState[elmntName] = {};
}
//get window size
let winWidth = window.innerWidth;
let winHeight = window.innerHeight;
//get necessary data for calculating element footprint
let draggableHeight = parseInt(getComputedStyle(elmnt).getPropertyValue('height').slice(0, -2));
let draggableWidth = parseInt(getComputedStyle(elmnt).getPropertyValue('width').slice(0, -2));
let draggableTop = parseInt(getComputedStyle(elmnt).getPropertyValue('top').slice(0, -2));
let draggableLeft = parseInt(getComputedStyle(elmnt).getPropertyValue('left').slice(0, -2));
let sheldWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sheldWidth').slice(0, -2));
let sheldWidth = parseInt($('html').css('--sheldWidth').slice(0, -2));
let topBarFirstX = (winWidth - sheldWidth) / 2;
let topBarLastX = topBarFirstX + sheldWidth;
let maxX = (width + left);
let maxY = (height + top);
//set the lowest and most-right pixel the element touches
let maxX = (draggableWidth + draggableLeft);
let maxY = (draggableHeight + draggableTop);
// calculate the new cursor position:
e = e || window.event;
e.preventDefault();
@@ -512,88 +614,69 @@ export function dragElement(elmnt) {
pos3 = e.clientX; //new mouse X
pos4 = e.clientY; //new mouse Y
elmnt.setAttribute('data-dragged', 'true');
elmnt.attr('data-dragged', 'true');
//fix over/underflows:
setTimeout(function () {
if (elmnt.offsetTop < 40) {
/* console.log('6'); */
if (maxX > topBarFirstX && maxX < topBarLastX) {
/* console.log('maxX inside topBar!'); */
elmnt.style.top = "42px";
}
if (elmnt.offsetLeft < topBarLastX && elmnt.offsetLeft > topBarFirstX) {
/* console.log('offsetLeft inside TopBar!'); */
elmnt.style.top = "42px";
}
if (elmnt.offset().top < 40) {
if (maxX > topBarFirstX && maxX < topBarLastX) {
elmnt.css('top', '42px');
}
if (elmnt.offsetTop - pos2 <= 0) {
/* console.log('1'); */
//prevent going out of window top + 42px barrier for TopBar (can hide grabber)
elmnt.style.top = "0px";
if (elmnt.offset().left < topBarLastX && elmnt.offset().left > topBarFirstX) {
elmnt.css('top', '42px');
}
if (elmnt.offsetLeft - pos1 <= 0) {
/* console.log('2'); */
//prevent moving out of window left
elmnt.style.left = "0px";
}
if (elmnt.offset().top - pos2 <= 0) {
elmnt.css('top', '0px');
}
if (elmnt.offset().left - pos1 <= 0) {
elmnt.css('left', '0px');
}
if (maxX >= winWidth) {
elmnt.css('left', elmnt.offset().left - 10 + "px");
}
if (maxY >= winHeight) {
elmnt.css('top', elmnt.offset().top - 10 + "px");
if (elmnt.offset().top - pos2 <= 40) {
elmnt.css('top', '20px');
}
}
elmnt.css('left', (elmnt.offset().left - pos1) + "px");
elmnt.css("top", (elmnt.offset().top - pos2) + "px");
elmnt.css('margin', 'unset');
if (maxX >= winWidth) {
/* console.log('3'); */
//bounce off right
elmnt.style.left = elmnt.offsetLeft - 10 + "px";
}
if (maxY >= winHeight) {
/* console.log('4'); */
//bounce off bottom
elmnt.style.top = elmnt.offsetTop - 10 + "px";
if (elmnt.offsetTop - pos2 <= 40) {
/* console.log('5'); */
//prevent going out of window top + 42px barrier for TopBar (can hide grabber)
/* console.log('caught Y bounce to <40Y top'); */
elmnt.style.top = "20px";
}
}
// if no problems, set element's new position
/* console.log('7'); */
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
$(elmnt).css("bottom", "unset");
$(elmnt).css("right", "unset");
$(elmnt).css("margin", "unset");
/* console.log(`
offsetLeft: ${elmnt.offsetLeft}, offsetTop: ${elmnt.offsetTop}
winWidth: ${winWidth}, winHeight: ${winHeight}
sheldWidth: ${sheldWidth}
X: ${elmnt.style.left}
Y: ${elmnt.style.top}
MaxX: ${maxX}, MaxY: ${maxY}
Topbar 1st X: ${((winWidth - sheldWidth) / 2)}
TopBar lastX: ${((winWidth - sheldWidth) / 2) + sheldWidth}
`); */
}, 50)
/* console.log("left/top: " + (elmnt.offsetLeft - pos1) + "/" + (elmnt.offsetTop - pos2) +
", win: " + winWidth + "/" + winHeight +
", max X / Y: " + maxX + " / " + maxY); */
/*
console.log(`
winWidth: ${winWidth}, winHeight: ${winHeight}
sheldWidth: ${sheldWidth}
X: ${$(elmnt).css('left')}
Y: ${$(elmnt).css('top')}
MaxX: ${maxX}, MaxY: ${maxY}
Topbar 1st X: ${((winWidth - sheldWidth) / 2)}
TopBar lastX: ${((winWidth - sheldWidth) / 2) + sheldWidth}
`);
*/
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
//revert scrolling to normal after drag to allow recovery of vastly misplaced elements
$("body").css("overflow", "auto");
console.debug('drag finished')
$(document).off('mouseup', closeDragElement);
$(document).off('mousemove', elementDrag);
$("body").css("overflow", "");
// Clear the "data-dragged" attribute
elmnt.attr('data-dragged', 'false');
}
}
export async function initMovingUI() {
if (isMobile() === false && power_user.movingUI === true) {
console.debug('START MOVING UI')
dragElement($("#sheld"));
dragElement($("#left-nav-panel"));
dragElement($("#right-nav-panel"));
dragElement($("#WorldInfo"));
await delay(1000)
console.debug('loading AN draggable function')
dragElement($("#floatingPrompt"))
}
}
@@ -603,7 +686,9 @@ export function dragElement(elmnt) {
$("document").ready(function () {
// initial status check
setTimeout(RA_checkOnlineStatus, 100);
setTimeout(() => {
RA_checkOnlineStatus();
}, 100);
// read the state of AutoConnect and AutoLoadChat.
$(AutoConnectCheckbox).prop("checked", LoadLocalBool("AutoConnectEnabled"));
@@ -790,7 +875,7 @@ $("document").ready(function () {
function isInputElementInFocus() {
//return $(document.activeElement).is(":input");
var focused = $(':focus');
if (focused.is('input') || focused.is('textarea') || focused.attr('contenteditable') == 'true') {
if (focused.is('input') || focused.is('textarea') || focused.prop('contenteditable') == 'true') {
if (focused.attr('id') === 'send_textarea') {
return false;
}

View File

@@ -51,6 +51,7 @@ const extension_settings = {
note: {
default: '',
chara: [],
wiAddition: [],
},
caption: {},
expressions: {},
@@ -421,12 +422,12 @@ async function loadExtensionSettings(settings) {
}
}
async function runGenerationInterceptors(chat) {
async function runGenerationInterceptors(chat, contextSize) {
for (const manifest of Object.values(manifests)) {
const interceptorKey = manifest.generate_interceptor;
if (typeof window[interceptorKey] === 'function') {
try {
await window[interceptorKey](chat);
await window[interceptorKey](chat, contextSize);
} catch (e) {
console.error(`Failed running interceptor for ${manifest.display_name}`, e);
}

View File

@@ -120,7 +120,7 @@ $(document).ready(function () {
<div class="background_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Character Backgrounds</b>
<b>Chat Backgrounds</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">

View File

@@ -1,5 +1,5 @@
{
"display_name": "Character Backgrounds",
"display_name": "Chat Backgrounds",
"loading_order": 7,
"requires": [],
"optional": [],

View File

@@ -161,7 +161,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
template.attr('data-avatar', avatar);
template.find('.drag-grabber').attr('id', `expression-${avatar}header`);
$('#visual-novel-wrapper').append(template);
dragElement(template[0]);
dragElement($(template[0]));
template.toggleClass('hidden', noSprites);
setImage(template.find('img'), defaultSpritePath || '');
const fadeInPromise = new Promise(resolve => {
@@ -533,6 +533,11 @@ function drawSpritesList(character, labels, sprites) {
$('.expression_settings').show();
$('#image_list').empty();
$('#image_list').data('name', character);
if (!Array.isArray(labels)) {
return [];
}
labels.sort().forEach((item) => {
const sprite = sprites.find(x => x.label == item);

View File

@@ -10,7 +10,7 @@ import { selected_group } from "../../group-chats.js";
import { ModuleWorkerWrapper, extension_settings, getContext, saveMetadataDebounced } from "../../extensions.js";
import { registerSlashCommand } from "../../slash-commands.js";
import { getCharaFilename, debounce } from "../../utils.js";
export { MODULE_NAME };
export { MODULE_NAME as NOTE_MODULE_NAME };
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
const UPDATE_INTERVAL = 1000;
@@ -18,14 +18,21 @@ const UPDATE_INTERVAL = 1000;
const DEFAULT_DEPTH = 4;
const DEFAULT_POSITION = 1;
const DEFAULT_INTERVAL = 1;
export var shouldWIAddPrompt = false;
const metadata_keys = {
export const metadata_keys = {
prompt: 'note_prompt',
interval: 'note_interval',
depth: 'note_depth',
position: 'note_position',
}
const chara_note_position = {
replace: 0,
before: 1,
after: 2,
}
function setNoteTextCommand(_, text) {
$('#extension_floating_prompt').val(text).trigger('input');
toastr.success("Author's Note text updated");
@@ -72,6 +79,12 @@ function setNotePositionCommand(_, text) {
toastr.info("Author's Note position updated");
}
function updateSettings() {
saveSettingsDebounced();
loadSettings();
setFloatingPrompt();
}
const setMainPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_prompt_token_counter').text(getTokenCount(value)), 1000);
const setCharaPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_chara_token_counter').text(getTokenCount(value)), 1000);
const setDefaultPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_default_token_counter').text(getTokenCount(value)), 1000);
@@ -79,11 +92,13 @@ const setDefaultPromptTokenCounterDebounced = debounce((value) => $('#extension_
async function onExtensionFloatingPromptInput() {
chat_metadata[metadata_keys.prompt] = $(this).val();
setMainPromptTokenCounterDebounced(chat_metadata[metadata_keys.prompt]);
updateSettings();
saveMetadataDebounced();
}
async function onExtensionFloatingIntervalInput() {
chat_metadata[metadata_keys.interval] = Number($(this).val());
updateSettings();
saveMetadataDebounced();
}
@@ -96,14 +111,26 @@ async function onExtensionFloatingDepthInput() {
}
chat_metadata[metadata_keys.depth] = value;
updateSettings();
saveMetadataDebounced();
}
async function onExtensionFloatingPositionInput(e) {
chat_metadata[metadata_keys.position] = e.target.value;
updateSettings();
saveMetadataDebounced();
}
async function onExtensionFloatingCharPositionInput(e) {
const value = e.target.value;
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
if (charaNote) {
charaNote.position = Number(value);
updateSettings();
}
}
function onExtensionFloatingCharaPromptInput() {
const tempPrompt = $(this).val();
const avatarName = getCharaFilename();
@@ -136,7 +163,7 @@ function onExtensionFloatingCharaPromptInput() {
if (!extension_settings.note.chara) {
extension_settings.note.chara = []
}
Object.assign(tempCharaNote, { useChara: false })
Object.assign(tempCharaNote, { useChara: false, position: chara_note_position.replace })
extension_settings.note.chara.push(tempCharaNote);
} else {
@@ -147,7 +174,7 @@ function onExtensionFloatingCharaPromptInput() {
return;
}
saveSettingsDebounced();
updateSettings();
}
function onExtensionFloatingCharaCheckboxChanged() {
@@ -157,14 +184,14 @@ function onExtensionFloatingCharaCheckboxChanged() {
if (charaNote) {
charaNote.useChara = value;
saveSettingsDebounced();
updateSettings();
}
}
function onExtensionFloatingDefaultInput() {
extension_settings.note.default = $(this).val();
setDefaultPromptTokenCounterDebounced(extension_settings.note.default);
saveSettingsDebounced();
updateSettings();
}
function loadSettings() {
@@ -177,28 +204,39 @@ function loadSettings() {
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
if (extension_settings.note.chara) {
if (extension_settings.note.chara && getContext().characterId) {
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
$('#extension_floating_chara').val(charaNote ? charaNote.prompt : '');
$('#extension_use_floating_chara').prop('checked', charaNote ? charaNote.useChara : false);
$(`input[name="extension_floating_char_position"][value="${charaNote?.position ?? chara_note_position.replace}"]`).prop('checked', true);
} else {
$('#extension_floating_chara').val('');
$('#extension_use_floating_chara').prop('checked', false);
$(`input[name="extension_floating_char_position"][value="${chara_note_position.replace}"]`).prop('checked', true);
}
$('#extension_floating_default').val(extension_settings.note.default);
}
async function moduleWorker() {
export function setFloatingPrompt() {
const context = getContext();
if (!context.groupId && context.characterId === undefined) {
console.debug('setFloatingPrompt: Not in a chat. Skipping.');
shouldWIAddPrompt = false;
return;
}
loadSettings();
// take the count of messages
let lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0;
console.debug(`
setFloatingPrompt entered
------
lastMessageNumber = ${lastMessageNumber}
metadata_keys.interval = ${chat_metadata[metadata_keys.interval]}
`)
// interval 1 should be inserted no matter what
if (chat_metadata[metadata_keys.interval] === 1) {
lastMessageNumber = 1;
@@ -207,6 +245,7 @@ async function moduleWorker() {
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
context.setExtensionPrompt(MODULE_NAME, '');
$('#extension_floating_counter').text('(disabled)');
shouldWIAddPrompt = false;
return;
}
@@ -214,17 +253,27 @@ async function moduleWorker() {
? (lastMessageNumber % chat_metadata[metadata_keys.interval])
: (chat_metadata[metadata_keys.interval] - lastMessageNumber);
const shouldAddPrompt = messagesTillInsertion == 0;
shouldWIAddPrompt = shouldAddPrompt;
let prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
if (shouldAddPrompt && extension_settings.note.chara) {
if (shouldAddPrompt && extension_settings.note.chara && getContext().characterId) {
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
// Only replace with the chara note if the user checked the box
if (charaNote && charaNote.useChara) {
prompt = charaNote.prompt;
switch (charaNote.position) {
case chara_note_position.before:
prompt = charaNote.prompt + '\n' + prompt;
break;
case chara_note_position.after:
prompt = prompt + '\n' + charaNote.prompt;
break;
default:
prompt = charaNote.prompt;
break;
}
}
}
context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
}
@@ -263,11 +312,18 @@ function onANMenuItemClick() {
}
function onChatChanged() {
loadSettings();
setFloatingPrompt();
const context = getContext();
// Disable the chara note if in a group
$('#extension_floating_chara').prop('disabled', context.groupId ? true : false);
const tokenCounter1 = chat_metadata[metadata_keys.prompt] ? getTokenCount(chat_metadata[metadata_keys.prompt]) : 0;
$('#extension_floating_prompt_token_counter').text(tokenCounter1);
let tokenCounter2;
if (extension_settings.note.chara) {
if (extension_settings.note.chara && context.characterId) {
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
if (charaNote) {
@@ -283,7 +339,10 @@ function onChatChanged() {
$('#extension_floating_default_token_counter').text(tokenCounter3);
}
(function () {
//for some reason exporting metadata_keys for WI usage caused this to throw errors
//"accessing eventSource before initialization"
//putting it on a 1ms Timeout solved this.
setTimeout(function () {
function addExtensionsSettings() {
const settingsHtml = `
<div id="floatingPrompt" class="drawer-content flexGap5">
@@ -335,16 +394,30 @@ function onChatChanged() {
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<small>Will be automatically added as the author's note for this character.</small>
<small>Will be automatically added as the author's note for this character. Will be used in groups, but can't be modified when a group chat is open.</small>
<textarea id="extension_floating_chara" class="text_pole" rows="8" maxlength="10000"
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
<div class="extension_token_counter">Tokens: <span id="extension_floating_chara_token_counter">0</small></div>
<label for="extension_use_floating_chara">
<label class="checkbox_label" for="extension_use_floating_chara">
<input id="extension_use_floating_chara" type="checkbox" />
<span data-i18n="Use character author's note">Use character author's note</span>
</label>
<span data-i18n="Use character author's note">Use character author's note</span>
</label>
<div class="floating_prompt_radio_group">
<label>
<input type="radio" name="extension_floating_char_position" value="0" />
Replace Author's Note
</label>
<label>
<input type="radio" name="extension_floating_char_position" value="1" />
Top of Author's Note
</label>
<label>
<input type="radio" name="extension_floating_char_position" value="2" />
Bottom of Author's Note
</label>
</div>
</div>
</div>
<hr class="sysHR">
@@ -380,6 +453,7 @@ function onChatChanged() {
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
$('#ANClose').on('click', function () {
$("#floatingPrompt").transition({
opacity: 0,
@@ -392,11 +466,9 @@ function onChatChanged() {
}
addExtensionsSettings();
const wrapper = new ModuleWorkerWrapper(moduleWorker);
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> sets an author's note for the currently selected chat", true, true);
registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> sets an author's note depth for in-chat positioning", true, true);
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> sets an author's note insertion frequency", true, true);
registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) sets an author's note position", true, true);
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
})();
}, 1);

View File

@@ -3,6 +3,7 @@
"loading_order": 1,
"requires": [],
"optional": [],
"generate_interceptor": "AuthorNote_generateInterceptor",
"js": "index.js",
"css": "style.css",
"author": "Cohee#1207",

View File

@@ -1,4 +1,4 @@
import { saveSettingsDebounced, getCurrentChatId, system_message_types, eventSource, event_types } from "../../../script.js";
import { saveSettingsDebounced, getCurrentChatId, system_message_types, eventSource, event_types, getRequestHeaders, CHARACTERS_PER_TOKEN_RATIO, substituteParams, } from "../../../script.js";
import { humanizedDateTime } from "../../RossAscends-mods.js";
import { getApiUrl, extension_settings, getContext, doExtrasFetch } from "../../extensions.js";
import { getFileText, onlyUnique, splitRecursive, IndexedDBStore } from "../../utils.js";
@@ -9,6 +9,7 @@ const dbStore = new IndexedDBStore('SillyTavern', MODULE_NAME);
const defaultSettings = {
strategy: 'original',
sort_strategy: 'date',
keep_context: 10,
keep_context_min: 1,
@@ -20,6 +21,12 @@ const defaultSettings = {
n_results_max: 500,
n_results_step: 1,
chroma_depth: 20,
chroma_depth_min: -1,
chroma_depth_max: 500,
chroma_depth_step: 1,
chroma_default_msg: "In a past conversation: [{{memories}}]",
split_length: 384,
split_length_min: 64,
split_length_max: 4096,
@@ -29,6 +36,14 @@ const defaultSettings = {
file_split_length_min: 512,
file_split_length_max: 4096,
file_split_length_step: 128,
keep_context_proportion: 0.5,
keep_context_proportion_min: 0.0,
keep_context_proportion_max: 1.0,
keep_context_proportion_step: 0.05,
auto_adjust: true,
freeze: false,
};
const postHeaders = {
@@ -88,18 +103,51 @@ async function loadSettings() {
"selected",
"true"
);
$("#chromadb_sort_strategy option[value=" + extension_settings.chromadb.sort_strategy + "]").attr(
"selected",
"true"
);
$('#chromadb_keep_context').val(extension_settings.chromadb.keep_context).trigger('input');
$('#chromadb_n_results').val(extension_settings.chromadb.n_results).trigger('input');
$('#chromadb_split_length').val(extension_settings.chromadb.split_length).trigger('input');
$('#chromadb_file_split_length').val(extension_settings.chromadb.file_split_length).trigger('input');
$('#chromadb_keep_context_proportion').val(extension_settings.chromadb.keep_context_proportion).trigger('input');
$('#chromadb_custom_depth').val(extension_settings.chromadb.chroma_depth).trigger('input');
$('#chromadb_custom_msg').val(extension_settings.chromadb.recall_msg).trigger('input');
$('#chromadb_auto_adjust').prop('checked', extension_settings.chromadb.auto_adjust);
$('#chromadb_freeze').prop('checked', extension_settings.chromadb.freeze);
enableDisableSliders();
onStrategyChange();
}
function onStrategyChange() {
console.debug('changing chromadb strat');
extension_settings.chromadb.strategy = $('#chromadb_strategy').val();
if(extension_settings.chromadb.strategy === "custom"){
$('#chromadb_custom_depth').show();
$('label[for="chromadb_custom_depth"]').show();
$('#chromadb_custom_msg').show();
$('label[for="chromadb_custom_msg"]').show();
} else {
$('#chromadb_custom_depth').hide();
$('label[for="chromadb_custom_depth"]').hide();
$('#chromadb_custom_msg').hide();
$('label[for="chromadb_custom_msg"]').hide();
}
saveSettingsDebounced();
}
function onRecallStrategyChange() {
console.log('changing chromadb recall strat');
extension_settings.chromadb.recall_strategy = $('#chromadb_recall_strategy').val();
saveSettingsDebounced();
}
function onSortStrategyChange() {
console.log('changing chromadb sort strat');
extension_settings.chromadb.sort_strategy = $('#chromadb_sort_strategy').val();
//$('#chromadb_strategy').select(extension_settings.chromadb.strategy);
saveSettingsDebounced();
}
@@ -115,6 +163,17 @@ function onNResultsInput() {
saveSettingsDebounced();
}
function onChromaDepthInput() {
extension_settings.chromadb.chroma_depth = Number($('#chromadb_custom_depth').val());
$('#chromadb_custom_depth_value').text(extension_settings.chromadb.chroma_depth);
saveSettingsDebounced();
}
function onChromaMsgInput() {
extension_settings.chromadb.recall_msg = $('#chromadb_custom_msg').val();
saveSettingsDebounced();
}
function onSplitLengthInput() {
extension_settings.chromadb.split_length = Number($('#chromadb_split_length').val());
$('#chromadb_split_length_value').text(extension_settings.chromadb.split_length);
@@ -328,6 +387,39 @@ async function queryMessages(chat_id, query) {
return [];
}
async function queryMultiMessages(chat_id, query) {
const context = getContext();
const response = await fetch("/getallchatsofcharacter", {
method: 'POST',
body: JSON.stringify({ avatar_url: context.characters[context.characterId].avatar}),
headers: getRequestHeaders(),
});
if (!response.ok) {
return;
}
let data = await response.json();
data = Object.values(data);
let chat_list = data.sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse();
// Extracting chat_ids from the chat_list
chat_list = chat_list.map(chat => chat.file_name.replace(/\.[^/.]+$/, ""));
const url = new URL(getApiUrl());
url.pathname = '/api/chromadb/multiquery';
const queryMessagesResult = await fetch(url, {
method: 'POST',
body: JSON.stringify({ chat_list, query, n_results: extension_settings.chromadb.n_results }),
headers: postHeaders,
});
if (queryMessagesResult.ok) {
const queryMessagesData = await queryMessagesResult.json();
return queryMessagesData;
}
return [];
}
async function onSelectInjectFile(e) {
const file = e.target.files[0];
const currentChatId = getCurrentChatId();
@@ -390,9 +482,57 @@ async function onSelectInjectFile(e) {
}
}
window.chromadb_interceptGeneration = async (chat) => {
/*
* Automatically adjusts the extension settings for the optimal number of messages to keep and query based
* on the chat history and a specified maximum context length.
*/
function doAutoAdjust(chat, maxContext) {
console.debug('CHROMADB: Auto-adjusting sliders (messages: %o, maxContext: %o)', chat.length, maxContext);
// Get mean message length
const meanMessageLength = chat.reduce((acc, cur) => acc + cur.mes.length, 0) / chat.length;
if (Number.isNaN(meanMessageLength)) {
console.debug('CHROMADB: Mean message length is NaN, aborting auto-adjust');
return;
}
console.debug('CHROMADB: Mean message length (characters): %o', meanMessageLength);
// Convert to number of "tokens"
const meanMessageLengthTokens = Math.ceil(meanMessageLength / CHARACTERS_PER_TOKEN_RATIO);
console.debug('CHROMADB: Mean message length (tokens): %o', meanMessageLengthTokens);
// Get number of messages in context
const contextMessages = Math.max(1, Math.ceil(maxContext / meanMessageLengthTokens));
// Round up to nearest 10
const contextMessagesRounded = Math.ceil(contextMessages / 10) * 10;
console.debug('CHROMADB: Estimated context messages (rounded): %o', contextMessagesRounded);
// Messages to keep (proportional, rounded to nearest 5, minimum 10, maximum 500)
const messagesToKeep = Math.min(defaultSettings.keep_context_max, Math.max(10, Math.ceil(contextMessagesRounded * extension_settings.chromadb.keep_context_proportion / 5) * 5));
console.debug('CHROMADB: Estimated messages to keep: %o', messagesToKeep);
// Messages to query (rounded, maximum 500)
const messagesToQuery = Math.min(defaultSettings.n_results_max, contextMessagesRounded - messagesToKeep);
console.debug('CHROMADB: Estimated messages to query: %o', messagesToQuery);
// Set extension settings
extension_settings.chromadb.keep_context = messagesToKeep;
extension_settings.chromadb.n_results = messagesToQuery;
// Update sliders
$('#chromadb_keep_context').val(messagesToKeep);
$('#chromadb_n_results').val(messagesToQuery);
// Update labels
$('#chromadb_keep_context_value').text(extension_settings.chromadb.keep_context);
$('#chromadb_n_results_value').text(extension_settings.chromadb.n_results);
}
window.chromadb_interceptGeneration = async (chat, maxContext) => {
if (extension_settings.chromadb.auto_adjust) {
doAutoAdjust(chat, maxContext);
}
const currentChatId = getCurrentChatId();
const selectedStrategy = extension_settings.chromadb.strategy;
const recallStrategy = extension_settings.chromadb.recall_strategy;
let recallMsg = extension_settings.chromadb.recall_msg || defaultSettings.chroma_default_msg;
const chromaDepth = extension_settings.chromadb.chroma_depth;
const chromaSortStrategy = extension_settings.chromadb.sort_strategy;
if (currentChatId) {
const messagesToStore = chat.slice(0, -extension_settings.chromadb.keep_context);
@@ -401,12 +541,26 @@ window.chromadb_interceptGeneration = async (chat) => {
const lastMessage = chat[chat.length - 1];
let queriedMessages;
console.debug(recallStrategy)
if (lastMessage) {
const queriedMessages = await queryMessages(currentChatId, lastMessage.mes);
if (recallStrategy === 'multichat'){
console.log("Utilizing multichat")
queriedMessages = await queryMultiMessages(currentChatId, lastMessage.mes);
}
else{
queriedMessages = await queryMessages(currentChatId, lastMessage.mes);
}
queriedMessages.sort((a, b) => a.date - b.date);
if(chromaSortStrategy === "date"){
queriedMessages.sort((a, b) => a.date - b.date);
}
else{
queriedMessages.sort((a, b) => b.distance - a.distance);
}
const newChat = [];
let newChat = [];
if (selectedStrategy === 'ross') {
//adds chroma to the end of chat and allows Generate() to cull old messages naturally.
@@ -433,7 +587,46 @@ window.chromadb_interceptGeneration = async (chat) => {
);
chat.splice(chat.length, 0, ...newChat);
}
if (selectedStrategy === 'custom') {
const context = getContext();
recallMsg = substituteParams(recallMsg, context.name1, context.name2);
if (!text.includes("{{memories}}")) {
text += " {{memories}}";
}
let recallStart = recallMsg.split('{{memories}}')[0]
let recallEnd = recallMsg.split('{{memories}}')[1]
newChat.push(
{
is_name: false,
is_user: false,
mes: recallStart,
name: "system",
send_date: 0,
}
);
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
newChat.push(
{
is_name: false,
is_user: false,
mes: recallEnd + `\n`,
name: "system",
send_date: 0,
}
);
//prototype chroma duplicate removal
let chatset = new Set(chat.map(obj => obj.mes));
newChat = newChat.filter(obj => !chatset.has(obj.mes));
if(chromaDepth === -1){
chat.splice(chat.length, 0, ...newChat);
}
else{
chat.splice(chromaDepth, 0, ...newChat);
}
}
if (selectedStrategy === 'original') {
//removes .length # messages from the start of 'kept messages'
//replaces them with chromaDB results (with no separator)
@@ -447,11 +640,37 @@ window.chromadb_interceptGeneration = async (chat) => {
}
}
function onFreezeInput() {
extension_settings.chromadb.freeze = $('#chromadb_freeze').is(':checked');
saveSettingsDebounced();
}
function onAutoAdjustInput() {
extension_settings.chromadb.auto_adjust = $('#chromadb_auto_adjust').is(':checked');
enableDisableSliders();
saveSettingsDebounced();
}
function enableDisableSliders() {
if (extension_settings.chromadb.auto_adjust) {
$('#chromadb_keep_context').prop('disabled', true).css('opacity', 0.5);
$('#chromadb_n_results').prop('disabled', true).css('opacity', 0.5);
$('#chromadb_keep_context_proportion').prop('disabled', false).css('opacity', 1);
}
else {
$('#chromadb_keep_context').prop('disabled', false).css('opacity', 1);
$('#chromadb_n_results').prop('disabled', false).css('opacity', 1);
$('#chromadb_keep_context_proportion').prop('disabled', true).css('opacity', 0.5);
}
}
function onKeepContextProportionInput() {
extension_settings.chromadb.keep_context_proportion = $('#chromadb_keep_context_proportion').val();
$('#chromadb_keep_context_proportion_value').text(Math.round(extension_settings.chromadb.keep_context_proportion * 100));
saveSettingsDebounced();
}
jQuery(async () => {
const settingsHtml = `
<div class="chromadb_settings">
@@ -467,11 +686,28 @@ jQuery(async () => {
<select id="chromadb_strategy">
<option value="original">Replace non-kept chat items with memories</option>
<option value="ross">Add memories after chat with a header tag</option>
<option value="custom">Add memories at custom depth with custom msg</option>
</select>
<label for="chromadb_custom_msg" hidden><small>Custom injection message:</small></label>
<textarea id="chromadb_custom_msg" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_msg}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_custom_depth" hidden><small>How deep should the memory messages be injected?: (<span id="chromadb_custom_depth_value"></span>)</small></label>
<input id="chromadb_custom_depth" type="range" min="${defaultSettings.chroma_depth_min}" max="${defaultSettings.chroma_depth_max}" step="${defaultSettings.chroma_depth_step}" value="${defaultSettings.chroma_depth}" hidden/>
<span>Memory Recall Strategy</span>
<select id="chromadb_recall_strategy">
<option value="original">Recall only from this chat</option>
<option value="multichat">Recall from all character chats (experimental)</option>
</select>
<span>Memory Sort Strategy</span>
<select id="chromadb_sort_strategy">
<option value="date">Sort memories by date</option>
<option value="distance">Sort memories by relevance</option>
</select>
<label for="chromadb_keep_context"><small>How many original chat messages to keep: (<span id="chromadb_keep_context_value"></span>) messages</small></label>
<input id="chromadb_keep_context" type="range" min="${defaultSettings.keep_context_min}" max="${defaultSettings.keep_context_max}" step="${defaultSettings.keep_context_step}" value="${defaultSettings.keep_context}" />
<label for="chromadb_n_results"><small>Maximum number of ChromaDB 'memories' to inject: (<span id="chromadb_n_results_value"></span>) messages</small></label>
<input id="chromadb_n_results" type="range" min="${defaultSettings.n_results_min}" max="${defaultSettings.n_results_max}" step="${defaultSettings.n_results_step}" value="${defaultSettings.n_results}" />
<label for="chromadb_keep_context_proportion"><small>Keep <span id="chromadb_keep_context_proportion_value"></span>% of in-context chat messages; replace the rest with memories</small></label>
<input id="chromadb_keep_context_proportion" type="range" min="${defaultSettings.keep_context_proportion_min}" max="${defaultSettings.keep_context_proportion_max}" step="${defaultSettings.keep_context_proportion_step}" value="${defaultSettings.keep_context_proportion}" />
<label for="chromadb_split_length"><small>Max length for each 'memory' pulled from the current chat history: (<span id="chromadb_split_length_value"></span>) characters</small></label>
<input id="chromadb_split_length" type="range" min="${defaultSettings.split_length_min}" max="${defaultSettings.split_length_max}" step="${defaultSettings.split_length_step}" value="${defaultSettings.split_length}" />
<label for="chromadb_file_split_length"><small>Max length for each 'memory' pulled from imported text files: (<span id="chromadb_file_split_length_value"></span>) characters</small></label>
@@ -480,6 +716,10 @@ jQuery(async () => {
<input type="checkbox" id="chromadb_freeze" />
<span>Freeze ChromaDB state</span>
</label>
<label class="checkbox_label for="chromadb_auto_adjust" title="Automatically adjusts the number of messages to keep based on the average number of messages in the current chat and the chosen proportion.">
<input type="checkbox" id="chromadb_auto_adjust" />
<span>Use % strategy</span>
</label>
<div class="flex-container spaceEvenly">
<div id="chromadb_inject" title="Upload custom textual data to use in the context of the current chat" class="menu_button">
<i class="fa-solid fa-file-arrow-up"></i>
@@ -506,8 +746,12 @@ jQuery(async () => {
$('#extensions_settings2').append(settingsHtml);
$('#chromadb_strategy').on('change', onStrategyChange);
$('#chromadb_recall_strategy').on('change', onRecallStrategyChange);
$('#chromadb_sort_strategy').on('change', onSortStrategyChange);
$('#chromadb_keep_context').on('input', onKeepContextInput);
$('#chromadb_n_results').on('input', onNResultsInput);
$('#chromadb_custom_depth').on('input', onChromaDepthInput);
$('#chromadb_custom_msg').on('input', onChromaMsgInput);
$('#chromadb_split_length').on('input', onSplitLengthInput);
$('#chromadb_file_split_length').on('input', onFileSplitLengthInput);
$('#chromadb_inject').on('click', () => $('#chromadb_inject_file').trigger('click'));
@@ -517,6 +761,8 @@ jQuery(async () => {
$('#chromadb_purge').on('click', onPurgeClick);
$('#chromadb_export').on('click', onExportClick);
$('#chromadb_freeze').on('input', onFreezeInput);
$('#chromadb_auto_adjust').on('input', onAutoAdjustInput);
$('#chromadb_keep_context_proportion').on('input', onKeepContextProportionInput);
await loadSettings();
// Not sure if this is needed, but it's here just in case

View File

@@ -1,10 +1,9 @@
import { getStringHash, debounce } from "../../utils.js";
import { getContext, getApiUrl, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
import { extension_prompt_types, is_send_press, saveSettingsDebounced } from "../../../script.js";
import { getStringHash, debounce, waitUntilCondition } from "../../utils.js";
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
import { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
export { MODULE_NAME };
const MODULE_NAME = '1_memory';
const UPDATE_INTERVAL = 5000;
let lastCharacterId = null;
let lastGroupId = null;
@@ -13,9 +12,16 @@ let lastMessageHash = null;
let lastMessageId = null;
let inApiCall = false;
const formatMemoryValue = (value) => value ? `Context: ${value.trim()}` : '';
const formatMemoryValue = (value) => value ? `Summary: ${value.trim()}` : '';
const saveChatDebounced = debounce(() => getContext().saveChat(), 2000);
const summary_sources = {
'extras': 'extras',
'main': 'main',
};
const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';
const defaultSettings = {
minLongMemory: 16,
maxLongMemory: 1024,
@@ -38,6 +44,16 @@ const defaultSettings = {
maxLengthPenalty: 4,
lengthPenaltyStep: 0.1,
memoryFrozen: false,
source: summary_sources.extras,
prompt: defaultPrompt,
promptWords: 200,
promptMinWords: 25,
promptMaxWords: 1000,
promptWordsStep: 25,
promptInterval: 10,
promptMinInterval: 1,
promptMaxInterval: 100,
promptIntervalStep: 1,
};
function loadSettings() {
@@ -45,12 +61,42 @@ function loadSettings() {
Object.assign(extension_settings.memory, defaultSettings);
}
if (extension_settings.memory.source === undefined) {
extension_settings.memory.source = defaultSettings.source;
}
if (extension_settings.memory.prompt === undefined) {
extension_settings.memory.prompt = defaultSettings.prompt;
}
if (extension_settings.memory.promptWords === undefined) {
extension_settings.memory.promptWords = defaultSettings.promptWords;
}
if (extension_settings.memory.promptInterval === undefined) {
extension_settings.memory.promptInterval = defaultSettings.promptInterval;
}
$('#summary_source').val(extension_settings.memory.source).trigger('change');
$('#memory_long_length').val(extension_settings.memory.longMemoryLength).trigger('input');
$('#memory_short_length').val(extension_settings.memory.shortMemoryLength).trigger('input');
$('#memory_repetition_penalty').val(extension_settings.memory.repetitionPenalty).trigger('input');
$('#memory_temperature').val(extension_settings.memory.temperature).trigger('input');
$('#memory_length_penalty').val(extension_settings.memory.lengthPenalty).trigger('input');
$('#memory_frozen').prop('checked', extension_settings.memory.memoryFrozen).trigger('input');
$('#memory_prompt').val(extension_settings.memory.prompt).trigger('input');
$('#memory_prompt_words').val(extension_settings.memory.promptWords).trigger('input');
$('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input');
}
function onSummarySourceChange(event) {
const value = event.target.value;
extension_settings.memory.source = value;
$('#memory_settings [data-source]').each((_, element) => {
const source = $(element).data('source');
$(element).toggle(source === value);
});
saveSettingsDebounced();
}
function onMemoryShortInput() {
@@ -104,6 +150,26 @@ function onMemoryFrozenInput() {
saveSettingsDebounced();
}
function onMemoryPromptWordsInput() {
const value = $(this).val();
extension_settings.memory.promptWords = Number(value);
$('#memory_prompt_words_value').text(extension_settings.memory.promptWords);
saveSettingsDebounced();
}
function onMemoryPromptIntervalInput() {
const value = $(this).val();
extension_settings.memory.promptInterval = Number(value);
$('#memory_prompt_interval_value').text(extension_settings.memory.promptInterval);
saveSettingsDebounced();
}
function onMemoryPromptInput() {
const value = $(this).val();
extension_settings.memory.prompt = value;
saveSettingsDebounced();
}
function saveLastValues() {
const context = getContext();
lastGroupId = context.groupId;
@@ -129,7 +195,14 @@ function getLatestMemoryFromChat(chat) {
return '';
}
async function moduleWorker() {
async function onChatEvent() {
// Module not enabled
if (extension_settings.memory.source === summary_sources.extras) {
if (!modules.includes('summarize')) {
return;
}
}
const context = getContext();
const chat = context.chat;
@@ -187,7 +260,93 @@ async function moduleWorker() {
}
}
async function forceSummarizeChat() {
const context = getContext();
if (!context.chatId) {
toastr.warning('No chat selected');
return;
}
toastr.info('Summarizing chat...', 'Please wait');
const value = await summarizeChatMain(context, true);
if (!value) {
toastr.warning('Failed to summarize chat');
return;
}
}
async function summarizeChat(context) {
switch (extension_settings.memory.source) {
case summary_sources.extras:
await summarizeChatExtras(context);
break;
case summary_sources.main:
await summarizeChatMain(context, false);
break;
default:
break;
}
}
async function summarizeChatMain(context, force) {
try {
// Wait for the send button to be released
waitUntilCondition(() => is_send_press === false, 10000, 100);
} catch {
console.debug('Timeout waiting for is_send_press');
return;
}
if (!context.chat.length) {
console.debug('No messages in chat to summarize');
return;
}
if (context.chat.length < extension_settings.memory.promptInterval && !force) {
console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`);
return;
}
let messagesSinceLastSummary = 0;
for (let i = context.chat.length - 1; i >= 0; i--) {
if (context.chat[i].extra && context.chat[i].extra.memory) {
break;
}
messagesSinceLastSummary++;
}
if (messagesSinceLastSummary < extension_settings.memory.promptInterval && !force) {
console.debug(`Not enough messages since last summary (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}`);
return;
}
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary);
const prompt = substituteParams(extension_settings.memory.prompt)
.replace(/{{words}}/gi, extension_settings.memory.promptWords);
if (!prompt) {
console.debug('Summarization prompt is empty. Skipping summarization.');
return;
}
const summary = await generateQuietPrompt(prompt);
const newContext = getContext();
// something changed during summarization request
if (newContext.groupId !== context.groupId
|| newContext.chatId !== context.chatId
|| (!newContext.groupId && (newContext.characterId !== context.characterId))) {
console.log('Context changed, summary discarded');
return;
}
setMemoryContext(summary, true);
return summary;
}
async function summarizeChatExtras(context) {
function getMemoryString() {
return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim();
}
@@ -301,6 +460,7 @@ function setMemoryContext(value, saveToMessage) {
const context = getContext();
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO);
$('#memory_contents').val(value);
console.log('Memory set to: ' + value);
if (saveToMessage && context.chat.length) {
const idx = context.chat.length - 2;
@@ -315,40 +475,55 @@ function setMemoryContext(value, saveToMessage) {
}
}
$(document).ready(function () {
jQuery(function () {
function addExtensionControls() {
const settingsHtml = `
<div id="memory_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Summarize</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="memory_contents">Current summary: </label>
<textarea id="memory_contents" class="text_pole" rows="8" placeholder="Context will be generated here..."></textarea>
<div class="memory_contents_controls">
<input id="memory_restore" class="menu_button" type="submit" value="Restore previous state" />
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Stop summarization updates</label>
</div>
<!--</div>
</div>
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Summarization parameters</b>
<b>Summarize</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">-->
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
<input id="memory_short_length" type="range" value="${defaultSettings.shortMemoryLength}" min="${defaultSettings.minShortMemory}" max="${defaultSettings.maxShortMemory}" step="${defaultSettings.shortMemoryStep}" />
<label for="memory_long_length">Summary output length (<span id="memory_long_length_tokens"></span> tokens)</label>
<input id="memory_long_length" type="range" value="${defaultSettings.longMemoryLength}" min="${defaultSettings.minLongMemory}" max="${defaultSettings.maxLongMemory}" step="${defaultSettings.longMemoryStep}" />
<label for="memory_temperature">Temperature (<span id="memory_temperature_value"></span>)</label>
<input id="memory_temperature" type="range" value="${defaultSettings.temperature}" min="${defaultSettings.minTemperature}" max="${defaultSettings.maxTemperature}" step="${defaultSettings.temperatureStep}" />
<label for="memory_repetition_penalty">Repetition penalty (<span id="memory_repetition_penalty_value"></span>)</label>
<input id="memory_repetition_penalty" type="range" value="${defaultSettings.repetitionPenalty}" min="${defaultSettings.minRepetitionPenalty}" max="${defaultSettings.maxRepetitionPenalty}" step="${defaultSettings.repetitionPenaltyStep}" />
<label for="memory_length_penalty">Length preference <small>[higher = longer summaries]</small> (<span id="memory_length_penalty_value"></span>)</label>
<input id="memory_length_penalty" type="range" value="${defaultSettings.lengthPenalty}" min="${defaultSettings.minLengthPenalty}" max="${defaultSettings.maxLengthPenalty}" step="${defaultSettings.lengthPenaltyStep}" />
<div class="inline-drawer-content">
<label for="summary_source">Summarization source:</label>
<select id="summary_source">
<option value="main">Main API</option>
<option value="extras">Extras API</option>
</select>
<label for="memory_contents">Current summary: </label>
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" placeholder="Summary will be generated here..."></textarea>
<div class="memory_contents_controls">
<input id="memory_restore" class="menu_button" type="button" value="Restore previous state" />
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Pause summarization</label>
</div>
<div data-source="main" class="memory_contents_controls">
</div>
<div data-source="main">
<label for="memory_prompt" class="title_restorable">
Summarization Prompt
<div id="memory_force_summarize" class="menu_button menu_button_icon">
<i class="fa-solid fa-database"></i>
<span>Generate now</span>
</div>
</label>
<textarea id="memory_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be used in summary generation. Insert {{words}} macro to use the "Number of words" parameter."></textarea>
<label for="memory_prompt_words">Number of words in the summary (<span id="memory_prompt_words_value"></span> words)</label>
<input id="memory_prompt_words" type="range" value="${defaultSettings.promptWords}" min="${defaultSettings.promptMinWords}" max="${defaultSettings.promptMaxWords}" step="${defaultSettings.promptWordsStep}" />
<label for="memory_prompt_interval">Update interval (<span id="memory_prompt_interval_value"></span> messages)</label>
<input id="memory_prompt_interval" type="range" value="${defaultSettings.promptInterval}" min="${defaultSettings.promptMinInterval}" max="${defaultSettings.promptMaxInterval}" step="${defaultSettings.promptIntervalStep}" />
</div>
<div data-source="extras">
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
<input id="memory_short_length" type="range" value="${defaultSettings.shortMemoryLength}" min="${defaultSettings.minShortMemory}" max="${defaultSettings.maxShortMemory}" step="${defaultSettings.shortMemoryStep}" />
<label for="memory_long_length">Summary output length (<span id="memory_long_length_tokens"></span> tokens)</label>
<input id="memory_long_length" type="range" value="${defaultSettings.longMemoryLength}" min="${defaultSettings.minLongMemory}" max="${defaultSettings.maxLongMemory}" step="${defaultSettings.longMemoryStep}" />
<label for="memory_temperature">Temperature (<span id="memory_temperature_value"></span>)</label>
<input id="memory_temperature" type="range" value="${defaultSettings.temperature}" min="${defaultSettings.minTemperature}" max="${defaultSettings.maxTemperature}" step="${defaultSettings.temperatureStep}" />
<label for="memory_repetition_penalty">Repetition penalty (<span id="memory_repetition_penalty_value"></span>)</label>
<input id="memory_repetition_penalty" type="range" value="${defaultSettings.repetitionPenalty}" min="${defaultSettings.minRepetitionPenalty}" max="${defaultSettings.maxRepetitionPenalty}" step="${defaultSettings.repetitionPenaltyStep}" />
<label for="memory_length_penalty">Length preference <small>[higher = longer summaries]</small> (<span id="memory_length_penalty_value"></span>)</label>
<input id="memory_length_penalty" type="range" value="${defaultSettings.lengthPenalty}" min="${defaultSettings.minLengthPenalty}" max="${defaultSettings.maxLengthPenalty}" step="${defaultSettings.lengthPenaltyStep}" />
</div>
</div>
</div>
</div>
@@ -362,10 +537,18 @@ $(document).ready(function () {
$('#memory_temperature').on('input', onMemoryTemperatureInput);
$('#memory_length_penalty').on('input', onMemoryLengthPenaltyInput);
$('#memory_frozen').on('input', onMemoryFrozenInput);
$('#summary_source').on('change', onSummarySourceChange);
$('#memory_prompt_words').on('input', onMemoryPromptWordsInput);
$('#memory_prompt_interval').on('input', onMemoryPromptIntervalInput);
$('#memory_prompt').on('input', onMemoryPromptInput);
$('#memory_force_summarize').on('click', forceSummarizeChat);
}
addExtensionControls();
loadSettings();
const wrapper = new ModuleWorkerWrapper(moduleWorker);
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
eventSource.on(event_types.MESSAGE_RECEIVED, onChatEvent);
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
eventSource.on(event_types.CHAT_CHANGED, onChatEvent);
});

View File

@@ -1,10 +1,10 @@
{
"display_name": "Memory",
"loading_order": 9,
"requires": [
"requires": [],
"optional": [
"summarize"
],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "Cohee#1207",

View File

@@ -8,17 +8,9 @@ const MODULE_NAME = 'quick-reply';
const UPDATE_INTERVAL = 1000;
const defaultSettings = {
quickReply1Mes: '',
quickReply1Label: '',
quickReply2Mes: '',
quickReply2Label: '',
quickReply3Mes: '',
quickReply3Label: '',
quickReply4Mes: '',
quickReply4Label: '',
quickReply5Mes: '',
quickReply5Label: '',
quickReplyEnabled: false,
numberOfSlots: 5,
quickReplySlots: [],
}
async function loadSettings() {
@@ -26,33 +18,43 @@ async function loadSettings() {
Object.assign(extension_settings.quickReply, defaultSettings);
}
// If the user has an old version of the extension, update it
if (!Array.isArray(extension_settings.quickReply.quickReplySlots)) {
extension_settings.quickReply.quickReplySlots = [];
extension_settings.quickReply.numberOfSlots = defaultSettings.numberOfSlots;
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
extension_settings.quickReply.quickReplySlots.push({
mes: extension_settings.quickReply[`quickReply${i}Mes`],
label: extension_settings.quickReply[`quickReply${i}Label`],
enabled: true,
});
delete extension_settings.quickReply[`quickReply${i}Mes`];
delete extension_settings.quickReply[`quickReply${i}Label`];
}
}
generateQuickReplyElements();
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
$(`#quickReply${i}Mes`).val(extension_settings.quickReply.quickReplySlots[i - 1]?.mes).trigger('input');
$(`#quickReply${i}Label`).val(extension_settings.quickReply.quickReplySlots[i - 1]?.label).trigger('input');
}
$('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled);
$('#quickReply1Mes').val(extension_settings.quickReply.quickReply1Mes).trigger('input');
$('#quickReply1Label').val(extension_settings.quickReply.quickReply1Label).trigger('input');
$('#quickReply2Mes').val(extension_settings.quickReply.quickReply2Mes).trigger('input');
$('#quickReply2Label').val(extension_settings.quickReply.quickReply2Label).trigger('input');
$('#quickReply3Mes').val(extension_settings.quickReply.quickReply3Mes).trigger('input');
$('#quickReply3Label').val(extension_settings.quickReply.quickReply3Label).trigger('input');
$('#quickReply4Mes').val(extension_settings.quickReply.quickReply4Mes).trigger('input');
$('#quickReply4Label').val(extension_settings.quickReply.quickReply4Label).trigger('input');
$('#quickReply5Mes').val(extension_settings.quickReply.quickReply5Mes).trigger('input');
$('#quickReply5Label').val(extension_settings.quickReply.quickReply5Label).trigger('input');
$('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots);
}
function onQuickReplyInput(id) {
extension_settings.quickReply[`quickReply${id}Mes`] = $(`#quickReply${id}Mes`).val();
extension_settings.quickReply.quickReplySlots[id - 1].mes = $(`#quickReply${id}Mes`).val();
$(`#quickReply${id}`).attr('title', ($(`#quickReply${id}Mes`).val()));
resetScrollHeight($(`#quickReply${id}Mes`));
saveSettingsDebounced();
}
function onQuickReplyLabelInput(id) {
extension_settings.quickReply[`quickReply${id}Label`] = $(`#quickReply${id}Label`).val();
extension_settings.quickReply.quickReplySlots[id - 1].label = $(`#quickReply${id}Label`).val();
$(`#quickReply${id}`).text($(`#quickReply${id}Label`).val());
saveSettingsDebounced();
}
@@ -66,44 +68,111 @@ async function onQuickReplyEnabledInput() {
saveSettingsDebounced();
}
async function sendQuickReply(id) {
var prompt = extension_settings.quickReply[`${id}Mes`];
async function sendQuickReply(index) {
const prompt = extension_settings.quickReply.quickReplySlots[index]?.mes || '';
if (!prompt) {
console.warn(`Quick reply slot ${index} is empty! Aborting.`);
return;
}
$("#send_textarea").val(prompt);
$("#send_but").trigger('click');
}
function addQuickReplyBar(numButtons) {
var numButtons = 5;
const quickReplyBarStartHtml = `
<div id="quickReplyBar" class="flex-container flexGap5">
<div id="quickReplies">
`;
function addQuickReplyBar() {
$('#quickReplyBar').remove();
let quickReplyButtonHtml = '';
for (let i = 0; i < numButtons; i++) {
let quickReplyMes = extension_settings.quickReply[`quickReply${i + 1}Mes`];
let quickReplyLabel = extension_settings.quickReply[`quickReply${i + 1}Label`];
//console.log(quickReplyMes);
quickReplyButtonHtml += `<div title="${quickReplyMes}" class="quickReplyButton" id="quickReply${i + 1}">${quickReplyLabel}</div>`;
for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) {
let quickReplyMes = extension_settings.quickReply.quickReplySlots[i]?.mes || '';
let quickReplyLabel = extension_settings.quickReply.quickReplySlots[i]?.label || '';
quickReplyButtonHtml += `<div title="${quickReplyMes}" class="quickReplyButton" data-index="${i}" id="quickReply${i + 1}">${quickReplyLabel}</div>`;
}
const quickReplyEndHtml = `</div></div>`
const quickReplyBarFullHtml = [quickReplyBarStartHtml, quickReplyButtonHtml, quickReplyEndHtml].join('');
const quickReplyBarFullHtml = `
<div id="quickReplyBar" class="flex-container flexGap5">
<div id="quickReplies">
${quickReplyButtonHtml}
</div>
</div>
`;
$('#send_form').prepend(quickReplyBarFullHtml);
$('.quickReplyButton').on('click', function () {
console.log('got quick reply click');
let quickReplyButtonID = $(this).attr('id');
sendQuickReply(quickReplyButtonID);
let index = $(this).data('index');
sendQuickReply(index);
});
}
async function moduleWorker() {
if (extension_settings.quickReply.quickReplyEnabled === true) {
$('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection');
}
}
async function onQuickReplyNumberOfSlotsInput() {
const $input = $('#quickReplyNumberOfSlots');
let numberOfSlots = Number($input.val());
if (isNaN(numberOfSlots)) {
numberOfSlots = defaultSettings.numberOfSlots;
}
// Clamp min and max values (from input attributes)
if (numberOfSlots < Number($input.attr('min'))) {
numberOfSlots = Number($input.attr('min'));
} else if (numberOfSlots > Number($input.attr('max'))) {
numberOfSlots = Number($input.attr('max'));
}
extension_settings.quickReply.numberOfSlots = numberOfSlots;
extension_settings.quickReply.quickReplySlots.length = numberOfSlots;
// Initialize new slots
for (let i = 0; i < numberOfSlots; i++) {
if (!extension_settings.quickReply.quickReplySlots[i]) {
extension_settings.quickReply.quickReplySlots[i] = {
mes: '',
label: '',
enabled: true,
};
}
}
await loadSettings();
addQuickReplyBar();
moduleWorker();
saveSettingsDebounced();
}
function generateQuickReplyElements() {
let quickReplyHtml = '';
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
quickReplyHtml += `
<div class="flex-container alignitemsflexstart">
<input class="text_pole wide30p" id="quickReply${i}Label" placeholder="(Add a button label)">
<textarea id="quickReply${i}Mes" placeholder="(custom message here)" class="text_pole widthUnset flex1" rows="2"></textarea>
</div>
`;
}
$('#quickReplyContainer').empty().append(quickReplyHtml);
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
$(`#quickReply${i}Mes`).on('input', function () { onQuickReplyInput(i); });
$(`#quickReply${i}Label`).on('input', function () { onQuickReplyLabelInput(i); });
}
$('.quickReplySettings .inline-drawer-toggle').off('click').on('click', function () {
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
initScrollHeight($(`#quickReply${i}Mes`));
}
});
}
jQuery(async () => {
moduleWorker();
@@ -116,57 +185,28 @@ jQuery(async () => {
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="checkbox_label">
<label class="checkbox_label marginBot10">
<input id="quickReplyEnabled" type="checkbox" />
Enable Quick Replies
</label>
<label for="quickReplyNumberOfSlots">Number of slots:</label>
<div class="flex-container flexGap5 flexnowrap">
<input id="quickReplyNumberOfSlots" class="text_pole" type="number" min="1" max="100" value="" />
<div class="menu_button menu_button_icon" id="quickReplyNumberOfSlotsApply">
<div class="fa-solid fa-check"></div>
<span>Apply</span>
</div>
</div>
<small><i>Customize your Quick Replies:</i></small><br>
<div class="flex-container alignitemsflexstart">
<input class="text_pole wide30p" id="quickReply1Label" placeholder="(Add a button label)">
<textarea id="quickReply1Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
</div>
<div class="flex-container alignitemsflexstart">
<input class="text_pole wide30p" id="quickReply2Label" placeholder="(Add a button label)">
<textarea id="quickReply2Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
</div>
<div class="flex-container alignitemsflexstart">
<input class="text_pole wide30p" id="quickReply3Label" placeholder="(Add a button label)">
<textarea id="quickReply3Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
</div>
<div class="flex-container alignitemsflexstart">
<input class="text_pole wide30p" id="quickReply4Label" placeholder="(Add a button label)">
<textarea id="quickReply4Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
</div>
<div class="flex-container alignitemsflexstart">
<input class="text_pole wide30p" id="quickReply5Label" placeholder="(Add a button label)">
<textarea id="quickReply5Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
<div id="quickReplyContainer">
</div>
</div>
</div>`;
$('#extensions_settings2').append(settingsHtml);
$('#quickReply1Mes').on('input', function () { onQuickReplyInput(1); });
$('#quickReply2Mes').on('input', function () { onQuickReplyInput(2); });
$('#quickReply3Mes').on('input', function () { onQuickReplyInput(3); });
$('#quickReply4Mes').on('input', function () { onQuickReplyInput(4); });
$('#quickReply5Mes').on('input', function () { onQuickReplyInput(5); });
$('#quickReply1Label').on('input', function () { onQuickReplyLabelInput(1); });
$('#quickReply2Label').on('input', function () { onQuickReplyLabelInput(2); });
$('#quickReply3Label').on('input', function () { onQuickReplyLabelInput(3); });
$('#quickReply4Label').on('input', function () { onQuickReplyLabelInput(4); });
$('#quickReply5Label').on('input', function () { onQuickReplyLabelInput(5); });
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
$('.quickReplySettings .inline-drawer-toggle').on('click', function () {
initScrollHeight($("#quickReply1Mes"));
initScrollHeight($("#quickReply2Mes"));
initScrollHeight($("#quickReply3Mes"));
initScrollHeight($("#quickReply4Mes"));
initScrollHeight($("#quickReply5Mes"));
})
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
await loadSettings();
addQuickReplyBar();

View File

@@ -10,6 +10,8 @@
justify-content: center;
width: 100%;
display: none;
max-width: 100%;
overflow-x: auto;
}
#quickReplies {
@@ -17,7 +19,7 @@
padding: 0;
display: flex;
justify-content: center;
flex-wrap: nowrap;
flex-wrap: wrap;
gap: 5px;
width: 100%;
}
@@ -41,4 +43,4 @@
opacity: 1;
filter: brightness(1.2);
cursor: pointer;
}
}

View File

@@ -11,7 +11,7 @@ import {
appendImageToMessage
} from "../../../script.js";
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js";
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment } from "../../utils.js";
export { MODULE_NAME };
// Wraps a string into monospace font-face span
@@ -557,7 +557,7 @@ async function sendMessage(prompt, image) {
is_system: context.groupId ? true : false,
is_user: false,
is_name: true,
send_date: Date.now(),
send_date: timestampToMoment(Date.now()).format('LL LT'),
mes: context.groupId ? p(messageText) : messageText,
extra: {
image: image,

View File

@@ -9,6 +9,7 @@ import {
updateMessageBlock,
} from "../../../script.js";
import { extension_settings, getContext } from "../../extensions.js";
import { secret_state, writeSecret } from "../../secrets.js";
const autoModeOptions = {
NONE: 'none',
@@ -134,6 +135,14 @@ const languageCodes = {
'Zulu': 'zu',
};
const KEY_REQUIRED = ['deepl'];
function showKeyButton() {
const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider);
$("#translate_key_button").toggle(providerRequiresKey);
$("#translate_key_button").toggleClass('success', Boolean(secret_state[extension_settings.translate.provider]));
}
function loadSettings() {
for (const key in defaultSettings) {
if (!extension_settings.translate.hasOwnProperty(key)) {
@@ -144,6 +153,7 @@ function loadSettings() {
$(`#translation_provider option[value="${extension_settings.translate.provider}"]`).attr('selected', true);
$(`#translation_target_language option[value="${extension_settings.translate.target_language}"]`).attr('selected', true);
$(`#translation_auto_mode option[value="${extension_settings.translate.auto_mode}"]`).attr('selected', true);
showKeyButton();
}
async function translateImpersonate(text) {
@@ -186,18 +196,39 @@ async function translateProviderGoogle(text, lang) {
throw new Error(response.statusText);
}
async function translateProviderDeepl(text, lang) {
if (!secret_state.deepl) {
throw new Error('No DeepL API key');
}
const response = await fetch('/deepl_translate', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ text: text, lang: lang }),
});
if (response.ok) {
const result = await response.text();
return result;
}
throw new Error(response.statusText);
}
async function translate(text, lang) {
try {
switch (extension_settings.translate.provider) {
case 'google':
return await translateProviderGoogle(text, lang);
case 'deepl':
return await translateProviderDeepl(text, lang);
default:
console.error('Unknown translation provider', extension_settings.translate.provider);
return text;
}
} catch (error) {
console.log(error);
toastr.error('Failed to translate message');
toastr.error(String(error), 'Failed to translate message');
}
}
@@ -331,9 +362,13 @@ jQuery(() => {
<option value="both">Translate both</option>
</select>
<label for="translation_provider">Provider</label>
<select id="translation_provider" name="provider">
<option value="google">Google</option>
<select>
<div class="flex-container gap5px flexnowrap marginBot5">
<select id="translation_provider" name="provider" class="margin0">
<option value="google">Google</option>
<option value="deepl">DeepL</option>
<select>
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
</div>
<label for="translation_target_language">Target Language</label>
<select id="translation_target_language" name="target_language"></select>
<div id="translation_clear" class="menu_button">
@@ -364,6 +399,7 @@ jQuery(() => {
});
$('#translation_provider').on('change', (event) => {
extension_settings.translate.provider = event.target.value;
showKeyButton();
saveSettingsDebounced();
});
$('#translation_target_language').on('change', (event) => {
@@ -371,6 +407,17 @@ jQuery(() => {
saveSettingsDebounced();
});
$(document).on('click', '.mes_translate', onMessageTranslateClick);
$('#translate_key_button').on('click', async () => {
const optionText = $('#translation_provider option:selected').text();
const key = await callPopup(`<h3>${optionText} API Key</h3>`, 'input');
if (key == false) {
return;
}
await writeSecret(extension_settings.translate.provider, key);
toastr.success('API Key saved');
});
loadSettings();

View File

@@ -2,6 +2,5 @@
width: fit-content;
display: flex;
gap: 10px;
align-items: baseline;
flex-direction: row;
}

View File

@@ -57,7 +57,7 @@ export function getPreviewString(lang) {
}
const fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'
return previewStrings[lang] ?? fallbackPreview;
return previewStrings[lang] ?? fallbackPreview;
}
let ttsProviders = {
@@ -173,7 +173,7 @@ function resetTtsPlayback() {
// Reset audio element
audioElement.currentTime = 0;
audioElement.src = '/sounds/silence.mp3';
audioElement.src = '';
// Clear any queue items
ttsJobQueue.splice(0, ttsJobQueue.length);
@@ -412,7 +412,6 @@ async function processTtsQueue() {
// Remove character name from start of the line if power user setting is disabled
if (char && !power_user.allow_name2_display) {
debugger;
const escapedChar = escapeRegex(char);
text = text.replace(new RegExp(`^${escapedChar}:`, 'gm'), '');
}
@@ -646,23 +645,23 @@ $(document).ready(function () {
<div>
<label class="checkbox_label" for="tts_enabled">
<input type="checkbox" id="tts_enabled" name="tts_enabled">
Enabled
<small>Enabled</small>
</label>
<label class="checkbox_label" for="tts_auto_generation">
<input type="checkbox" id="tts_auto_generation">
Auto Generation
</label>
<label class="checkbox_label" for="tts_narrate_dialogues">
<input type="checkbox" id="tts_narrate_dialogues">
Narrate dialogues only
<small>Auto Generation</small>
</label>
<label class="checkbox_label" for="tts_narrate_quoted">
<input type="checkbox" id="tts_narrate_quoted">
Narrate quoted only
<small>Only narrate "quotes"</small>
</label>
<label class="checkbox_label" for="tts_narrate_dialogues">
<input type="checkbox" id="tts_narrate_dialogues">
<small>Ignore *text, even "quotes", inside asterisks*</small>
</label>
<label class="checkbox_label" for="tts_narrate_translated_only">
<input type="checkbox" id="tts_narrate_translated_only">
Narrate only the translated text
<small>Narrate only the translated text</small>
</label>
</div>
<label>Voice Map</label>
@@ -704,26 +703,4 @@ $(document).ready(function () {
const wrapper = new ModuleWorkerWrapper(moduleWorker);
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL) // Init depends on all the things
eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
// Mobiles need to "activate" the Audio element with click before it can be played
if (isMobile()) {
console.debug('Activating mobile audio element on first click');
let audioActivated = false;
// Play silence on first click
$(document).on('click touchend', function () {
// Prevent multiple activations
if (audioActivated) {
return;
}
console.debug('Activating audio element...');
audioActivated = true;
audioElement.src = '/sounds/silence.mp3';
// Reset volume to 1
audioElement.onended = function () {
console.debug('Audio element activated');
};
});
}
})

View File

@@ -219,6 +219,7 @@ class SystemTtsProvider {
chunkLength: 200,
}, function () {
//some code to execute when done
resolve(silence);
console.log('System TTS done');
});
});

View File

@@ -3,6 +3,8 @@ import {
onlyUnique,
debounce,
delay,
isDataURL,
createThumbnail,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime } from "./RossAscends-mods.js";
import { sortCharactersList, sortGroupMembers } from './power-user.js';
@@ -56,6 +58,8 @@ import {
eventSource,
event_types,
getCurrentChatId,
setScenarioOverride,
getCropPopup,
} from "../script.js";
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
@@ -356,6 +360,14 @@ function updateGroupAvatar(group) {
}
function getGroupAvatar(group) {
if (!group) {
return $(`<div class="avatar"><img src="${default_avatar}"></div>`);
}
if (isDataURL(group.avatar_url)) {
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
}
const memberAvatars = [];
if (group && Array.isArray(group.members) && group.members.length) {
for (const member of group.members) {
@@ -489,7 +501,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
activatedMembers = activateListOrder(group.members.slice(0, 1));
}
}
else if (type === "swipe") {
else if (type === "swipe" || type === 'continue') {
activatedMembers = activateSwipe(group.members);
if (activatedMembers.length === 0) {
@@ -512,7 +524,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
toastr.warning('All group members are disabled. Enable at least one to get a reply.');
// Send user message as is
const bias = getBiasStrings(userInput);
const bias = getBiasStrings(userInput, type);
await sendMessageAsUser(userInput, bias.messageBias);
await saveChatConditional();
$('#send_textarea').val('').trigger('input');
@@ -522,7 +534,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
for (const chId of activatedMembers) {
deactivateSendButtons();
isGenerationDone = false;
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" || type == 'continue' ? type : "group_chat";
setCharacterId(chId);
setCharacterName(characters[chId].name)
@@ -924,6 +936,8 @@ function select_group_chats(groupId, skipAnimation) {
const group = groupId && groups.find((x) => x.id == groupId);
const groupName = group?.name ?? "";
setMenuType(!!group ? 'group_edit' : 'group_create');
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
$("#rm_group_chat_name").val(groupName);
$("#rm_group_chat_name").off();
$("#rm_group_chat_name").on("input", async function () {
@@ -960,12 +974,19 @@ function select_group_chats(groupId, skipAnimation) {
? getThumbnailUrl('avatar', character.avatar)
: default_avatar;
const template = $("#group_member_template .group_member").clone();
const isFav = character.fav || character.fav == 'true';
template.data("id", character.avatar);
template.find(".avatar img").attr("src", avatar);
template.find(".avatar img").attr("title", character.avatar);
template.find(".ch_name").text(character.name);
template.attr("chid", characters.indexOf(character));
template.toggleClass('is_fav', character.fav || character.fav == 'true');
template.find('.ch_fav').val(isFav);
template.toggleClass('is_fav', isFav);
// Display inline tags
const tags = getTagsList(character.avatar);
const tagsElement = template.find('.tags');
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
if (!group) {
template.find('[data-action="speak"]').hide();
@@ -985,7 +1006,6 @@ function select_group_chats(groupId, skipAnimation) {
}
sortGroupMembers("#rm_group_add_members .group_member");
filterMembersByFavorites(false);
const groupHasMembers = !!$("#rm_group_members").children().length;
$("#rm_group_submit").prop("disabled", !groupHasMembers);
@@ -1048,6 +1068,67 @@ function select_group_chats(groupId, skipAnimation) {
$("#rm_group_automode_label").hide();
}
$("#group_avatar_button").off('input').on("input", uploadGroupAvatar);
$("#rm_group_restore_avatar").off('click').on("click", restoreGroupAvatar);
async function uploadGroupAvatar(event) {
const file = event.target.files[0];
if (!file) {
return;
}
const e = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(file);
});
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
if (!croppedImage) {
return;
}
const thumbnail = await createThumbnail(croppedImage, 96, 144);
if (!groupId) {
$('#group_avatar_preview img').attr('src', thumbnail);
$('#rm_group_restore_avatar').show();
return;
}
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.avatar_url = thumbnail;
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
$("#rm_group_restore_avatar").show();
await editGroup(groupId, true, true);
}
async function restoreGroupAvatar() {
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
if (!confirm) {
return;
}
if (!groupId) {
$("#group_avatar_preview img").attr("src", default_avatar);
$("#rm_group_restore_avatar").hide();
return;
}
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.avatar_url = '';
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
$("#rm_group_restore_avatar").hide();
await editGroup(groupId, true, true);
}
$(document).off("click", ".group_member .right_menu_button");
$(document).on("click", ".group_member .right_menu_button", async function (event) {
event.stopPropagation();
@@ -1173,8 +1254,7 @@ async function createGroup() {
name = `Group: ${memberNames}`;
}
// placeholder
const avatar_url = 'img/five.png';
const avatar_url = $('#group_avatar_preview img').attr('src');
const chatName = humanizedDateTime();
const chats = [chatName];
@@ -1185,7 +1265,7 @@ async function createGroup() {
body: JSON.stringify({
name: name,
members: members,
avatar_url: avatar_url,
avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
allow_self_responses: allow_self_responses,
activation_strategy: activation_strategy,
disabled_members: [],
@@ -1204,24 +1284,6 @@ async function createGroup() {
}
}
function toggleFilterByFavorites() {
filterMembersByFavorites(!fav_filter_on);
}
function filterMembersByFavorites(value) {
fav_filter_on = value;
$('#group_fav_filter').toggleClass('fav_on', fav_filter_on);
if (!fav_filter_on) {
$("#rm_group_add_members .group_member").removeClass('hiddenByFav');
} else {
$("#rm_group_add_members .group_member").each(function () {
const isValidSearch = $(this).hasClass("is_fav");
$(this).toggleClass('hiddenByFav', !isValidSearch);
});
}
}
export async function createNewGroupChat(groupId) {
const group = groups.find(x => x.id === groupId);
@@ -1397,27 +1459,6 @@ export async function saveGroupBookmarkChat(groupId, name, metadata) {
});
}
function setGroupScenario() {
if (!selected_group) {
return;
}
const template = $('#group_scenario_template .group_scenario').clone();
const metadataValue = chat_metadata['scenario'] || '';
template.find('.group_chat_scenario').text(metadataValue);
callPopup(template.get(0).outerHTML, 'text');
}
function onGroupScenarioInput() {
const value = $(this).val();
const metadata = { scenario: value, };
updateChatMetadata(metadata, false);
}
function onGroupScenarioRemoveClick() {
$(this).closest('.group_scenario').find('.group_chat_scenario').val('').trigger('input');
}
function onSendTextareaInput() {
if (is_group_automode_enabled) {
// Wait for current automode generation to finish
@@ -1437,12 +1478,9 @@ function stopAutoModeGeneration() {
jQuery(() => {
$(document).on("click", ".group_select", selectGroup);
$(document).on("input", ".group_chat_scenario", onGroupScenarioInput);
$(document).on("click", ".remove_scenario_override", onGroupScenarioRemoveClick);
$("#rm_group_filter").on("input", filterGroupMembers);
$("#group_fav_filter").on("click", toggleFilterByFavorites);
$("#rm_group_submit").on("click", createGroup);
$("#rm_group_scenario").on("click", setGroupScenario);
$("#rm_group_scenario").on("click", setScenarioOverride);
$("#rm_group_automode").on("input", function () {
const value = $(this).prop("checked");
is_group_automode_enabled = value;

View File

@@ -225,12 +225,10 @@ async function showKudos() {
}
jQuery(function () {
let hordeModelSelectScrollTop = null;
$("#horde_model").on('mousedown change', async function (e) {
//desktop-only routine for multi-select without CTRL
if (deviceInfo.device.type === 'desktop') {
/*if (deviceInfo.device.type === 'desktop') {
let hordeModelSelectScrollTop = null;
e.preventDefault();
const option = $(e.target);
const selectElement = $(this)[0];
@@ -238,7 +236,7 @@ jQuery(function () {
option.prop('selected', !option.prop('selected'));
await delay(1);
selectElement.scrollTop = hordeModelSelectScrollTop;
}
}*/
horde_settings.models = $('#horde_model').val();
console.log('Updated Horde models', horde_settings.models);
});
@@ -265,4 +263,22 @@ jQuery(function () {
$("#horde_refresh").on("click", getHordeModels);
$("#horde_kudos").on("click", showKudos);
// Not needed on mobile
if (deviceInfo.device.type === 'desktop') {
$('#horde_model').select2({
width: '100%',
placeholder: 'Select Horde models',
allowClear: true,
closeOnSelect: false,
templateSelection: function(data) {
// Customize the pillbox text by shortening the full text
return data.id;
},
templateResult: function(data) {
// Return the full text for the dropdown
return data.text;
},
});
}
})

View File

@@ -26,6 +26,7 @@ const kai_settings = {
single_line: false,
use_stop_sequence: false,
streaming_kobold: false,
sampler_order: [0, 1, 2, 3, 4, 5, 6],
};
const MIN_STOP_SEQUENCE_VERSION = '1.2.2';
@@ -68,11 +69,12 @@ function loadKoboldSettings(preset) {
}
}
function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, this_max_context, isImpersonate) {
function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, this_max_context, isImpersonate, type) {
const sampler_order = kai_settings.sampler_order || this_settings.sampler_order;
let generate_data = {
prompt: finalPromt,
gui_settings: false,
sampler_order: this_settings.sampler_order,
sampler_order: sampler_order,
max_context_length: parseInt(this_max_context),
max_length: this_amount_gen,
rep_pen: parseFloat(kai_settings.rep_pen),
@@ -84,17 +86,17 @@ function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, thi
top_k: kai_settings.top_k,
top_p: kai_settings.top_p,
typical: kai_settings.typical,
s1: this_settings.sampler_order[0],
s2: this_settings.sampler_order[1],
s3: this_settings.sampler_order[2],
s4: this_settings.sampler_order[3],
s5: this_settings.sampler_order[4],
s6: this_settings.sampler_order[5],
s7: this_settings.sampler_order[6],
s1: sampler_order[0],
s2: sampler_order[1],
s3: sampler_order[2],
s4: sampler_order[3],
s5: sampler_order[4],
s6: sampler_order[5],
s7: sampler_order[6],
use_world_info: false,
singleline: kai_settings.single_line,
stop_sequence: kai_settings.use_stop_sequence ? getStoppingStrings(isImpersonate, false) : undefined,
streaming: kai_settings.streaming_kobold && kai_settings.can_use_streaming,
streaming: kai_settings.streaming_kobold && kai_settings.can_use_streaming && type !== 'quiet',
can_abort: kai_settings.can_use_streaming,
};
return generate_data;
@@ -206,6 +208,13 @@ const sliders = [
format: (val) => val,
setValue: (val) => { kai_settings.rep_pen_slope = Number(val); },
},
{
name: "sampler_order",
sliderId: "#no_op_selector",
counterId: "#no_op_selector",
format: (val) => val,
setValue: (val) => { sortItemsByOrder(val); },
}
];
function canUseKoboldStopSequence(version) {
@@ -213,11 +222,22 @@ function canUseKoboldStopSequence(version) {
}
function canUseKoboldStreaming(koboldVersion) {
if (koboldVersion.result == 'KoboldCpp') {
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
return (koboldVersion.version || '0.0').localeCompare(MIN_STREAMING_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
} else return false;
}
function sortItemsByOrder(orderArray) {
console.debug('Preset samplers order: ' + orderArray);
const $draggableItems = $("#kobold_order");
for (let i = 0; i < orderArray.length; i++) {
const index = orderArray[i];
const $item = $draggableItems.find(`[data-id="${index}"]`).detach();
$draggableItems.append($item);
}
}
$(document).ready(function () {
sliders.forEach(slider => {
$(document).on("input", slider.sliderId, function () {
@@ -240,4 +260,16 @@ $(document).ready(function () {
kai_settings.streaming_kobold = value;
saveSettingsDebounced();
});
$('#kobold_order').sortable({
stop: function () {
const order = [];
$('#kobold_order').children().each(function () {
order.push($(this).data('id'));
});
kai_settings.sampler_order = order;
console.log('Samplers reordered:', kai_settings.sampler_order);
saveSettingsDebounced();
},
});
});

View File

@@ -20,6 +20,7 @@ import {
system_message_types,
replaceBiasMarkup,
is_send_press,
main_api,
} from "../script.js";
import { groups, selected_group } from "./group-chats.js";
@@ -35,6 +36,7 @@ import {
import {
delay,
download,
getFileText,
getStringHash,
parseJsonFile,
stringFormat,
@@ -84,7 +86,8 @@ const gpt3_16k_max = 16383;
const gpt4_max = 8191;
const gpt_neox_max = 2048;
const gpt4_32k_max = 32767;
const claude_max = 7500;
const claude_max = 8000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
const palm2_max = 7500; // The real context window is 8192, spare some for padding due to using turbo tokenizer
const claude_100k_max = 99000;
const unlocked_max = 100 * 1024;
const oai_max_temp = 2.0;
@@ -130,6 +133,7 @@ const default_settings = {
legacy_streaming: false,
chat_completion_source: chat_completion_sources.OPENAI,
max_context_unlocked: false,
use_openrouter: false,
};
const oai_settings = {
@@ -163,6 +167,7 @@ const oai_settings = {
legacy_streaming: false,
chat_completion_source: chat_completion_sources.OPENAI,
max_context_unlocked: false,
use_openrouter: false,
};
let openai_setting_names;
@@ -209,7 +214,7 @@ function setOpenAIMessages(chat) {
}
// for groups or sendas command - prepend a character's name
if (selected_group || chat[j].force_avatar) {
if (selected_group || (chat[j].force_avatar && chat[j].name !== name1)) {
content = `${chat[j].name}: ${content}`;
}
@@ -319,7 +324,7 @@ function formatWorldInfo(value) {
return stringFormat(oai_settings.wi_format, value);
}
async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldInfoBefore, worldInfoAfter, extensionPrompt, bias, type, quietPrompt, jailbreakPrompt } = {}) {
async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldInfoBefore, worldInfoAfter, extensionPrompt, bias, type, quietPrompt, jailbreakPrompt, cyclePrompt } = {}) {
const isImpersonate = type == "impersonate";
let this_max_context = oai_settings.openai_max_context;
let enhance_definitions_prompt = "";
@@ -336,7 +341,7 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
let whole_prompt = getSystemPrompt(systemPrompt, nsfw_toggle_prompt, enhance_definitions_prompt, wiBefore, storyString, wiAfter, extensionPrompt, isImpersonate);
// Join by a space and replace placeholders with real user/char names
storyString = substituteParams(whole_prompt.join("\n")).replace(/\r/gm, '').trim();
storyString = substituteParams(whole_prompt.join("\n"), name1, name2, oai_settings.main_prompt).replace(/\r/gm, '').trim();
let prompt_msg = { "role": "system", "content": storyString }
let examples_tosend = [];
@@ -385,7 +390,7 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
const jailbreak = power_user.prefer_character_jailbreak && jailbreakPrompt ? jailbreakPrompt : oai_settings.jailbreak_prompt;
if (oai_settings.jailbreak_system && jailbreak) {
const jailbreakMessage = { "role": "system", "content": substituteParams(jailbreak) };
const jailbreakMessage = { "role": "system", "content": substituteParams(jailbreak, name1, name2, oai_settings.jailbreak_prompt) };
openai_msgs.push(jailbreakMessage);
total_count += handler_instance.count([jailbreakMessage], true, 'jailbreak');
@@ -406,6 +411,14 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
await delay(1);
}
if (type == 'continue') {
const continueNudge = { "role": "system", "content": stringFormat('[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message:\n\n{0}]', cyclePrompt || '') };
openai_msgs.push(continueNudge);
total_count += handler_instance.count([continueNudge], true, 'continue');
await delay(1);
}
// The user wants to always have all example messages in the context
if (power_user.pin_examples) {
// first we send *all* example messages
@@ -566,8 +579,8 @@ async function sendWindowAIRequest(openai_msgs_tosend, signal, stream) {
const currentModel = await window.ai.getCurrentModel();
let temperature = parseFloat(oai_settings.temp_openai);
if (currentModel.includes('claude') && temperature > claude_max_temp) {
console.warn(`Claude model only supports temperature up to ${claude_max_temp}. Clamping ${temperature} to ${claude_max_temp}.`);
if ((currentModel.includes('claude') || currentModel.includes('palm-2')) && temperature > claude_max_temp) {
console.warn(`Claude and PaLM models only supports temperature up to ${claude_max_temp}. Clamping ${temperature} to ${claude_max_temp}.`);
temperature = claude_max_temp;
}
@@ -647,6 +660,19 @@ async function sendWindowAIRequest(openai_msgs_tosend, signal, stream) {
}
}
function getChatCompletionModel() {
switch (oai_settings.chat_completion_source) {
case chat_completion_sources.CLAUDE:
return oai_settings.claude_model;
case chat_completion_sources.OPENAI:
return oai_settings.openai_model;
case chat_completion_sources.WINDOWAI:
return oai_settings.windowai_model;
default:
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
}
}
async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
// Provide default abort signal
if (!signal) {
@@ -659,23 +685,24 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
let logit_bias = {};
const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
const isOpenRouter = oai_settings.use_openrouter && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI;
const stream = type !== 'quiet' && oai_settings.stream_openai;
// If we're using the window.ai extension, use that instead
// Doesn't support logit bias yet
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && !oai_settings.use_openrouter) {
return sendWindowAIRequest(openai_msgs_tosend, signal, stream);
}
if (oai_settings.bias_preset_selected
&& !isClaude // Claude doesn't support logit bias
&& oai_settings.chat_completion_source == chat_completion_sources.OPENAI
&& Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
&& oai_settings.bias_presets[oai_settings.bias_preset_selected].length) {
logit_bias = biasCache || await calculateLogitBias();
biasCache = logit_bias;
}
const model = isClaude ? oai_settings.claude_model : oai_settings.openai_model;
const model = getChatCompletionModel();
const generate_data = {
"messages": openai_msgs_tosend,
"model": model,
@@ -689,6 +716,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
"reverse_proxy": oai_settings.reverse_proxy,
"logit_bias": logit_bias,
"use_claude": isClaude,
"use_openrouter": isOpenRouter,
};
const generate_url = '/generate_openai';
@@ -764,8 +792,8 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
function getStreamingReply(getMessage, data) {
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
getMessage = data.completion || "";
} else{
getMessage += data.choices[0]["delta"]["content"] || "";
} else {
getMessage += data.choices[0]?.delta?.content || data.choices[0]?.message?.content || "";
}
return getMessage;
}
@@ -925,16 +953,15 @@ function getTokenizerModel() {
return turboTokenizer;
}
else if (oai_settings.windowai_model.includes('claude')) {
return turboTokenizer;
return 'claude';
}
else if (oai_settings.windowai_model.includes('GPT-NeoXT')) {
return 'gpt2';
}
}
// We don't have a Claude tokenizer for JS yet. Turbo 3.5 should be able to handle this.
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
return turboTokenizer;
return 'claude';
}
// Default to Turbo 3.5
@@ -978,6 +1005,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.claude_model = settings.claude_model ?? default_settings.claude_model;
oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model;
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
oai_settings.use_openrouter = settings.use_openrouter ?? default_settings.use_openrouter;
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
@@ -989,8 +1017,11 @@ function loadOpenAISettings(data, settings) {
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
$('#model_openai_select').val(oai_settings.openai_model);
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
$('#model_claude_select').val(oai_settings.claude_model);
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
$('#model_windowai_select').val(oai_settings.windowai_model);
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true);
$('#openai_max_context').val(oai_settings.openai_max_context);
$('#openai_max_context_counter').text(`${oai_settings.openai_max_context}`);
@@ -1051,11 +1082,12 @@ function loadOpenAISettings(data, settings) {
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
$('#use_openrouter').prop('checked', oai_settings.use_openrouter);
}
async function getStatusOpen() {
if (is_get_status_openai) {
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && !oai_settings.use_openrouter) {
let status;
if ('ai' in window) {
@@ -1078,6 +1110,7 @@ async function getStatusOpen() {
let data = {
reverse_proxy: oai_settings.reverse_proxy,
use_openrouter: oai_settings.use_openrouter && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI,
};
return jQuery.ajax({
@@ -1085,7 +1118,7 @@ async function getStatusOpen() {
url: '/getstatus_openai', //
data: JSON.stringify(data),
beforeSend: function () {
if (oai_settings.reverse_proxy) {
if (oai_settings.reverse_proxy && !data.use_openrouter) {
validateReverseProxy();
}
},
@@ -1134,6 +1167,11 @@ function trySelectPresetByName(name) {
}
}
// Don't change if the current preset is the same
if (preset_found && preset_found === oai_settings.preset_settings_openai) {
return;
}
if (preset_found) {
oai_settings.preset_settings_openai = preset_found;
const value = openai_setting_names[preset_found]
@@ -1148,6 +1186,7 @@ async function saveOpenAIPreset(name, settings) {
openai_model: settings.openai_model,
claude_model: settings.claude_model,
windowai_model: settings.windowai_model,
use_openrouter: settings.use_openrouter,
temperature: settings.temp_openai,
frequency_penalty: settings.freq_pen_openai,
presence_penalty: settings.pres_pen_openai,
@@ -1171,6 +1210,7 @@ async function saveOpenAIPreset(name, settings) {
max_context_unlocked: settings.max_context_unlocked,
nsfw_avoidance_prompt: settings.nsfw_avoidance_prompt,
wi_format: settings.wi_format,
stream_openai: settings.stream_openai,
};
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
@@ -1198,6 +1238,8 @@ async function saveOpenAIPreset(name, settings) {
option.innerText = data.name;
$('#settings_perset_openai').append(option).trigger('change');
}
} else {
toastr.error('Failed to save preset');
}
}
@@ -1303,8 +1345,80 @@ function addLogitBiasPresetOption(name) {
$('#openai_logit_bias_preset').trigger('change');
}
function onImportPresetClick() {
$('#openai_preset_import_file').trigger('click');
}
function onLogitBiasPresetImportClick() {
$('#openai_logit_bias_import_file').click();
$('#openai_logit_bias_import_file').trigger('click');
}
async function onPresetImportFileChange(e) {
const file = e.target.files[0];
if (!file) {
return;
}
const name = file.name.replace(/\.[^/.]+$/, "");
const importedFile = await getFileText(file);
let presetBody;
e.target.value = '';
try {
presetBody = JSON.parse(importedFile);
} catch (err) {
toastr.error('Invalid file');
return;
}
if (name in openai_setting_names) {
const confirm = await callPopup('Preset name already exists. Overwrite?', 'confirm');
if (!confirm) {
return;
}
}
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
method: 'POST',
headers: getRequestHeaders(),
body: importedFile,
});
if (!savePresetSettings.ok) {
toastr.error('Failed to save preset');
return;
}
const data = await savePresetSettings.json();
if (Object.keys(openai_setting_names).includes(data.name)) {
oai_settings.preset_settings_openai = data.name;
const value = openai_setting_names[data.name];
Object.assign(openai_settings[value], presetBody);
$(`#settings_perset_openai option[value="${value}"]`).attr('selected', true);
$('#settings_perset_openai').trigger('change');
} else {
openai_settings.push(presetBody);
openai_setting_names[data.name] = openai_settings.length - 1;
const option = document.createElement('option');
option.selected = true;
option.value = openai_settings.length - 1;
option.innerText = data.name;
$('#settings_perset_openai').append(option).trigger('change');
}
}
async function onExportPresetClick() {
if (!oai_settings.preset_settings_openai) {
toastr.error('No preset selected');
return;
}
const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]];
const presetJsonString = JSON.stringify(preset, null, 4);
download(presetJsonString, oai_settings.preset_settings_openai, 'application/json');
}
async function onLogitBiasPresetImportFileChange(e) {
@@ -1351,7 +1465,7 @@ function onLogitBiasPresetExportClick() {
return;
}
const presetJsonString = JSON.stringify(oai_settings.bias_presets[oai_settings.bias_preset_selected]);
const presetJsonString = JSON.stringify(oai_settings.bias_presets[oai_settings.bias_preset_selected], null, 4);
download(presetJsonString, oai_settings.bias_preset_selected, 'application/json');
}
@@ -1445,6 +1559,8 @@ function onSettingsPresetChange() {
legacy_streaming: ['#legacy_streaming', 'legacy_streaming', true],
nsfw_avoidance_prompt: ['#nsfw_avoidance_prompt_textarea', 'nsfw_avoidance_prompt', false],
wi_format: ['#wi_format_textarea', 'wi_format', false],
stream_openai: ['#stream_toggle', 'stream_openai', true],
use_openrouter: ['#use_openrouter', 'use_openrouter', true],
};
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
@@ -1463,12 +1579,12 @@ function onSettingsPresetChange() {
saveSettingsDebounced();
}
function onModelChange() {
const value = $(this).val();
async function onModelChange() {
let value = $(this).val();
if ($(this).is('#model_claude_select')) {
console.log('Claude model changed to', value);
oai_settings.claude_model = value;
oai_settings.claude_model = value;
}
if ($(this).is('#model_windowai_select')) {
@@ -1490,10 +1606,11 @@ function onModelChange() {
}
else {
$('#openai_max_context').attr('max', claude_max);
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, claude_max);
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
}
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
$('#openai_reverse_proxy').attr('placeholder', 'https://api.anthropic.com/v1');
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
@@ -1501,6 +1618,10 @@ function onModelChange() {
}
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
if (value == '' && 'ai' in window) {
value = (await window.ai.getCurrentModel()) || '';
}
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
}
@@ -1516,9 +1637,15 @@ function onModelChange() {
else if (value.includes('gpt-3.5')) {
$('#openai_max_context').attr('max', gpt3_max);
}
else if (value.includes('gpt-4-32k')) {
$('#openai_max_context').attr('max', gpt4_32k_max);
}
else if (value.includes('gpt-4')) {
$('#openai_max_context').attr('max', gpt4_max);
}
else if (value.includes('palm-2')) {
$('#openai_max_context').attr('max', palm2_max);
}
else if (value.includes('GPT-NeoXT')) {
$('#openai_max_context').attr('max', gpt_neox_max);
}
@@ -1527,10 +1654,10 @@ function onModelChange() {
$('#openai_max_context').attr('max', gpt3_max);
}
oai_settings.openai_max_context = Math.max(Number($('#openai_max_context').val()), oai_settings.openai_max_context);
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
if (value.includes('claude')) {
if (value.includes('claude') || value.includes('palm-2')) {
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
}
@@ -1557,7 +1684,7 @@ function onModelChange() {
$('#openai_max_context').attr('max', gpt3_max);
}
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
$('#openai_reverse_proxy').attr('placeholder', 'https://api.openai.com/v1');
@@ -1596,6 +1723,17 @@ async function onConnectButtonClick(e) {
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
is_get_status_openai = true;
is_api_button_press_openai = true;
const api_key_openrouter = $('#api_key_openrouter').val().trim();
if (api_key_openrouter.length) {
await writeSecret(SECRET_KEYS.OPENROUTER, api_key_openrouter);
}
if (oai_settings.use_openrouter && !secret_state[SECRET_KEYS.OPENROUTER]) {
console.log('No secret key saved for OpenRouter');
return;
}
return await getStatusOpen();
}
@@ -1667,6 +1805,12 @@ async function testApiConnection() {
}
}
function reconnectOpenAi() {
setOnlineStatus('no_connection');
resultCheckStatusOpen();
$('#api_button_openai').trigger('click');
}
$(document).ready(function () {
$('#test_api_button').on('click', testApiConnection);
@@ -1850,10 +1994,11 @@ $(document).ready(function () {
$('#chat_completion_source').on('change', function () {
oai_settings.chat_completion_source = $(this).find(":selected").val();
toggleChatCompletionForms();
setOnlineStatus('no_connection');
resultCheckStatusOpen();
$('#api_button_openai').trigger('click');
saveSettingsDebounced();
if (main_api == 'openai') {
reconnectOpenAi();
}
});
$('#oai_max_context_unlocked').on('input', function () {
@@ -1862,6 +2007,12 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$('#use_openrouter').on('input', function () {
oai_settings.use_openrouter = !!$(this).prop('checked');
reconnectOpenAi();
saveSettingsDebounced();
});
$("#api_button_openai").on("click", onConnectButtonClick);
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
$("#model_openai_select").on("change", onModelChange);
@@ -1875,7 +2026,10 @@ $(document).ready(function () {
$("#openai_logit_bias_new_preset").on("click", createNewLogitBiasPreset);
$("#openai_logit_bias_new_entry").on("click", createNewLogitBiasEntry);
$("#openai_logit_bias_import_file").on("input", onLogitBiasPresetImportFileChange);
$("#openai_preset_import_file").on("input", onPresetImportFileChange);
$("#export_oai_preset").on("click", onExportPresetClick);
$("#openai_logit_bias_import_preset").on("click", onLogitBiasPresetImportClick);
$("#openai_logit_bias_export_preset").on("click", onLogitBiasPresetExportClick);
$("#openai_logit_bias_delete_preset").on("click", onLogitBiasPresetDeleteClick);
$("#import_oai_preset").on("click", onImportPresetClick);
});

View File

@@ -8,13 +8,16 @@ import {
eventSource,
event_types,
scrollChatToBottom,
name1,
name2,
} from "../script.js";
import { power_user } from "./power-user.js";
import {
SECRET_KEYS,
secret_state,
writeSecret,
} from "./secrets.js";
import { delay, splitRecursive } from "./utils.js";
import { RateLimiter, delay, splitRecursive } from "./utils.js";
export {
is_get_status_poe,
@@ -44,8 +47,8 @@ If you have any objections to these requirements, please mention them specifical
If you accept the requirements, please confirm this by replying with "${DEFAULT_JAILBREAK_RESPONSE}", and nothing more. Upon receiving your accurate confirmation message, I will specify the context of the scene and {{char}}'s characteristics, background, and personality in the next message.`;
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Unless otherwise stated by {{user}}, your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response.]";
const DEFAULT_IMPERSONATION_PROMPT = "[Write 1 reply only in internet RP style from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Don't write as {{char}} or system.]";
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Unless otherwise stated by {{user}}, your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response. Never reply with a full stop.]";
const DEFAULT_IMPERSONATION_PROMPT = "[Write a reply only from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Don't write as {{char}} or system.]";
const poe_settings = {
bot: 'a2',
@@ -66,6 +69,8 @@ let is_get_status_poe = false;
let is_poe_button_press = false;
let abortControllerSuggest = null;
const rateLimiter = new RateLimiter((60 / 10 * 1000)); // 10 requests per minute
function loadPoeSettings(settings) {
if (settings.poe_settings) {
Object.assign(poe_settings, settings.poe_settings);
@@ -184,13 +189,16 @@ function onBotChange() {
saveSettingsDebounced();
}
export function appendPoeAnchors(type, prompt) {
export function appendPoeAnchors(type, prompt, jailbreakPrompt) {
const isImpersonate = type === 'impersonate';
const isQuiet = type === 'quiet';
if (poe_settings.character_nudge && !isQuiet && !isImpersonate) {
let characterNudge = '\n' + substituteParams(poe_settings.character_nudge_message);
prompt += characterNudge;
if (power_user.prefer_character_jailbreak && jailbreakPrompt) {
prompt += '\n' + substituteParams(jailbreakPrompt, name1, name2, poe_settings.character_nudge_message);
} else {
prompt += '\n' + substituteParams(poe_settings.character_nudge_message);
}
}
if (poe_settings.impersonation_prompt && isImpersonate) {
@@ -263,24 +271,27 @@ async function generatePoe(type, finalPrompt, signal) {
}
const isQuiet = type === 'quiet';
const isImpersonate = type === 'impersonate';
const isContinue = type === 'continue';
const suggestReplies = !isQuiet && !isImpersonate && !isContinue;
let reply = '';
if (max_context > POE_TOKEN_LENGTH && poe_settings.bot !== 'a2_100k') {
console.debug('Prompt is too long, sending in chunks');
const result = await sendChunkedMessage(finalPrompt, !isQuiet, signal)
const result = await sendChunkedMessage(finalPrompt, !isQuiet, suggestReplies, signal)
reply = result.reply;
messages_to_purge = result.chunks + 1; // +1 for the reply
}
else {
console.debug('Sending prompt in one message');
reply = await sendMessage(finalPrompt, !isQuiet, !isQuiet, signal);
reply = await sendMessage(finalPrompt, !isQuiet, suggestReplies, signal);
messages_to_purge = 2; // prompt and the reply
}
return reply;
}
async function sendChunkedMessage(finalPrompt, withStreaming, signal) {
async function sendChunkedMessage(finalPrompt, withStreaming, withSuggestions, signal) {
const fastReplyPrompt = '\n[Reply to this message with a full stop only]';
const promptChunks = splitRecursive(finalPrompt, CHUNKED_PROMPT_LENGTH - fastReplyPrompt.length);
console.debug(`Splitting prompt into ${promptChunks.length} chunks`, promptChunks);
@@ -291,7 +302,7 @@ async function sendChunkedMessage(finalPrompt, withStreaming, signal) {
console.debug(`Sending chunk ${i + 1}/${promptChunks.length}: ${promptChunk}`);
if (i == promptChunks.length - 1) {
// Extract reply of the last chunk
reply = await sendMessage(promptChunk, withStreaming, true, signal);
reply = await sendMessage(promptChunk, withStreaming, withSuggestions, signal);
} else {
// Add fast reply prompt to the chunk
promptChunk += fastReplyPrompt;
@@ -333,6 +344,8 @@ async function sendMessage(prompt, withStreaming, withSuggestions, signal) {
signal = new AbortController().signal;
}
await rateLimiter.waitForResolve(signal);
const body = JSON.stringify({
bot: poe_settings.bot,
streaming: withStreaming && poe_settings.streaming,

View File

@@ -12,18 +12,26 @@ import {
eventSource,
event_types,
getCurrentChatId,
is_send_press,
printCharacters,
name1,
name2,
replaceCurrentChat,
setCharacterId
} from "../script.js";
import { favsToHotswap } from "./RossAscends-mods.js";
import { favsToHotswap, isMobile, initMovingUI } from "./RossAscends-mods.js";
import {
groups,
resetSelectedGroup,
selected_group,
} from "./group-chats.js";
import { registerSlashCommand } from "./slash-commands.js";
import { delay } from "./utils.js";
export {
loadPowerUserSettings,
loadMovingUIState,
collapseNewlines,
playMessageSound,
sortGroupMembers,
@@ -43,9 +51,10 @@ const avatar_styles = {
RECTANGULAR: 1,
}
const chat_styles = {
export const chat_styles = {
DEFAULT: 0,
BUBBLES: 1,
DOCUMENT: 2,
}
const sheld_width = {
@@ -74,6 +83,13 @@ const send_on_enter_options = {
ENABLED: 1,
}
export const persona_description_positions = {
BEFORE_CHAR: 0,
AFTER_CHAR: 1,
TOP_AN: 2,
BOTTOM_AN: 3,
}
let power_user = {
tokenizer: tokenizers.CLASSIC,
token_padding: 64,
@@ -88,17 +104,24 @@ let power_user = {
trim_sentences: false,
include_newline: false,
always_force_name2: false,
user_prompt_bias: '',
show_user_prompt_bias: true,
multigen: false,
multigen_first_chunk: 50,
multigen_next_chunks: 30,
custom_chat_separator: '',
markdown_escape_strings: '',
fast_ui_mode: true,
avatar_style: avatar_styles.ROUND,
chat_display: chat_styles.DEFAULT,
sheld_width: sheld_width.DEFAULT,
never_resize_avatars: false,
show_card_avatar_urls: false,
play_message_sound: false,
play_sound_unfocused: true,
auto_save_msg_edits: false,
sort_field: 'name',
sort_order: 'asc',
sort_rule: null,
@@ -109,14 +132,16 @@ let power_user = {
main_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBodyColor').trim()}`,
italics_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeEmColor').trim()}`,
quote_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeQuoteColor').trim()}`,
fastui_bg_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeFastUIBGColor').trim()}`,
blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBlurTintColor').trim()}`,
user_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeUserMesBlurTintColor').trim()}`,
bot_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBotMesBlurTintColor').trim()}`,
shadow_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeShadowColor').trim()}`,
waifuMode: false,
movingUI: false,
movingUIState: {},
noShadows: false,
theme: 'Default (Dark)',
theme: 'Default (Dark) 1.7.1',
auto_swipe: false,
auto_swipe_minimum_length: 0,
@@ -129,11 +154,16 @@ let power_user = {
render_formulas: false,
allow_name1_display: false,
allow_name2_display: false,
//removeXML: false,
hotswap_enabled: true,
timer_enabled: true,
timestamps_enabled: true,
mesIDDisplay_enabled: false,
max_context_unlocked: false,
prefer_character_prompt: true,
prefer_character_jailbreak: true,
continue_on_send: false,
trim_spaces: true,
instruct: {
enabled: false,
@@ -146,10 +176,15 @@ let power_user = {
output_sequence: '### Response:',
preset: 'Alpaca',
separator_sequence: '',
macro: false,
},
personas: {},
default_persona: null,
persona_descriptions: {},
persona_description: '',
persona_description_position: persona_description_positions.BEFORE_CHAR,
};
let themes = [];
@@ -165,8 +200,9 @@ const storage_keys = {
main_text_color: "TavernAI_main_text_color",
italics_text_color: "TavernAI_italics_text_color",
quote_text_color: "TavernAI_quote_text_color",
fastui_bg_color: "TavernAI_fastui_bg_color",
blur_tint_color: "TavernAI_blur_tint_color",
user_mes_blur_tint_color: "TavernAI_user_mes_blur_tint_color",
bot_mes_blur_tint_color: "TavernAI_bot_mes_blur_tint_color",
blur_strength: "TavernAI_blur_strength",
shadow_color: "TavernAI_shadow_color",
shadow_width: "TavernAI_shadow_width",
@@ -177,6 +213,8 @@ const storage_keys = {
hotswap_enabled: 'HotswapEnabled',
timer_enabled: 'TimerEnabled',
timestamps_enabled: 'TimestampsEnabled',
mesIDDisplay_enabled: 'mesIDDisplayEnabled',
};
let browser_has_focus = true;
@@ -247,6 +285,20 @@ function switchTimer() {
$("#messageTimerEnabled").prop("checked", power_user.timer_enabled);
}
function switchTimestamps() {
const value = localStorage.getItem(storage_keys.timestamps_enabled);
power_user.timestamps_enabled = value === null ? true : value == "true";
$("body").toggleClass("no-timestamps", !power_user.timestamps_enabled);
$("#messageTimestampsEnabled").prop("checked", power_user.timestamps_enabled);
}
function switchMesIDDisplay() {
const value = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
power_user.mesIDDisplay_enabled = value === null ? true : value == "true";
$("body").toggleClass("no-mesIDDisplay", !power_user.mesIDDisplay_enabled);
$("#MesIDDisplayEnabled").prop("checked", power_user.mesIDDisplay_enabled);
}
function switchUiMode() {
const fastUi = localStorage.getItem(storage_keys.fast_ui_mode);
power_user.fast_ui_mode = fastUi === null ? true : fastUi == "true";
@@ -259,7 +311,6 @@ function toggleWaifu() {
}
function switchWaifuMode() {
console.log(`switching waifu to ${power_user.waifuMode}`);
$("body").toggleClass("waifuMode", power_user.waifuMode);
$("#waifuMode").prop("checked", power_user.waifuMode);
scrollChatToBottom();
@@ -269,7 +320,12 @@ function switchMovingUI() {
const movingUI = localStorage.getItem(storage_keys.movingUI);
power_user.movingUI = movingUI === null ? false : movingUI == "true";
$("body").toggleClass("movingUI", power_user.movingUI);
scrollChatToBottom();
if (power_user.movingUI === true) {
initMovingUI()
if (power_user.movingUIState) {
loadMovingUIState();
}
};
}
function noShadows() {
@@ -288,10 +344,35 @@ function applyAvatarStyle() {
}
function applyChatDisplay() {
power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
$("body").toggleClass("bubblechat", power_user.chat_display === chat_styles.BUBBLES);
$(`input[name="chat_display"][value="${power_user.chat_display}"]`).prop("checked", true);
if (!power_user.chat_display === (null || undefined)) {
console.debug('applyChatDisplay: saw no chat display type defined')
return
}
console.debug(`applyChatDisplay: applying ${power_user.chat_display}`)
$(`#chat_display option[value=${power_user.chat_display}]`).attr("selected", true)
switch (power_user.chat_display) {
case 0: {
console.log('applying default chat')
$("body").removeClass("bubblechat");
$("body").removeClass("documentstyle");
break
}
case 1: {
console.log('applying bubblechat')
$("body").addClass("bubblechat");
$("body").removeClass("documentstyle");
break
}
case 2: {
console.log('applying document style')
$("body").removeClass("bubblechat");
$("body").addClass("documentstyle");
break
}
}
}
function applySheldWidth() {
@@ -316,12 +397,18 @@ async function applyThemeColor(type) {
if (type === 'quote') {
document.documentElement.style.setProperty('--SmartThemeQuoteColor', power_user.quote_text_color);
}
if (type === 'fastUIBG') {
document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
}
/* if (type === 'fastUIBG') {
document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
} */
if (type === 'blurTint') {
document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color);
}
if (type === 'userMesBlurTint') {
document.documentElement.style.setProperty('--SmartThemeUserMesBlurTintColor', power_user.user_mes_blur_tint_color);
}
if (type === 'botMesBlurTint') {
document.documentElement.style.setProperty('--SmartThemeBotMesBlurTintColor', power_user.bot_mes_blur_tint_color);
}
if (type === 'shadow') {
document.documentElement.style.setProperty('--SmartThemeShadowColor', power_user.shadow_color);
}
@@ -361,8 +448,9 @@ async function applyTheme(name) {
{ key: 'main_text_color', selector: '#main-text-color-picker', type: 'main' },
{ key: 'italics_text_color', selector: '#italics-color-picker', type: 'italics' },
{ key: 'quote_text_color', selector: '#quote-color-picker', type: 'quote' },
{ key: 'fastui_bg_color', selector: '#fastui-bg-color-picker', type: 'fastUIBG' },
{ key: 'blur_tint_color', selector: '#blur-tint-color-picker', type: 'blurTint' },
{ key: 'user_mes_blur_tint_color', selector: '#user-mes-blur-tint-color-picker', type: 'userMesBlurTint' },
{ key: 'bot_mes_blur_tint_color', selector: '#bot-mes-blur-tint-color-picker', type: 'botMesBlurTint' },
{ key: 'shadow_color', selector: '#shadow-color-picker', type: 'shadow' },
{
key: 'blur_strength',
@@ -434,6 +522,20 @@ async function applyTheme(name) {
switchTimer();
}
},
{
key: 'timestamps_enabled',
action: async () => {
localStorage.setItem(storage_keys.timestamps_enabled, power_user.timestamps_enabled);
switchTimestamps();
}
},
{
key: 'mesIDDisplay_enabled',
action: async () => {
localStorage.setItem(storage_keys.mesIDDisplay_enabled, power_user.mesIDDisplay_enabled);
switchMesIDDisplay();
}
},
{
key: 'hotswap_enabled',
action: async () => {
@@ -449,6 +551,10 @@ async function applyTheme(name) {
if (selector) $(selector).attr('color', power_user[key]);
if (type) await applyThemeColor(type);
if (action) await action();
} else {
if (selector) { $(selector).attr('color', 'rgba(0,0,0,0)') };
console.debug(`Empty theme key: ${key}`);
power_user[key] = '';
}
}
@@ -462,11 +568,12 @@ applySheldWidth();
applyAvatarStyle();
applyBlurStrength();
applyShadowWidth();
applyChatDisplay();
switchMovingUI();
noShadows();
switchHotswap();
switchTimer();
switchTimestamps();
switchMesIDDisplay();
function loadPowerUserSettings(settings, data) {
// Load from settings.json
@@ -488,17 +595,23 @@ function loadPowerUserSettings(settings, data) {
const noShadows = localStorage.getItem(storage_keys.noShadows);
const hotswap = localStorage.getItem(storage_keys.hotswap_enabled);
const timer = localStorage.getItem(storage_keys.timer_enabled);
const timestamps = localStorage.getItem(storage_keys.timestamps_enabled);
const mesIDDisplay = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
power_user.fast_ui_mode = fastUi === null ? true : fastUi == "true";
power_user.movingUI = movingUI === null ? false : movingUI == "true";
power_user.noShadows = noShadows === null ? false : noShadows == "true";
power_user.hotswap_enabled = hotswap === null ? true : hotswap == "true";
power_user.timer_enabled = timer === null ? true : timer == "true";
power_user.timestamps_enabled = timestamps === null ? true : timestamps == "true";
power_user.mesIDDisplay_enabled = mesIDDisplay === null ? true : mesIDDisplay == "true";
power_user.avatar_style = Number(localStorage.getItem(storage_keys.avatar_style) ?? avatar_styles.ROUND);
power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
//power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
power_user.sheld_width = Number(localStorage.getItem(storage_keys.sheld_width) ?? sheld_width.DEFAULT);
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
power_user.blur_strength = Number(localStorage.getItem(storage_keys.blur_strength) ?? 10);
$('#trim_spaces').prop("checked", power_user.trim_spaces);
$('#continue_on_send').prop("checked", power_user.continue_on_send);
$('#auto_swipe').prop("checked", power_user.auto_swipe);
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
@@ -510,6 +623,7 @@ function loadPowerUserSettings(settings, data) {
$(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
$(`#pygmalion_formatting option[value=${power_user.pygmalion_formatting}]`).attr("selected", true);
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr("selected", true);
$("#import_card_tags").prop("checked", power_user.import_card_tags);
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
$("#disable-description-formatting-checkbox").prop("checked", power_user.disable_description_formatting);
@@ -522,24 +636,32 @@ function loadPowerUserSettings(settings, data) {
$("#include_newline_checkbox").prop("checked", power_user.include_newline);
$('#render_formulas').prop("checked", power_user.render_formulas);
$("#custom_chat_separator").val(power_user.custom_chat_separator);
$("#markdown_escape_strings").val(power_user.markdown_escape_strings);
$("#fast_ui_mode").prop("checked", power_user.fast_ui_mode);
$("#waifuMode").prop("checked", power_user.waifuMode);
$("#movingUImode").prop("checked", power_user.movingUI);
$("#noShadowsmode").prop("checked", power_user.noShadows);
$("#start_reply_with").val(power_user.user_prompt_bias);
$("#chat-show-reply-prefix-checkbox").prop("checked", power_user.show_user_prompt_bias);
$("#multigen").prop("checked", power_user.multigen);
$("#multigen_first_chunk").val(power_user.multigen_first_chunk);
$("#multigen_next_chunks").val(power_user.multigen_next_chunks);
$("#play_message_sound").prop("checked", power_user.play_message_sound);
$("#play_sound_unfocused").prop("checked", power_user.play_sound_unfocused);
$("#never_resize_avatars").prop("checked", power_user.never_resize_avatars);
$("#show_card_avatar_urls").prop("checked", power_user.show_card_avatar_urls);
$("#auto_save_msg_edits").prop("checked", power_user.auto_save_msg_edits);
$("#allow_name1_display").prop("checked", power_user.allow_name1_display);
$("#allow_name2_display").prop("checked", power_user.allow_name2_display);
//$("#removeXML").prop("checked", power_user.removeXML);
$("#hotswapEnabled").prop("checked", power_user.hotswap_enabled);
$("#messageTimerEnabled").prop("checked", power_user.timer_enabled);
$("#messageTimestampsEnabled").prop("checked", power_user.timestamps_enabled);
$("#mesIDDisplayEnabled").prop("checked", power_user.mesIDDisplay_enabled);
$("#prefer_character_prompt").prop("checked", power_user.prefer_character_prompt);
$("#prefer_character_jailbreak").prop("checked", power_user.prefer_character_jailbreak);
$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true);
$(`input[name="chat_display"][value="${power_user.chat_display}"]`).prop("checked", true);
$(`#chat_display option[value=${power_user.chat_display}]`).attr("selected", true).trigger('change');
$(`input[name="sheld_width"][value="${power_user.sheld_width}"]`).prop("checked", true);
$("#token_padding").val(power_user.token_padding);
@@ -555,8 +677,10 @@ function loadPowerUserSettings(settings, data) {
$("#main-text-color-picker").attr('color', power_user.main_text_color);
$("#italics-color-picker").attr('color', power_user.italics_text_color);
$("#quote-color-picker").attr('color', power_user.quote_text_color);
$("#fastui-bg-color-picker").attr('color', power_user.fastui_bg_color);
//$("#fastui-bg-color-picker").attr('color', power_user.fastui_bg_color);
$("#blur-tint-color-picker").attr('color', power_user.blur_tint_color);
$("#user-mes-blur-tint-color-picker").attr('color', power_user.user_mes_blur_tint_color);
$("#bot-mes-blur-tint-color-picker").attr('color', power_user.bot_mes_blur_tint_color);
$("#shadow-color-picker").attr('color', power_user.shadow_color);
for (const theme of themes) {
@@ -573,6 +697,34 @@ function loadPowerUserSettings(settings, data) {
loadInstructMode();
loadMaxContextUnlocked();
switchWaifuMode();
loadMovingUIState();
//console.log(power_user)
}
function loadMovingUIState() {
if (isMobile() === false
&& power_user.movingUIState
&& power_user.movingUI === true) {
console.debug('loading movingUI state')
for (var elmntName of Object.keys(power_user.movingUIState)) {
var elmntState = power_user.movingUIState[elmntName];
try {
var elmnt = $('#' + $.escapeSelector(elmntName));
if (elmnt.length) {
console.debug(`loading state for ${elmntName}`)
elmnt.css(elmntState);
} else {
console.debug(`skipping ${elmntName} because it doesn't exist in the DOM`)
}
} catch (err) {
console.debug(`error occurred while processing ${elmntName}: ${err}`)
}
}
} else {
console.debug('skipping movingUI state load')
return
}
}
function loadMaxContextUnlocked() {
@@ -607,6 +759,7 @@ function loadInstructMode() {
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
{ id: "instruct_names", property: "names", isCheckbox: true },
{ id: "instruct_macro", property: "macro", isCheckbox: true },
];
controls.forEach(control => {
@@ -659,11 +812,11 @@ function loadInstructMode() {
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
const sequence = substituteParams(
(isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence,
name1,
name2
);
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
if (power_user.instruct.macro) {
sequence = substituteParams(sequence, name1, name2);
}
const separator = power_user.instruct.wrap ? '\n' : '';
const separatorSequence = power_user.instruct.separator_sequence && !isUser
@@ -678,20 +831,20 @@ export function formatInstructStoryString(story, systemPrompt) {
// If the character has a custom system prompt AND user has it preferred, use that instead of the default
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt;
const sequence = power_user.instruct.system_sequence || '';
const prompt = substituteParams(systemPrompt) || '';
const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || '';
const separator = power_user.instruct.wrap ? '\n' : '';
const textArray = [sequence, prompt + '\n' + story, separator];
const textArray = [sequence, prompt + '\n' + story];
const text = textArray.filter(x => x).join(separator);
return text;
}
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
const includeNames = power_user.instruct.names || !!selected_group;
const sequence = substituteParams(
isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence,
name1,
name2
);
let sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
if (power_user.instruct.macro) {
sequence = substituteParams(sequence, name1, name2);
}
const separator = power_user.instruct.wrap ? '\n' : '';
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
@@ -775,8 +928,10 @@ async function saveTheme() {
main_text_color: power_user.main_text_color,
italics_text_color: power_user.italics_text_color,
quote_text_color: power_user.quote_text_color,
fastui_bg_color: power_user.fastui_bg_color,
//fastui_bg_color: power_user.fastui_bg_color,
blur_tint_color: power_user.blur_tint_color,
user_mes_blur_tint_color: power_user.user_mes_blur_tint_color,
bot_mes_blur_tint_color: power_user.bot_mes_blur_tint_color,
shadow_color: power_user.shadow_color,
shadow_width: power_user.shadow_width,
font_scale: power_user.font_scale,
@@ -787,6 +942,8 @@ async function saveTheme() {
noShadows: power_user.noShadows,
sheld_width: power_user.sheld_width,
timer_enabled: power_user.timer_enabled,
timestamps_enabled: power_user.timestamps_enabled,
mesIDDisplay_enabled: power_user.mesIDDisplay_enabled,
hotswap_enabled: power_user.hotswap_enabled,
};
@@ -819,54 +976,43 @@ async function saveTheme() {
}
function resetMovablePanels() {
document.getElementById("sheld").style.top = '';
document.getElementById("sheld").style.left = '';
document.getElementById("sheld").style.bottom = '';
document.getElementById("sheld").style.right = '';
document.getElementById("sheld").style.height = '';
document.getElementById("sheld").style.width = '';
document.getElementById("sheld").style.margin = '';
const panelIds = [
'sheld',
'left-nav-panel',
'right-nav-panel',
'WorldInfo',
'floatingPrompt',
'expression-holder',
];
const panelStyles = ['top', 'left', 'right', 'bottom', 'height', 'width', 'margin',];
document.getElementById("left-nav-panel").style.top = '';
document.getElementById("left-nav-panel").style.left = '';
document.getElementById("left-nav-panel").style.height = '';
document.getElementById("left-nav-panel").style.width = '';
document.getElementById("left-nav-panel").style.margin = '';
panelIds.forEach((id) => {
const panel = document.getElementById(id);
document.getElementById("right-nav-panel").style.top = '';
document.getElementById("right-nav-panel").style.left = '';
document.getElementById("right-nav-panel").style.right = '';
document.getElementById("right-nav-panel").style.height = '';
document.getElementById("right-nav-panel").style.width = '';
document.getElementById("right-nav-panel").style.margin = '';
if (panel) {
panelStyles.forEach((style) => {
panel.style[style] = '';
});
}
});
document.getElementById("expression-holder").style.top = '';
document.getElementById("expression-holder").style.left = '';
document.getElementById("expression-holder").style.right = '';
document.getElementById("expression-holder").style.bottom = '';
document.getElementById("expression-holder").style.height = '';
document.getElementById("expression-holder").style.width = '';
document.getElementById("expression-holder").style.margin = '';
const zoomedAvatar = document.querySelector('.zoomed_avatar');
if (zoomedAvatar) {
panelStyles.forEach((style) => {
zoomedAvatar.style[style] = '';
});
}
document.getElementById("avatar_zoom_popup").style.top = '';
document.getElementById("avatar_zoom_popup").style.left = '';
document.getElementById("avatar_zoom_popup").style.right = '';
document.getElementById("avatar_zoom_popup").style.bottom = '';
document.getElementById("avatar_zoom_popup").style.height = '';
document.getElementById("avatar_zoom_popup").style.width = '';
document.getElementById("avatar_zoom_popup").style.margin = '';
document.getElementById("WorldInfo").style.top = '';
document.getElementById("WorldInfo").style.left = '';
document.getElementById("WorldInfo").style.right = '';
document.getElementById("WorldInfo").style.bottom = '';
document.getElementById("WorldInfo").style.height = '';
document.getElementById("WorldInfo").style.width = '';
document.getElementById("WorldInfo").style.margin = '';
$('*[data-dragged="true"]').removeAttr('data-dragged');
$('[data-dragged="true"]').removeAttr('data-dragged');
power_user.movingUIState = {};
saveSettingsDebounced();
eventSource.emit(event_types.MOVABLE_PANELS_RESET);
eventSource.once(event_types.SETTINGS_UPDATED, () => {
toastr.success('Panel positions reset');
});
}
function doNewChat() {
@@ -879,10 +1025,84 @@ function doNewChat() {
}, 1);
}
function doDelMode() {
function doRandomChat() {
resetSelectedGroup();
setCharacterId(Math.floor(Math.random() * characters.length));
setTimeout(() => {
$("#option_delete_mes").trigger('click')
replaceCurrentChat();
}, 1);
}
async function doMesCut(_, text) {
//reject invalid args or no args
if (text && isNaN(text) || !text) {
toastr.error(`Must enter a single number only, non-number characters disallowed.`)
return
}
//reject attempts to delete firstmes
if (text === 0) {
toastr.error('Cannot delete the First Message')
return
}
let mesIDToCut = Number(text).toFixed(0)
let mesToCut = $("#chat").find(`.mes[mesid=${mesIDToCut}]`)
if (!mesToCut.length) {
toastr.error(`Could not find message with ID: ${mesIDToCut}`)
return
}
mesToCut.find('.mes_edit_delete').trigger('click');
$('#dialogue_popup_ok').trigger('click');
}
async function doDelMode(_, text) {
//first enter delmode
$("#option_delete_mes").trigger('click')
//reject invalid args
if (text && isNaN(text)) {
toastr.warning('Must enter a number or nothing.')
await delay(300) //unsure why 300 is neccessary here, but any shorter and it wont see the delmode UI
$("#dialogue_del_mes_cancel").trigger('click');
return
}
//parse valid args
if (text) {
await delay(300) //same as above, need event signal for 'entered del mode'
console.debug('parsing msgs to del')
let numMesToDel = Number(text).toFixed(0)
let lastMesID = $('.last_mes').attr('mesid')
let oldestMesIDToDel = lastMesID - numMesToDel + 1;
//disallow targeting first message
if (oldestMesIDToDel <= 0) {
oldestMesIDToDel = 1
}
let oldestMesToDel = $('#chat').find(`.mes[mesid=${oldestMesIDToDel}]`)
let oldestDelMesCheckbox = $(oldestMesToDel).find('.del_checkbox');
let newLastMesID = oldestMesIDToDel - 1;
console.debug(`DelMesReport -- numMesToDel: ${numMesToDel}, lastMesID: ${lastMesID}, oldestMesIDToDel:${oldestMesIDToDel}, newLastMesID: ${newLastMesID}`)
oldestDelMesCheckbox.trigger('click');
let trueNumberOfDeletedMessage = lastMesID - oldestMesIDToDel + 1
//await delay(1)
$('#dialogue_del_mes_ok').trigger('click');
toastr.success(`Deleted ${trueNumberOfDeletedMessage} messages.`)
return
}
}
function doResetPanels() {
$("#movingUIreset").trigger('click');
}
$(document).ready(() => {
@@ -960,6 +1180,23 @@ $(document).ready(() => {
reloadMarkdownProcessor(power_user.render_formulas);
});
$("#markdown_escape_strings").on('input', function () {
power_user.markdown_escape_strings = $(this).val();
saveSettingsDebounced();
reloadMarkdownProcessor(power_user.render_formulas);
});
$("#start_reply_with").on('input', function() {
power_user.user_prompt_bias = $(this).val();
saveSettingsDebounced();
});
$("#chat-show-reply-prefix-checkbox").change(function () {
power_user.show_user_prompt_bias = !!$(this).prop("checked");
reloadCurrentChat();
saveSettingsDebounced();
})
$("#multigen").change(function () {
power_user.multigen = $(this).prop("checked");
saveSettingsDebounced();
@@ -998,10 +1235,13 @@ $(document).ready(() => {
applyAvatarStyle();
});
$(`input[name="chat_display"]`).on('input', function (e) {
power_user.chat_display = Number(e.target.value);
localStorage.setItem(storage_keys.chat_display, power_user.chat_display);
$("#chat_display").on('change', function () {
console.debug('###CHAT DISPLAY SELECTOR CHANGE###')
const value = $(this).find(':selected').val();
power_user.chat_display = Number(value);
saveSettingsDebounced();
applyChatDisplay();
});
$(`input[name="sheld_width"]`).on('input', function (e) {
@@ -1050,18 +1290,24 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$("#fastui-bg-color-picker").on('change', (evt) => {
power_user.fastui_bg_color = evt.detail.rgba;
applyThemeColor('fastUIBG');
saveSettingsDebounced();
});
$("#blur-tint-color-picker").on('change', (evt) => {
power_user.blur_tint_color = evt.detail.rgba;
applyThemeColor('blurTint');
saveSettingsDebounced();
});
$("#user-mes-blur-tint-color-picker").on('change', (evt) => {
power_user.user_mes_blur_tint_color = evt.detail.rgba;
applyThemeColor('userMesBlurTint');
saveSettingsDebounced();
});
$("#bot-mes-blur-tint-color-picker").on('change', (evt) => {
power_user.bot_mes_blur_tint_color = evt.detail.rgba;
applyThemeColor('botMesBlurTint');
saveSettingsDebounced();
});
$("#shadow-color-picker").on('change', (evt) => {
power_user.shadow_color = evt.detail.rgba;
applyThemeColor('shadow');
@@ -1077,6 +1323,16 @@ $(document).ready(() => {
$("#ui-preset-save-button").on('click', saveTheme);
$("#never_resize_avatars").on('input', function () {
power_user.never_resize_avatars = !!$(this).prop('checked');
saveSettingsDebounced();
});
$("#show_card_avatar_urls").on('input', function () {
power_user.show_card_avatar_urls = !!$(this).prop('checked');
printCharacters();
saveSettingsDebounced();
});
$("#play_message_sound").on('input', function () {
power_user.play_message_sound = !!$(this).prop('checked');
saveSettingsDebounced();
@@ -1173,6 +1429,11 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$("#import_card_tags").on('input', function () {
power_user.import_card_tags = !!$(this).prop('checked');
saveSettingsDebounced();
});
$("#render_formulas").on("input", function () {
power_user.render_formulas = !!$(this).prop('checked');
reloadMarkdownProcessor(power_user.render_formulas);
@@ -1199,6 +1460,12 @@ $(document).ready(() => {
saveSettingsDebounced();
});
/* $("#removeXML").on("input", function () {
power_user.removeXML = !!$(this).prop('checked');
reloadCurrentChat();
saveSettingsDebounced();
}); */
$("#token_padding").on("input", function () {
power_user.token_padding = Number($(this).val());
saveSettingsDebounced();
@@ -1211,6 +1478,20 @@ $(document).ready(() => {
switchTimer();
});
$("#messageTimestampsEnabled").on("input", function () {
const value = !!$(this).prop('checked');
power_user.timestamps_enabled = value;
localStorage.setItem(storage_keys.timestamps_enabled, power_user.timestamps_enabled);
switchTimestamps();
});
$("#mesIDDisplayEnabled").on("input", function () {
const value = !!$(this).prop('checked');
power_user.mesIDDisplay_enabled = value;
localStorage.setItem(storage_keys.mesIDDisplay_enabled, power_user.mesIDDisplay_enabled);
switchMesIDDisplay();
});
$("#hotswapEnabled").on("input", function () {
const value = !!$(this).prop('checked');
power_user.hotswap_enabled = value;
@@ -1230,6 +1511,18 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$("#continue_on_send").on("input", function () {
const value = !!$(this).prop('checked');
power_user.continue_on_send = value;
saveSettingsDebounced();
});
$("#trim_spaces").on("input", function () {
const value = !!$(this).prop('checked');
power_user.trim_spaces = value;
saveSettingsDebounced();
});
$(window).on('focus', function () {
browser_has_focus = true;
});
@@ -1238,7 +1531,10 @@ $(document).ready(() => {
browser_has_focus = false;
});
registerSlashCommand('vn', toggleWaifu, ['vn'], ' swaps Visual Novel Mode On/Off', false, true);
registerSlashCommand('vn', toggleWaifu, [], ' swaps Visual Novel Mode On/Off', false, true);
registerSlashCommand('newchat', doNewChat, ['newchat'], ' start a new chat with current character', true, true);
registerSlashCommand('delmode', doDelMode, ['delmode'], ' enter message deletion mode', true, true);
registerSlashCommand('random', doRandomChat, ['random'], ' start a new chat with a random character', true, true);
registerSlashCommand('delmode', doDelMode, ['del'], '<span class="monospace">(optional number)</span> enter message deletion mode, and auto-deletes N messages if numeric argument is provided', true, true);
registerSlashCommand('cut', doMesCut, [], ' <span class="monospace">(requred number)</span> cuts the specified message from the chat', true, true);
registerSlashCommand('resetpanels', doResetPanels, ['resetui'], ' resets UI panels to original state.', true, true);
});

View File

@@ -6,6 +6,7 @@ export const SECRET_KEYS = {
POE: 'api_key_poe',
NOVEL: 'api_key_novel',
CLAUDE: 'api_key_claude',
OPENROUTER: 'api_key_openrouter',
}
const INPUT_MAP = {
@@ -14,6 +15,7 @@ const INPUT_MAP = {
[SECRET_KEYS.POE]: '#poe_token',
[SECRET_KEYS.NOVEL]: '#api_key_novel',
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
[SECRET_KEYS.OPENROUTER]: '#api_key_openrouter',
}
async function clearSecret() {
@@ -54,7 +56,7 @@ async function viewSecrets() {
table.classList.add('responsiveTable');
$(table).append('<thead><th>Key</th><th>Value</th></thead>');
for (const [key,value] of Object.entries(data)) {
for (const [key, value] of Object.entries(data)) {
$(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`);
}
@@ -94,13 +96,63 @@ export async function readSecretState() {
if (response.ok) {
secret_state = await response.json();
updateSecretDisplay();
await checkOpenRouterAuth();
}
} catch {
console.error('Could not read secrets file');
}
}
jQuery(() => {
function authorizeOpenRouter() {
const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(location.origin)}`;
location.href = openRouterUrl;
}
async function checkOpenRouterAuth() {
const params = new URLSearchParams(location.search);
if (params.has('code')) {
const code = params.get('code');
try {
const response = await fetch("https://openrouter.ai/api/v1/auth/keys", {
method: 'POST',
body: JSON.stringify({ code }),
});
if (!response.ok) {
throw new Error('OpenRouter exchange error');
}
const data = await response.json();
if (!data || !data.key) {
throw new Error('OpenRouter invalid response');
}
await writeSecret(SECRET_KEYS.OPENROUTER, data.key);
if (secret_state[SECRET_KEYS.OPENROUTER]) {
toastr.success('OpenRouter token saved');
// Remove the code from the URL
const currentUrl = window.location.href;
const urlWithoutSearchParams = currentUrl.split("?")[0];
window.history.pushState({}, "", urlWithoutSearchParams);
} else {
throw new Error('OpenRouter token not saved');
}
} catch (err) {
toastr.error('Could not verify OpenRouter token. Please try again.');
return;
}
}
}
jQuery(async () => {
$('#viewSecrets').on('click', viewSecrets);
$(document).on('click', '.clear-api-key', clearSecret);
$(document).on('input', Object.values(INPUT_MAP).join(','), function () {
const id = $(this).attr('id');
const value = $(this).val();
const warningElement = $(`[data-for="${id}"]`);
warningElement.toggle(value.length > 0);
});
$('#openrouter_authorize').on('click', authorizeOpenRouter);
});

2
public/scripts/select2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +0,0 @@
import { power_user } from './power-user.js';
// Showdown extension to make chat separators (dinkuses) ignore markdown formatting
export const dinkusExtension = () => {
if (!power_user) {
console.log("Showdown-dinkus extension: power_user wasn't found! Returning.");
return []
}
// Create an escaped sequence so the regex can work with any character
const savedDinkus = power_user.custom_chat_separator
// No dinkus? No extension!
if (!savedDinkus || savedDinkus.trim().length === 0) {
return []
}
const escapedDinkus = savedDinkus.split('').map((e) => `\\${e}`).join('');
const replaceRegex = new RegExp(`^(${escapedDinkus})\n`, "gm")
return [{
type: "lang",
regex: replaceRegex,
replace: (match) => match.replace(replaceRegex, `<div>${savedDinkus}</div>`).trim()
}];
}

View File

@@ -0,0 +1,36 @@
import { power_user } from './power-user.js';
// Showdown extension to make chat separators (dinkuses) ignore markdown formatting
export const markdownExclusionExt = () => {
if (!power_user) {
console.log("Showdown-dinkus extension: power_user wasn't found! Returning.");
return []
}
let combinedExcludeString = '';
if (power_user.custom_chat_separator) {
combinedExcludeString += `${power_user.custom_chat_separator},`;
}
if (power_user.markdown_escape_strings) {
combinedExcludeString += power_user.markdown_escape_strings;
}
const escapedExclusions = combinedExcludeString
.split(",")
.filter((element) => element.length > 0)
.map((element) => `(${element.split('').map((char) => `\\${char}`).join('')})`);
// No exclusions? No extension!
if (!combinedExcludeString || combinedExcludeString.length === 0 || escapedExclusions.length === 0) {
return [];
}
const replaceRegex = new RegExp(`^(${escapedExclusions.join("|")})\n`, "gm");
return [{
type: "lang",
regex: replaceRegex,
replace: ((match) => match.replace(replaceRegex, `\u0000${match} \n`))
}];
}

View File

@@ -14,11 +14,14 @@ import {
sendSystemMessage,
setUserName,
substituteParams,
comment_avatar,
system_avatar,
system_message_types
system_message_types,
name1,
saveSettings,
} from "../script.js";
import { humanizedDateTime } from "./RossAscends-mods.js";
import { power_user } from "./power-user.js";
import { chat_styles, power_user } from "./power-user.js";
export {
executeSlashCommands,
registerSlashCommand,
@@ -103,9 +106,21 @@ parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="mon
parser.addCommand('sendas', sendMessageAs, [], ` sends message as a specific character.<br>Example:<br><pre><code>/sendas Chloe\nHello, guys!</code></pre>will send "Hello, guys!" from "Chloe".<br>Uses character avatar if it exists in the characters list.`, true, true);
parser.addCommand('sys', sendNarratorMessage, [], '<span class="monospace">(text)</span> sends message as a system narrator', false, true);
parser.addCommand('sysname', setNarratorName, [], '<span class="monospace">(name)</span> sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.', true, true);
parser.addCommand('comment', sendCommentMessage, [], '<span class="monospace">(text)</span> adds a note/comment message not part of the chat', false, true);
parser.addCommand('single', setStoryModeCallback, ['story'], ' sets the message style to single document mode without names or avatars visible', true, true);
parser.addCommand('bubble', setBubbleModeCallback, ['bubbles'], ' sets the message style to bubble chat mode', true, true);
parser.addCommand('flat', setFlatModeCallback, ['default'], ' sets the message style to flat chat mode', true, true);
parser.addCommand('continue', continueChatCallback, ['cont'], ' continues the last message in the chat', true, true);
const NARRATOR_NAME_KEY = 'narrator_name';
const NARRATOR_NAME_DEFAULT = 'System';
const COMMENT_NAME_DEFAULT = 'Note';
function continueChatCallback() {
// Prevent infinite recursion
$('#send_textarea').val('');
$('#option_continue').trigger('click', { fromSlashCommand: true });
}
function syncCallback() {
$('#sync_name_button').trigger('click');
@@ -115,21 +130,36 @@ function bindCallback() {
$('#lock_user_name').trigger('click');
}
function setStoryModeCallback() {
$('#chat_display').val(chat_styles.DOCUMENT).trigger('change');
}
function setBubbleModeCallback() {
$('#chat_display').val(chat_styles.BUBBLES).trigger('change');
}
function setFlatModeCallback() {
$('#chat_display').val(chat_styles.DEFAULT).trigger('change');
}
function setNameCallback(_, name) {
if (!name) {
toastr.warning('you must specify a name to change to')
return;
}
name = name.trim();
// If the name is a persona, auto-select it
if (Object.values(power_user.personas).map(x => x.toLowerCase()).includes(name.toLowerCase())) {
autoSelectPersona(name);
for (let persona of Object.values(power_user.personas)) {
if (persona.toLowerCase() === name.toLowerCase()) {
autoSelectPersona(name);
return;
}
}
// Otherwise, set just the name
else {
setUserName(name);
}
setUserName(name); //this prevented quickReply usage
}
function setNarratorName(_, text) {
@@ -221,6 +251,31 @@ async function sendNarratorMessage(_, text) {
saveChatConditional();
}
async function sendCommentMessage(_, text) {
if (!text) {
return;
}
const message = {
name: COMMENT_NAME_DEFAULT,
is_user: false,
is_name: true,
is_system: true,
send_date: humanizedDateTime(),
mes: substituteParams(text.trim()),
force_avatar: comment_avatar,
extra: {
type: system_message_types.COMMENT,
gen_id: Date.now(),
},
};
chat.push(message);
addOneMessage(message);
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
saveChatConditional();
}
function helpCommandCallback() {
sendSystemMessage(system_message_types.HELP);
}
@@ -244,7 +299,7 @@ function executeSlashCommands(text) {
// Hack to allow multi-line slash commands
// All slash command messages should begin with a slash
const lines = [text];
const lines = text.split('|').map(line => line.trim());
const linesToRemove = [];
let interrupt = false;

View File

@@ -5,6 +5,7 @@ import {
callPopup,
menu_type,
updateVisibleDivs,
getCharacters,
} from "../script.js";
import { selected_group } from "./group-chats.js";
@@ -13,23 +14,40 @@ export {
tags,
tag_map,
loadTagsSettings,
printTags,
printTagFilters,
isElementTagged,
getTagsList,
appendTagToList,
createTagMapFromList,
renameTagKey,
importTags,
};
const random_id = () => Math.round(Date.now() * Math.random()).toString();
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
const CHARACTER_SELECTOR = '#rm_print_characters_block > div';
const GROUP_MEMBER_SELECTOR = '#rm_group_add_members > div';
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
function getCharacterSelector(listSelector) {
if ($(listSelector).is(GROUP_FILTER_SELECTOR)) {
return GROUP_MEMBER_SELECTOR;
}
return CHARACTER_SELECTOR;
}
export const tag_filter_types = {
character: 0,
group_member: 1,
};
const ACTIONABLE_TAGS = {
FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: applyFavFilter, icon: 'fa-solid fa-star' },
GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users' },
HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags' },
FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: applyFavFilter, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
}
const InListActionable = {
@@ -48,14 +66,14 @@ const DEFAULT_TAGS = [
let tags = [];
let tag_map = {};
function applyFavFilter() {
function applyFavFilter(characterSelector) {
const isSelected = $(this).hasClass('selected');
const displayFavoritesOnly = !isSelected;
$(this).toggleClass('selected', displayFavoritesOnly);
$(CHARACTER_SELECTOR).removeClass('hiddenByFav');
$(characterSelector).removeClass('hiddenByFav');
$(CHARACTER_SELECTOR).each(function () {
$(characterSelector).each(function () {
if (displayFavoritesOnly) {
if ($(this).find(".ch_fav").length !== 0) {
const shouldBeDisplayed = $(this).find(".ch_fav").val().toLowerCase().includes(true);
@@ -67,13 +85,13 @@ function applyFavFilter() {
updateVisibleDivs('#rm_print_characters_block', true);
}
function filterByGroups() {
function filterByGroups(characterSelector) {
const isSelected = $(this).hasClass('selected');
const displayGroupsOnly = !isSelected;
$(this).toggleClass('selected', displayGroupsOnly);
$(CHARACTER_SELECTOR).removeClass('hiddenByGroup');
$(characterSelector).removeClass('hiddenByGroup');
$(CHARACTER_SELECTOR).each((_, element) => {
$(characterSelector).each((_, element) => {
$(element).toggleClass('hiddenByGroup', displayGroupsOnly && !$(element).hasClass('group_select'));
});
updateVisibleDivs('#rm_print_characters_block', true);
@@ -195,12 +213,63 @@ function selectTag(event, ui, listSelector) {
appendTagToList(getInlineListSelector(), tag, { removable: false });
addTagToMap(tag.id);
saveSettingsDebounced();
printTags();
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
// need to return false to keep the input clear
return false;
}
function getExistingTags(new_tags) {
let existing_tags = [];
for (let tag of new_tags) {
let foundTag = tags.find(t => t.name.toLowerCase() === tag.toLowerCase())
if (foundTag) {
existing_tags.push(foundTag.name);
}
}
return existing_tags
}
async function importTags(imported_char) {
let imported_tags = imported_char.tags.filter(t => t !== "ROOT" && t !== "TAVERN");
let existingTags = await getExistingTags(imported_tags);
//make this case insensitive
let newTags = imported_tags.filter(t => !existingTags.some(existingTag => existingTag.toLowerCase() === t.toLowerCase()));
let selected_tags = "";
const existingTagsString = existingTags.length ? (': ' + existingTags.join(', ')) : '';
if (newTags.length === 0) {
await callPopup(`<h3>Importing Tags</h3><p>${existingTags.length} existing tags have been found${existingTagsString}.</p>`, 'text');
} else {
selected_tags = await callPopup(`<h3>Importing Tags</h3><p>${existingTags.length} existing tags have been found${existingTagsString}.</p><p>The following ${newTags.length} new tags will be imported.</p>`, 'input', newTags.join(', '));
}
selected_tags = existingTags.concat(selected_tags.split(','));
selected_tags = selected_tags.map(t => t.trim()).filter(t => t !== "");
//Anti-troll measure
if (selected_tags.length > 15) {
selected_tags = selected_tags.slice(0, 15);
}
for (let tagName of selected_tags) {
let tag = tags.find(t => t.name === tagName);
if (!tag) {
tag = createNewTag(tagName);
}
addTagToMap(tag.id);
tag_map[imported_char.avatar].push(tag.id);
};
saveSettingsDebounced();
await getCharacters();
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
// need to return false to keep the input clear
return false;
}
function createNewTag(tagName) {
const tag = {
id: random_id(),
@@ -211,11 +280,13 @@ function createNewTag(tagName) {
return tag;
}
function appendTagToList(listElement, tag, { removable, selectable, action }) {
function appendTagToList(listElement, tag, { removable, selectable, action, isGeneralList }) {
if (!listElement) {
return;
}
const characterSelector = getCharacterSelector($(listElement));
let tagElement = $('#tag_template .tag').clone();
tagElement.attr('id', tag.id);
@@ -226,16 +297,24 @@ function appendTagToList(listElement, tag, { removable, selectable, action }) {
const removeButton = tagElement.find(".tag_remove");
removable ? removeButton.show() : removeButton.hide();
if (tag.class) {
tagElement.addClass(tag.class);
}
if (tag.icon) {
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
}
if (tag.excluded) {
isGeneralList ? $(tagElement).addClass('excluded') : $(listElement).parent().parent().addClass('hiddenByTag');
}
if (selectable) {
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement));
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement, characterSelector));
}
if (action) {
tagElement.on('click', () => action.bind(tagElement)());
tagElement.on('click', () => action.bind(tagElement)(characterSelector));
tagElement.addClass('actionable');
}
if (action && tag.id === 2) {
@@ -245,33 +324,59 @@ function appendTagToList(listElement, tag, { removable, selectable, action }) {
$(listElement).append(tagElement);
}
function onTagFilterClick(listElement) {
const wasSelected = $(this).hasClass('selected');
$(CHARACTER_SELECTOR).removeClass('hiddenByTag');
function onTagFilterClick(listElement, characterSelector) {
let excludeTag;
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
$(this).addClass('excluded');
excludeTag = true
}
else if ($(this).hasClass('excluded')) {
$(this).removeClass('excluded');
excludeTag = false;
}
else {
$(this).addClass('selected');
}
$(this).toggleClass('selected', !wasSelected);
// Manual undefined check required for three-state boolean
if (excludeTag !== undefined) {
const tagId = $(this).attr('id');
const existingTag = tags.find((tag) => tag.id === tagId);
if (existingTag) {
existingTag.excluded = excludeTag;
saveSettingsDebounced();
}
}
// TODO: Overhaul this somehow to use settings tag IDs instead
const tagIds = [...($(listElement).find(".tag.selected:not(.actionable)").map((_, el) => $(el).attr("id")))];
$(CHARACTER_SELECTOR).each((_, element) => applyFilterToElement(tagIds, element));
const excludedTagIds = [...($(listElement).find(".tag.excluded:not(.actionable)").map((_, el) => $(el).attr("id")))];
$(characterSelector).each((_, element) => applyFilterToElement(tagIds, excludedTagIds, element));
updateVisibleDivs('#rm_print_characters_block', true);
}
function applyFilterToElement(tagIds, element) {
if (tagIds.length === 0) {
$(element).removeClass('hiddenByTag');
return;
}
function applyFilterToElement(tagIds, excludedTagIds, element) {
const tagFlags = tagIds.map(tagId => isElementTagged(element, tagId));
const trueFlags = tagFlags.filter(x => x);
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
$(element).toggleClass('hiddenByTag', !isTagged);
const excludedTagFlags = excludedTagIds.map(tagId => isElementTagged(element, tagId));
const isExcluded = excludedTagFlags.includes(true);
if (isExcluded) {
$(element).addClass('hiddenByTag');
} else if (tagIds.length > 0 && !isTagged) {
$(element).addClass('hiddenByTag');
} else {
$(element).removeClass('hiddenByTag');
}
}
function isElementTagged(element, tagId) {
const isGroup = $(element).hasClass('group_select');
const isCharacter = $(element).hasClass('character_select');
const isCharacter = $(element).hasClass('character_select') || $(element).hasClass('group_member');
const idAttr = isGroup ? 'grid' : 'chid';
const elementId = $(element).attr(idAttr);
const lookupValue = isCharacter ? characters[elementId].avatar : elementId;
@@ -279,13 +384,13 @@ function isElementTagged(element, tagId) {
return isTagged;
}
function clearTagsFilter() {
$('#rm_tag_filter .tag').removeClass('selected');
$(CHARACTER_SELECTOR).removeClass('hiddenByTag');
function clearTagsFilter(characterSelector) {
$('.rm_tag_filter .tag').removeClass('selected');
$(characterSelector).removeClass('hiddenByTag');
}
function printTags() {
const FILTER_SELECTOR = '#rm_tag_filter';
function printTagFilters(type = tag_filter_types.character) {
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
const selectedTagIds = [...($(FILTER_SELECTOR).find(".tag.selected").map((_, el) => $(el).attr("id")))];
$(FILTER_SELECTOR).empty();
const characterTagIds = Object.values(tag_map).flat();
@@ -294,16 +399,16 @@ function printTags() {
.sort((a, b) => a.name.localeCompare(b.name));
for (const tag of Object.values(ACTIONABLE_TAGS)) {
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action });
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action, isGeneralList: true });
}
$(FILTER_SELECTOR).find('.actionable').last().addClass('margin-right-10px');
for (const tag of Object.values(InListActionable)) {
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action });
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action, isGeneralList: true });
}
for (const tag of tagsToDisplay) {
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: true, });
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: true, isGeneralList: true });
}
for (const tagId of selectedTagIds) {
@@ -319,7 +424,8 @@ function onTagRemoveClick(event) {
removeTagFromMap(tagId);
$(`${getInlineListSelector()} .tag[id="${tagId}"]`).remove();
printTags();
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
saveSettingsDebounced();
}
@@ -339,6 +445,8 @@ function onCharacterCreateClick() {
function onGroupCreateClick() {
$("#groupTagList").empty();
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
}
export function applyTagsOnCharacterSelect() {
@@ -360,6 +468,8 @@ function applyTagsOnGroupSelect() {
const tags = getTagsList(key);
$("#groupTagList").empty();
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
for (const tag of tags) {
appendTagToList("#groupTagList", tag, { removable: true });

View File

@@ -17,6 +17,8 @@ let textgenerationwebui_settings = {
top_k: 40,
top_a: 0,
tfs: 1,
epsilon_cutoff: 0,
eta_cutoff: 0,
typical_p: 1,
rep_pen: 1.2,
no_repeat_ngram_size: 0,
@@ -49,6 +51,8 @@ const setting_names = [
"top_p",
"top_a",
"tfs",
"epsilon_cutoff",
"eta_cutoff",
"typical_p",
"penalty_alpha",
"num_beams",
@@ -218,5 +222,7 @@ export function getTextGenGenerationData(finalPromt, this_amount_gen, isImperson
'skip_special_tokens': textgenerationwebui_settings.skip_special_tokens,
'top_a': textgenerationwebui_settings.top_a,
'tfs': textgenerationwebui_settings.tfs,
'epsilon_cutoff': textgenerationwebui_settings.epsilon_cutoff,
'eta_cutoff': textgenerationwebui_settings.eta_cutoff,
};
}

View File

@@ -50,6 +50,19 @@ export function getFileText(file) {
});
}
export function getFileBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
resolve(reader.result);
};
reader.onerror = function (error) {
reject(error);
};
});
}
export function getBase64Async(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
@@ -273,18 +286,39 @@ export function isOdd(number) {
}
export function timestampToMoment(timestamp) {
if (!timestamp) {
return moment.invalid();
}
// Unix time (legacy TAI)
if (typeof timestamp === 'number') {
return moment(timestamp);
}
// ST "humanized" format pattern
const pattern = /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/;
const replacement = (match, year, month, day, hour, minute, second, millisecond) => {
const pattern1 = /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/;
const replacement1 = (match, year, month, day, hour, minute, second, millisecond) => {
return `${year.padStart(4, "0")}-${month.padStart(2, "0")}-${day.padStart(2, "0")}T${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:${second.padStart(2, "0")}.${millisecond.padStart(3, "0")}Z`;
};
const isoTimestamp = timestamp.replace(pattern, replacement);
return moment(isoTimestamp);
const isoTimestamp1 = timestamp.replace(pattern1, replacement1);
if (moment(isoTimestamp1).isValid()) {
return moment(isoTimestamp1);
}
// New format pattern: "June 19, 2023 4:13pm"
const pattern2 = /(\w+)\s(\d{1,2}),\s(\d{4})\s(\d{1,2}):(\d{1,2})(am|pm)/i;
const replacement2 = (match, month, day, year, hour, minute, meridiem) => {
const monthNum = moment().month(month).format("MM");
const hour24 = meridiem.toLowerCase() === 'pm' ? (parseInt(hour, 10) % 12) + 12 : parseInt(hour, 10) % 12;
return `${year}-${monthNum}-${day.padStart(2, "0")}T${hour24.toString().padStart(2, "0")}:${minute.padStart(2, "0")}:00`;
};
const isoTimestamp2 = timestamp.replace(pattern2, replacement2);
if (moment(isoTimestamp2).isValid()) {
return moment(isoTimestamp2);
}
// If none of the patterns match, return an invalid moment object
return moment.invalid();
}
export function sortMoments(a, b) {
@@ -423,9 +457,9 @@ export function isDataURL(str) {
return regex.test(str);
}
export function getCharaFilename() {
export function getCharaFilename(chid) {
const context = getContext();
const fileName = context.characters[context.characterId].avatar;
const fileName = context.characters[chid ?? context.characterId].avatar;
if (fileName) {
return fileName.replace(/\.[^/.]+$/, "")
@@ -435,3 +469,210 @@ export function getCharaFilename() {
export function escapeRegex(string) {
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
}
export class RateLimiter {
constructor(intervalMillis) {
this._intervalMillis = intervalMillis;
this._lastResolveTime = 0;
this._pendingResolve = Promise.resolve();
}
_waitRemainingTime(abortSignal) {
const currentTime = Date.now();
const elapsedTime = currentTime - this._lastResolveTime;
const remainingTime = Math.max(0, this._intervalMillis - elapsedTime);
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
resolve();
}, remainingTime);
if (abortSignal) {
abortSignal.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new Error('Aborted'));
});
}
});
}
async waitForResolve(abortSignal) {
await this._pendingResolve;
this._pendingResolve = this._waitRemainingTime(abortSignal);
// Update the last resolve time
this._lastResolveTime = Date.now() + this._intervalMillis;
console.debug(`RateLimiter.waitForResolve() ${this._lastResolveTime}`);
}
}
// Taken from https://github.com/LostRuins/lite.koboldai.net/blob/main/index.html
//import tavern png data. adapted from png-chunks-extract under MIT license
//accepts png input data, and returns the extracted JSON
export function extractDataFromPng(data, identifier = 'chara') {
console.log("Attempting PNG import...");
let uint8 = new Uint8Array(4);
let uint32 = new Uint32Array(uint8.buffer);
//check if png header is valid
if (!data || data[0] !== 0x89 || data[1] !== 0x50 || data[2] !== 0x4E || data[3] !== 0x47 || data[4] !== 0x0D || data[5] !== 0x0A || data[6] !== 0x1A || data[7] !== 0x0A) {
console.log("PNG header invalid")
return null;
}
let ended = false;
let chunks = [];
let idx = 8;
while (idx < data.length) {
// Read the length of the current chunk,
// which is stored as a Uint32.
uint8[3] = data[idx++];
uint8[2] = data[idx++];
uint8[1] = data[idx++];
uint8[0] = data[idx++];
// Chunk includes name/type for CRC check (see below).
let length = uint32[0] + 4;
let chunk = new Uint8Array(length);
chunk[0] = data[idx++];
chunk[1] = data[idx++];
chunk[2] = data[idx++];
chunk[3] = data[idx++];
// Get the name in ASCII for identification.
let name = (
String.fromCharCode(chunk[0]) +
String.fromCharCode(chunk[1]) +
String.fromCharCode(chunk[2]) +
String.fromCharCode(chunk[3])
);
// The IHDR header MUST come first.
if (!chunks.length && name !== 'IHDR') {
console.log('Warning: IHDR header missing');
}
// The IEND header marks the end of the file,
// so on discovering it break out of the loop.
if (name === 'IEND') {
ended = true;
chunks.push({
name: name,
data: new Uint8Array(0)
});
break;
}
// Read the contents of the chunk out of the main buffer.
for (let i = 4; i < length; i++) {
chunk[i] = data[idx++];
}
// Read out the CRC value for comparison.
// It's stored as an Int32.
uint8[3] = data[idx++];
uint8[2] = data[idx++];
uint8[1] = data[idx++];
uint8[0] = data[idx++];
// The chunk data is now copied to remove the 4 preceding
// bytes used for the chunk name/type.
let chunkData = new Uint8Array(chunk.buffer.slice(4));
chunks.push({
name: name,
data: chunkData
});
}
if (!ended) {
console.log('.png file ended prematurely: no IEND header was found');
}
//find the chunk with the chara name, just check first and last letter
let found = chunks.filter(x => (
x.name == "tEXt"
&& x.data.length > identifier.length
&& x.data.slice(0, identifier.length).every((v, i) => String.fromCharCode(v) == identifier[i])));
if (found.length == 0) {
console.log('PNG Image contains no data');
return null;
} else {
try {
let b64buf = "";
let bytes = found[0].data; //skip the chara
for (let i = identifier.length + 1; i < bytes.length; i++) {
b64buf += String.fromCharCode(bytes[i]);
}
let decoded = JSON.parse(atob(b64buf));
console.log(decoded);
return decoded;
} catch (e) {
console.log("Error decoding b64 in image: " + e);
return null;
}
}
}
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = dataUrl;
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Calculate the thumbnail dimensions while maintaining the aspect ratio
const aspectRatio = img.width / img.height;
let thumbnailWidth = maxWidth;
let thumbnailHeight = maxHeight;
if (img.width > img.height) {
thumbnailHeight = maxWidth / aspectRatio;
} else {
thumbnailWidth = maxHeight * aspectRatio;
}
// Set the canvas dimensions and draw the resized image
canvas.width = thumbnailWidth;
canvas.height = thumbnailHeight;
ctx.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight);
// Convert the canvas to a data URL and resolve the promise
const thumbnailDataUrl = canvas.toDataURL('image/jpeg');
resolve(thumbnailDataUrl);
};
img.onerror = () => {
reject(new Error('Failed to load the image.'));
};
});
}
export async function waitUntilCondition(condition, timeout = 1000, interval = 100) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
clearInterval(intervalId);
reject(new Error('Timed out waiting for condition to be true'));
}, timeout);
const intervalId = setInterval(() => {
if (condition()) {
clearTimeout(timeoutId);
clearInterval(intervalId);
resolve();
}
}, interval);
});
}
export function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "Default (Dark) 1.7.1",
"blur_strength": 10,
"main_text_color": "rgba(220, 220, 210, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)",
"quote_text_color": "rgba(225, 138, 36, 1)",
"blur_tint_color": "rgba(23, 23, 23, 1)",
"user_mes_blur_tint_color": "rgba(0, 0, 0, 0.9)",
"bot_mes_blur_tint_color": "rgba(0, 0, 0, 0.9)",
"shadow_color": "rgba(0, 0, 0, 1)",
"shadow_width": 2,
"font_scale": 1,
"fast_ui_mode": true,
"waifuMode": false,
"avatar_style": 0,
"chat_display": 0,
"noShadows": true,
"sheld_width": 0,
"timer_enabled": false,
"hotswap_enabled": true
}

View File

@@ -0,0 +1,21 @@
{
"name": "Ross v2",
"blur_strength": 10,
"main_text_color": "rgba(230, 230, 220, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)",
"quote_text_color": "rgba(73, 179, 255, 0.91)",
"blur_tint_color": "rgba(0, 0, 0, 0.5)",
"user_mes_blur_tint_color": "rgba(51, 51, 51, 0.2)",
"bot_mes_blur_tint_color": "rgba(97, 97, 97, 0.43)",
"shadow_color": "rgba(0, 0, 0, 0.5)",
"shadow_width": 2,
"font_scale": 0.95,
"fast_ui_mode": false,
"waifuMode": false,
"avatar_style": 1,
"chat_display": 1,
"noShadows": false,
"sheld_width": 1,
"timer_enabled": true,
"hotswap_enabled": true
}

543
server.js
View File

@@ -128,10 +128,13 @@ let response_getstatus;
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const { SentencePieceProcessor, cleanText } = require("sentencepiece-js");
const { Tokenizer } = require('@mlc-ai/web-tokenizers');
const CHARS_PER_TOKEN = 3.35;
let spp_llama;
let spp_nerd;
let spp_nerd_v2;
let claude_tokenizer;
async function loadSentencepieceTokenizer(modelPath) {
try {
@@ -147,7 +150,7 @@ async function loadSentencepieceTokenizer(modelPath) {
async function countSentencepieceTokens(spp, text) {
// Fallback to strlen estimation
if (!spp) {
return Math.ceil(text.length / 3.35);
return Math.ceil(text.length / CHARS_PER_TOKEN);
}
let cleaned = cleanText(text);
@@ -156,9 +159,36 @@ async function countSentencepieceTokens(spp, text) {
return ids.length;
}
async function loadClaudeTokenizer(modelPath) {
try {
const arrayBuffer = fs.readFileSync(modelPath).buffer;
const instance = await Tokenizer.fromJSON(arrayBuffer);
return instance;
} catch (error) {
console.error("Claude tokenizer failed to load: " + modelPath, error);
return null;
}
}
function countClaudeTokens(tokenizer, messages) {
const convertedPrompt = convertClaudePrompt(messages, false, false);
// Fallback to strlen estimation
if (!tokenizer) {
return Math.ceil(convertedPrompt.length / CHARS_PER_TOKEN);
}
const count = tokenizer.encode(convertedPrompt).length;
return count;
}
const tokenizersCache = {};
function getTokenizerModel(requestModel) {
if (requestModel.includes('claude')) {
return 'claude';
}
if (requestModel.includes('gpt-4-32k')) {
return 'gpt-4-32k';
}
@@ -226,6 +256,7 @@ const directories = {
extensions: 'public/scripts/extensions',
instruct: 'public/instruct',
context: 'public/context',
backups: 'backups/',
};
// CSRF Protection //
@@ -329,7 +360,7 @@ app.use('/characters', (req, res) => {
res.send(data);
});
});
app.use(multer({ dest: "uploads" }).single("avatar"));
app.use(multer({ dest: "uploads", limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar"));
app.get("/", function (request, response) {
response.sendFile(process.cwd() + "/public/index.html");
});
@@ -357,14 +388,17 @@ app.post("/generate", jsonParser, async function (request, response_generate = r
request.socket.on('close', async function () {
if (request.body.can_abort && !response_generate.finished) {
try {
console.log('Aborting Kobold generation...');
// send abort signal to koboldcpp
await fetch(`${api_server}/extra/abort`, {
const abortResponse = await fetch(`${api_server}/extra/abort`, {
method: 'POST',
});
} catch {
if ('status' in error) {
console.log('Status Code from Kobold:', error.status);
if (!abortResponse.ok) {
console.log('Error sending abort request to Kobold:', abortResponse.status);
}
} catch (error) {
console.log(error);
}
}
controller.abort();
@@ -575,7 +609,7 @@ app.post("/savechat", jsonParser, function (request, response) {
var dir_name = String(request.body.avatar_url).replace('.png', '');
let chat_data = request.body.chat;
let jsonlData = chat_data.map(JSON.stringify).join('\n');
fs.writeFileSync(`${chatsPath + dir_name}/${sanitize(String(request.body.file_name))}.jsonl`, jsonlData, 'utf8');
fs.writeFileSync(`${chatsPath + sanitize(dir_name)}/${sanitize(String(request.body.file_name))}.jsonl`, jsonlData, 'utf8');
return response.send({ result: "ok" });
} catch (error) {
response.send(error);
@@ -718,6 +752,8 @@ function convertToV2(char) {
creator_notes: char.creatorcomment,
talkativeness: char.talkativeness,
fav: char.fav,
creator: char.creator,
tags: char.tags,
});
result.chat = char.chat;
@@ -725,6 +761,12 @@ function convertToV2(char) {
return result;
}
function unsetFavFlag(char) {
const _ = require('lodash');
_.set(char, 'fav', false);
_.set(char, 'data.extensions.fav', false);
}
function readFromV2(char) {
const _ = require('lodash');
if (_.isUndefined(char.data)) {
@@ -741,6 +783,7 @@ function readFromV2(char) {
mes_example: 'mes_example',
talkativeness: 'extensions.talkativeness',
fav: 'extensions.fav',
tags: 'tags',
};
_.forEach(fieldMappings, (v2Path, charField) => {
@@ -766,7 +809,7 @@ function readFromV2(char) {
return;
}
}
if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && char[charField] !== v2Value) {
if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) {
console.debug(`Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value);
}
char[charField] = v2Value;
@@ -819,7 +862,7 @@ function charaFormatData(data) {
_.set(char, 'data.creator_notes', data.creator_notes || '');
_.set(char, 'data.system_prompt', data.system_prompt || '');
_.set(char, 'data.post_history_instructions', data.post_history_instructions || '');
_.set(char, 'data.tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : []);
_.set(char, 'data.tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : data.tags || []);
_.set(char, 'data.creator', data.creator || '');
_.set(char, 'data.character_version', data.character_version || '');
_.set(char, 'data.alternate_greetings', getAlternateGreetings(data));
@@ -827,12 +870,29 @@ function charaFormatData(data) {
// ST extension fields to V2 object
_.set(char, 'data.extensions.talkativeness', data.talkativeness);
_.set(char, 'data.extensions.fav', data.fav == 'true');
_.set(char, 'data.extensions.world', data.world || '');
//_.set(char, 'data.extensions.create_date', humanizedISO8601DateTime());
//_.set(char, 'data.extensions.avatar', 'none');
//_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
// TODO: Character book
_//.set(char, 'data.character_book', undefined);
if (data.world) {
try {
const file = readWorldInfoFile(data.world);
// File was imported - save it to the character book
if (file && file.originalData) {
_.set(char, 'data.character_book', file.originalData);
}
// File was not imported - convert the world info to the character book
if (file && file.entries) {
_.set(char, 'data.character_book', convertWorldInfoToCharacterBook(data.world, file.entries));
}
} catch {
console.debug(`Failed to read world info file: ${data.world}. Character book will not be available.`);
}
}
return char;
}
@@ -1029,13 +1089,19 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
async function tryReadImage(img_url, crop) {
try {
let rawImg = await jimp.read(img_url);
let final_width = rawImg.bitmap.width, final_height = rawImg.bitmap.height
// Apply crop if defined
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
rawImg = rawImg.crop(crop.x, crop.y, crop.width, crop.height);
// Apply standard resize if requested
if (crop.want_resize) {
final_width = AVATAR_WIDTH
final_height = AVATAR_HEIGHT
}
}
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
const image = await rawImg.cover(final_width, final_height).getBufferAsync(jimp.MIME_PNG);
return image;
}
// If it's an unsupported type of image (APNG) - just read the file as buffer
@@ -1195,9 +1261,12 @@ app.post("/delchat", jsonParser, function (request, response) {
return response.sendStatus(403);
}
const fileName = path.join(directories.chats, '/', sanitize(request.body.id), '/', sanitize(request.body.chatfile));
if (!fs.existsSync(fileName)) {
console.log('Chat file not found');
const dirName = String(request.body.avatar_url).replace('.png', '');
const fileName = `${chatsPath + dirName}/${sanitize(String(request.body.chatfile))}`;
const chatFileExists = fs.existsSync(fileName);
if (!chatFileExists) {
console.log(`Chat file not found '${fileName}'`);
return response.sendStatus(400);
} else {
console.log('found the chat file: ' + fileName);
@@ -1421,11 +1490,43 @@ app.post('/savetheme', jsonParser, (request, response) => {
}
const filename = path.join(directories.themes, sanitize(request.body.name) + '.json');
fs.writeFileSync(filename, JSON.stringify(request.body), 'utf8');
fs.writeFileSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
return response.sendStatus(200);
});
function convertWorldInfoToCharacterBook(name, entries) {
const result = { entries: [], name };
for (const index in entries) {
const entry = entries[index];
const originalEntry = {
id: entry.uid,
keys: entry.key,
secondary_keys: entry.keysecondary,
comment: entry.comment,
content: entry.content,
constant: entry.constant,
selective: entry.selective,
insertion_order: entry.order,
enabled: !entry.disable,
position: entry.position == 0 ? 'before_char' : 'after_char',
extensions: {
position: entry.position,
exclude_recursion: entry.excludeRecursion,
display_index: entry.displayIndex,
probability: entry.probability ?? null,
useProbability: entry.useProbability ?? false,
}
};
result.entries.push(originalEntry);
}
return result;
}
function readWorldInfoFile(worldInfoName) {
if (!worldInfoName) {
return { entries: {} };
@@ -1680,6 +1781,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
if (jsonData.spec !== undefined) {
console.log('importing from v2 json');
importRisuSprites(jsonData);
unsetFavFlag(jsonData);
jsonData = readFromV2(jsonData);
png_name = getPngName(jsonData.data?.name || jsonData.name);
let char = JSON.stringify(jsonData);
@@ -1687,12 +1790,14 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
} else if (jsonData.name !== undefined) {
console.log('importing from v1 json');
jsonData.name = sanitize(jsonData.name);
if (jsonData.creator_notes) {
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
}
png_name = getPngName(jsonData.name);
let char = {
"name": jsonData.name,
"description": jsonData.description ?? '',
"creatorcomment": jsonData.creatorcomment ?? '',
"creatorcomment": jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
"personality": jsonData.personality ?? '',
"first_mes": jsonData.first_mes ?? '',
"avatar": 'none',
@@ -1700,7 +1805,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
"mes_example": jsonData.mes_example ?? '',
"scenario": jsonData.scenario ?? '',
"create_date": humanizedISO8601DateTime(),
"talkativeness": jsonData.talkativeness ?? 0.5
"talkativeness": jsonData.talkativeness ?? 0.5,
"creator": jsonData.creator ?? '',
"tags": jsonData.tags ?? '',
};
char = convertToV2(char);
char = JSON.stringify(char);
@@ -1708,12 +1815,14 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
console.log('importing from gradio json');
jsonData.char_name = sanitize(jsonData.char_name);
if (jsonData.creator_notes) {
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
}
png_name = getPngName(jsonData.char_name);
let char = {
"name": jsonData.char_name,
"description": jsonData.char_persona ?? '',
"creatorcomment": '',
"creatorcomment": jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
"personality": '',
"first_mes": jsonData.char_greeting ?? '',
"avatar": 'none',
@@ -1721,7 +1830,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
"mes_example": jsonData.example_dialogue ?? '',
"scenario": jsonData.world_scenario ?? '',
"create_date": humanizedISO8601DateTime(),
"talkativeness": jsonData.talkativeness ?? 0.5
"talkativeness": jsonData.talkativeness ?? 0.5,
"creator": jsonData.creator ?? '',
"tags": jsonData.tags ?? '',
};
char = convertToV2(char);
char = JSON.stringify(char);
@@ -1753,15 +1864,22 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
if (jsonData.spec !== undefined) {
console.log('Found a v2 character file.');
importRisuSprites(jsonData);
unsetFavFlag(jsonData);
jsonData = readFromV2(jsonData);
let char = JSON.stringify(jsonData);
charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
} else if (jsonData.name !== undefined) {
console.log('Found a v1 character file.');
if (jsonData.creator_notes) {
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
}
let char = {
"name": jsonData.name,
"description": jsonData.description ?? '',
"creatorcomment": jsonData.creatorcomment ?? '',
"creatorcomment": jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
"personality": jsonData.personality ?? '',
"first_mes": jsonData.first_mes ?? '',
"avatar": 'none',
@@ -1769,7 +1887,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
"mes_example": jsonData.mes_example ?? '',
"scenario": jsonData.scenario ?? '',
"create_date": humanizedISO8601DateTime(),
"talkativeness": jsonData.talkativeness ?? 0.5
"talkativeness": jsonData.talkativeness ?? 0.5,
"creator": jsonData.creator ?? '',
"tags": jsonData.tags ?? '',
};
char = convertToV2(char);
char = JSON.stringify(char);
@@ -1836,8 +1956,29 @@ app.post("/exportchat", jsonParser, async function (request, response) {
return response.status(404).json(errorMessage);
}
try {
// Short path for JSONL files
if (request.body.format == 'jsonl') {
try {
const rawFile = fs.readFileSync(filename, 'utf8');
const successMessage = {
message: `Chat saved to ${exportfilename}`,
result: rawFile,
}
console.log(`Chat exported as ${exportfilename}`);
return response.status(200).json(successMessage);
}
catch (err) {
console.error(err);
const errorMessage = {
message: `Could not read JSONL file to export. Source chat file: ${filename}.`
}
console.log(errorMessage.message);
return response.status(500).json(errorMessage);
}
}
const readline = require('readline');
const fs = require('fs');
const readStream = fs.createReadStream(filename);
const rl = readline.createInterface({
input: readStream,
@@ -2073,15 +2214,17 @@ app.post("/importchat", urlencodedParser, function (request, response) {
app.post('/importworldinfo', urlencodedParser, (request, response) => {
if (!request.file) return response.sendStatus(400);
const filename = sanitize(request.file.originalname);
const filename = `${path.parse(sanitize(request.file.originalname)).name}.json`;
if (path.parse(filename).ext.toLowerCase() !== '.json') {
return response.status(400).send('Only JSON files are supported.')
let fileContents = null;
if (request.body.convertedData) {
fileContents = request.body.convertedData;
} else {
const pathToUpload = path.join('./uploads/', request.file.filename);
fileContents = fs.readFileSync(pathToUpload, 'utf8');
}
const pathToUpload = path.join('./uploads/', request.file.filename);
const fileContents = fs.readFileSync(pathToUpload, 'utf8');
try {
const worldContent = json5.parse(fileContents);
if (!('entries' in worldContent)) {
@@ -2122,7 +2265,7 @@ app.post('/editworldinfo', jsonParser, (request, response) => {
const filename = `${request.body.name}.json`;
const pathToFile = path.join(directories.worlds, filename);
fs.writeFileSync(pathToFile, JSON.stringify(request.body.data));
fs.writeFileSync(pathToFile, JSON.stringify(request.body.data, null, 4));
return response.send({ ok: true });
});
@@ -2141,7 +2284,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
const filename = `${Date.now()}.png`;
const filename = request.body.overwrite_name || `${Date.now()}.png`;
const pathToNewFile = path.join(directories.avatars, filename);
fs.writeFileSync(pathToNewFile, image);
fs.rmSync(pathToUpload);
@@ -2748,15 +2891,30 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_openai = response) {
if (!request.body) return response_getstatus_openai.sendStatus(400);
const api_key_openai = readSecret(SECRET_KEYS.OPENAI);
let api_url;
let api_key_openai;
let headers;
if (!api_key_openai) {
return response_getstatus_openai.sendStatus(401);
if (request.body.use_openrouter == false) {
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
headers = {};
} else {
api_url = 'https://openrouter.ai/api/v1';
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
headers = { 'HTTP-Referer': request.headers.referer };
}
if (!api_key_openai) {
return response_getstatus_openai.status(401).send({ error: true });
}
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
const args = {
headers: { "Authorization": "Bearer " + api_key_openai }
headers: {
"Authorization": "Bearer " + api_key_openai,
...headers,
},
};
client.get(api_url + "/models", args, function (data, response) {
if (response.statusCode == 200) {
@@ -2788,6 +2946,12 @@ app.post("/openai_bias", jsonParser, async function (request, response) {
let result = {};
const model = getTokenizerModel(String(request.query.model || ''));
// no bias for claude
if (model == 'claude') {
return response.send(result);
}
const tokenizer = getTiktokenTokenizer(model);
for (const entry of request.body) {
@@ -2860,7 +3024,7 @@ app.post("/deletepreset_openai", jsonParser, function (request, response) {
});
// Prompt Conversion script taken from RisuAI by @kwaroran (GPLv3).
function convertClaudePrompt(messages) {
function convertClaudePrompt(messages, addHumanPrefix, addAssistantPostfix) {
// Claude doesn't support message names, so we'll just add them to the message content.
for (const message of messages) {
if (message.name && message.role !== "system") {
@@ -2890,7 +3054,16 @@ function convertClaudePrompt(messages) {
break
}
return prefix + v.content;
}).join('') + '\n\nAssistant: ';
}).join('');
if (addHumanPrefix) {
requestPrompt = "\n\nHuman: " + requestPrompt;
}
if (addAssistantPostfix) {
requestPrompt = requestPrompt + '\n\nAssistant: ';
}
return requestPrompt;
}
@@ -2911,14 +3084,14 @@ async function sendClaudeRequest(request, response) {
controller.abort();
});
const requestPrompt = convertClaudePrompt(request.body.messages);
const requestPrompt = convertClaudePrompt(request.body.messages, true, true);
console.log('Claude request:', requestPrompt);
const generateResponse = await fetch(api_url + '/complete', {
method: "POST",
signal: controller.signal,
body: JSON.stringify({
prompt: "\n\nHuman: " + requestPrompt,
prompt: requestPrompt,
model: request.body.model,
max_tokens_to_sample: request.body.max_tokens,
stop_sequences: ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"],
@@ -2976,9 +3149,20 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
return sendClaudeRequest(request, response_generate_openai);
}
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
let api_url;
let api_key_openai;
let headers;
const api_key_openai = readSecret(SECRET_KEYS.OPENAI);
if (request.body.use_openrouter == false) {
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
headers = {};
} else {
api_url = 'https://openrouter.ai/api/v1';
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
headers = { 'HTTP-Referer': request.headers.referer };
}
if (!api_key_openai) {
return response_generate_openai.status(401).send({ error: true });
@@ -2996,7 +3180,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
url: api_url + '/chat/completions',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + api_key_openai
'Authorization': 'Bearer ' + api_key_openai,
...headers,
},
data: {
"messages": request.body.messages,
@@ -3084,15 +3269,20 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_openai = response) {
if (!request.body) return response_tokenize_openai.sendStatus(400);
let num_tokens = 0;
const model = getTokenizerModel(String(request.query.model || ''));
if (model == 'claude') {
num_tokens = countClaudeTokens(claude_tokenizer, request.body);
return response_tokenize_openai.send({ "token_count": num_tokens });
}
const tokensPerName = model.includes('gpt-4') ? 1 : -1;
const tokensPerMessage = model.includes('gpt-4') ? 3 : 4;
const tokensPadding = 3;
const tokenizer = getTiktokenTokenizer(model);
let num_tokens = 0;
for (const msg of request.body) {
num_tokens += tokensPerMessage;
for (const [key, value] of Object.entries(msg)) {
@@ -3118,7 +3308,7 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
const filename = `${name}.settings`;
const fullpath = path.join(directories.openAI_Settings, filename);
fs.writeFileSync(fullpath, JSON.stringify(request.body), 'utf-8');
fs.writeFileSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');
return response.send({ name });
});
@@ -3193,6 +3383,7 @@ const setupTasks = async function () {
console.log(`SillyTavern ${version.pkgVersion}` + (version.gitBranch ? ` '${version.gitBranch}' (${version.gitRevision})` : ''));
backupSettings();
migrateSecrets();
ensurePublicDirectoriesExist();
await ensureThumbnailCache();
@@ -3200,10 +3391,11 @@ const setupTasks = async function () {
// Colab users could run the embedded tool
if (!is_colab) await convertWebp();
[spp_llama, spp_nerd, spp_nerd_v2] = await Promise.all([
[spp_llama, spp_nerd, spp_nerd_v2, claude_tokenizer] = await Promise.all([
loadSentencepieceTokenizer('src/sentencepiece/tokenizer.model'),
loadSentencepieceTokenizer('src/sentencepiece/nerdstash.model'),
loadSentencepieceTokenizer('src/sentencepiece/nerdstash_v2.model'),
loadClaudeTokenizer('src/claude.json'),
]);
console.log('Launching...');
@@ -3283,6 +3475,41 @@ async function convertWebp() {
}
}
function backupSettings() {
const MAX_BACKUPS = 25;
function generateTimestamp() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
}
try {
if (!fs.existsSync(directories.backups)) {
fs.mkdirSync(directories.backups);
}
const backupFile = path.join(directories.backups, `settings_${generateTimestamp()}.json`);
fs.copyFileSync(SETTINGS_FILE, backupFile);
let files = fs.readdirSync(directories.backups);
if (files.length > MAX_BACKUPS) {
files = files.map(f => path.join(directories.backups, f));
files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
fs.rmSync(files[0]);
}
} catch (err) {
console.log('Could not backup settings file', err);
}
}
function ensurePublicDirectoriesExist() {
for (const dir of Object.values(directories)) {
if (!fs.existsSync(dir)) {
@@ -3299,6 +3526,8 @@ const SECRET_KEYS = {
POE: 'api_key_poe',
NOVEL: 'api_key_novel',
CLAUDE: 'api_key_claude',
DEEPL: 'deepl',
OPENROUTER: 'api_key_openrouter',
}
function migrateSecrets() {
@@ -3549,6 +3778,53 @@ app.post('/google_translate', jsonParser, async (request, response) => {
});
});
app.post('/deepl_translate', jsonParser, async (request, response) => {
const key = readSecret(SECRET_KEYS.DEEPL);
if (!key) {
return response.sendStatus(401);
}
const text = request.body.text;
const lang = request.body.lang;
if (!text || !lang) {
return response.sendStatus(400);
}
console.log('Input text: ' + text);
const fetch = require('node-fetch').default;
const params = new URLSearchParams();
params.append('text', text);
params.append('target_lang', lang);
try {
const result = await fetch('https://api-free.deepl.com/v2/translate', {
method: 'POST',
body: params,
headers: {
'Accept': 'application/json',
'Authorization': `DeepL-Auth-Key ${key}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout: 0,
});
if (!result.ok) {
return response.sendStatus(result.status);
}
const json = await result.json();
console.log('Translated text: ' + json.translations[0].text);
return response.send(json.translations[0].text);
} catch (error) {
console.log("Translation error: " + error.message);
return response.sendStatus(500);
}
});
app.post('/novel_tts', jsonParser, async (request, response) => {
const token = readSecret(SECRET_KEYS.NOVEL);
@@ -3714,6 +3990,177 @@ app.post('/upload_sprite', urlencodedParser, async (request, response) => {
}
});
app.post('/import_custom', jsonParser, async (request, response) => {
if (!request.body.url) {
return response.sendStatus(400);
}
try {
const url = request.body.url;
let result;
const chubParsed = parseChubUrl(url);
if (chubParsed?.type === 'character') {
console.log('Downloading chub character:', chubParsed.id);
result = await downloadChubCharacter(chubParsed.id);
}
else if (chubParsed?.type === 'lorebook') {
console.log('Downloading chub lorebook:', chubParsed.id);
result = await downloadChubLorebook(chubParsed.id);
}
else {
return response.sendStatus(404);
}
response.set('Content-Type', result.fileType);
response.set('Content-Disposition', `attachment; filename="${result.fileName}"`);
response.set('X-Custom-Content-Type', chubParsed?.type);
return response.send(result.buffer);
} catch (error) {
console.log('Importing custom content failed', error);
return response.sendStatus(500);
}
});
async function downloadChubLorebook(id) {
const fetch = require('node-fetch').default;
const result = await fetch('https://api.chub.ai/api/lorebooks/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"fullPath": id,
"format": "SILLYTAVERN",
}),
});
if (!result.ok) {
throw new Error('Failed to download lorebook');
}
const name = id.split('/').pop();
const buffer = await result.buffer();
const fileName = `${sanitize(name)}.json`;
const fileType = result.headers.get('content-type');
return { buffer, fileName, fileType };
}
async function downloadChubCharacter(id) {
const fetch = require('node-fetch').default;
const result = await fetch('https://api.chub.ai/api/characters/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"format": "tavern",
"fullPath": id,
})
});
if (!result.ok) {
throw new Error('Failed to download character');
}
const buffer = await result.buffer();
const fileName = result.headers.get('content-disposition')?.split('filename=')[1] || `${sanitize(id)}.png`;
const fileType = result.headers.get('content-type');
return { buffer, fileName, fileType };
}
function parseChubUrl(str) {
const splitStr = str.split('/');
const length = splitStr.length;
if (length < 2) {
return null;
}
const domainIndex = splitStr.indexOf('chub.ai');
const lastTwo = domainIndex !== -1 ? splitStr.slice(domainIndex + 1) : splitStr;
const firstPart = lastTwo[0].toLowerCase();
if (firstPart === 'characters' || firstPart === 'lorebooks') {
const type = firstPart === 'characters' ? 'character' : 'lorebook';
const id = type === 'character' ? lastTwo.slice(1).join('/') : lastTwo.join('/');
return {
id: id,
type: type
};
} else if (length === 2) {
return {
id: lastTwo.join('/'),
type: 'character'
};
}
return null;
}
function importRisuSprites(data) {
try {
const name = data?.data?.name;
const risuData = data?.data?.extensions?.risuai;
// Not a Risu AI character
if (!risuData || !name) {
return;
}
let images = [];
if (Array.isArray(risuData.additionalAssets)) {
images = images.concat(risuData.additionalAssets);
}
if (Array.isArray(risuData.emotions)) {
images = images.concat(risuData.emotions);
}
// No sprites to import
if (images.length === 0) {
return;
}
// Create sprites folder if it doesn't exist
const spritesPath = path.join(directories.characters, name);
if (!fs.existsSync(spritesPath)) {
fs.mkdirSync(spritesPath);
}
// Path to sprites is not a directory. This should never happen.
if (!fs.statSync(spritesPath).isDirectory()) {
return;
}
console.log(`RisuAI: Found ${images.length} sprites for ${name}. Writing to disk.`);
const files = fs.readdirSync(spritesPath);
outer: for (const [label, fileBase64] of images) {
// Remove existing sprite with the same label
for (const file of files) {
if (path.parse(file).name === label) {
console.log(`RisuAI: The sprite ${label} for ${name} already exists. Skipping.`);
continue outer;
}
}
const filename = label + '.png';
const pathToFile = path.join(spritesPath, filename);
fs.writeFileSync(pathToFile, fileBase64, { encoding: 'base64' });
}
// Remove additionalAssets and emotions from data (they are now in the sprites folder)
delete data.data.extensions.risuai.additionalAssets;
delete data.data.extensions.risuai.emotions;
} catch (error) {
console.error(error);
}
}
function writeSecret(key, value) {
if (!fs.existsSync(SECRETS_FILE)) {
const emptyFile = JSON.stringify({});

1
src/claude.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,7 @@ const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');
const _ = require('lodash');
const directory = __dirname;
@@ -80,7 +81,7 @@ function extractFormKey(html) {
}
const formKey = formKeyList.join("");
return formKey;
return formKey.slice(0, -1);
}
@@ -281,6 +282,33 @@ async function request_with_retries(method, attempts = 10) {
throw new Error(`Failed to download ${url} too many times.`);
}
function findKey(obj, key, path = []) {
if (obj && typeof obj === 'object') {
if (key in obj) {
return [...path, key];
}
for (const k in obj) {
const result = findKey(obj[k], key, [...path, k]);
if (result) {
return result;
}
}
}
return false;
}
function logObjectStructure(obj, indent = 0, depth = Infinity) {
const keys = Object.keys(obj);
keys.forEach((key) => {
console.log(`${' '.repeat(indent)}${key}`);
if (typeof obj[key] === 'object' && obj[key] !== null && indent < depth) {
logObjectStructure(obj[key], indent + 1, depth);
}
});
}
class Client {
gql_url = "https://poe.com/api/gql_POST";
gql_recv_url = "https://poe.com/api/receive_POST";
@@ -363,19 +391,69 @@ class Client {
async get_next_data() {
logger.info('Downloading next_data...');
//these keys are used as of June 29, 2023
//if API changes in the future, just change these to find the new path
const viewerKeyName = 'viewer'
const botNameKeyName = 'chatOfBotHandle'
const defaultBotKeyName = 'defaultBotNickname'
const r = await request_with_retries(() => this.session.get(this.home_url));
const jsonRegex = /<script id="__NEXT_DATA__" type="application\/json">(.+?)<\/script>/;
const jsonText = jsonRegex.exec(r.data)[1];
const nextData = JSON.parse(jsonText);
const viewerPath = findKey(nextData, viewerKeyName);
const botNamePath = findKey(nextData, botNameKeyName);
const defaultBotPath = findKey(nextData, defaultBotKeyName);
let viewer = null;
if (viewerPath) {
viewer = _.get(nextData, viewerPath.join('.'));
}
//if the API changes, these reports will tell us how it changed
if (viewerPath) {
console.log(`'${viewerKeyName}' key: ${viewerPath.join('.')}`);
} else {
console.log(`ERROR: '${viewerKeyName}' key not found.`);
//console.log(logObjectStructure(nextData, 0, 2));
}
if (botNamePath) {
console.log(`'${botNameKeyName}' key: ${botNamePath.join('.')}`);
} else {
console.log(`ERROR: '${botNameKeyName}' key not found.`);
//console.log(logObjectStructure(nextData, 0, 2));
}
if (defaultBotPath) {
console.log(`'${defaultBotKeyName}' key: ${defaultBotPath.join('.')}`);
} else {
console.log(`ERROR: '${defaultBotKeyName}' key not found.`);
}
if (!viewerPath || !botNamePath || !defaultBotPath) {
console.log('-----------------')
console.log("ERROR READING POE API! THIS IS THE RESPONSE STRUCTURE:")
console.log("SEARCH THIS LIST FOR 'chatOfBotDisplayName', 'viewer', AND 'defaultBotNickname'...")
console.log("-----------------")
console.log(logObjectStructure(nextData, 0, 4));
console.log("-----------------")
}
this.formkey = extractFormKey(r.data);
this.viewer = nextData.props.pageProps.payload.viewer;
this.viewer = viewer;
//old hard coded message no longer needed
//this.viewer = nextData.props.pageProps.payload?.viewer || nextData.props.pageProps.data?.viewer;
return nextData;
}
async get_bots() {
const viewer = this.next_data.props.pageProps.payload.viewer;
const viewer = this.viewer;
if (!viewer.availableBotsConnection) {
throw new Error('Invalid token.');
}
@@ -393,12 +471,12 @@ class Client {
r = cached_bots[url];
}
else {
logger.info(`Downloading ${url}`);
logger.info(`Downloading ${bot.displayName}`);
r = await request_with_retries(() => this.session.get(url), retries);
cached_bots[url] = r;
}
const chatData = r.data.pageProps.payload.chatOfBotDisplayName;
const chatData = r.data.pageProps.payload?.chatOfBotDisplayName || r.data.pageProps.data?.chatOfBotHandle;
bots[chatData.defaultBotObject.nickname] = chatData;
resolve();
@@ -661,14 +739,14 @@ class Client {
signal.throwIfAborted();
}
if (timeout == 0) {
if (timeout <= 0) {
throw new Error("Response timed out.");
}
const message = this.message_queues[humanMessageId].shift();
if (!message) {
timeout -= 1;
await delay(1000);
timeout -= 0.1;
await delay(100);
continue;
//throw new Error("Queue is empty");
}

View File

@@ -3,19 +3,19 @@
if ! command -v npm &> /dev/null
then
read -p "npm is not installed. Do you want to install nodejs and npm? (y/n)" choice
case "$choice" in
y|Y )
case "$choice" in
y|Y )
echo "Installing nvm..."
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
source ~/.bashrc
nvm install lts
nvm use lts;;
n|N )
nvm install --lts
nvm use --lts;;
n|N )
echo "Nodejs and npm will not be installed."
exit;;
* )
* )
echo "Invalid option. Nodejs and npm will not be installed."
exit;;
esac
@@ -28,7 +28,7 @@ if [ ! -z "$REPL_ID" ]; then
fi
echo "Installing Node Modules..."
npm i
npm i --no-audit
echo "Entering SillyTavern..."
node "$(dirname "$0")/server.js"
node "$(dirname "$0")/server.js"