mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
655 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6eb74cb715 | ||
|
4d67d7d748 | ||
|
cee304fe29 | ||
|
2a3e71bf6e | ||
|
d3a7466929 | ||
|
26d506874f | ||
|
4828bd95f3 | ||
|
336674b724 | ||
|
f181d1a847 | ||
|
7149f46c9a | ||
|
2670709237 | ||
|
7fe329b5cf | ||
|
175a91f979 | ||
|
fcc00e0b26 | ||
|
2b50ab398b | ||
|
bf28ae07b3 | ||
|
76b822c627 | ||
|
bb39e852b8 | ||
|
f7d3a1c942 | ||
|
75099d3a22 | ||
|
5c3b799d65 | ||
|
1d6f038601 | ||
|
cba2b54531 | ||
|
d994528548 | ||
|
003066a036 | ||
|
be08e62fc1 | ||
|
a287ccfca2 | ||
|
38792d071b | ||
|
c34150fef0 | ||
|
e98f38b6da | ||
|
1c69ba1ae3 | ||
|
3803714465 | ||
|
fa1d45635b | ||
|
54c772622e | ||
|
1ab674ba28 | ||
|
b3577024f4 | ||
|
7ea560307c | ||
|
cc9eca8427 | ||
|
4e33253a91 | ||
|
b62cbdeebd | ||
|
0c129f6dbe | ||
|
c69c5e07e3 | ||
|
62a14fb74b | ||
|
bbb1a6e578 | ||
|
6b204ada9f | ||
|
889a552629 | ||
|
89e5562494 | ||
|
29ff0876a7 | ||
|
8136293593 | ||
|
902dfbcdcc | ||
|
e713b32bdc | ||
|
d3be6caaa1 | ||
|
46830a27d0 | ||
|
190bed8025 | ||
|
2293828f8e | ||
|
985c2dd031 | ||
|
043eead149 | ||
|
6653757c5c | ||
|
b29d32d518 | ||
|
b97dceeb7a | ||
|
11ca0dd22e | ||
|
9666b9920a | ||
|
7cf5a4cb2e | ||
|
54e111886b | ||
|
b8295ac8f5 | ||
|
cf56bfb6a9 | ||
|
537cfbc027 | ||
|
599c55938b | ||
|
8608bc92ae | ||
|
bd5592de7b | ||
|
79b8dc98eb | ||
|
4e083ebd4f | ||
|
5075534b2e | ||
|
b8ae54fb2c | ||
|
d5016ad672 | ||
|
d737c0f285 | ||
|
d084f579c5 | ||
|
124cbfdfa4 | ||
|
7bf793d2be | ||
|
f73986d23f | ||
|
1c6c9efba1 | ||
|
efb9fbcc7e | ||
|
360c2985f5 | ||
|
d64d16bdf2 | ||
|
52a803b6ab | ||
|
3b7540da05 | ||
|
b80b2d9a74 | ||
|
5b002c6e46 | ||
|
886b6fee64 | ||
|
54fb7a9030 | ||
|
aceca89080 | ||
|
719539c2ab | ||
|
ef0772bc9f | ||
|
8b1492a2d9 | ||
|
b1fa4d3038 | ||
|
d9536ae3a8 | ||
|
112e26a0ff | ||
|
584d0e6222 | ||
|
4e7232f13e | ||
|
ec58d9272a | ||
|
ff5f89bd5e | ||
|
cd9013cf73 | ||
|
717c524b01 | ||
|
c55452d0ea | ||
|
c8411b6dfb | ||
|
0c402e2a5f | ||
|
9113fae4fe | ||
|
071a77fe1a | ||
|
8034564c3e | ||
|
2ef6004bd5 | ||
|
d188795591 | ||
|
3e1b54c6f0 | ||
|
c3461307a0 | ||
|
1188cb46b8 | ||
|
0298849953 | ||
|
a030237641 | ||
|
24ae2b6fa6 | ||
|
01d38f9218 | ||
|
8dab4ecb06 | ||
|
083ea43971 | ||
|
d0f59edf09 | ||
|
2687618840 | ||
|
20a23c5e31 | ||
|
0276a2ef71 | ||
|
0f00adca0c | ||
|
bbd4d7e2fd | ||
|
6b716980be | ||
|
0e0bd0d3d9 | ||
|
650755198d | ||
|
45ae8d1060 | ||
|
e0000bade6 | ||
|
974b98ed8e | ||
|
3b1bd97845 | ||
|
990130d7c2 | ||
|
62a1cb1dce | ||
|
444705a5f8 | ||
|
675e7b1de3 | ||
|
1efc26759f | ||
|
d2b2856630 | ||
|
c8b9b62d8a | ||
|
508b685fdc | ||
|
cf9a5383a9 | ||
|
7a27c29695 | ||
|
db8fec7757 | ||
|
41ab90bb8e | ||
|
b188c176fd | ||
|
4b58a822db | ||
|
e7ab43527a | ||
|
a3dbcf3c2a | ||
|
b89afe6d13 | ||
|
55483e76e0 | ||
|
8b9afff30d | ||
|
893f4f3ed6 | ||
|
9059621dab | ||
|
14879af678 | ||
|
80496db482 | ||
|
3b03561d27 | ||
|
7e3e75875d | ||
|
e0a404e099 | ||
|
66210e9c0f | ||
|
8b5224e274 | ||
|
5db2254548 | ||
|
d1dd3a5433 | ||
|
59e1f9cca1 | ||
|
7c57132710 | ||
|
89a2e266a0 | ||
|
de7a5085b1 | ||
|
f2d64a7d08 | ||
|
3dcb4dee59 | ||
|
39362fd566 | ||
|
5de80f4c6d | ||
|
fa9ae4c979 | ||
|
946994af22 | ||
|
bd9c10c2eb | ||
|
b105a2ef24 | ||
|
a85ac96f82 | ||
|
4d493ca733 | ||
|
9b17f4e0c0 | ||
|
eb8f4bebe0 | ||
|
f3327c06ab | ||
|
6594b3c7fa | ||
|
2012bb49d2 | ||
|
e736283785 | ||
|
38cc4f789b | ||
|
e2593215bf | ||
|
278b526898 | ||
|
5a50ed97be | ||
|
d0b6243f77 | ||
|
eba0f54477 | ||
|
3a15e44d0f | ||
|
03cfbca7cf | ||
|
a161ebfcaf | ||
|
58a85fa0c8 | ||
|
7642b66a0e | ||
|
48621f1d50 | ||
|
42766a715d | ||
|
3e27f0213a | ||
|
9ec8aa3bf9 | ||
|
7875a65b44 | ||
|
461b73facf | ||
|
4b4ee7409b | ||
|
323f34f5d4 | ||
|
de1910268a | ||
|
a39a1a7cec | ||
|
d64647280a | ||
|
8564d6faa8 | ||
|
b8830e34d3 | ||
|
b448568aa3 | ||
|
b513043e4a | ||
|
0fe19bca47 | ||
|
b4559d3fd8 | ||
|
6c9f3a868d | ||
|
0f92c90b71 | ||
|
36ecf8a717 | ||
|
aa16ac446d | ||
|
a6e2692e52 | ||
|
26eb5f0926 | ||
|
07da2461d0 | ||
|
c79f1e4360 | ||
|
87915a5f79 | ||
|
d64b265a39 | ||
|
7c2b475e46 | ||
|
d02fbbb42f | ||
|
37930caade | ||
|
c6c8f91c99 | ||
|
473e11c773 | ||
|
9c2de78ad3 | ||
|
abb186db01 | ||
|
a00560d2b3 | ||
|
791ce3da86 | ||
|
6380e0a062 | ||
|
62bc550d3a | ||
|
e3714e9b6a | ||
|
da6d77cffd | ||
|
824d0a9b63 | ||
|
1ede346cbc | ||
|
3ab5cc1766 | ||
|
48077d200b | ||
|
30765550c8 | ||
|
f2cc66d414 | ||
|
56710fee39 | ||
|
7667231137 | ||
|
0c69b698b9 | ||
|
feb8321147 | ||
|
3092c68a05 | ||
|
2a1704add0 | ||
|
9c3cad2df2 | ||
|
aa473dd749 | ||
|
ab7b07ba28 | ||
|
e9f93ba748 | ||
|
a5baa3605f | ||
|
f092269c01 | ||
|
ffc84f5118 | ||
|
461b1a9d87 | ||
|
514ac27d00 | ||
|
2a4d11e6a6 | ||
|
c684bfbf52 | ||
|
0ffad7f4fe | ||
|
8d5876c2c8 | ||
|
75dfe87054 | ||
|
8e8b6b353a | ||
|
10fd2e1334 | ||
|
41befc3587 | ||
|
8812e09e8d | ||
|
00b44071a6 | ||
|
9923018a49 | ||
|
e6bd46acef | ||
|
190400eb6b | ||
|
e5c8a920ee | ||
|
fc488574c6 | ||
|
dca81aef3d | ||
|
e7772f04a4 | ||
|
7249294ffd | ||
|
e861a406a3 | ||
|
22d598c0f5 | ||
|
c3cbf33ba0 | ||
|
dadef92fdf | ||
|
88b6331aed | ||
|
a9c4422c87 | ||
|
08a0b1e828 | ||
|
b09e86fb53 | ||
|
feb7675d2f | ||
|
4d25856b4f | ||
|
c8eaa15f18 | ||
|
fe5289c495 | ||
|
5843bb788f | ||
|
4607b79a83 | ||
|
bca99a4d7f | ||
|
1d3914324f | ||
|
be9f34ab8a | ||
|
7f7ecdcca8 | ||
|
66d609c35f | ||
|
6f7ef25369 | ||
|
fca626d246 | ||
|
065d453477 | ||
|
316df6ed17 | ||
|
f67ed6d22a | ||
|
1a061c6ae5 | ||
|
1467c4539e | ||
|
60b09a431a | ||
|
e3a46df010 | ||
|
5ed4bd8748 | ||
|
a9143e8ea2 | ||
|
037ba84916 | ||
|
c7dc63200a | ||
|
084aa794f8 | ||
|
d75b30d51a | ||
|
5e44403346 | ||
|
861decd5c9 | ||
|
0fe579e782 | ||
|
aa513e1e3d | ||
|
7f284a3752 | ||
|
e5d9f2937e | ||
|
974d6275bf | ||
|
e5ba96d6aa | ||
|
716d407753 | ||
|
34e8cf476a | ||
|
51d7ba728f | ||
|
bba16f5263 | ||
|
67b7cbe920 | ||
|
4892f04a2a | ||
|
5860719780 | ||
|
101c735d91 | ||
|
3ca6795cde | ||
|
339428a4e9 | ||
|
3cafc22e1d | ||
|
788a313024 | ||
|
67dc5e5252 | ||
|
2ea0d6466c | ||
|
86ad8416df | ||
|
a91ba2a277 | ||
|
1ac2241d2c | ||
|
405fc1458c | ||
|
b22bc47c4f | ||
|
bda4958cb3 | ||
|
2aa8564522 | ||
|
5cb319771d | ||
|
cef65a17f9 | ||
|
3ede4aafbe | ||
|
560119bc3e | ||
|
4c81215a60 | ||
|
5699eb115d | ||
|
64698ac073 | ||
|
bdf7fccbae | ||
|
96f04a1c49 | ||
|
aa4bdec79c | ||
|
10a4e54a3b | ||
|
839d79f407 | ||
|
a1a9f0002c | ||
|
84ee968ab4 | ||
|
9c3176b29f | ||
|
d69263923a | ||
|
abed49c277 | ||
|
75512842d0 | ||
|
9a5d0e829b | ||
|
679b3587b5 | ||
|
860a2f6929 | ||
|
bb09f5a292 | ||
|
9fb9253dcc | ||
|
ea21de89c3 | ||
|
593f9b5832 | ||
|
4e447a59b5 | ||
|
b3e57dae85 | ||
|
d1ed983106 | ||
|
a20b2a566d | ||
|
fc03fea00a | ||
|
b814ba5b35 | ||
|
d14af1592e | ||
|
3e60d9e4d8 | ||
|
c8f3a0be40 | ||
|
de7f8de3e3 | ||
|
230215a211 | ||
|
bb48dfe084 | ||
|
1685f6ded0 | ||
|
3dfe10815d | ||
|
67f2c380a3 | ||
|
1ae6f05d09 | ||
|
2c171fdcfd | ||
|
305d60a28e | ||
|
4e822eeebb | ||
|
1dd21caa66 | ||
|
60b7164c28 | ||
|
10da7eb474 | ||
|
d98d811cc1 | ||
|
f05d90bada | ||
|
66fd973830 | ||
|
d1824acee0 | ||
|
61906d8dbe | ||
|
4dcb2acba5 | ||
|
64711109a6 | ||
|
d1b533cbfa | ||
|
d31eb639dc | ||
|
fae6ff481e | ||
|
bcfc4d5c64 | ||
|
39721b6a8f | ||
|
e2089b1e44 | ||
|
671b7ef7cb | ||
|
76c35d269b | ||
|
ff241dd0a9 | ||
|
179a099954 | ||
|
2c787f23c7 | ||
|
ff680f46cc | ||
|
0a3e91287d | ||
|
f04bbdf112 | ||
|
c911265dbd | ||
|
858e5f2efb | ||
|
144376fbb4 | ||
|
6bed373f0a | ||
|
cebaf2ee08 | ||
|
340b3920ac | ||
|
55a95c910f | ||
|
e8aba9fa5f | ||
|
6ac81c06db | ||
|
b8cbd93618 | ||
|
3bc68a1ac4 | ||
|
54660e2d66 | ||
|
e9d4a982c0 | ||
|
5cc4242c6f | ||
|
98905e0e53 | ||
|
9b969b283e | ||
|
91061c1d55 | ||
|
e93bc49b36 | ||
|
9318f94f08 | ||
|
9128c2128e | ||
|
d8949fddc7 | ||
|
89d1bc8341 | ||
|
6ca71c3e2c | ||
|
65cf9c8f4d | ||
|
20d12dc98e | ||
|
a41fe1d801 | ||
|
b559f2f559 | ||
|
2e23e78937 | ||
|
358d40f502 | ||
|
c939c544e2 | ||
|
d3327f7829 | ||
|
61968cb58f | ||
|
77b80da520 | ||
|
961e778a75 | ||
|
dbf1aa6816 | ||
|
d501c6cf6e | ||
|
abafdadf33 | ||
|
5ec2f33cb0 | ||
|
07cfc1fb0b | ||
|
7af27bb6a9 | ||
|
886f5adce7 | ||
|
1c6671df31 | ||
|
62eb790b0b | ||
|
e660ec1f14 | ||
|
760af12252 | ||
|
6228d1d3b1 | ||
|
e0ba516551 | ||
|
e3ec65fd31 | ||
|
716366070b | ||
|
43f52d5436 | ||
|
2c911a3ea2 | ||
|
6c3118549f | ||
|
d25ba41fb5 | ||
|
6a832bdf2a | ||
|
bc94bcb25c | ||
|
9ff2da4c8c | ||
|
e007fe7529 | ||
|
283bb2fa89 | ||
|
4eb6657b51 | ||
|
31eb0235c2 | ||
|
b904f501ba | ||
|
097894308e | ||
|
d350dbf0d7 | ||
|
110d343eea | ||
|
24b6f99abf | ||
|
2aeaf43c28 | ||
|
f27a83ef73 | ||
|
b833f36c75 | ||
|
7d983adc6e | ||
|
309eb80748 | ||
|
865c48bcc0 | ||
|
2b3dfc5ae2 | ||
|
c858fccc5f | ||
|
e66b270811 | ||
|
1d32749ed2 | ||
|
0024f96a99 | ||
|
4528655bb7 | ||
|
965dac6514 | ||
|
80e104e723 | ||
|
a6e6677c32 | ||
|
66db820c9e | ||
|
62a1919402 | ||
|
99e09f0b91 | ||
|
8726def6e0 | ||
|
1bc45d2869 | ||
|
2c049e5611 | ||
|
630111c737 | ||
|
311fb261a4 | ||
|
24224dc0b1 | ||
|
35e21c3568 | ||
|
813b9e6a4b | ||
|
b8e8e96f01 | ||
|
678a0ee136 | ||
|
00fc40408a | ||
|
31f4a34f5a | ||
|
ee2b09ec4c | ||
|
ef137f68c4 | ||
|
cb381595f9 | ||
|
b545185f1a | ||
|
fa6fc45e6f | ||
|
c6745d76a8 | ||
|
1e0efb73c5 | ||
|
73e6e3725d | ||
|
83bfe59991 | ||
|
df3552d0d8 | ||
|
c3544ba07d | ||
|
1c7e696549 | ||
|
b23f6944f1 | ||
|
1e15be34b6 | ||
|
dc8530049f | ||
|
a20c6bb01e | ||
|
33b22bd4f8 | ||
|
4f2543f7ae | ||
|
d9582062d2 | ||
|
439ef0dc5e | ||
|
da4f0f53be | ||
|
b20cf52fe6 | ||
|
761f903fdb | ||
|
a717e2ace8 | ||
|
5c3ad3e0bc | ||
|
1ed1e18304 | ||
|
0ebac0e2af | ||
|
800c94cb93 | ||
|
2f2a4fca35 | ||
|
d5f6849c8e | ||
|
61e5c32cd2 | ||
|
8bcb1ef2db | ||
|
0e7eff155d | ||
|
66454bb711 | ||
|
e1dfbc0bea | ||
|
7dc3b06d0f | ||
|
4d161768c0 | ||
|
967a7980f5 | ||
|
5450bacf0f | ||
|
97965b2de5 | ||
|
26572458b6 | ||
|
3a5dfadac5 | ||
|
33cec69df9 | ||
|
039f3b875b | ||
|
1f46d334b1 | ||
|
8a8e8a89dc | ||
|
a11231dd2e | ||
|
92cb70213a | ||
|
ab8c67ede6 | ||
|
23d3b85696 | ||
|
65c3dfb694 | ||
|
e8b96fec02 | ||
|
a251849f8f | ||
|
75a1ef4304 | ||
|
d33ca68620 | ||
|
29d817d549 | ||
|
6b66bc41fe | ||
|
46cd47bdfc | ||
|
5e970c8a51 | ||
|
bce8627644 | ||
|
0b95ea3f7b | ||
|
3dd4f2b94a | ||
|
d011d60351 | ||
|
a9c3a808ac | ||
|
d5533854cc | ||
|
74b6ed97c2 | ||
|
99d143263d | ||
|
a12df762a0 | ||
|
f5fccc0387 | ||
|
0371bf4e9f | ||
|
f56fecaa26 | ||
|
255cd1310d | ||
|
56392c1789 | ||
|
8b7a858e1f | ||
|
103c460f0a | ||
|
c1d8896db9 | ||
|
aae95f70c4 | ||
|
e582bb9117 | ||
|
3616c2acf0 | ||
|
19630735e0 | ||
|
c2ed5d07e2 | ||
|
319b0a543c | ||
|
e0ac189acc | ||
|
2c69d77fae | ||
|
f24aa1fa5f | ||
|
86af5ac217 | ||
|
0b06f9686b | ||
|
bac00659ef | ||
|
b1c9fee29e | ||
|
226852233f | ||
|
630b72f13a | ||
|
c21deb3a8c | ||
|
055defa204 | ||
|
0275f2ec15 | ||
|
d2ce1e17b3 | ||
|
7efe9cf209 | ||
|
5c243fa465 | ||
|
456e1124a3 | ||
|
9b1a254553 | ||
|
a119a5cbfb | ||
|
c2592d7d86 | ||
|
927dc4394d | ||
|
04ad1011f0 | ||
|
56d0ffc1fd | ||
|
3dc4c8ca39 | ||
|
09a575b783 | ||
|
3cd3890263 | ||
|
435821348a | ||
|
03bb911ee7 | ||
|
63f96f4dc7 | ||
|
db2c9a9926 | ||
|
1fc88e97f4 | ||
|
3d3914645d | ||
|
68ade7b384 | ||
|
071f232611 | ||
|
dff5ca7e92 | ||
|
a47dda79a3 | ||
|
549b2d52a4 | ||
|
ad3f677a9e | ||
|
0d68473010 | ||
|
6ed7729b18 | ||
|
0c36d4e67d | ||
|
d6a02e3c47 | ||
|
a8c9fe4dce | ||
|
454a71922d | ||
|
b4dc66f950 | ||
|
d902d0d202 | ||
|
b6cb08101b | ||
|
c10323424d | ||
|
df2710bcbe | ||
|
5506c81397 | ||
|
ed2e7a2f47 | ||
|
27e3a9201e | ||
|
863e0c3643 | ||
|
8d2b7a15dc | ||
|
67381cf493 | ||
|
86f54dccdc | ||
|
edf981a5a1 | ||
|
ab75680ed3 | ||
|
8c11d7e8e8 | ||
|
8b776491e8 | ||
|
884f26924c | ||
|
5f79579a4d | ||
|
82f56da16b | ||
|
bc2035d362 | ||
|
41f25edb15 | ||
|
cf28d6653c | ||
|
1eca18f287 | ||
|
6671c9aa80 | ||
|
699d640845 | ||
|
93c3e9e1dd | ||
|
118d2c5bcf | ||
|
6023eac4bb | ||
|
4e99c3e4cb | ||
|
6c44f5b3fd | ||
|
42083b371b |
@@ -8,3 +8,5 @@ Start.bat
|
||||
cloudflared.exe
|
||||
access.log
|
||||
/data
|
||||
/cache
|
||||
.DS_Store
|
||||
|
@@ -5,7 +5,7 @@ end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js, conf, json}]
|
||||
[*.{js, conf, json, css, less, html}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
3
.github/pull_request_template.md
vendored
Normal file
3
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
## Checklist:
|
||||
|
||||
- [ ] I have read the [Contributing guidelines](https://github.com/SillyTavern/SillyTavern/blob/release/CONTRIBUTING.md).
|
100
.github/readme.md
vendored
100
.github/readme.md
vendored
@@ -144,12 +144,14 @@ A full list of included extensions and tutorials on how to use them can be found
|
||||
8. The server will then start, and SillyTavern will pop up in your browser.
|
||||
|
||||
## Installing via SillyTavern Launcher
|
||||
1. Install [Git for Windows](https://gitforwindows.org/)
|
||||
2. Open Windows Explorer (`Win+E`) and make or choose a folder where you wanna install the launcher to
|
||||
3. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
|
||||
4. When you see a black box, insert the following command: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git`
|
||||
5. Double-click on `installer.bat` and choose what you wanna install
|
||||
6. After installation double-click on `launcher.bat`
|
||||
1. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git:
|
||||
```shell
|
||||
cmd /c winget install -e --id Git.Git
|
||||
```
|
||||
2. On your keyboard: press **`WINDOWS + E`** to open File Explorer, then navigate to the folder where you want to install the launcher. Once in the desired folder, type `cmd` into the address bar and press enter. Then, run the following command:
|
||||
```shell
|
||||
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
|
||||
```
|
||||
|
||||
## Installing via GitHub Desktop
|
||||
(This allows git usage **only** in GitHub Desktop, if you want to use `git` on the command line too, you also need to install [Git for Windows](https://gitforwindows.org/))
|
||||
@@ -183,18 +185,79 @@ For MacOS / Linux all of these will be done in a Terminal.
|
||||
|
||||
### For Linux users
|
||||
1. Open your favorite terminal and install git
|
||||
2. Download Sillytavern Launcher with: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git`
|
||||
3. Navigate to the SillyTavern-Launcher with: `cd SillyTavern-Launcher`
|
||||
4. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install
|
||||
5. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh`
|
||||
2. Git clone the Sillytavern-Launcher with:
|
||||
```shell
|
||||
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
|
||||
```
|
||||
3. Start the installer.sh with:
|
||||
```shell
|
||||
chmod +x install.sh && ./install.sh
|
||||
```
|
||||
4. After installation start the launcher.sh with:
|
||||
```shell
|
||||
chmod +x launcher.sh && ./launcher.sh
|
||||
```
|
||||
|
||||
### For Mac users
|
||||
1. Open a terminal and install brew with: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
|
||||
2. Then install git with: `brew install git`
|
||||
3. Download Sillytavern Launcher with: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git`
|
||||
4. Navigate to the SillyTavern-Launcher with: `cd SillyTavern-Launcher`
|
||||
5. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install
|
||||
6. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh`
|
||||
1. Open a terminal and install brew with:
|
||||
```shell
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
```
|
||||
2. Install git with:
|
||||
```shell
|
||||
brew install git
|
||||
```
|
||||
3. Git clone the Sillytavern-Launcher with:
|
||||
```shell
|
||||
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
|
||||
```
|
||||
4. Start the installer.sh with:
|
||||
```shell
|
||||
chmod +x install.sh && ./install.sh
|
||||
```
|
||||
5. After installation start the launcher.sh with:
|
||||
```shell
|
||||
chmod +x launcher.sh && ./launcher.sh
|
||||
```
|
||||
|
||||
## 🐋 Installing via Docker
|
||||
|
||||
These instructions assume you have installed Docker, are able to access your command line for the installation of containers, and familiar with their general operation.
|
||||
|
||||
### Building the image yourself
|
||||
|
||||
We have a comprehensive guide on using SillyTavern in Docker [here](http://docs.sillytavern.app/installation/docker/) which covers installations on Windows, macOS and Linux! Give it a read if you wish to build the image yourself.
|
||||
|
||||
### Using the GitHub Container Registry (easiest)
|
||||
|
||||
You will need two mandatory directory mappings and a port mapping to allow SillyTavern to function. In the command, replace your selections in the following places:
|
||||
|
||||
#### Container Variables
|
||||
|
||||
##### Volume Mappings
|
||||
|
||||
- [config] - The directory where SillyTavern configuration files will be stored on your host machine
|
||||
- [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
|
||||
- [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
|
||||
|
||||
##### Port Mappings
|
||||
|
||||
- [PublicPort] - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
|
||||
|
||||
##### Additional Settings
|
||||
|
||||
- [TimeZone] - The timezone your instance should use. This is useful for making logs match your local time for easier troubleshooting. Use your TZ Identifier. (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
|
||||
- [DockerNet] - The docker network that the container should be created with a connection to. If you don't know what it is, see the [official Docker documentation](https://docs.docker.com/reference/cli/docker/network/).
|
||||
- [version] - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" and "release" tags that point to the nightly images of the respective branches, but this may not be appropriate, if you are utilizing extensions that could be broken, and may need time to update.
|
||||
|
||||
#### Install command
|
||||
|
||||
1. Open your Command Line
|
||||
2. Run the following command
|
||||
|
||||
`docker create --name='sillytavern' --net='[DockerNet]' -e TZ="[TimeZone]" -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`
|
||||
|
||||
> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
|
||||
|
||||
## 📱 Mobile - Installing via termux
|
||||
|
||||
@@ -205,7 +268,7 @@ For MacOS / Linux all of these will be done in a Terminal.
|
||||
|
||||
## API keys management
|
||||
|
||||
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
|
||||
SillyTavern saves your API keys to a `secrets.json` file in the user data directory (`/data/default-user/secrets.json` is the default path).
|
||||
|
||||
By default, they will not be exposed to a frontend after you enter them and reload the page.
|
||||
|
||||
@@ -220,7 +283,7 @@ Most often this is for people who want to use SillyTavern on their mobile phones
|
||||
|
||||
However, it can be used to allow remote connections from anywhere as well.
|
||||
|
||||
**IMPORTANT: SillyTavern is a single-user program, so anyone who logs in will be able to see all characters and chats, and be able to change any settings inside the UI.**
|
||||
**IMPORTANT: Refer to the official guide if you want to configure SillyTavern user accounts with (optional) password protection: [Users](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users).**
|
||||
|
||||
### 1. Managing whitelisted IPs
|
||||
|
||||
@@ -347,6 +410,7 @@ GNU Affero General Public License for more details.**
|
||||
* Korean translation by @doloroushyeonse
|
||||
* k_euler_a support for Horde by <https://github.com/Teashrock>
|
||||
* Chinese translation by [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3
|
||||
* Docker guide by [@mrguymiah](https://github.com/mrguymiah) and [@Bronya-Rand](https://github.com/Bronya-Rand)
|
||||
|
||||
<!-- LINK GROUP -->
|
||||
[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square
|
||||
|
16
.github/workflows/docker-publish.yml
vendored
16
.github/workflows/docker-publish.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
run: |
|
||||
echo "IMAGE_NAME=${REPO,,}" >> ${GITHUB_ENV}
|
||||
|
||||
# Using the following workaround because currently GitHub Actions
|
||||
# Using the following workaround because currently GitHub Actions
|
||||
# does not support logical AND/OR operations on triggers
|
||||
# It's currently not possible to have `branches` under the `schedule` trigger
|
||||
- name: Checkout the release branch (on release)
|
||||
@@ -65,7 +65,12 @@ jobs:
|
||||
id: metadata
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: ${{ env.BRANCH_NAME }}
|
||||
# Release version tag if the workflow is triggered by a release
|
||||
# Branch name tag if the workflow is triggered by a push
|
||||
# Latest tag if the branch is release and the workflow is triggered by a push
|
||||
tags: |
|
||||
${{ github.event_name == 'release' && github.ref_name || env.BRANCH_NAME }}
|
||||
${{ github.event_name == 'push' && env.BRANCH_NAME == 'release' && 'latest' || '' }}
|
||||
|
||||
# Login into package repository as the person who created the release
|
||||
- name: Log in to the Container registry
|
||||
@@ -87,10 +92,3 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
|
||||
# If the workflow is triggered by a release, marks and push the image as such
|
||||
- name: Docker tag latest and push
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
run: |
|
||||
docker tag $IMAGE_NAME:${{ github.ref_name }} $IMAGE_NAME:latest
|
||||
docker push $IMAGE_NAME:latest
|
||||
|
32
.github/workflows/update-i18n.yaml
vendored
Normal file
32
.github/workflows/update-i18n.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Update i18n data
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: # Job-level permissions configuration starts here
|
||||
contents: write # 'write' access to repository contents
|
||||
steps:
|
||||
- name: disable auto crlf
|
||||
uses: steve02081504/disable-autocrlf@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository.
|
||||
- name: Create local changes
|
||||
run: |
|
||||
aria2c https://raw.githubusercontent.com/SillyTavern/SillyTavern-i18n/main/generate.py
|
||||
aria2c https://raw.githubusercontent.com/SillyTavern/SillyTavern-i18n/main/requirements.txt
|
||||
pip install -r ./requirements.txt
|
||||
python ./generate.py "" --sort-keys
|
||||
rm -f ./generate.py ./requirements.txt
|
||||
- name: add all
|
||||
run: git add -A
|
||||
- name: push
|
||||
uses: actions-go/push@master
|
||||
with:
|
||||
author-email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||
author-name: github-actions[bot]
|
||||
commit-message: 'i18n changes'
|
||||
remote: origin
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,3 +48,4 @@ public/css/user.css
|
||||
/plugins/
|
||||
/data
|
||||
/default/scaffold
|
||||
public/scripts/extensions/third-party
|
||||
|
@@ -5,4 +5,6 @@ node_modules/
|
||||
secrets.json
|
||||
/dist
|
||||
/backups/
|
||||
/data
|
||||
/cache
|
||||
access.log
|
||||
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -4,7 +4,8 @@
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig"
|
||||
"EditorConfig.EditorConfig",
|
||||
"mrcrowl.easy-less"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
|
32
CONTRIBUTING.md
Normal file
32
CONTRIBUTING.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# How to contribute to SillyTavern
|
||||
|
||||
## Setting up the dev environment
|
||||
|
||||
1. Required software: git and node.
|
||||
2. Recommended editor: Visual Studio Code.
|
||||
3. You can also use GitHub Codespaces which sets up everything for you.
|
||||
|
||||
## Getting the code ready
|
||||
|
||||
1. Register a GitHub account.
|
||||
2. Fork this repository under your account.
|
||||
3. Clone the fork onto your machine.
|
||||
4. Open the cloned repository in the code editor.
|
||||
5. Create a git branch (recommended).
|
||||
6. Make your changes and test them locally.
|
||||
7. Commit the changes and push the branch to the remote repo.
|
||||
8. Go to GitHub, and open a pull request, targeting the upstream branch.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
1. Our standards are pretty low, but make sure the code is not too ugly:
|
||||
- Run VS Code's autoformat when you're done.
|
||||
- Check with ESLint by running `npm run lint`, then fix the errors.
|
||||
- Use common sense and follow existing naming conventions.
|
||||
2. Create pull requests for the staging branch, 99% of contributions should go there. That way people could test your code before the next stable release.
|
||||
3. You can still send a pull request for release in the following scenarios:
|
||||
- Updating README.
|
||||
- Updating GitHub Actions.
|
||||
- Hotfixing a critical bug.
|
||||
4. Project maintainers will test and can change your code before merging.
|
||||
5. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.
|
9
backups/!README.md
Normal file
9
backups/!README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Looking for setting snapshots or chat backups?
|
||||
|
||||
Individual user backups are now located in the data directory.
|
||||
|
||||
Example for the default user under default data root:
|
||||
|
||||
/data/default-user/backups
|
||||
|
||||
This folder remains for historical purposes only.
|
@@ -11,6 +11,14 @@
|
||||
"filename": "themes/Cappuccino.json",
|
||||
"type": "theme"
|
||||
},
|
||||
{
|
||||
"filename": "themes/Celestial Macaron.json",
|
||||
"type": "theme"
|
||||
},
|
||||
{
|
||||
"filename": "themes/Dark V 1.0.json",
|
||||
"type": "theme"
|
||||
},
|
||||
{
|
||||
"filename": "backgrounds/__transparent.png",
|
||||
"type": "background"
|
||||
@@ -476,7 +484,11 @@
|
||||
"type": "context"
|
||||
},
|
||||
{
|
||||
"filename": "presets/context/DreamGen Role-Play V1.json",
|
||||
"filename": "presets/context/DreamGen Role-Play V1 ChatML.json",
|
||||
"type": "context"
|
||||
},
|
||||
{
|
||||
"filename": "presets/context/DreamGen Role-Play V1 Llama3.json",
|
||||
"type": "context"
|
||||
},
|
||||
{
|
||||
@@ -556,7 +568,11 @@
|
||||
"type": "instruct"
|
||||
},
|
||||
{
|
||||
"filename": "presets/instruct/DreamGen Role-Play V1.json",
|
||||
"filename": "presets/instruct/DreamGen Role-Play V1 ChatML.json",
|
||||
"type": "instruct"
|
||||
},
|
||||
{
|
||||
"filename": "presets/instruct/DreamGen Role-Play V1 Llama3.json",
|
||||
"type": "instruct"
|
||||
},
|
||||
{
|
||||
|
@@ -8,5 +8,5 @@
|
||||
"trim_sentences": true,
|
||||
"include_newline": false,
|
||||
"single_line": false,
|
||||
"name": "DreamGen Role-Play V1"
|
||||
"name": "DreamGen Role-Play V1 ChatML"
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"story_string": "<|start_header_id|>system<|end_header_id|>\n\n{{#if system}}{{system}}\n\n\n{{/if}}## Overall plot description:\n\n{{#if scenario}}{{scenario}}{{else}}Conversation between {{char}} and {{user}}.{{/if}}{{#if wiBefore}}\n\n{{wiBefore}}{{/if}}\n\n\n## Characters:\n\n### {{char}}\n\n{{#if description}}{{description}}\n\n{{/if}}{{#if personality}}{{personality}}\n\n{{/if}}### {{user}}\n\n{{#if persona}}{{persona}}{{else}}{{user}} is the protagonist of the role-play.{{/if}}{{#if wiAfter}}\n\n{{wiAfter}}{{/if}}{{#if mesExamples}}\n\n{{mesExamples}}{{/if}}",
|
||||
"example_separator": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nWrite an example narrative / conversation that is not part of the main story.",
|
||||
"chat_start": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nStart the role-play between {{char}} and {{user}}.",
|
||||
"use_stop_strings": false,
|
||||
"allow_jailbreak": false,
|
||||
"always_force_name2": false,
|
||||
"trim_sentences": true,
|
||||
"include_newline": false,
|
||||
"single_line": false,
|
||||
"name": "DreamGen Role-Play V1 Llama3"
|
||||
}
|
@@ -20,5 +20,5 @@
|
||||
"user_alignment_message": "",
|
||||
"system_same_as_user": true,
|
||||
"last_system_sequence": "",
|
||||
"name": "DreamGen Role-Play V1"
|
||||
"name": "DreamGen Role-Play V1 ChatML"
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.",
|
||||
"input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",
|
||||
"output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "",
|
||||
"separator_sequence": "",
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": false,
|
||||
"names_force_groups": false,
|
||||
"activation_regex": "",
|
||||
"skip_examples": false,
|
||||
"name": "DreamGen Role-Play V1 Llama3"
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Cappuccino",
|
||||
"blur_strength": 3,
|
||||
"main_text_color": "rgba(255, 255, 255, 1)",
|
||||
"main_text_color": "rgba(235, 235, 235, 1)",
|
||||
"italics_text_color": "rgba(230, 210, 190, 1)",
|
||||
"underline_text_color": "rgba(205, 180, 160, 1)",
|
||||
"quote_text_color": "rgba(165, 140, 115, 1)",
|
||||
|
37
default/content/themes/Celestial Macaron.json
Normal file
37
default/content/themes/Celestial Macaron.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "Celestial Macaron",
|
||||
"blur_strength": 10,
|
||||
"main_text_color": "rgba(229, 175, 162, 1)",
|
||||
"italics_text_color": "rgba(146, 147, 161, 1)",
|
||||
"underline_text_color": "rgba(157, 215, 198, 1)",
|
||||
"quote_text_color": "rgba(197, 202, 206, 1)",
|
||||
"blur_tint_color": "rgba(23, 36, 55, 0.9)",
|
||||
"chat_tint_color": "rgba(18, 26, 40, 0.9)",
|
||||
"user_mes_blur_tint_color": "rgba(51, 67, 90, 0.7)",
|
||||
"bot_mes_blur_tint_color": "rgba(23, 36, 55, 0.75)",
|
||||
"shadow_color": "rgba(0, 0, 0, 0.3)",
|
||||
"shadow_width": 1,
|
||||
"border_color": "rgba(60, 74, 110, 0.93)",
|
||||
"font_scale": 1,
|
||||
"fast_ui_mode": false,
|
||||
"waifuMode": false,
|
||||
"avatar_style": 0,
|
||||
"chat_display": 1,
|
||||
"noShadows": true,
|
||||
"chat_width": 58,
|
||||
"timer_enabled": true,
|
||||
"timestamps_enabled": true,
|
||||
"timestamp_model_icon": false,
|
||||
"mesIDDisplay_enabled": true,
|
||||
"hideChatAvatars_enabled": false,
|
||||
"message_token_count_enabled": true,
|
||||
"expand_message_actions": true,
|
||||
"enableZenSliders": false,
|
||||
"enableLabMode": false,
|
||||
"hotswap_enabled": true,
|
||||
"custom_css": "",
|
||||
"bogus_folders": true,
|
||||
"zoomed_avatar_magnification": false,
|
||||
"reduced_motion": false,
|
||||
"compact_input_area": true
|
||||
}
|
37
default/content/themes/Dark V 1.0.json
Normal file
37
default/content/themes/Dark V 1.0.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "Dark V 1.0",
|
||||
"blur_strength": 13,
|
||||
"main_text_color": "rgba(207, 207, 197, 1)",
|
||||
"italics_text_color": "rgba(145, 145, 145, 1)",
|
||||
"underline_text_color": "rgba(145, 145, 145, 1)",
|
||||
"quote_text_color": "rgba(198, 193, 151, 1)",
|
||||
"blur_tint_color": "rgba(29, 33, 40, 0.9)",
|
||||
"chat_tint_color": "rgba(29, 33, 40, 0.9)",
|
||||
"user_mes_blur_tint_color": "rgba(29, 33, 40, 0.9)",
|
||||
"bot_mes_blur_tint_color": "rgba(29, 33, 40, 0.9)",
|
||||
"shadow_color": "rgba(0, 0, 0, 0.9)",
|
||||
"shadow_width": 2,
|
||||
"border_color": "rgba(0, 0, 0, 1)",
|
||||
"font_scale": 1,
|
||||
"fast_ui_mode": false,
|
||||
"waifuMode": false,
|
||||
"avatar_style": 0,
|
||||
"chat_display": 0,
|
||||
"noShadows": false,
|
||||
"chat_width": 55,
|
||||
"timer_enabled": false,
|
||||
"timestamps_enabled": false,
|
||||
"timestamp_model_icon": false,
|
||||
"mesIDDisplay_enabled": false,
|
||||
"hideChatAvatars_enabled": false,
|
||||
"message_token_count_enabled": false,
|
||||
"expand_message_actions": false,
|
||||
"enableZenSliders": false,
|
||||
"enableLabMode": false,
|
||||
"hotswap_enabled": true,
|
||||
"custom_css": "",
|
||||
"bogus_folders": true,
|
||||
"zoomed_avatar_magnification": true,
|
||||
"reduced_motion": true,
|
||||
"compact_input_area": false
|
||||
}
|
@@ -10,4 +10,5 @@ services:
|
||||
volumes:
|
||||
- "./config:/home/node/app/config"
|
||||
- "./data:/home/node/app/data"
|
||||
- "./plugins:/home/node/app/plugins"
|
||||
restart: unless-stopped
|
||||
|
@@ -15,6 +15,11 @@
|
||||
"**/node_modules/*",
|
||||
"public/lib",
|
||||
"backups/*",
|
||||
"data/*"
|
||||
"data/*",
|
||||
"**/dist/*",
|
||||
"dist/*",
|
||||
"cache/*",
|
||||
"src/tokenizers/*",
|
||||
"docker/*",
|
||||
]
|
||||
}
|
||||
|
81
package-lock.json
generated
81
package-lock.json
generated
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@agnai/sentencepiece-js": "^1.1.1",
|
||||
"@agnai/web-tokenizers": "^0.1.3",
|
||||
"@zeldafan0225/ai_horde": "^4.0.1",
|
||||
"@zeldafan0225/ai_horde": "^5.1.0",
|
||||
"archiver": "^7.0.1",
|
||||
"bing-translate-api": "^2.9.1",
|
||||
"body-parser": "^1.20.2",
|
||||
@@ -25,7 +25,6 @@
|
||||
"express": "^4.19.2",
|
||||
"form-data": "^4.0.0",
|
||||
"google-translate-api-browser": "^3.0.1",
|
||||
"gpt3-tokenizer": "^1.1.5",
|
||||
"he": "^1.2.0",
|
||||
"helmet": "^7.1.0",
|
||||
"ip-matching": "^2.1.2",
|
||||
@@ -49,7 +48,7 @@
|
||||
"vectra": "^0.2.2",
|
||||
"wavefile": "^11.0.0",
|
||||
"write-file-atomic": "^5.0.1",
|
||||
"ws": "^8.13.0",
|
||||
"ws": "^8.17.1",
|
||||
"yaml": "^2.3.4",
|
||||
"yargs": "^17.7.1",
|
||||
"yauzl": "^2.10.0"
|
||||
@@ -61,6 +60,9 @@
|
||||
"@types/jquery": "^3.5.29",
|
||||
"eslint": "^8.55.0",
|
||||
"jquery": "^3.6.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -878,12 +880,14 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@zeldafan0225/ai_horde": {
|
||||
"version": "4.0.1",
|
||||
"license": "MIT",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@zeldafan0225/ai_horde/-/ai_horde-5.1.0.tgz",
|
||||
"integrity": "sha512-rPC0nmmFSXK808Oon0zFPA7yGSUKBXiLtMejkmKTyfAzzOHHQt/i2lO4ccfN2e355LzX1lBLwSi+nlATVA43Sw==",
|
||||
"dependencies": {
|
||||
"@thunder04/supermap": "^3.0.2",
|
||||
"centra": "^2.5.0",
|
||||
"esbuild": "^0.12.28"
|
||||
"@thunder04/supermap": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
@@ -1210,10 +1214,6 @@
|
||||
"version": "1.1.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/array-keyed-map": {
|
||||
"version": "2.1.3",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
@@ -2124,12 +2124,6 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"name": "dry-uninstall",
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/dry-uninstall/-/dry-uninstall-0.3.0.tgz",
|
||||
"integrity": "sha512-b8h94RVpETWkVV59x62NsY++79bM7Si6Dxq7a4iVxRcJU3ZJJ4vaiC7wUZwM8WDK0ySRL+i+T/1SMAzbJLejYA=="
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"license": "MIT",
|
||||
@@ -2735,16 +2729,6 @@
|
||||
"version": "1.1.4",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gpt3-tokenizer": {
|
||||
"version": "1.1.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"array-keyed-map": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -3885,7 +3869,6 @@
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -4435,8 +4418,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"license": "MIT"
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
|
||||
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
@@ -4585,19 +4575,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"license": "BSD-2-Clause"
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.18",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"license": "MIT",
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
|
||||
"integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
"tr46": "^5.0.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
@@ -4661,8 +4659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.13.0",
|
||||
"license": "MIT",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
14
package.json
14
package.json
@@ -2,7 +2,7 @@
|
||||
"dependencies": {
|
||||
"@agnai/sentencepiece-js": "^1.1.1",
|
||||
"@agnai/web-tokenizers": "^0.1.3",
|
||||
"@zeldafan0225/ai_horde": "^4.0.1",
|
||||
"@zeldafan0225/ai_horde": "^5.1.0",
|
||||
"archiver": "^7.0.1",
|
||||
"bing-translate-api": "^2.9.1",
|
||||
"body-parser": "^1.20.2",
|
||||
@@ -15,7 +15,6 @@
|
||||
"express": "^4.19.2",
|
||||
"form-data": "^4.0.0",
|
||||
"google-translate-api-browser": "^3.0.1",
|
||||
"gpt3-tokenizer": "^1.1.5",
|
||||
"he": "^1.2.0",
|
||||
"helmet": "^7.1.0",
|
||||
"ip-matching": "^2.1.2",
|
||||
@@ -39,11 +38,14 @@
|
||||
"vectra": "^0.2.2",
|
||||
"wavefile": "^11.0.0",
|
||||
"write-file-atomic": "^5.0.1",
|
||||
"ws": "^8.13.0",
|
||||
"ws": "^8.17.1",
|
||||
"yaml": "^2.3.4",
|
||||
"yargs": "^17.7.1",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"overrides": {
|
||||
"parse-bmfont-xml": {
|
||||
"xml2js": "^0.5.0"
|
||||
@@ -57,8 +59,8 @@
|
||||
"axios": {
|
||||
"follow-redirects": "^1.15.4"
|
||||
},
|
||||
"@zeldafan0225/ai_horde": {
|
||||
"esbuild": "npm:dry-uninstall"
|
||||
"node-fetch": {
|
||||
"whatwg-url": "^14.0.0"
|
||||
}
|
||||
},
|
||||
"name": "sillytavern",
|
||||
@@ -68,7 +70,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.12.0",
|
||||
"version": "1.12.2",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"start:no-csrf": "node server.js --disableCsrf",
|
||||
|
122
public/css/animations.css
Normal file
122
public/css/animations.css
Normal file
@@ -0,0 +1,122 @@
|
||||
/* Fade animations with opacity */
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pop animations with opacity and vertical scaling */
|
||||
@keyframes pop-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
|
||||
/* Make the scaling faster on pop-in, otherwise it looks a bit weird */
|
||||
33% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pop-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Flashing for highlighting animation */
|
||||
@keyframes flash {
|
||||
|
||||
20%,
|
||||
60%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
0%,
|
||||
40%,
|
||||
80% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pulsing highlight, slightly resizing the element */
|
||||
@keyframes pulse {
|
||||
from {
|
||||
transform: scale(1);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1.01);
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Ellipsis animation */
|
||||
@keyframes ellipsis {
|
||||
0% {
|
||||
content: ""
|
||||
}
|
||||
|
||||
25% {
|
||||
content: "."
|
||||
}
|
||||
|
||||
50% {
|
||||
content: ".."
|
||||
}
|
||||
|
||||
75% {
|
||||
content: "..."
|
||||
}
|
||||
}
|
||||
|
||||
/* HEINOUS */
|
||||
@keyframes infinite-spinning {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* STscript animation */
|
||||
@keyframes script_progress_pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
border-top-color: var(--progColor);
|
||||
}
|
||||
|
||||
50% {
|
||||
border-top-color: var(--progFlashColor);
|
||||
}
|
||||
}
|
@@ -36,7 +36,6 @@ label[for="extensions_autoconnect"] {
|
||||
|
||||
.extensions_info {
|
||||
text-align: left;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
.extensions_info h3 {
|
||||
@@ -97,114 +96,11 @@ input.extension_missing[type="checkbox"] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/** LEFT COLUMN **/
|
||||
/* Must be always on top */
|
||||
#extensions_settings>#assets_ui {
|
||||
order: -1;
|
||||
/* Fixes order of settings for extensions */
|
||||
.extension_container {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
#extensions_settings>.expression_settings {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensions_settings>.background_settings {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensions_settings>.sd_settings {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensions_settings>#tts_settings {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensions_settings>#rvc_settings {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensions_settings>.objective-settings {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensions_settings>#speech_recognition_settings {
|
||||
order: 8;
|
||||
}
|
||||
|
||||
#extensions_settings>#audio_settings {
|
||||
order: 9;
|
||||
}
|
||||
|
||||
/** RIGHT COLUMN **/
|
||||
#extensions_settings2>.translation_settings {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensions_settings2>.caption_settings {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensions_settings2>.quickReplySettings {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensions_settings2>.idle-settings {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensions_settings2>#memory_settings {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensions_settings2>.hypebot_settings {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensions_settings2>.regex_settings {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensions_settings2>.vectors_settings {
|
||||
order: 8;
|
||||
}
|
||||
|
||||
#extensions_settings2>.chromadb_settings {
|
||||
order: 9;
|
||||
}
|
||||
|
||||
#extensions_settings2>.randomizer_settings {
|
||||
order: 10;
|
||||
}
|
||||
|
||||
/** WAND MENU **/
|
||||
#extensionsMenu>#ttsExtensionMenuItem {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensionsMenu>#sd_gen {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensionsMenu>#send_picture {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensionsMenu>#token_counter {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensionsMenu>#objective-task-manual-check-menu-item {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensionsMenu>#roll_dice {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensionsMenu>#translate_chat {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensionsMenu>#translate_input_message {
|
||||
order: 8;
|
||||
#extensionsMenu>div.extension_container:empty {
|
||||
display: none;
|
||||
}
|
||||
|
@@ -98,7 +98,7 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logprobs_top_candidate:not([disabled]):hover, .logprobs_top_candidate:not([disabled]):focus {
|
||||
.logprobs_top_candidate:not([disabled]):hover {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
|
@@ -117,6 +117,11 @@
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
#wiActivationSettings,
|
||||
#wiTopBlock {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#top-settings-holder,
|
||||
#top-bar {
|
||||
position: fixed;
|
||||
|
8
public/css/popup-safari-fix.css
Normal file
8
public/css/popup-safari-fix.css
Normal file
@@ -0,0 +1,8 @@
|
||||
/* iPhone copium land */
|
||||
@media screen and (max-width: 1000px) {
|
||||
.ios .popup .popup-body {
|
||||
height: fit-content;
|
||||
max-height: 90vh;
|
||||
max-height: 90svh;
|
||||
}
|
||||
}
|
175
public/css/popup.css
Normal file
175
public/css/popup.css
Normal file
@@ -0,0 +1,175 @@
|
||||
@import url('./popup-safari-fix.css');
|
||||
|
||||
dialog {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
/* Closed state of the dialog */
|
||||
.popup {
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
box-shadow: 0px 0px 14px var(--black70a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
padding: 4px 14px;
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
max-height: calc(100svh - 2em);
|
||||
max-width: calc(100svw - 2em);
|
||||
min-height: fit-content;
|
||||
|
||||
/* Overflow visible so elements (like toasts) can appear outside of the dialog. '.popup-body' is hiding overflow for the real content. */
|
||||
overflow: visible;
|
||||
|
||||
/* Fix weird animation issue with font-scaling during popup open */
|
||||
backface-visibility: hidden;
|
||||
transform: translateZ(0);
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
}
|
||||
|
||||
.popup .popup-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.popup .popup-content {
|
||||
margin-top: 10px;
|
||||
padding: 0 8px;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.popup .popup-content h3:first-child {
|
||||
/* No double spacing for the first heading needed, the .popup-content already has margin */
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.popup.vertical_scrolling_dialogue_popup .popup-content {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.popup.horizontal_scrolling_dialogue_popup .popup-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Opening animation */
|
||||
.popup[opening] {
|
||||
animation: pop-in var(--animation-duration-slow) ease-in-out;
|
||||
}
|
||||
|
||||
.popup[opening]::backdrop {
|
||||
animation: fade-in var(--animation-duration-slow) ease-in-out;
|
||||
}
|
||||
|
||||
/* Open state of the dialog */
|
||||
.popup[open] {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.popup[open]::backdrop {
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
-webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
background-color: var(--black30a);
|
||||
}
|
||||
|
||||
body.no-blur .popup[open]::backdrop {
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
/* Closing animation */
|
||||
.popup[closing] {
|
||||
animation: pop-out var(--animation-duration-slow) ease-in-out;
|
||||
}
|
||||
|
||||
.popup[closing]::backdrop {
|
||||
animation: fade-out var(--animation-duration-slow) ease-in-out;
|
||||
}
|
||||
|
||||
.popup #toast-container {
|
||||
/* Fix toastr in dialogs by actually placing it at the top of the screen via transform */
|
||||
height: 100svh;
|
||||
top: calc(50% + var(--topBarBlockSize));
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
/* Fix text align, popups are centered by default. toasts should not. */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.popup-crop-wrap {
|
||||
margin: 10px auto;
|
||||
max-height: 75vh;
|
||||
max-height: 75svh;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.popup-crop-wrap img {
|
||||
max-width: 100%;
|
||||
/* This rule is very important, please do not ignore this! */
|
||||
}
|
||||
|
||||
.popup-inputs {
|
||||
margin-top: 10px;
|
||||
font-size: smaller;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.popup-input {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.popup-controls {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-self: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.menu_button.menu_button_default {
|
||||
box-shadow: 0 0 5px var(--white20a);
|
||||
}
|
||||
|
||||
.menu_button.popup-button-ok {
|
||||
background-color: var(--crimson70a);
|
||||
}
|
||||
|
||||
.menu_button.popup-button-ok:hover {
|
||||
background-color: var(--crimson-hover);
|
||||
}
|
||||
|
||||
.popup-controls .menu_button {
|
||||
/* Popup buttons should not scale to smallest size, otherwise they will always break to multiline if multiple words */
|
||||
width: unset;
|
||||
|
||||
/* Fix weird animation issue with fonts on brightness filter */
|
||||
backface-visibility: hidden;
|
||||
transform: translateZ(0);
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
}
|
||||
|
||||
.popup-controls .menu_button:hover:focus-visible {
|
||||
filter: brightness(1.3) saturate(1.3);
|
||||
}
|
||||
|
||||
.popup .popup-button-close {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 20px;
|
||||
padding: 2px 3px 3px 2px;
|
||||
|
||||
filter: brightness(0.8);
|
||||
|
||||
/* Fix weird animation issue with font-scaling during popup open */
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list li {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 80px 40px;
|
||||
grid-template-columns: 4fr 80px 45px;
|
||||
margin-bottom: 0.5em;
|
||||
width: 100%
|
||||
}
|
||||
|
@@ -23,6 +23,14 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.select2-selection--single .select2-selection__placeholder {
|
||||
color: var(--SmartThemeEmColor);
|
||||
}
|
||||
|
||||
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
|
||||
color: var(--SmartThemeEmColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
line-height: revert;
|
||||
@@ -49,7 +57,7 @@
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
font-family: var(--mainFontFamily);
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
@@ -77,7 +85,7 @@
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
font-family: var(--mainFontFamily);
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
@@ -173,8 +181,9 @@
|
||||
}
|
||||
|
||||
.select2-selection__choice__display {
|
||||
/* Fix weird alignment on the left side */
|
||||
margin-left: 1px;
|
||||
/* Fix weird alignment of the inside block */
|
||||
margin-left: 3px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
/* Styling for choice remove icon */
|
||||
@@ -194,11 +203,14 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
|
||||
.select2_choice_clickable+span.select2-container .select2-selection__choice__display {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black50a);
|
||||
white-space: break-spaces;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display:hover {
|
||||
@@ -209,7 +221,9 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
|
||||
.select2_multi_sameline+span.select2-container .select2-selection--multiple {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
|
||||
}
|
||||
|
||||
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
|
||||
/* Allow search placeholder to take up all space if needed */
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -218,6 +232,14 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
|
||||
/* Fix weird styling choice or huge margin around selected options */
|
||||
margin-block-start: 2px;
|
||||
margin-block-end: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-selection__choice {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search__field {
|
||||
|
@@ -220,7 +220,7 @@
|
||||
}
|
||||
|
||||
.monospace {
|
||||
font-family: monospace;
|
||||
font-family: var(--monoFontFamily);
|
||||
}
|
||||
|
||||
.expander {
|
||||
@@ -292,6 +292,14 @@
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.inline-flex {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.alignitemscenter,
|
||||
.alignItemsCenter {
|
||||
align-items: center;
|
||||
@@ -348,6 +356,10 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.margin-r2 {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.flex0 {
|
||||
flex: 0;
|
||||
}
|
||||
@@ -572,3 +584,23 @@ textarea:disabled {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
ul.li-padding-b-1 li {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
ul.li-padding-b-2 li {
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
ul.li-padding-b-5 li {
|
||||
padding-bottom: 5em;
|
||||
}
|
||||
|
||||
ul.li-padding-bot5 li {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
ul.li-padding-bot10 li {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 6px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,19 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tag_view_color_picker {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tag_view_color_picker .link_icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tag_delete {
|
||||
padding-right: 0;
|
||||
padding: 2px 4px;
|
||||
color: var(--SmartThemeBodyColor) !important;
|
||||
}
|
||||
|
||||
@@ -108,6 +119,14 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#tagList .tag:has(.tag_remove:hover) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#tagList .tag:has(.tag_remove:hover) .tag_name {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tags.tags_inline {
|
||||
opacity: 0.6;
|
||||
column-gap: 0.2rem;
|
||||
|
@@ -257,3 +257,8 @@ select.keyselect+span.select2-container .select2-selection--multiple {
|
||||
.switch_input_type_icon:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#wiCheckboxes {
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
41
public/global.d.ts
vendored
41
public/global.d.ts
vendored
@@ -1358,3 +1358,44 @@ declare namespace moment {
|
||||
declare global {
|
||||
const moment: typeof moment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback data for the `LLM_FUNCTION_TOOL_REGISTER` event type that is triggered when a function tool can be registered.
|
||||
*/
|
||||
interface FunctionToolRegister {
|
||||
/**
|
||||
* The type of generation that is being used
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* Generation data, including messages and sampling parameters
|
||||
*/
|
||||
data: Record<string, object>;
|
||||
/**
|
||||
* Callback to register an LLM function tool.
|
||||
*/
|
||||
registerFunctionTool: typeof registerFunctionTool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback data for the `LLM_FUNCTION_TOOL_REGISTER` event type that is triggered when a function tool is registered.
|
||||
* @param name Name of the function tool to register
|
||||
* @param description Description of the function tool
|
||||
* @param params JSON schema for the parameters of the function tool
|
||||
* @param required Whether the function tool should be forced to be used
|
||||
*/
|
||||
declare function registerFunctionTool(name: string, description: string, params: object, required: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Callback data for the `LLM_FUNCTION_TOOL_CALL` event type that is triggered when a function tool is called.
|
||||
*/
|
||||
interface FunctionToolCall {
|
||||
/**
|
||||
* Name of the function tool to call
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* JSON object with the parameters to pass to the function tool
|
||||
*/
|
||||
arguments: string;
|
||||
}
|
||||
|
59
public/img/01ai.svg
Normal file
59
public/img/01ai.svg
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="363.44339"
|
||||
height="375.68854"
|
||||
viewBox="0 0 363.44339 375.68854"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="Yi_logo_icon_dark.svg"
|
||||
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.1073359"
|
||||
inkscape:cx="192.35355"
|
||||
inkscape:cy="196.86889"
|
||||
inkscape:window-width="1512"
|
||||
inkscape:window-height="857"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<rect
|
||||
x="287.14771"
|
||||
y="224.04056"
|
||||
width="42.3862"
|
||||
height="151.64799"
|
||||
rx="21.1931"
|
||||
id="rect1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="m 299.41969,17.362538 c -8.916,-7.5830004 -22.291,-6.503 -29.874,2.414 l -118.432,139.253002 c -3.056,3.593 -4.705,7.911 -5.001,12.281 -0.166,1.069 -0.252,2.164 -0.252,3.279 v 178.022 c 0,11.705 9.488,21.193 21.193,21.193 11.705,0 21.193,-9.488 21.193,-21.193 v -171.819 l 113.587,-133.556002 c 7.583,-8.916 6.502,-22.291 -2.414,-29.874 z"
|
||||
id="path1" />
|
||||
<rect
|
||||
x="-18.236605"
|
||||
y="8.6596518"
|
||||
width="42.3862"
|
||||
height="174.745"
|
||||
rx="21.1931"
|
||||
transform="rotate(-39.3441)"
|
||||
id="rect2" />
|
||||
<circle
|
||||
cx="337.54071"
|
||||
cy="163.28656"
|
||||
r="25.9027"
|
||||
id="circle2" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
39
public/img/featherless.svg
Normal file
39
public/img/featherless.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
class="logo"
|
||||
width="36"
|
||||
height="30.9767"
|
||||
viewBox="0 0 36 30.9767"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="featherless.svg"
|
||||
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="4.0920245"
|
||||
inkscape:cx="75.268366"
|
||||
inkscape:cy="15.151424"
|
||||
inkscape:window-width="1512"
|
||||
inkscape:window-height="857"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M 34.0866,1.68482 C 32.2902,0.5825 29.863,0 27.0672,0 22.7842,0 18.0653,1.35865 13.8276,3.72206 L 13.7979,3.71083 c 0,0 -0.0042,0.02261 -0.0065,0.0334 C 12.5086,4.4617 11.2656,5.2629 10.0981,6.15731 3.22112,11.4248 1.29519,17.6748 2.92004,21.0156 1.14142,24.0728 0.0457,27.2332 0,30.9767 3.41949,24.421 5.4719,19.108 16.6146,10.1637 13.4309,10.8501 7.9281,14.1057 4.2271,19.0459 3.87793,16.156 6.1477,11.4895 11.2033,7.6174 11.8435,7.127 12.5092,6.66864 13.1886,6.23374 12.6577,7.8934 12.8269,7.4806 11.7254,9.8076 c 1.6289,-1.551 2.7014,-2.5081 4.3096,-5.16615 2.088,-1.03181 4.2598,-1.80301 6.4132,-2.2691 -0.3563,1.18836 -1.0345,3.20231 -1.9527,4.79455 0,0 2.3303,-0.50255 4.2563,-0.38902 -1.0523,1.16802 -1.9991,2.43152 -2.9592,3.72332 -1.3149,1.7684 -2.6742,3.5971 -4.4148,5.2993 -0.2095,0.2049 -0.4098,0.3907 -0.6129,0.5825 -2.6747,-0.2576 -4.4414,0.7485 -6.0966,2.5259 1.3054,-0.6123 3.059,-1.1165 4.1583,-0.813 -2.0258,1.662 -5.216,3.8529 -7.8373,3.6725 -0.4971,0.7611 -0.5285,0.7844 -1.0749,1.7038 4.252,1.0648 9.5926,-3.2817 12.7354,-6.3561 1.8428,-1.803 3.2466,-3.6904 4.6036,-5.5149 2.7947,-3.7585 5.2082,-7.0038 10.5619,-8.2388 L 36,2.85877 Z"
|
||||
class="logo-mark"
|
||||
id="path1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
40
public/img/huggingface.svg
Normal file
40
public/img/huggingface.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="88.001465mm"
|
||||
height="81.280983mm"
|
||||
version="1.1"
|
||||
id="svg9"
|
||||
sodipodi:docname="huggingface.svg"
|
||||
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||
viewBox="0 0 88.001465 81.280983"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs9" />
|
||||
<sodipodi:namedview
|
||||
id="namedview9"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.68605868"
|
||||
inkscape:cx="424.16197"
|
||||
inkscape:cy="154.50573"
|
||||
inkscape:window-width="1512"
|
||||
inkscape:window-height="857"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg9"
|
||||
inkscape:clip-to-page="false"
|
||||
inkscape:document-units="mm" />
|
||||
<path
|
||||
id="path2-9"
|
||||
style="display:inline;"
|
||||
d="M 40.855186,0.10840487 A 38.75,38.75 0 0 0 5.0016702,38.750983 a 38.75,38.75 0 0 0 1.7871095,11.589844 7.1,7.1 0 0 1 1.871094,0.291015 5.97,5.97 0 0 1 1.330078,-3.761718 c 0.02089,-0.02502 0.04515,-0.04576 0.06641,-0.07031 a 34.75,34.75 0 0 1 -1.0547201,-8.048831 34.750014,34.750014 0 0 1 69.5000291,0 34.75,34.75 0 0 1 -0.957032,7.630859 c 0.163358,0.152193 0.321565,0.31255 0.466797,0.488282 a 5.97,5.97 0 0 1 1.330078,3.761718 7.1,7.1 0 0 1 1.337891,-0.207031 A 38.75,38.75 0 0 0 82.501671,38.750983 38.75,38.75 0 0 0 40.855186,0.10840487 Z M 48.015342,73.165045 a 34.75,34.75 0 0 1 -8.044921,0.03906 c -0.396448,0.901178 -0.898324,1.811009 -1.529297,2.736328 -0.233308,0.342701 -0.489288,0.664577 -0.75586,0.974609 a 38.75,38.75 0 0 0 12.574219,-0.06641 c -0.245421,-0.290144 -0.482504,-0.589875 -0.699219,-0.908203 -0.639915,-0.938432 -1.14623,-1.86177 -1.544922,-2.775391 z M 73.940733,45.000983 c 1.62,0 3.07,0.66 4.07,1.87 a 5.97,5.97 0 0 1 1.33,3.76 7.1,7.1 0 0 1 1.95,-0.3 c 1.55,0 2.95,0.59 3.94,1.66 a 5.8,5.8 0 0 1 0.8,7 5.3,5.3 0 0 1 1.78,2.82 c 0.24,0.9 0.48,2.8 -0.8,4.74 a 5.22,5.22 0 0 1 0.37,5.02 c -1.02,2.32 -3.57,4.14 -8.51,6.1 -3.08,1.22 -5.9,2 -5.92,2.01 a 44.33,44.33 0 0 1 -10.93,1.6 c -5.86,0 -10.05,-1.8 -12.46,-5.34 -3.88,-5.69 -3.33,-10.9 1.7,-15.92 2.78,-2.78 4.63,-6.87 5.01,-7.77 0.78,-2.66 2.83,-5.62 6.24,-5.62 a 5.7,5.7 0 0 1 4.6,2.46 c 1,-1.26 1.98,-2.25 2.87,-2.82 a 7.4,7.4 0 0 1 3.96,-1.27 z m 0,4 c -0.51,0 -1.13,0.22 -1.82,0.65 -2.13,1.36 -6.25,8.43 -7.76,11.18 a 2.43,2.43 0 0 1 -2.14,1.31 c -1.54,0 -2.75,-1.53 -0.14,-3.48 3.91,-2.93 2.54,-7.72 0.67,-8.01 a 1.54,1.54 0 0 0 -0.24,-0.02 c -1.7,0 -2.45,2.93 -2.45,2.93 0,0 -2.2,5.52 -5.97,9.3 -3.78,3.77 -3.98,6.8 -1.22,10.83 1.87,2.75 5.47,3.58 9.15,3.58 3.82,0 7.73,-0.9 9.93,-1.46 0.1,-0.03 13.45,-3.8 11.76,-7 -0.29,-0.54 -0.75,-0.76 -1.34,-0.76 -2.38,0 -6.71,3.54 -8.57,3.54 -0.42,0 -0.71,-0.17 -0.83,-0.6 -0.8,-2.85 12.05,-4.05 10.97,-8.17 -0.19,-0.73 -0.7,-1.02 -1.44,-1.02 -3.14,0 -10.2,5.53 -11.68,5.53 -0.1,0 -0.19,-0.03 -0.23,-0.1 -0.74,-1.2 -0.34,-2.04 4.88,-5.2 5.23,-3.16 8.9,-5.06 6.8,-7.33 -0.23,-0.26 -0.57,-0.38 -0.98,-0.38 -3.18,0 -10.67,6.82 -10.67,6.82 0,0 -2.02,2.1 -3.24,2.1 a 0.74,0.74 0 0 1 -0.68,-0.38 c -0.87,-1.46 8.05,-8.22 8.55,-11.01 0.34,-1.9 -0.24,-2.85 -1.31,-2.85 z m -6.69,-15 a 3.25,3.25 0 1 0 0,-6.5 3.25,3.25 0 0 0 0,6.5 z m -46.5,0 a 3.25,3.25 0 1 0 0,-6.5 3.25,3.25 0 0 0 0,6.5 z m -6.69,11 c -1.62,0 -3.06,0.66 -4.0700003,1.87 a 5.97,5.97 0 0 0 -1.33,3.76 7.1,7.1 0 0 0 -1.94,-0.3 c -1.55,0 -2.95,0.59 -3.94,1.66 a 5.8,5.8 0 0 0 -0.8,7 5.3,5.3 0 0 0 -1.79000004,2.82 c -0.24,0.9 -0.48,2.8 0.8,4.74 a 5.22,5.22 0 0 0 -0.37,5.02 c 1.02000004,2.32 3.57000004,4.14 8.52000004,6.1 3.0700003,1.22 5.8900003,2 5.9100003,2.01 a 44.33,44.33 0 0 0 10.93,1.6 c 5.86,0 10.05,-1.8 12.46,-5.34 3.88,-5.69 3.33,-10.9 -1.7,-15.92 -2.77,-2.78 -4.62,-6.87 -5,-7.77 -0.78,-2.66 -2.84,-5.62 -6.25,-5.62 a 5.7,5.7 0 0 0 -4.6,2.46 c -1,-1.26 -1.98,-2.25 -2.86,-2.82 a 7.4,7.4 0 0 0 -3.97,-1.27 z m 0,4 c 0.51,0 1.14,0.22 1.82,0.65 2.14,1.36 6.25,8.43 7.76,11.18 0.5,0.92 1.37,1.31 2.14,1.31 1.55,0 2.75,-1.53 0.15,-3.48 -3.92,-2.93 -2.55,-7.72 -0.68,-8.01 0.08,-0.02 0.17,-0.02 0.24,-0.02 1.7,0 2.45,2.93 2.45,2.93 0,0 2.2,5.52 5.98,9.3 3.77,3.77 3.97,6.8 1.22,10.83 -1.88,2.75 -5.47,3.58 -9.16,3.58 -3.81,0 -7.73,-0.9 -9.92,-1.46 -0.11,-0.03 -13.4500003,-3.8 -11.7600003,-7 0.28,-0.54 0.75,-0.76 1.34,-0.76 2.38,0 6.7000003,3.54 8.5700003,3.54 0.41,0 0.7,-0.17 0.83,-0.6 0.79,-2.85 -12.0600003,-4.05 -10.9800003,-8.17 0.2,-0.73 0.71,-1.02 1.44,-1.02 3.14,0 10.2000003,5.53 11.6800003,5.53 0.11,0 0.2,-0.03 0.24,-0.1 0.74,-1.2 0.33,-2.04 -4.9,-5.2 -5.2100003,-3.16 -8.8800003,-5.06 -6.8000003,-7.33 0.24,-0.26 0.58,-0.38 1,-0.38 3.17,0 10.6600003,6.82 10.6600003,6.82 0,0 2.02,2.1 3.25,2.1 0.28,0 0.52,-0.1 0.68,-0.38 0.86,-1.46 -8.06,-8.22 -8.56,-11.01 -0.34,-1.9 0.24,-2.85 1.31,-2.85 z m 21.91,2 a 8.7,8.7 0 0 1 5.3,-4.49 c 0.4,-0.12 0.81,0.57 1.24,1.28 0.4,0.68 0.82,1.37 1.24,1.37 0.45,0 0.9,-0.68 1.33,-1.35 0.45,-0.7 0.89,-1.38 1.32,-1.25 a 8.61,8.61 0 0 1 5,4.17 c 3.73,-2.94 5.1,-7.74 5.1,-10.7 0,-2.34 -1.57,-1.6 -4.09,-0.36 l -0.14,0.07 c -2.31,1.15 -5.39,2.67 -8.77,2.67 -3.38,0 -6.45,-1.52 -8.77,-2.67 -2.6,-1.29 -4.23,-2.1 -4.23,0.29 0,3.05 1.46,8.06 5.47,10.97 z m 19.07,-21.7 c 1.28,0.44 1.78,3.06 3.07,2.38 a 5,5 0 1 0 -6.76,-2.07 c 0.61,1.15 2.55,-0.72 3.7,-0.32 z m -23.55,0 c -1.28,0.44 -1.79,3.06 -3.07,2.38 a 5,5 0 1 1 6.76,-2.07 c -0.61,1.15 -2.56,-0.72 -3.7,-0.32 z" />
|
||||
</svg>
|
After Width: | Height: | Size: 5.6 KiB |
6
public/img/manual.svg
Normal file
6
public/img/manual.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.3252 3.05011L8.66765 20.4323L10.5995 20.9499L15.257 3.56775L13.3252 3.05011Z" />
|
||||
<path d="M7.61222 18.3608L8.97161 16.9124L8.9711 16.8933L3.87681 12.1121L8.66724 7.00798L7.20892 5.63928L1.0498 12.2017L7.61222 18.3608Z" />
|
||||
<path d="M16.3883 18.3608L15.0289 16.9124L15.0294 16.8933L20.1237 12.1121L15.3333 7.00798L16.7916 5.63928L22.9507 12.2017L16.3883 18.3608Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 514 B |
1198
public/index.html
1198
public/index.html
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
"checkJs": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"allowUmdGlobalAccess": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
|
@@ -96,7 +96,7 @@ EventEmitter.prototype.removeListener = function (event, listener) {
|
||||
|
||||
EventEmitter.prototype.emit = async function (event) {
|
||||
if (localStorage.getItem('eventTracing') === 'true') {
|
||||
console.trace('Event emitted: ' + event);
|
||||
console.trace('Event emitted: ' + event, args);
|
||||
} else {
|
||||
console.debug('Event emitted: ' + event);
|
||||
}
|
||||
@@ -121,7 +121,7 @@ EventEmitter.prototype.emit = async function (event) {
|
||||
|
||||
EventEmitter.prototype.emitAndWait = function (event) {
|
||||
if (localStorage.getItem('eventTracing') === 'true') {
|
||||
console.trace('Event emitted: ' + event);
|
||||
console.trace('Event emitted: ' + event, args);
|
||||
} else {
|
||||
console.debug('Event emitted: ' + event);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@
|
||||
<link rel="stylesheet" type="text/css" href="css/login.css">
|
||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
|
||||
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
||||
<link href="webfonts/NotoSansMono/stylesheet.css" rel="stylesheet">
|
||||
<!-- fontawesome webfonts-->
|
||||
<link href="css/fontawesome.min.css" rel="stylesheet">
|
||||
<link href="css/solid.min.css" rel="stylesheet">
|
||||
|
1118
public/script.js
1118
public/script.js
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ import {
|
||||
characterGroupOverlay,
|
||||
callPopup,
|
||||
characters,
|
||||
deleteCharacter,
|
||||
event_types,
|
||||
eventSource,
|
||||
getCharacters,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
buildAvatarList,
|
||||
characterToEntity,
|
||||
printCharactersDebounced,
|
||||
deleteCharacter,
|
||||
} from '../script.js';
|
||||
|
||||
import { favsToHotswap } from './RossAscends-mods.js';
|
||||
@@ -115,24 +115,7 @@ class CharacterContextMenu {
|
||||
static delete = async (characterId, deleteChats = false) => {
|
||||
const character = CharacterContextMenu.#getCharacter(characterId);
|
||||
|
||||
return fetch('/api/characters/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ avatar_url: character.avatar, delete_chats: deleteChats }),
|
||||
cache: 'no-cache',
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
eventSource.emit(event_types.CHARACTER_DELETED, { id: characterId, character: character });
|
||||
return deleteCharacter(character.name, character.avatar, false).then(() => {
|
||||
if (deleteChats) getPastCharacterChats(characterId).then(pastChats => {
|
||||
for (const chat of pastChats) {
|
||||
const name = chat.file_name.replace('.jsonl', '');
|
||||
eventSource.emit(event_types.CHAT_DELETED, name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
await deleteCharacter(character.avatar, { deleteChats: deleteChats });
|
||||
};
|
||||
|
||||
static #getCharacter = (characterId) => characters[characterId] ?? null;
|
||||
|
@@ -6,6 +6,7 @@ import { Message, TokenHandler } from './openai.js';
|
||||
import { power_user } from './power-user.js';
|
||||
import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
let timeoutId;
|
||||
@@ -250,7 +251,7 @@ class PromptManager {
|
||||
this.error = null;
|
||||
|
||||
/** Dry-run for generate, must return a promise */
|
||||
this.tryGenerate = () => { };
|
||||
this.tryGenerate = async () => { };
|
||||
|
||||
/** Called to persist the configuration, must return a promise */
|
||||
this.saveServiceSettings = () => { };
|
||||
@@ -684,6 +685,23 @@ class PromptManager {
|
||||
this.log('Initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scroll position of the prompt manager
|
||||
* @returns {number} - Scroll position of the prompt manager
|
||||
*/
|
||||
#getScrollPosition() {
|
||||
return document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scroll position of the prompt manager
|
||||
* @param {number} scrollPosition - The scroll position to set
|
||||
*/
|
||||
#setScrollPosition(scrollPosition) {
|
||||
if (scrollPosition === undefined || scrollPosition === null) return;
|
||||
document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTo(0, scrollPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main rendering function
|
||||
*
|
||||
@@ -695,24 +713,28 @@ class PromptManager {
|
||||
if ('character' === this.configuration.promptOrder.strategy && null === this.activeCharacter) return;
|
||||
this.error = null;
|
||||
|
||||
waitUntilCondition(() => !is_send_press && !is_group_generating, 1024 * 1024, 100).then(() => {
|
||||
waitUntilCondition(() => !is_send_press && !is_group_generating, 1024 * 1024, 100).then(async () => {
|
||||
if (true === afterTryGenerate) {
|
||||
// Executed during dry-run for determining context composition
|
||||
this.profileStart('filling context');
|
||||
this.tryGenerate().finally(() => {
|
||||
this.tryGenerate().finally(async () => {
|
||||
this.profileEnd('filling context');
|
||||
this.profileStart('render');
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems();
|
||||
const scrollPosition = this.#getScrollPosition();
|
||||
await this.renderPromptManager();
|
||||
await this.renderPromptManagerListItems();
|
||||
this.makeDraggable();
|
||||
this.#setScrollPosition(scrollPosition);
|
||||
this.profileEnd('render');
|
||||
});
|
||||
} else {
|
||||
// Executed during live communication
|
||||
this.profileStart('render');
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems();
|
||||
const scrollPosition = this.#getScrollPosition();
|
||||
await this.renderPromptManager();
|
||||
await this.renderPromptManagerListItems();
|
||||
this.makeDraggable();
|
||||
this.#setScrollPosition(scrollPosition);
|
||||
this.profileEnd('render');
|
||||
}
|
||||
}).catch(() => {
|
||||
@@ -1338,7 +1360,7 @@ class PromptManager {
|
||||
/**
|
||||
* Empties, then re-assembles the container containing the prompt list.
|
||||
*/
|
||||
renderPromptManager() {
|
||||
async renderPromptManager() {
|
||||
let selectedPromptIndex = 0;
|
||||
const existingAppendSelect = document.getElementById(`${this.configuration.prefix}prompt_manager_footer_append_prompt`);
|
||||
if (existingAppendSelect instanceof HTMLSelectElement) {
|
||||
@@ -1347,26 +1369,16 @@ class PromptManager {
|
||||
const promptManagerDiv = this.containerElement;
|
||||
promptManagerDiv.innerHTML = '';
|
||||
|
||||
const errorDiv = `
|
||||
const errorDiv = this.error ? `
|
||||
<div class="${this.configuration.prefix}prompt_manager_error">
|
||||
<span class="fa-solid tooltip fa-triangle-exclamation text_danger"></span> ${this.error}
|
||||
<span class="fa-solid tooltip fa-triangle-exclamation text_danger"></span> ${DOMPurify.sanitize(this.error)}
|
||||
</div>
|
||||
`;
|
||||
` : '';
|
||||
|
||||
const totalActiveTokens = this.tokenUsage;
|
||||
|
||||
promptManagerDiv.insertAdjacentHTML('beforeend', `
|
||||
<div class="range-block">
|
||||
${this.error ? errorDiv : ''}
|
||||
<div class="${this.configuration.prefix}prompt_manager_header">
|
||||
<div class="${this.configuration.prefix}prompt_manager_header_advanced">
|
||||
<span data-i18n="Prompts">Prompts</span>
|
||||
</div>
|
||||
<div>Total Tokens: ${totalActiveTokens} </div>
|
||||
</div>
|
||||
<ul id="${this.configuration.prefix}prompt_manager_list" class="text_pole"></ul>
|
||||
</div>
|
||||
`);
|
||||
const headerHtml = await renderTemplateAsync('promptManagerHeader', { error: this.error, errorDiv, prefix: this.configuration.prefix, totalActiveTokens });
|
||||
promptManagerDiv.insertAdjacentHTML('beforeend', headerHtml);
|
||||
|
||||
this.listElement = promptManagerDiv.querySelector(`#${this.configuration.prefix}prompt_manager_list`);
|
||||
|
||||
@@ -1384,22 +1396,9 @@ class PromptManager {
|
||||
selectedPromptIndex = 0;
|
||||
}
|
||||
|
||||
const footerHtml = `
|
||||
<div class="${this.configuration.prefix}prompt_manager_footer">
|
||||
<select id="${this.configuration.prefix}prompt_manager_footer_append_prompt" class="text_pole" name="append-prompt">
|
||||
${promptsHtml}
|
||||
</select>
|
||||
<a class="menu_button fa-chain fa-solid" title="Insert prompt" data-i18n="[title]Insert prompt"></a>
|
||||
<a class="caution menu_button fa-x fa-solid" title="Delete prompt" data-i18n="[title]Delete prompt"></a>
|
||||
<a class="menu_button fa-file-import fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="[title]Import a prompt list"></a>
|
||||
<a class="menu_button fa-file-export fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="[title]Export this prompt list"></a>
|
||||
<a class="menu_button fa-undo fa-solid" id="prompt-manager-reset-character" title="Reset current character" data-i18n="[title]Reset current character"></a>
|
||||
<a class="menu_button fa-plus-square fa-solid" title="New prompt" data-i18n="[title]New prompt"></a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const rangeBlockDiv = promptManagerDiv.querySelector('.range-block');
|
||||
const headerDiv = promptManagerDiv.querySelector('.completion_prompt_manager_header');
|
||||
const footerHtml = await renderTemplateAsync('promptManagerFooter', { promptsHtml, prefix: this.configuration.prefix });
|
||||
headerDiv.insertAdjacentHTML('afterend', footerHtml);
|
||||
rangeBlockDiv.querySelector('#prompt-manager-reset-character').addEventListener('click', this.handleCharacterReset);
|
||||
|
||||
@@ -1410,23 +1409,9 @@ class PromptManager {
|
||||
footerDiv.querySelector('select').selectedIndex = selectedPromptIndex;
|
||||
|
||||
// Add prompt export dialogue and options
|
||||
const exportForCharacter = `
|
||||
<div class="row">
|
||||
<a class="export-promptmanager-prompts-character list-group-item" data-i18n="Export for character">Export for character</a>
|
||||
<span class="tooltip fa-solid fa-info-circle" title="Export prompts for this character, including their order."></span>
|
||||
</div>`;
|
||||
const exportPopup = `
|
||||
<div id="prompt-manager-export-format-popup" class="list-group">
|
||||
<div class="prompt-manager-export-format-popup-flex">
|
||||
<div class="row">
|
||||
<a class="export-promptmanager-prompts-full list-group-item" data-i18n="Export all">Export all</a>
|
||||
<span class="tooltip fa-solid fa-info-circle" title="Export all your prompts to a file"></span>
|
||||
</div>
|
||||
${'global' === this.configuration.promptOrder.strategy ? '' : exportForCharacter}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const exportForCharacter = await renderTemplateAsync('promptManagerExportForCharacter');
|
||||
const exportPopup = await renderTemplateAsync('promptManagerExportPopup', { isGlobalStrategy: 'global' === this.configuration.promptOrder.strategy, exportForCharacter });
|
||||
rangeBlockDiv.insertAdjacentHTML('beforeend', exportPopup);
|
||||
|
||||
// Destroy previous popper instance if it exists
|
||||
@@ -1460,7 +1445,7 @@ class PromptManager {
|
||||
/**
|
||||
* Empties, then re-assembles the prompt list
|
||||
*/
|
||||
renderPromptManagerListItems() {
|
||||
async renderPromptManagerListItems() {
|
||||
if (!this.serviceSettings.prompts) return;
|
||||
|
||||
const promptManagerList = this.listElement;
|
||||
@@ -1468,16 +1453,7 @@ class PromptManager {
|
||||
|
||||
const { prefix } = this.configuration;
|
||||
|
||||
let listItemHtml = `
|
||||
<li class="${prefix}prompt_manager_list_head">
|
||||
<span data-i18n="Name">Name</span>
|
||||
<span></span>
|
||||
<span class="prompt_manager_prompt_tokens" data-i18n="Tokens">Tokens</span>
|
||||
</li>
|
||||
<li class="${prefix}prompt_manager_list_separator">
|
||||
<hr>
|
||||
</li>
|
||||
`;
|
||||
let listItemHtml = await renderTemplateAsync('promptManagerListHeader', { prefix });
|
||||
|
||||
this.getPromptsForCharacter(this.activeCharacter).forEach(prompt => {
|
||||
if (!prompt) return;
|
||||
@@ -1551,7 +1527,7 @@ class PromptManager {
|
||||
${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''}
|
||||
${isUserPrompt ? '<span class="fa-fw fa-solid fa-user" title="User Prompt"></span>' : ''}
|
||||
${isInjectionPrompt ? '<span class="fa-fw fa-solid fa-syringe" title="In-Chat Injection"></span>' : ''}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${encodedName}</a>` : encodedName}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a title="${encodedName}" class="prompt-manager-inspect-action">${encodedName}</a>` : `<span title="${encodedName}">${encodedName}</span>`}
|
||||
${isInjectionPrompt ? `<small class="prompt-manager-injection-depth">@ ${prompt.injection_depth}</small>` : ''}
|
||||
${isOverriddenPrompt ? '<small class="fa-solid fa-address-card prompt-manager-overridden" title="Pulled from a character card"></small>' : ''}
|
||||
</span>
|
||||
@@ -1602,7 +1578,7 @@ class PromptManager {
|
||||
data: data,
|
||||
};
|
||||
|
||||
const serializedObject = JSON.stringify(promptExport);
|
||||
const serializedObject = JSON.stringify(promptExport, null, 4);
|
||||
const blob = new Blob([serializedObject], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const downloadLink = document.createElement('a');
|
||||
|
@@ -16,7 +16,6 @@ import {
|
||||
eventSource,
|
||||
menu_type,
|
||||
substituteParams,
|
||||
callPopup,
|
||||
sendTextareaMessage,
|
||||
} from '../script.js';
|
||||
|
||||
@@ -39,6 +38,7 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex
|
||||
import { debounce_timeout } from './constants.js';
|
||||
|
||||
import Bowser from '../lib/bowser.min.js';
|
||||
import { Popup } from './popup.js';
|
||||
|
||||
var RPanelPin = document.getElementById('rm_button_panel_pin');
|
||||
var LPanelPin = document.getElementById('lm_button_panel_pin');
|
||||
@@ -303,7 +303,7 @@ export async function favsToHotswap() {
|
||||
return;
|
||||
}
|
||||
|
||||
buildAvatarList(container, favs, { selectable: true, highlightFavs: false });
|
||||
buildAvatarList(container, favs, { interactable: true, highlightFavs: false });
|
||||
}
|
||||
|
||||
//changes input bar and send button display depending on connection status
|
||||
@@ -360,6 +360,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (textgen_settings.type === textgen_types.INFERMATICAI && secret_state[SECRET_KEYS.INFERMATICAI])
|
||||
|| (textgen_settings.type === textgen_types.DREAMGEN && secret_state[SECRET_KEYS.DREAMGEN])
|
||||
|| (textgen_settings.type === textgen_types.OPENROUTER && secret_state[SECRET_KEYS.OPENROUTER])
|
||||
|| (textgen_settings.type === textgen_types.FEATHERLESS && secret_state[SECRET_KEYS.FEATHERLESS])
|
||||
) {
|
||||
$('#api_button_textgenerationwebui').trigger('click');
|
||||
}
|
||||
@@ -379,6 +380,7 @@ function RA_autoconnect(PrevApi) {
|
||||
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|
||||
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
||||
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|
||||
|| (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
|
||||
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
||||
) {
|
||||
$('#api_button_openai').trigger('click');
|
||||
@@ -424,7 +426,7 @@ function restoreUserInput() {
|
||||
|
||||
const userInput = LoadLocal('userInput');
|
||||
if (userInput) {
|
||||
$('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles:true }));
|
||||
$('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,10 +438,8 @@ const saveUserInputDebounced = debounce(saveUserInput);
|
||||
|
||||
// Make the DIV element draggable:
|
||||
|
||||
// THIRD UPDATE, prevent resize window breaks and smartly handle saving
|
||||
|
||||
export function dragElement(elmnt) {
|
||||
var hasBeenDraggedByUser = false;
|
||||
var isHeaderBeingDragged = false;
|
||||
var isMouseDown = false;
|
||||
|
||||
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||
@@ -450,16 +450,16 @@ export function dragElement(elmnt) {
|
||||
var elmntName = elmnt.attr('id');
|
||||
console.debug(`dragElement called for ${elmntName}`);
|
||||
const elmntNameEscaped = $.escapeSelector(elmntName);
|
||||
console.debug(`dragElement escaped name: ${elmntNameEscaped}`);
|
||||
const elmntHeader = $(`#${elmntNameEscaped}header`);
|
||||
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
hasBeenDraggedByUser = true;
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => { //listener for drag handle repositioning
|
||||
isHeaderBeingDragged = true;
|
||||
isMouseDown = true;
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
dragMouseDown(e);
|
||||
});
|
||||
$(elmnt).off('mousedown').on('mousedown', () => {
|
||||
$(elmnt).off('mousedown').on('mousedown', () => { //listener for resize
|
||||
isMouseDown = true;
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
});
|
||||
@@ -467,20 +467,19 @@ export function dragElement(elmnt) {
|
||||
|
||||
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()
|
||||
if (!$(target).is(':visible') //abort if element is invisible
|
||||
|| $(target).hasClass('resizing') //being auto-resized by other JS code
|
||||
|| Number((String(target.height).replace('px', ''))) < 50 //too short
|
||||
|| Number((String(target.width).replace('px', ''))) < 50 //too narrow
|
||||
|| power_user.movingUI === false // if MUI is not turned on
|
||||
|| isMobile() // if it's a mobile screen
|
||||
) {
|
||||
console.debug('aborting mutator');
|
||||
return;
|
||||
}
|
||||
//console.debug(left + width, winWidth, hasBeenDraggedByUser, isMouseDown)
|
||||
const style = getComputedStyle(target); //use computed values because not all CSS are set by default
|
||||
height = target.offsetHeight;
|
||||
width = target.offsetWidth;
|
||||
|
||||
const style = getComputedStyle(target);
|
||||
height = parseInt(style.height);
|
||||
width = parseInt(style.width);
|
||||
top = parseInt(style.top);
|
||||
left = parseInt(style.left);
|
||||
right = parseInt(style.right);
|
||||
@@ -495,53 +494,53 @@ export function dragElement(elmnt) {
|
||||
topBarFirstX = parseInt(topbarstyle.marginInline);
|
||||
topBarLastY = parseInt(topbarstyle.height);
|
||||
|
||||
/*console.log(`
|
||||
winWidth: ${winWidth}, winHeight: ${winHeight}
|
||||
sheldWidth: ${sheldWidth}
|
||||
X: ${$(elmnt).css('left')}
|
||||
Y: ${$(elmnt).css('top')}
|
||||
MaxX: ${maxX}, MaxY: ${maxY}
|
||||
height: ${height}
|
||||
width: ${width}
|
||||
Topbar 1st X: ${topBarFirstX}
|
||||
TopBar lastX: ${topBarLastX}
|
||||
`);*/
|
||||
|
||||
|
||||
//prepare an empty poweruser object for the item being altered if we don't have one already
|
||||
if (!power_user.movingUIState[elmntName]) {
|
||||
console.debug(`adding config property for ${elmntName}`);
|
||||
power_user.movingUIState[elmntName] = {};
|
||||
}
|
||||
|
||||
//only record position changes if caused by a user click-drag
|
||||
if (hasBeenDraggedByUser && isMouseDown) {
|
||||
power_user.movingUIState[elmntName].top = top;
|
||||
power_user.movingUIState[elmntName].left = left;
|
||||
power_user.movingUIState[elmntName].right = right;
|
||||
power_user.movingUIState[elmntName].bottom = bottom;
|
||||
power_user.movingUIState[elmntName].margin = 'unset';
|
||||
}
|
||||
|
||||
//handle resizing
|
||||
if (!hasBeenDraggedByUser && isMouseDown) {
|
||||
console.debug('saw resize, NOT header drag');
|
||||
if (!isHeaderBeingDragged && isMouseDown) { //if user is dragging the resize handle (not in header)
|
||||
let imgHeight, imgWidth, imageAspectRatio;
|
||||
let containerAspectRatio = height / width;
|
||||
|
||||
//prevent resizing offscreen
|
||||
if (top + elmnt.height() >= winHeight) {
|
||||
console.debug('resizing height to prevent offscreen');
|
||||
elmnt.css('height', winHeight - top - 1 + 'px');
|
||||
}
|
||||
//force aspect ratio for zoomed avatars
|
||||
if ($(elmnt).attr('id').startsWith('zoomFor_')) {
|
||||
let zoomedAvatarImage = $(elmnt).find('.zoomed_avatar_img');
|
||||
imgHeight = zoomedAvatarImage.height();
|
||||
imgWidth = zoomedAvatarImage.width();
|
||||
imageAspectRatio = imgHeight / imgWidth;
|
||||
|
||||
if (left + elmnt.width() >= winWidth) {
|
||||
console.debug('resizing width to prevent offscreen');
|
||||
elmnt.css('width', winWidth - left - 1 + 'px');
|
||||
// Maintain aspect ratio
|
||||
if (containerAspectRatio !== imageAspectRatio) {
|
||||
elmnt.css('width', elmnt.width());
|
||||
elmnt.css('height', elmnt.width() * imageAspectRatio);
|
||||
}
|
||||
|
||||
// Prevent resizing offscreen
|
||||
if (top + elmnt.height() >= winHeight) {
|
||||
elmnt.css('height', winHeight - top - 1 + 'px');
|
||||
elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
|
||||
}
|
||||
|
||||
if (left + elmnt.width() >= winWidth) {
|
||||
elmnt.css('width', winWidth - left - 1 + 'px');
|
||||
elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
|
||||
}
|
||||
} else { //prevent divs that are not zoomedAvatars from resizing offscreen
|
||||
|
||||
if (top + elmnt.height() >= winHeight) {
|
||||
elmnt.css('height', winHeight - top - 1 + 'px');
|
||||
}
|
||||
|
||||
if (left + elmnt.width() >= winWidth) {
|
||||
elmnt.css('width', winWidth - left - 1 + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
//prevent resizing from top left into the top bar
|
||||
if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX
|
||||
) {
|
||||
console.debug('prevent topbar underlap resize');
|
||||
if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {
|
||||
elmnt.css('width', width - 1 + 'px');
|
||||
}
|
||||
|
||||
@@ -550,11 +549,11 @@ export function dragElement(elmnt) {
|
||||
elmnt.css('top', top);
|
||||
|
||||
//set a listener for mouseup to save new width/height
|
||||
elmnt.off('mouseup').on('mouseup', () => {
|
||||
console.debug(`Saving ${elmntName} Height/Width`);
|
||||
$(window).off('mouseup').on('mouseup', () => {
|
||||
console.log(`Saving ${elmntName} Height/Width`);
|
||||
// check if the height or width actually changed
|
||||
if (power_user.movingUIState[elmntName].width === width && power_user.movingUIState[elmntName].height === height) {
|
||||
console.debug('no change detected, aborting save');
|
||||
if (power_user.movingUIState[elmntName].width === elmnt.width() && power_user.movingUIState[elmntName].height === elmnt.height()) {
|
||||
console.log('no change detected, aborting save');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -562,12 +561,27 @@ export function dragElement(elmnt) {
|
||||
power_user.movingUIState[elmntName].height = height;
|
||||
eventSource.emit('resizeUI', elmntName);
|
||||
saveSettingsDebounced();
|
||||
imgHeight = null;
|
||||
imgWidth = null;
|
||||
height = null;
|
||||
width = null;
|
||||
|
||||
containerAspectRatio = null;
|
||||
imageAspectRatio = null;
|
||||
$(window).off('mouseup');
|
||||
});
|
||||
}
|
||||
|
||||
//handle dragging hit detection
|
||||
if (hasBeenDraggedByUser && isMouseDown) {
|
||||
//prevent dragging offscreen
|
||||
//only record position changes if header is being dragged
|
||||
power_user.movingUIState[elmntName].top = top;
|
||||
power_user.movingUIState[elmntName].left = left;
|
||||
power_user.movingUIState[elmntName].right = right;
|
||||
power_user.movingUIState[elmntName].bottom = bottom;
|
||||
power_user.movingUIState[elmntName].margin = 'unset';
|
||||
|
||||
//handle dragging hit detection to prevent dragging offscreen
|
||||
if (isHeaderBeingDragged && isMouseDown) {
|
||||
|
||||
if (top <= 0) {
|
||||
elmnt.css('top', '0px');
|
||||
} else if (maxY >= winHeight) {
|
||||
@@ -579,27 +593,14 @@ export function dragElement(elmnt) {
|
||||
} else if (maxX >= winWidth) {
|
||||
elmnt.css('left', winWidth - maxX + left - 1 + 'px');
|
||||
}
|
||||
|
||||
//prevent underlap with topbar div
|
||||
/*
|
||||
if (top < topBarLastY
|
||||
&& (maxX >= topBarFirstX && left <= topBarFirstX //elmnt is hitting topbar from left side
|
||||
|| left <= topBarLastX && maxX >= topBarLastX //elmnt is hitting topbar from right side
|
||||
|| left >= topBarFirstX && maxX <= topBarLastX) //elmnt hitting topbar in the middle
|
||||
) {
|
||||
console.debug('topbar hit')
|
||||
elmnt.css('top', top + 1 + "px");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Check if the element header exists and set the listener on the grabber
|
||||
// Check if the element header exists and set the reposition listener on the grabber in the header
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
console.debug('listener started from header');
|
||||
dragMouseDown(e);
|
||||
});
|
||||
} else {
|
||||
} else { //if no header, put the listener on the elmnt itself.
|
||||
elmnt.off('mousedown').on('mousedown', dragMouseDown);
|
||||
}
|
||||
});
|
||||
@@ -607,7 +608,7 @@ export function dragElement(elmnt) {
|
||||
function dragMouseDown(e) {
|
||||
|
||||
if (e) {
|
||||
hasBeenDraggedByUser = true;
|
||||
isHeaderBeingDragged = true;
|
||||
e.preventDefault();
|
||||
pos3 = e.clientX; //mouse X at click
|
||||
pos4 = e.clientY; //mouse Y at click
|
||||
@@ -639,34 +640,20 @@ export function dragElement(elmnt) {
|
||||
elmnt.css('margin', 'unset');
|
||||
elmnt.css('left', (elmnt.offset().left - pos1) + 'px');
|
||||
elmnt.css('top', (elmnt.offset().top - pos2) + 'px');
|
||||
elmnt.css('right', ((winWidth - maxX) + 'px'));
|
||||
elmnt.css('bottom', ((winHeight - maxY) + 'px'));
|
||||
/* elmnt.css('right', ((winWidth - maxX) + 'px'));
|
||||
elmnt.css('bottom', ((winHeight - maxY) + 'px')); */
|
||||
|
||||
// Height/Width here are for visuals only, and are not saved to settings
|
||||
// required because some divs do hot have a set width/height..
|
||||
// and will defaults to shrink to min value of 100px set in CSS file
|
||||
// Height/Width here are for visuals only, and are not saved to settings.
|
||||
// This is required because some divs do hot have a set width/height
|
||||
// and will default to shrink to min value of 100px set in CSS file
|
||||
elmnt.css('height', height);
|
||||
elmnt.css('width', width);
|
||||
/*
|
||||
console.log(`
|
||||
winWidth: ${winWidth}, winHeight: ${winHeight}
|
||||
sheldWidth: ${sheldWidth}
|
||||
X: ${$(elmnt).css('left')}
|
||||
Y: ${$(elmnt).css('top')}
|
||||
MaxX: ${maxX}, MaxY: ${maxY}
|
||||
height: ${height}
|
||||
width: ${width}
|
||||
Topbar 1st X: ${topBarFirstX}
|
||||
TopBar lastX: ${topBarLastX}
|
||||
`);
|
||||
*/
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
console.debug('drag finished');
|
||||
hasBeenDraggedByUser = false;
|
||||
isHeaderBeingDragged = false;
|
||||
isMouseDown = false;
|
||||
$(document).off('mouseup', closeDragElement);
|
||||
$(document).off('mousemove', elementDrag);
|
||||
@@ -676,6 +663,12 @@ export function dragElement(elmnt) {
|
||||
observer.disconnect();
|
||||
console.debug(`Saving ${elmntName} UI position`);
|
||||
saveSettingsDebounced();
|
||||
top = null;
|
||||
left = null;
|
||||
right = null;
|
||||
bottom = null;
|
||||
maxX = null;
|
||||
maxY = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,6 +725,10 @@ export function initRossMods() {
|
||||
RA_autoconnect();
|
||||
}
|
||||
|
||||
if (getParsedUA()?.os?.name === 'iOS') {
|
||||
document.body.classList.add('ios');
|
||||
}
|
||||
|
||||
$('#main_api').change(function () {
|
||||
var PrevAPI = main_api;
|
||||
setTimeout(() => RA_autoconnect(PrevAPI), 100);
|
||||
@@ -935,8 +932,8 @@ export function initRossMods() {
|
||||
return false;
|
||||
}
|
||||
|
||||
$(document).on('keydown', function (event) {
|
||||
processHotkeys(event.originalEvent);
|
||||
$(document).on('keydown', async function (event) {
|
||||
await processHotkeys(event.originalEvent);
|
||||
});
|
||||
|
||||
const hotkeyTargets = {
|
||||
@@ -948,7 +945,7 @@ export function initRossMods() {
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
function processHotkeys(event) {
|
||||
async function processHotkeys(event) {
|
||||
//Enter to send when send_textarea in focus
|
||||
if (document.activeElement == hotkeyTargets['send_textarea']) {
|
||||
const sendOnEnter = shouldSendOnEnter();
|
||||
@@ -1012,20 +1009,17 @@ export function initRossMods() {
|
||||
if (skipConfirm) {
|
||||
doRegenerate();
|
||||
} else {
|
||||
const popupText = `
|
||||
<div class="marginBot10">Are you sure you want to regenerate the latest message?</div>
|
||||
<label class="checkbox_label justifyCenter" for="regenerateWithCtrlEnter">
|
||||
<input type="checkbox" id="regenerateWithCtrlEnter">
|
||||
Don't ask again
|
||||
</label>`;
|
||||
callPopup(popupText, 'confirm').then(result => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
const regenerateWithCtrlEnter = $('#regenerateWithCtrlEnter').prop('checked');
|
||||
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
|
||||
doRegenerate();
|
||||
let regenerateWithCtrlEnter = false;
|
||||
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
|
||||
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
|
||||
onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
|
||||
doRegenerate();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
@@ -1105,6 +1099,9 @@ export function initRossMods() {
|
||||
}
|
||||
|
||||
if (event.key == 'Escape') { //closes various panels
|
||||
// Do not close panels if we are currently inside a popup
|
||||
if (Popup.util.isPopupOpen())
|
||||
return;
|
||||
|
||||
//dont override Escape hotkey functions from script.js
|
||||
//"close edit box" and "cancel stream generation".
|
||||
|
@@ -38,6 +38,7 @@ const chara_note_position = {
|
||||
function setNoteTextCommand(_, text) {
|
||||
$('#extension_floating_prompt').val(text).trigger('input');
|
||||
toastr.success('Author\'s Note text updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function setNoteDepthCommand(_, text) {
|
||||
@@ -50,6 +51,7 @@ function setNoteDepthCommand(_, text) {
|
||||
|
||||
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
|
||||
toastr.success('Author\'s Note depth updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function setNoteIntervalCommand(_, text) {
|
||||
@@ -62,6 +64,7 @@ function setNoteIntervalCommand(_, text) {
|
||||
|
||||
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
|
||||
toastr.success('Author\'s Note frequency updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function setNotePositionCommand(_, text) {
|
||||
@@ -79,6 +82,7 @@ function setNotePositionCommand(_, text) {
|
||||
|
||||
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
|
||||
toastr.info('Author\'s Note position updated');
|
||||
return '';
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
|
@@ -6,6 +6,7 @@ import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
|
||||
import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||
import { Popup, getTopmostModalLayer } from '../popup.js';
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {Number}*/
|
||||
@@ -386,11 +387,15 @@ export class AutoComplete {
|
||||
// no result and no input? hide autocomplete
|
||||
return this.hide();
|
||||
}
|
||||
if (this.effectiveParserResult instanceof AutoCompleteSecondaryNameResult && !this.effectiveParserResult.forceMatch) {
|
||||
// no result and matching is no forced? hide autocomplete
|
||||
return this.hide();
|
||||
}
|
||||
// otherwise add "no match" notice
|
||||
const option = new BlankAutoCompleteOption(
|
||||
this.name.length ?
|
||||
this.effectiveParserResult.makeNoMatchText()
|
||||
: this.effectiveParserResult.makeNoOptionstext()
|
||||
: this.effectiveParserResult.makeNoOptionsText()
|
||||
,
|
||||
);
|
||||
this.result.push(option);
|
||||
@@ -438,7 +443,7 @@ export class AutoComplete {
|
||||
}
|
||||
this.dom.append(frag);
|
||||
this.updatePosition();
|
||||
document.body.append(this.domWrap);
|
||||
getTopmostModalLayer().append(this.domWrap);
|
||||
} else {
|
||||
this.domWrap.remove();
|
||||
}
|
||||
@@ -453,7 +458,7 @@ export class AutoComplete {
|
||||
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
||||
this.detailsDom.innerHTML = '';
|
||||
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
||||
document.body.append(this.detailsWrap);
|
||||
getTopmostModalLayer().append(this.detailsWrap);
|
||||
this.updateDetailsPositionDebounced();
|
||||
}
|
||||
|
||||
@@ -469,7 +474,7 @@ export class AutoComplete {
|
||||
const rect = {};
|
||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||
rect[AUTOCOMPLETE_WIDTH.FULL] = document.body.getBoundingClientRect();
|
||||
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
|
||||
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
||||
@@ -481,8 +486,8 @@ export class AutoComplete {
|
||||
this.domWrap.style.setProperty('--leftOffset', `max(1vw, ${rect[power_user.stscript.autocomplete.width.left].left}px)`);
|
||||
this.domWrap.style.setProperty('--rightOffset', `calc(100vw - min(99vw, ${rect[power_user.stscript.autocomplete.width.right].right}px)`);
|
||||
}
|
||||
this.updateDetailsPosition();
|
||||
}
|
||||
this.updateDetailsPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -496,7 +501,7 @@ export class AutoComplete {
|
||||
const rect = {};
|
||||
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||
rect[AUTOCOMPLETE_WIDTH.FULL] = document.body.getBoundingClientRect();
|
||||
rect[AUTOCOMPLETE_WIDTH.FULL] = getTopmostModalLayer().getBoundingClientRect();
|
||||
if (this.isReplaceable) {
|
||||
this.detailsWrap.classList.remove('full');
|
||||
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
||||
@@ -592,7 +597,7 @@ export class AutoComplete {
|
||||
}
|
||||
this.clone.style.position = 'fixed';
|
||||
this.clone.style.visibility = 'hidden';
|
||||
document.body.append(this.clone);
|
||||
getTopmostModalLayer().append(this.clone);
|
||||
const mo = new MutationObserver(muts=>{
|
||||
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
||||
this.clone.remove();
|
||||
@@ -745,8 +750,10 @@ export class AutoComplete {
|
||||
}
|
||||
// autocomplete shown or not, cursor anywhere
|
||||
switch (evt.key) {
|
||||
// The first is a non-breaking space, the second is a regular space.
|
||||
case ' ':
|
||||
case ' ': {
|
||||
if (evt.ctrlKey) {
|
||||
if (evt.ctrlKey || evt.altKey) {
|
||||
if (this.isActive && this.isReplaceable) {
|
||||
// ctrl-space to toggle details for selected item
|
||||
this.toggleDetails();
|
||||
@@ -754,6 +761,8 @@ export class AutoComplete {
|
||||
// ctrl-space to force show autocomplete
|
||||
this.show(false, true);
|
||||
}
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
@@ -10,7 +10,7 @@ export class AutoCompleteNameResult {
|
||||
/**@type {AutoCompleteOption[]} */ optionList = [];
|
||||
/**@type {boolean} */ canBeQuoted = false;
|
||||
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
|
||||
/**@type {()=>string} */ makeNoOptionstext = ()=>'No options';
|
||||
/**@type {()=>string} */ makeNoOptionsText = ()=>'No options';
|
||||
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export class AutoCompleteNameResult {
|
||||
this.optionList = optionList;
|
||||
this.canBeQuoted = canBeQuoted;
|
||||
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
|
||||
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionstext;
|
||||
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionsText;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
|
||||
export class AutoCompleteOption {
|
||||
/**@type {string}*/ name;
|
||||
/**@type {string}*/ typeIcon;
|
||||
/**@type {string}*/ type;
|
||||
/**@type {number}*/ nameOffset = 0;
|
||||
/**@type {AutoCompleteFuzzyScore}*/ score;
|
||||
/**@type {string}*/ replacer;
|
||||
@@ -24,9 +25,10 @@ export class AutoCompleteOption {
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
constructor(name, typeIcon = ' ') {
|
||||
constructor(name, typeIcon = ' ', type = '') {
|
||||
this.name = name;
|
||||
this.typeIcon = typeIcon;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +143,11 @@ export class AutoCompleteOption {
|
||||
}
|
||||
li.append(specs);
|
||||
}
|
||||
const stopgap = document.createElement('span'); {
|
||||
stopgap.classList.add('stopgap');
|
||||
stopgap.textContent = '';
|
||||
li.append(stopgap);
|
||||
}
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
const content = document.createElement('span'); {
|
||||
@@ -181,6 +188,7 @@ export class AutoCompleteOption {
|
||||
let li;
|
||||
li = this.makeItem(this.name, this.typeIcon, true);
|
||||
li.setAttribute('data-name', this.name);
|
||||
li.setAttribute('data-option-type', this.type);
|
||||
return li;
|
||||
}
|
||||
|
||||
|
@@ -2,4 +2,5 @@ import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
|
||||
|
||||
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult {
|
||||
/**@type {boolean}*/ isRequired = false;
|
||||
/**@type {boolean}*/ forceMatch = true;
|
||||
}
|
||||
|
@@ -95,7 +95,7 @@ function onLockBackgroundClick(e) {
|
||||
|
||||
if (!chatName) {
|
||||
toastr.warning('Select a chat to lock the background for it');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const relativeBgImage = getUrlParameter(this);
|
||||
@@ -103,6 +103,7 @@ function onLockBackgroundClick(e) {
|
||||
saveBackgroundMetadata(relativeBgImage);
|
||||
setCustomBackground();
|
||||
highlightLockedBackground();
|
||||
return '';
|
||||
}
|
||||
|
||||
function onUnlockBackgroundClick(e) {
|
||||
@@ -110,6 +111,7 @@ function onUnlockBackgroundClick(e) {
|
||||
removeBackgroundMetadata();
|
||||
unsetCustomBackground();
|
||||
highlightLockedBackground();
|
||||
return '';
|
||||
}
|
||||
|
||||
function hasCustomBackground() {
|
||||
@@ -319,7 +321,7 @@ async function autoBackgroundCommand() {
|
||||
const options = bgTitles.map(x => ({ element: x, text: x.innerText.trim() })).filter(x => x.text.length > 0);
|
||||
if (options.length == 0) {
|
||||
toastr.warning('No backgrounds to choose from. Please upload some images to the "backgrounds" folder.');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
const list = options.map(option => `- ${option.text}`).join('\n');
|
||||
@@ -330,11 +332,12 @@ async function autoBackgroundCommand() {
|
||||
|
||||
if (bestMatch.length == 0) {
|
||||
toastr.warning('No match found. Please try again.');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
console.debug('Automatically choosing background:', bestMatch);
|
||||
bestMatch[0].item.element.click();
|
||||
return '';
|
||||
}
|
||||
|
||||
export async function getBackgrounds() {
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
getCharacters,
|
||||
chat,
|
||||
saveChatConditional,
|
||||
saveItemizedPrompts,
|
||||
} from '../script.js';
|
||||
import { humanizedDateTime } from './RossAscends-mods.js';
|
||||
import {
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
saveGroupBookmarkChat,
|
||||
selected_group,
|
||||
} from './group-chats.js';
|
||||
import { Popup } from './popup.js';
|
||||
import { createTagMapFromList } from './tags.js';
|
||||
|
||||
import {
|
||||
@@ -199,6 +201,7 @@ async function createNewBookmark(mesId) {
|
||||
|
||||
const mainChat = selected_group ? groups?.find(x => x.id == selected_group)?.chat_id : characters[this_chid].chat;
|
||||
const newMetadata = { main_chat: mainChat };
|
||||
await saveItemizedPrompts(name);
|
||||
|
||||
if (selected_group) {
|
||||
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
|
||||
@@ -237,8 +240,7 @@ async function convertSoloToGroupChat() {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup('Are you sure you want to convert this chat to a group chat?', 'confirm');
|
||||
|
||||
const confirm = await Popup.show.confirm('Convert to group chat', 'Are you sure you want to convert this chat to a group chat?<br />This cannot be reverted.');
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
@@ -334,6 +336,7 @@ async function convertSoloToGroupChat() {
|
||||
|
||||
if (!createChatResponse.ok) {
|
||||
console.error('Group chat creation unsuccessful');
|
||||
toastr.error('Group chat creation unsuccessful');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -94,6 +94,9 @@ function enableBulkSelect() {
|
||||
});
|
||||
$(el).prepend(checkbox);
|
||||
});
|
||||
$('#rm_print_characters_block.group_overlay_mode_select .bogus_folder_select, #rm_print_characters_block.group_overlay_mode_select .group_select')
|
||||
.addClass('disabled');
|
||||
|
||||
$('#rm_print_characters_block').addClass('bulk_select');
|
||||
// We also need to disable the default click event for the character_select divs
|
||||
$(document).on('click', '.bulk_select_checkbox', function (event) {
|
||||
@@ -106,6 +109,8 @@ function enableBulkSelect() {
|
||||
*/
|
||||
function disableBulkSelect() {
|
||||
$('.bulk_select_checkbox').remove();
|
||||
$('#rm_print_characters_block.group_overlay_mode_select .bogus_folder_select, #rm_print_characters_block.group_overlay_mode_select .group_select')
|
||||
.removeClass('disabled');
|
||||
$('#rm_print_characters_block').removeClass('bulk_select');
|
||||
}
|
||||
|
||||
|
@@ -68,13 +68,32 @@
|
||||
* @property {number} depth_prompt.depth - The level of detail or nuance targeted by the prompt.
|
||||
* @property {string} depth_prompt.prompt - The actual prompt text used for deeper character interaction.
|
||||
* @property {"system" | "user" | "assistant"} depth_prompt.role - The role the character takes on during the prompted interaction (system, user, or assistant).
|
||||
* @property {RegexScriptData[]} regex_scripts - Custom regex scripts for the character.
|
||||
* // Non-standard extensions added by external tools
|
||||
* @property {string} [pygmalion_id] - The unique identifier assigned to the character by the Pygmalion.chat.
|
||||
* @property {string} [github_repo] - The gitHub repository associated with the character.
|
||||
* @property {string} [source_url] - The source URL associated with the character.
|
||||
* @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
|
||||
* @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} RegexScriptData
|
||||
* @property {string} id - UUID of the script
|
||||
* @property {string} scriptName - The name of the script
|
||||
* @property {string} findRegex - The regex to find
|
||||
* @property {string} replaceString - The string to replace
|
||||
* @property {string[]} trimStrings - The strings to trim
|
||||
* @property {number[]} placement - The placement of the script
|
||||
* @property {boolean} disabled - Whether the script is disabled
|
||||
* @property {boolean} markdownOnly - Whether the script only applies to Markdown
|
||||
* @property {boolean} promptOnly - Whether the script only applies to prompts
|
||||
* @property {boolean} runOnEdit - Whether the script runs on edit
|
||||
* @property {boolean} substituteRegex - Whether the regex should be substituted
|
||||
* @property {number} minDepth - The minimum depth
|
||||
* @property {number} maxDepth - The maximum depth
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} v1CharData
|
||||
* @property {string} name - the name of the character
|
||||
|
@@ -4,7 +4,6 @@ import css from '../lib/css-parser.mjs';
|
||||
import {
|
||||
addCopyToCodeBlocks,
|
||||
appendMediaToMessage,
|
||||
callPopup,
|
||||
characters,
|
||||
chat,
|
||||
eventSource,
|
||||
@@ -35,8 +34,10 @@ import {
|
||||
extractTextFromOffice,
|
||||
} from './utils.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { ScraperManager } from './scrapers.js';
|
||||
import { DragAndDropHandler } from './dragdrop.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileAttachment
|
||||
@@ -184,18 +185,19 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const slug = getStringHash(file.name);
|
||||
const fileNamePrefix = `${Date.now()}_${slug}`;
|
||||
const fileBase64 = await getBase64Async(file);
|
||||
let base64Data = fileBase64.split(',')[1];
|
||||
|
||||
// If file is image
|
||||
if (file.type.startsWith('image/')) {
|
||||
const extension = file.type.split('/')[1];
|
||||
const imageUrl = await saveBase64AsFile(base64Data, name2, file.name, extension);
|
||||
const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
|
||||
message.extra.image = imageUrl;
|
||||
message.extra.inline_image = true;
|
||||
} else {
|
||||
const slug = getStringHash(file.name);
|
||||
const uniqueFileName = `${Date.now()}_${slug}.txt`;
|
||||
const uniqueFileName = `${fileNamePrefix}.txt`;
|
||||
|
||||
if (isConvertible(file.type)) {
|
||||
try {
|
||||
@@ -318,12 +320,10 @@ export function hasPendingFileAttachment() {
|
||||
|
||||
/**
|
||||
* Displays file information in the message sending form.
|
||||
* @param {File} file File object
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function onFileAttach() {
|
||||
const fileInput = document.getElementById('file_form_input');
|
||||
if (!(fileInput instanceof HTMLInputElement)) return;
|
||||
const file = fileInput.files[0];
|
||||
async function onFileAttach(file) {
|
||||
if (!file) return;
|
||||
|
||||
const isValid = await validateFile(file);
|
||||
@@ -418,6 +418,7 @@ function embedMessageFile(messageId, messageBlock) {
|
||||
}
|
||||
|
||||
await populateFileAttachment(message, 'embed_file_input');
|
||||
await eventSource.emit(event_types.MESSAGE_FILE_EMBEDDED, messageId);
|
||||
appendMediaToMessage(message, messageBlock);
|
||||
await saveChatConditional();
|
||||
}
|
||||
@@ -463,33 +464,50 @@ export function encodeStyleTags(text) {
|
||||
*/
|
||||
export function decodeStyleTags(text) {
|
||||
const styleDecodeRegex = /<custom-style>(.+?)<\/custom-style>/gms;
|
||||
const mediaAllowed = isExternalMediaAllowed();
|
||||
|
||||
function sanitizeRule(rule) {
|
||||
if (Array.isArray(rule.selectors)) {
|
||||
for (let i = 0; i < rule.selectors.length; i++) {
|
||||
const selector = rule.selectors[i];
|
||||
if (selector) {
|
||||
const selectors = (selector.split(' ') ?? []).map((v) => {
|
||||
if (v.startsWith('.')) {
|
||||
return '.custom-' + v.substring(1);
|
||||
}
|
||||
return v;
|
||||
}).join(' ');
|
||||
|
||||
rule.selectors[i] = '.mes_text ' + selectors;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!mediaAllowed && Array.isArray(rule.declarations) && rule.declarations.length > 0) {
|
||||
rule.declarations = rule.declarations.filter(declaration => !declaration.value.includes('://'));
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeRuleSet(ruleSet) {
|
||||
if (Array.isArray(ruleSet.selectors) || Array.isArray(ruleSet.declarations)) {
|
||||
sanitizeRule(ruleSet);
|
||||
}
|
||||
|
||||
if (Array.isArray(ruleSet.rules)) {
|
||||
ruleSet.rules = ruleSet.rules.filter(rule => rule.type !== 'import');
|
||||
|
||||
for (const mediaRule of ruleSet.rules) {
|
||||
sanitizeRuleSet(mediaRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text.replaceAll(styleDecodeRegex, (_, style) => {
|
||||
try {
|
||||
let styleCleaned = unescape(style).replaceAll(/<br\/>/g, '');
|
||||
const ast = css.parse(styleCleaned);
|
||||
const rules = ast?.stylesheet?.rules;
|
||||
if (rules) {
|
||||
for (const rule of rules) {
|
||||
|
||||
if (rule.type === 'rule') {
|
||||
if (rule.selectors) {
|
||||
for (let i = 0; i < rule.selectors.length; i++) {
|
||||
let selector = rule.selectors[i];
|
||||
if (selector) {
|
||||
let selectors = (selector.split(' ') ?? []).map((v) => {
|
||||
if (v.startsWith('.')) {
|
||||
return '.custom-' + v.substring(1);
|
||||
}
|
||||
return v;
|
||||
}).join(' ');
|
||||
|
||||
rule.selectors[i] = '.mes_text ' + selectors;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const sheet = ast?.stylesheet;
|
||||
if (sheet) {
|
||||
sanitizeRuleSet(ast.stylesheet);
|
||||
}
|
||||
return `<style>${css.stringify(ast)}</style>`;
|
||||
} catch (error) {
|
||||
@@ -506,7 +524,7 @@ async function openExternalMediaOverridesDialog() {
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $('#forbid_media_override_template > .forbid_media_override').clone();
|
||||
const template = $(await renderTemplateAsync('forbidMedia'));
|
||||
template.find('.forbid_media_global_state_forbidden').toggle(power_user.forbid_external_media);
|
||||
template.find('.forbid_media_global_state_allowed').toggle(!power_user.forbid_external_media);
|
||||
|
||||
@@ -520,7 +538,7 @@ async function openExternalMediaOverridesDialog() {
|
||||
template.find('#forbid_media_override_global').prop('checked', true);
|
||||
}
|
||||
|
||||
callPopup(template, 'text', '', { wide: false, large: false });
|
||||
callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: false, large: false });
|
||||
}
|
||||
|
||||
export function getCurrentEntityId() {
|
||||
@@ -548,7 +566,7 @@ export function isExternalMediaAllowed() {
|
||||
return !power_user.forbid_external_media;
|
||||
}
|
||||
|
||||
function enlargeMessageImage() {
|
||||
async function enlargeMessageImage() {
|
||||
const mesBlock = $(this).closest('.mes');
|
||||
const mesId = mesBlock.attr('mesid');
|
||||
const message = chat[mesId];
|
||||
@@ -562,14 +580,28 @@ function enlargeMessageImage() {
|
||||
const img = document.createElement('img');
|
||||
img.classList.add('img_enlarged');
|
||||
img.src = imgSrc;
|
||||
const imgHolder = document.createElement('div');
|
||||
imgHolder.classList.add('img_enlarged_holder');
|
||||
imgHolder.append(img);
|
||||
const imgContainer = $('<div><pre><code></code></pre></div>');
|
||||
imgContainer.prepend(img);
|
||||
imgContainer.prepend(imgHolder);
|
||||
imgContainer.addClass('img_enlarged_container');
|
||||
imgContainer.find('code').addClass('txt').text(title);
|
||||
const titleEmpty = !title || title.trim().length === 0;
|
||||
imgContainer.find('pre').toggle(!titleEmpty);
|
||||
addCopyToCodeBlocks(imgContainer);
|
||||
callGenericPopup(imgContainer, POPUP_TYPE.TEXT, '', { wide: true, large: true });
|
||||
|
||||
const popup = new Popup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true, transparent: true });
|
||||
|
||||
popup.dlg.style.width = 'unset';
|
||||
popup.dlg.style.height = 'unset';
|
||||
|
||||
img.addEventListener('click', () => {
|
||||
const shouldZoom = !img.classList.contains('zoomed');
|
||||
img.classList.toggle('zoomed', shouldZoom);
|
||||
});
|
||||
|
||||
await popup.show();
|
||||
}
|
||||
|
||||
async function deleteMessageImage() {
|
||||
@@ -584,6 +616,8 @@ async function deleteMessageImage() {
|
||||
const message = chat[mesId];
|
||||
delete message.extra.image;
|
||||
delete message.extra.inline_image;
|
||||
delete message.extra.title;
|
||||
delete message.extra.append_title;
|
||||
mesBlock.find('.mes_img_container').removeClass('img_extra');
|
||||
mesBlock.find('.mes_img').attr('src', '');
|
||||
await saveChatConditional();
|
||||
@@ -751,7 +785,7 @@ async function moveAttachment(attachment, source, callback) {
|
||||
* @param {boolean} [confirm=true] If true, show a confirmation dialog
|
||||
* @returns {Promise<void>} A promise that resolves when the attachment is deleted.
|
||||
*/
|
||||
async function deleteAttachment(attachment, source, callback, confirm = true) {
|
||||
export async function deleteAttachment(attachment, source, callback, confirm = true) {
|
||||
if (confirm) {
|
||||
const result = await callGenericPopup('Are you sure you want to delete this attachment?', POPUP_TYPE.CONFIRM);
|
||||
|
||||
@@ -838,6 +872,12 @@ async function openAttachmentManager() {
|
||||
[ATTACHMENT_SOURCE.CHAT]: '.chatAttachmentsList',
|
||||
};
|
||||
|
||||
const selected = template
|
||||
.find(sources[source])
|
||||
.find('.attachmentListItemCheckbox:checked')
|
||||
.map((_, el) => $(el).closest('.attachmentListItem').attr('data-attachment-url'))
|
||||
.get();
|
||||
|
||||
template.find(sources[source]).empty();
|
||||
|
||||
// Sort attachments by sortField and sortOrder, and apply filter
|
||||
@@ -847,6 +887,8 @@ async function openAttachmentManager() {
|
||||
const isDisabled = isAttachmentDisabled(attachment);
|
||||
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
|
||||
attachmentTemplate.toggleClass('disabled', isDisabled);
|
||||
attachmentTemplate.attr('data-attachment-url', attachment.url);
|
||||
attachmentTemplate.attr('data-attachment-source', source);
|
||||
attachmentTemplate.find('.attachmentFileIcon').attr('title', attachment.url);
|
||||
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
|
||||
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
|
||||
@@ -859,6 +901,10 @@ async function openAttachmentManager() {
|
||||
attachmentTemplate.find('.enableAttachmentButton').toggle(isDisabled).on('click', () => enableAttachment(attachment, renderAttachments));
|
||||
attachmentTemplate.find('.disableAttachmentButton').toggle(!isDisabled).on('click', () => disableAttachment(attachment, renderAttachments));
|
||||
template.find(sources[source]).append(attachmentTemplate);
|
||||
|
||||
if (selected.includes(attachment.url)) {
|
||||
attachmentTemplate.find('.attachmentListItemCheckbox').prop('checked', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,49 +1008,24 @@ async function openAttachmentManager() {
|
||||
template.find('.chatAttachmentsName').text(chatName);
|
||||
}
|
||||
|
||||
function addDragAndDrop() {
|
||||
$(document.body).on('dragover', '.dialogue_popup', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.target).closest('.dialogue_popup').addClass('dragover');
|
||||
const dragDropHandler = new DragAndDropHandler('.popup', async (files, event) => {
|
||||
let selectedTarget = ATTACHMENT_SOURCE.GLOBAL;
|
||||
const targets = getAvailableTargets();
|
||||
|
||||
const targetSelectTemplate = $(await renderExtensionTemplateAsync('attachments', 'files-dropped', { count: files.length, targets: targets }));
|
||||
targetSelectTemplate.find('.droppedFilesTarget').on('input', function () {
|
||||
selectedTarget = String($(this).val());
|
||||
});
|
||||
|
||||
$(document.body).on('dragleave', '.dialogue_popup', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.target).closest('.dialogue_popup').removeClass('dragover');
|
||||
});
|
||||
|
||||
$(document.body).on('drop', '.dialogue_popup', async (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$(event.target).closest('.dialogue_popup').removeClass('dragover');
|
||||
|
||||
const files = Array.from(event.originalEvent.dataTransfer.files);
|
||||
let selectedTarget = ATTACHMENT_SOURCE.GLOBAL;
|
||||
const targets = getAvailableTargets();
|
||||
|
||||
const targetSelectTemplate = $(await renderExtensionTemplateAsync('attachments', 'files-dropped', { count: files.length, targets: targets }));
|
||||
targetSelectTemplate.find('.droppedFilesTarget').on('input', function () {
|
||||
selectedTarget = String($(this).val());
|
||||
});
|
||||
const result = await callGenericPopup(targetSelectTemplate, POPUP_TYPE.CONFIRM, '', { wide: false, large: false, okButton: 'Upload', cancelButton: 'Cancel' });
|
||||
if (result !== POPUP_RESULT.AFFIRMATIVE) {
|
||||
console.log('File upload cancelled');
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
await uploadFileAttachmentToServer(file, selectedTarget);
|
||||
}
|
||||
renderAttachments();
|
||||
});
|
||||
}
|
||||
|
||||
function removeDragAndDrop() {
|
||||
$(document.body).off('dragover', '.shadow_popup');
|
||||
$(document.body).off('dragleave', '.shadow_popup');
|
||||
$(document.body).off('drop', '.shadow_popup');
|
||||
}
|
||||
const result = await callGenericPopup(targetSelectTemplate, POPUP_TYPE.CONFIRM, '', { wide: false, large: false, okButton: 'Upload', cancelButton: 'Cancel' });
|
||||
if (result !== POPUP_RESULT.AFFIRMATIVE) {
|
||||
console.log('File upload cancelled');
|
||||
return;
|
||||
}
|
||||
for (const file of files) {
|
||||
await uploadFileAttachmentToServer(file, selectedTarget);
|
||||
}
|
||||
renderAttachments();
|
||||
});
|
||||
|
||||
let sortField = localStorage.getItem('DataBank_sortField') || 'created';
|
||||
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
|
||||
@@ -1027,15 +1048,83 @@ async function openAttachmentManager() {
|
||||
localStorage.setItem('DataBank_sortOrder', sortOrder);
|
||||
renderAttachments();
|
||||
});
|
||||
function handleBulkAction(action) {
|
||||
return async () => {
|
||||
const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked');
|
||||
|
||||
if (selectedAttachments.length === 0) {
|
||||
toastr.info('No attachments selected.', 'Data Bank');
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.confirmMessage) {
|
||||
const confirm = await callGenericPopup(action.confirmMessage, POPUP_TYPE.CONFIRM);
|
||||
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const includeDisabled = true;
|
||||
const attachments = getDataBankAttachments(includeDisabled);
|
||||
selectedAttachments.forEach(async (checkbox) => {
|
||||
const listItem = checkbox.closest('.attachmentListItem');
|
||||
if (!(listItem instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const url = listItem.dataset.attachmentUrl;
|
||||
const source = listItem.dataset.attachmentSource;
|
||||
const attachment = attachments.find(a => a.url === url);
|
||||
if (!attachment) {
|
||||
return;
|
||||
}
|
||||
await action.perform(attachment, source);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.attachmentListItemCheckbox, .attachmentsBulkEditCheckbox').forEach(checkbox => {
|
||||
if (checkbox instanceof HTMLInputElement) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
await renderAttachments();
|
||||
};
|
||||
}
|
||||
|
||||
template.find('.bulkActionDisable').on('click', handleBulkAction({
|
||||
perform: (attachment) => disableAttachment(attachment, () => { }),
|
||||
}));
|
||||
|
||||
template.find('.bulkActionEnable').on('click', handleBulkAction({
|
||||
perform: (attachment) => enableAttachment(attachment, () => { }),
|
||||
}));
|
||||
|
||||
template.find('.bulkActionDelete').on('click', handleBulkAction({
|
||||
confirmMessage: 'Are you sure you want to delete the selected attachments?',
|
||||
perform: async (attachment, source) => await deleteAttachment(attachment, source, () => { }, false),
|
||||
}));
|
||||
|
||||
template.find('.bulkActionSelectAll').on('click', () => {
|
||||
$('.attachmentListItemCheckbox:visible').each((_, checkbox) => {
|
||||
if (checkbox instanceof HTMLInputElement) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
template.find('.bulkActionSelectNone').on('click', () => {
|
||||
$('.attachmentListItemCheckbox:visible').each((_, checkbox) => {
|
||||
if (checkbox instanceof HTMLInputElement) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const cleanupFn = await renderButtons();
|
||||
await verifyAttachments();
|
||||
await renderAttachments();
|
||||
addDragAndDrop();
|
||||
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
|
||||
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close', allowVerticalScrolling: true });
|
||||
|
||||
cleanupFn();
|
||||
removeDragAndDrop();
|
||||
dragDropHandler.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1099,7 +1188,7 @@ async function runScraper(scraperId, target, callback) {
|
||||
* Uploads a file attachment to the server.
|
||||
* @param {File} file File to upload
|
||||
* @param {string} target Target for the attachment
|
||||
* @returns
|
||||
* @returns {Promise<string>} Path to the uploaded file
|
||||
*/
|
||||
export async function uploadFileAttachmentToServer(file, target) {
|
||||
const isValid = await validateFile(file);
|
||||
@@ -1156,6 +1245,8 @@ export async function uploadFileAttachmentToServer(file, target) {
|
||||
saveSettingsDebounced();
|
||||
break;
|
||||
}
|
||||
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
function ensureAttachmentsExist() {
|
||||
@@ -1183,36 +1274,42 @@ function ensureAttachmentsExist() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all currently available attachments. Ignores disabled attachments.
|
||||
* Gets all currently available attachments. Ignores disabled attachments by default.
|
||||
* @param {boolean} [includeDisabled=false] If true, include disabled attachments
|
||||
* @returns {FileAttachment[]} List of attachments
|
||||
*/
|
||||
export function getDataBankAttachments() {
|
||||
export function getDataBankAttachments(includeDisabled = false) {
|
||||
ensureAttachmentsExist();
|
||||
const globalAttachments = extension_settings.attachments ?? [];
|
||||
const chatAttachments = chat_metadata.attachments ?? [];
|
||||
const characterAttachments = extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
||||
|
||||
return [...globalAttachments, ...chatAttachments, ...characterAttachments].filter(x => !isAttachmentDisabled(x));
|
||||
return [...globalAttachments, ...chatAttachments, ...characterAttachments].filter(x => includeDisabled || !isAttachmentDisabled(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all attachments for a specific source. Includes disabled attachments.
|
||||
* Gets all attachments for a specific source. Includes disabled attachments by default.
|
||||
* @param {string} source Attachment source
|
||||
* @param {boolean} [includeDisabled=true] If true, include disabled attachments
|
||||
* @returns {FileAttachment[]} List of attachments
|
||||
*/
|
||||
export function getDataBankAttachmentsForSource(source) {
|
||||
export function getDataBankAttachmentsForSource(source, includeDisabled = true) {
|
||||
ensureAttachmentsExist();
|
||||
|
||||
switch (source) {
|
||||
case ATTACHMENT_SOURCE.GLOBAL:
|
||||
return extension_settings.attachments ?? [];
|
||||
case ATTACHMENT_SOURCE.CHAT:
|
||||
return chat_metadata.attachments ?? [];
|
||||
case ATTACHMENT_SOURCE.CHARACTER:
|
||||
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
||||
function getBySource() {
|
||||
switch (source) {
|
||||
case ATTACHMENT_SOURCE.GLOBAL:
|
||||
return extension_settings.attachments ?? [];
|
||||
case ATTACHMENT_SOURCE.CHAT:
|
||||
return chat_metadata.attachments ?? [];
|
||||
case ATTACHMENT_SOURCE.CHARACTER:
|
||||
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
return getBySource().filter(x => includeDisabled || !isAttachmentDisabled(x));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1370,10 +1467,11 @@ jQuery(function () {
|
||||
});
|
||||
}
|
||||
|
||||
callPopup(wrapper, 'text', '', { wide: true, large: true });
|
||||
callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });
|
||||
});
|
||||
|
||||
$(document).on('click', 'body.documentstyle .mes .mes_text', function () {
|
||||
if (window.getSelection().toString()) return;
|
||||
if ($('.edit_textarea').length) return;
|
||||
$(this).closest('.mes').find('.mes_edit').trigger('click');
|
||||
});
|
||||
@@ -1407,8 +1505,28 @@ jQuery(function () {
|
||||
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
|
||||
$(document).on('click', '.mes_img_delete', deleteMessageImage);
|
||||
|
||||
$('#file_form_input').on('change', onFileAttach);
|
||||
$('#file_form_input').on('change', async () => {
|
||||
const fileInput = document.getElementById('file_form_input');
|
||||
if (!(fileInput instanceof HTMLInputElement)) return;
|
||||
const file = fileInput.files[0];
|
||||
await onFileAttach(file);
|
||||
});
|
||||
$('#file_form').on('reset', function () {
|
||||
$('#file_form').addClass('displayNone');
|
||||
});
|
||||
|
||||
document.getElementById('send_textarea').addEventListener('paste', async function (event) {
|
||||
if (event.clipboardData.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const fileInput = document.getElementById('file_form_input');
|
||||
if (!(fileInput instanceof HTMLInputElement)) return;
|
||||
|
||||
fileInput.files = event.clipboardData.files;
|
||||
await onFileAttach(fileInput.files[0]);
|
||||
});
|
||||
});
|
||||
|
107
public/scripts/dragdrop.js
vendored
Normal file
107
public/scripts/dragdrop.js
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
import { debounce_timeout } from './constants.js';
|
||||
|
||||
/**
|
||||
* Drag and drop handler
|
||||
*
|
||||
* Can be used on any element, enabling drag&drop styling and callback on drop.
|
||||
*/
|
||||
export class DragAndDropHandler {
|
||||
/** @private @type {JQuery.Selector} */ selector;
|
||||
/** @private @type {(files: File[], event:JQuery.DropEvent<HTMLElement, undefined, any, any>) => void} */ onDropCallback;
|
||||
/** @private @type {NodeJS.Timeout} Remark: Not actually NodeJS timeout, but it's close */ dragLeaveTimeout;
|
||||
|
||||
/** @private @type {boolean} */ noAnimation;
|
||||
|
||||
/**
|
||||
* Create a DragAndDropHandler
|
||||
* @param {JQuery.Selector} selector - The CSS selector for the elements to enable drag and drop
|
||||
* @param {(files: File[], event:JQuery.DropEvent<HTMLElement, undefined, any, any>) => void} onDropCallback - The callback function to handle the drop event
|
||||
*/
|
||||
constructor(selector, onDropCallback, { noAnimation = false } = {}) {
|
||||
this.selector = selector;
|
||||
this.onDropCallback = onDropCallback;
|
||||
this.dragLeaveTimeout = null;
|
||||
|
||||
this.noAnimation = noAnimation;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the drag and drop functionality
|
||||
*/
|
||||
destroy() {
|
||||
if (this.selector === 'body') {
|
||||
$(document.body).off('dragover', this.handleDragOver.bind(this));
|
||||
$(document.body).off('dragleave', this.handleDragLeave.bind(this));
|
||||
$(document.body).off('drop', this.handleDrop.bind(this));
|
||||
} else {
|
||||
$(document.body).off('dragover', this.selector, this.handleDragOver.bind(this));
|
||||
$(document.body).off('dragleave', this.selector, this.handleDragLeave.bind(this));
|
||||
$(document.body).off('drop', this.selector, this.handleDrop.bind(this));
|
||||
}
|
||||
|
||||
$(this.selector).remove('drop_target no_animation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the drag and drop functionality
|
||||
* Automatically called on construction
|
||||
* @private
|
||||
*/
|
||||
init() {
|
||||
if (this.selector === 'body') {
|
||||
$(document.body).on('dragover', this.handleDragOver.bind(this));
|
||||
$(document.body).on('dragleave', this.handleDragLeave.bind(this));
|
||||
$(document.body).on('drop', this.handleDrop.bind(this));
|
||||
} else {
|
||||
$(document.body).on('dragover', this.selector, this.handleDragOver.bind(this));
|
||||
$(document.body).on('dragleave', this.selector, this.handleDragLeave.bind(this));
|
||||
$(document.body).on('drop', this.selector, this.handleDrop.bind(this));
|
||||
}
|
||||
|
||||
$(this.selector).addClass('drop_target');
|
||||
if (this.noAnimation) $(this.selector).addClass('no_animation');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JQuery.DragOverEvent<HTMLElement, undefined, any, any>} event - The dragover event
|
||||
* @private
|
||||
*/
|
||||
handleDragOver(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearTimeout(this.dragLeaveTimeout);
|
||||
$(this.selector).addClass('drop_target dragover');
|
||||
if (this.noAnimation) $(this.selector).addClass('no_animation');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JQuery.DragLeaveEvent<HTMLElement, undefined, any, any>} event - The dragleave event
|
||||
* @private
|
||||
*/
|
||||
handleDragLeave(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Debounce the removal of the class, so it doesn't "flicker" on dragging over
|
||||
clearTimeout(this.dragLeaveTimeout);
|
||||
this.dragLeaveTimeout = setTimeout(() => {
|
||||
$(this.selector).removeClass('dragover');
|
||||
}, debounce_timeout.quick);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JQuery.DropEvent<HTMLElement, undefined, any, any>} event - The drop event
|
||||
* @private
|
||||
*/
|
||||
handleDrop(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearTimeout(this.dragLeaveTimeout);
|
||||
$(this.selector).removeClass('dragover');
|
||||
|
||||
const files = Array.from(event.originalEvent.dataTransfer.files);
|
||||
this.onDropCallback(files, event);
|
||||
}
|
||||
}
|
162
public/scripts/dynamic-styles.js
Normal file
162
public/scripts/dynamic-styles.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/** @type {CSSStyleSheet} */
|
||||
let dynamicStyleSheet = null;
|
||||
/** @type {CSSStyleSheet} */
|
||||
let dynamicExtensionStyleSheet = null;
|
||||
|
||||
/**
|
||||
* An observer that will check if any new stylesheets are added to the head
|
||||
* @type {MutationObserver}
|
||||
*/
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.type !== 'childList') return;
|
||||
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node instanceof HTMLLinkElement && node.tagName === 'LINK' && node.rel === 'stylesheet') {
|
||||
node.addEventListener('load', () => {
|
||||
try {
|
||||
applyDynamicFocusStyles(node.sheet);
|
||||
} catch (e) {
|
||||
console.warn('Failed to process new stylesheet:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Generates dynamic focus styles based on the given stylesheet, taking its hover styles as reference
|
||||
*
|
||||
* @param {CSSStyleSheet} styleSheet - The stylesheet to process
|
||||
* @param {object} [options] - Optional configuration options
|
||||
* @param {boolean} [options.fromExtension=false] - Indicates if the styles are from an extension
|
||||
*/
|
||||
function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) {
|
||||
/** @type {{baseSelector: string, rule: CSSStyleRule}[]} */
|
||||
const hoverRules = [];
|
||||
/** @type {Set<string>} */
|
||||
const focusRules = new Set();
|
||||
|
||||
const PLACEHOLDER = ':__PLACEHOLDER__';
|
||||
|
||||
/**
|
||||
* Processes the CSS rules and separates selectors for hover and focus
|
||||
* @param {CSSRuleList} rules - The CSS rules to process
|
||||
*/
|
||||
function processRules(rules) {
|
||||
Array.from(rules).forEach(rule => {
|
||||
if (rule instanceof CSSImportRule) {
|
||||
// Make sure that @import rules are processed recursively
|
||||
processImportedStylesheet(rule.styleSheet);
|
||||
} else if (rule instanceof CSSStyleRule) {
|
||||
// Separate multiple selectors on a rule
|
||||
const selectors = rule.selectorText.split(',').map(s => s.trim());
|
||||
|
||||
// We collect all hover and focus rules to be able to later decide which hover rules don't have a matching focus rule
|
||||
selectors.forEach(selector => {
|
||||
const isHover = selector.includes(':hover'), isFocus = selector.includes(':focus');
|
||||
if (isHover && isFocus) {
|
||||
// We currently do nothing here. Rules containing both hover and focus are very specific and should never be automatically touched
|
||||
}
|
||||
else if (isHover) {
|
||||
const baseSelector = selector.replace(':hover', PLACEHOLDER).trim();
|
||||
hoverRules.push({ baseSelector, rule });
|
||||
} else if (isFocus) {
|
||||
// We need to make sure that we remember all existing :focus, :focus-within and :focus-visible rules
|
||||
const baseSelector = selector.replace(':focus-within', PLACEHOLDER).replace(':focus-visible', PLACEHOLDER).replace(':focus', PLACEHOLDER).trim();
|
||||
focusRules.add(baseSelector);
|
||||
}
|
||||
});
|
||||
} else if (rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule) {
|
||||
// Recursively process nested rules
|
||||
processRules(rule.cssRules);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the CSS rules of an imported stylesheet recursively
|
||||
* @param {CSSStyleSheet} sheet - The imported stylesheet to process
|
||||
*/
|
||||
function processImportedStylesheet(sheet) {
|
||||
if (sheet && sheet.cssRules) {
|
||||
processRules(sheet.cssRules);
|
||||
}
|
||||
}
|
||||
|
||||
processRules(styleSheet.cssRules);
|
||||
|
||||
/** @type {CSSStyleSheet} */
|
||||
let targetStyleSheet = null;
|
||||
|
||||
// Now finally create the dynamic focus rules
|
||||
hoverRules.forEach(({ baseSelector, rule }) => {
|
||||
if (!focusRules.has(baseSelector)) {
|
||||
// Only initialize the dynamic stylesheet if needed
|
||||
targetStyleSheet ??= getDynamicStyleSheet({ fromExtension });
|
||||
|
||||
// The closest keyboard-equivalent to :hover styling is utilizing the :focus-visible rule from modern browsers.
|
||||
// It let's the browser decide whether a focus highlighting is expected and makes sense.
|
||||
// So we take all :hover rules that don't have a manually defined focus rule yet, and create their
|
||||
// :focus-visible counterpart, which will make the styling work the same for keyboard and mouse.
|
||||
// If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside,
|
||||
// it should be manually defined in CSS.
|
||||
const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible');
|
||||
const focusRule = `${focusSelector} { ${rule.style.cssText} }`;
|
||||
|
||||
try {
|
||||
targetStyleSheet.insertRule(focusRule, targetStyleSheet.cssRules.length);
|
||||
} catch (e) {
|
||||
console.warn('Failed to insert focus rule:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stylesheet that should be used for dynamic rules
|
||||
*
|
||||
* @param {object} options - The options object
|
||||
* @param {boolean} [options.fromExtension=false] - Indicates whether the rules are coming from extensions
|
||||
* @return {CSSStyleSheet} The dynamic stylesheet
|
||||
*/
|
||||
function getDynamicStyleSheet({ fromExtension = false } = {}) {
|
||||
if (fromExtension) {
|
||||
if (!dynamicExtensionStyleSheet) {
|
||||
const styleSheetElement = document.createElement('style');
|
||||
styleSheetElement.setAttribute('id', 'dynamic-extension-styles');
|
||||
document.head.appendChild(styleSheetElement);
|
||||
dynamicExtensionStyleSheet = styleSheetElement.sheet;
|
||||
}
|
||||
return dynamicExtensionStyleSheet;
|
||||
} else {
|
||||
if (!dynamicStyleSheet) {
|
||||
const styleSheetElement = document.createElement('style');
|
||||
styleSheetElement.setAttribute('id', 'dynamic-styles');
|
||||
document.head.appendChild(styleSheetElement);
|
||||
dynamicStyleSheet = styleSheetElement.sheet;
|
||||
}
|
||||
return dynamicStyleSheet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes dynamic styles for ST
|
||||
*/
|
||||
export function initDynamicStyles() {
|
||||
// Start observing the head for any new added stylesheets
|
||||
observer.observe(document.head, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Process all stylesheets on initial load
|
||||
Array.from(document.styleSheets).forEach(sheet => {
|
||||
try {
|
||||
applyDynamicFocusStyles(sheet, { fromExtension: sheet.href.toLowerCase().includes('scripts/extensions') });
|
||||
} catch (e) {
|
||||
console.warn('Failed to process stylesheet on initial load:', e);
|
||||
}
|
||||
});
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import { callPopup, eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
|
||||
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
||||
import { isSubsetOf, setValueByPath } from './utils.js';
|
||||
export {
|
||||
@@ -122,7 +123,9 @@ const extension_settings = {
|
||||
custom: [],
|
||||
},
|
||||
dice: {},
|
||||
/** @type {import('./char-data.js').RegexScriptData[]} */
|
||||
regex: [],
|
||||
character_allowed_regex: [],
|
||||
tts: {},
|
||||
sd: {
|
||||
prompts: {},
|
||||
@@ -345,14 +348,12 @@ function autoConnectInputHandler() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function addExtensionsButtonAndMenu() {
|
||||
const buttonHTML =
|
||||
'<div id="extensionsMenuButton" style="display: none;" class="fa-solid fa-magic-wand-sparkles" title="Extras Extensions" /></div>';
|
||||
const extensionsMenuHTML = '<div id="extensionsMenu" class="options-content" style="display: none;"></div>';
|
||||
async function addExtensionsButtonAndMenu() {
|
||||
const buttonHTML = await renderTemplateAsync('wandButton');
|
||||
const extensionsMenuHTML = await renderTemplateAsync('wandMenu');
|
||||
|
||||
$(document.body).append(extensionsMenuHTML);
|
||||
|
||||
$('#leftSendForm').prepend(buttonHTML);
|
||||
$('#leftSendForm').append(buttonHTML);
|
||||
|
||||
const button = $('#extensionsMenuButton');
|
||||
const dropdown = $('#extensionsMenu');
|
||||
@@ -503,7 +504,7 @@ function addExtensionScript(name, manifest) {
|
||||
* @param {boolean} isDisabled - Whether the extension is disabled or not.
|
||||
* @param {boolean} isExternal - Whether the extension is external or not.
|
||||
* @param {string} checkboxClass - The class for the checkbox HTML element.
|
||||
* @return {string} - The HTML string that represents the extension.
|
||||
* @return {Promise<string>} - The HTML string that represents the extension.
|
||||
*/
|
||||
async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {
|
||||
const displayName = manifest.display_name;
|
||||
@@ -555,8 +556,10 @@ async function generateExtensionHtml(name, manifest, isActive, isDisabled, isExt
|
||||
} else if (!isDisabled) { // Neither active nor disabled
|
||||
const requirements = new Set(manifest.requires);
|
||||
modules.forEach(x => requirements.delete(x));
|
||||
const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
|
||||
extensionHtml += `<p>Missing modules: <span class="failure">${requirementsString}</span></p>`;
|
||||
if (requirements.size > 0) {
|
||||
const requirementsString = DOMPurify.sanitize([...requirements].join(', '));
|
||||
extensionHtml += `<p>Missing modules: <span class="failure">${requirementsString}</span></p>`;
|
||||
}
|
||||
}
|
||||
|
||||
return extensionHtml;
|
||||
@@ -631,7 +634,18 @@ async function showExtensionsDetails() {
|
||||
${htmlDefault}
|
||||
${htmlExternal}
|
||||
`;
|
||||
popupPromise = callPopup(`<div class="extensions_info">${html}</div>`, 'text', '', { okButton: 'Close', wide: true, large: true });
|
||||
/** @type {import('./popup.js').CustomPopupButton} */
|
||||
const updateAllButton = {
|
||||
text: 'Update all',
|
||||
appendAtEnd: true,
|
||||
action: async () => {
|
||||
requiresReload = true;
|
||||
await autoUpdateExtensions(true);
|
||||
popup.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||
},
|
||||
};
|
||||
const popup = new Popup(`<div class="extensions_info">${html}</div>`, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, customButtons: [updateAllButton], allowVerticalScrolling: true });
|
||||
popupPromise = popup.show();
|
||||
} catch (error) {
|
||||
toastr.error('Error loading extensions. See browser console for details.');
|
||||
console.error(error);
|
||||
@@ -700,8 +714,8 @@ async function updateExtension(extensionName, quiet) {
|
||||
async function onDeleteClick() {
|
||||
const extensionName = $(this).data('name');
|
||||
// use callPopup to create a popup for the user to confirm before delete
|
||||
const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension');
|
||||
if (confirmation) {
|
||||
const confirmation = await callGenericPopup(`Are you sure you want to delete ${extensionName}?`, POPUP_TYPE.CONFIRM, '', {});
|
||||
if (confirmation === POPUP_RESULT.AFFIRMATIVE) {
|
||||
await deleteExtension(extensionName);
|
||||
}
|
||||
}
|
||||
@@ -797,7 +811,7 @@ async function loadExtensionSettings(settings, versionChanged) {
|
||||
manifests = await getManifests(extensionNames);
|
||||
|
||||
if (versionChanged) {
|
||||
await autoUpdateExtensions();
|
||||
await autoUpdateExtensions(false);
|
||||
}
|
||||
|
||||
await activateExtensions();
|
||||
@@ -860,7 +874,12 @@ async function checkForExtensionUpdates(force) {
|
||||
}
|
||||
}
|
||||
|
||||
async function autoUpdateExtensions() {
|
||||
/**
|
||||
* Updates all 3rd-party extensions that have auto-update enabled.
|
||||
* @param {boolean} forceAll Force update all even if not auto-updating
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function autoUpdateExtensions(forceAll) {
|
||||
if (!Object.values(manifests).some(x => x.auto_update)) {
|
||||
return;
|
||||
}
|
||||
@@ -868,7 +887,7 @@ async function autoUpdateExtensions() {
|
||||
const banner = toastr.info('Auto-updating extensions. This may take several minutes.', 'Please wait...', { timeOut: 10000, extendedTimeOut: 10000 });
|
||||
const promises = [];
|
||||
for (const [id, manifest] of Object.entries(manifests)) {
|
||||
if (manifest.auto_update && id.startsWith('third-party')) {
|
||||
if ((forceAll || manifest.auto_update) && id.startsWith('third-party')) {
|
||||
console.debug(`Auto-updating 3rd-party extension: ${manifest.display_name} (${id})`);
|
||||
promises.push(updateExtension(id.replace('third-party', ''), true));
|
||||
}
|
||||
@@ -959,8 +978,8 @@ export async function writeExtensionField(characterId, key, value) {
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
addExtensionsButtonAndMenu();
|
||||
jQuery(async function () {
|
||||
await addExtensionsButtonAndMenu();
|
||||
$('#extensionsMenuButton').css('display', 'flex');
|
||||
|
||||
$('#extensions_connect').on('click', connectClickHandler);
|
||||
@@ -988,14 +1007,14 @@ jQuery(function () {
|
||||
<p><b>Disclaimer:</b> Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.</p>
|
||||
<br>
|
||||
<p>Example: <tt> https://github.com/author/extension-name </tt></p>`;
|
||||
const input = await callPopup(html, 'input');
|
||||
const input = await callGenericPopup(html, POPUP_TYPE.INPUT, '');
|
||||
|
||||
if (!input) {
|
||||
console.debug('Extension install cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = input.trim();
|
||||
const url = String(input).trim();
|
||||
await installExtension(url);
|
||||
});
|
||||
});
|
||||
|
@@ -440,7 +440,7 @@ jQuery(async () => {
|
||||
});
|
||||
|
||||
windowHtml.find('#assets_filters').hide();
|
||||
$('#extensions_settings').append(windowHtml);
|
||||
$('#assets_container').append(windowHtml);
|
||||
|
||||
eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {
|
||||
openCharacterBrowser(forceDefault);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<div class="flex-container flexFlowColumn padding5">
|
||||
<div class="contestWinners flex-container flexFlowColumn">
|
||||
<h3 class="flex-container alignItemsBaseline justifyCenter" title="These characters are the winners of character design contests and have outstandable quality.">
|
||||
<h3 class="flex-container alignItemsBaseline justifyCenter" data-i18n="[title]These characters are the winners of character design contests and have outstandable quality." title="These characters are the winners of character design contests and have outstandable quality.">
|
||||
<span data-i18n="Contest Winners">Contest Winners</span>
|
||||
<i class="fa-solid fa-star"></i>
|
||||
</h3>
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<hr>
|
||||
<div class="featuredCharacters flex-container flexFlowColumn">
|
||||
<h3 class="flex-container alignItemsBaseline justifyCenter" title="These characters are the finalists of character design contests and have remarkable quality.">
|
||||
<h3 class="flex-container alignItemsBaseline justifyCenter" data-i18n="[title]These characters are the finalists of character design contests and have remarkable quality." title="These characters are the finalists of character design contests and have remarkable quality.">
|
||||
<span data-i18n="Featured Characters">Featured Characters</span>
|
||||
<i class="fa-solid fa-thumbs-up"></i>
|
||||
</h3>
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<div id="assets_ui">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Download Extensions & Assets</b>
|
||||
<b data-i18n="Download Extensions & Assets">Download Extensions & Assets</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="assets-json-url-field">Assets URL</label>
|
||||
<label for="assets-json-url-field" data-i18n="Assets URL">Assets URL</label>
|
||||
<div class="assets-connect-div">
|
||||
<input id="assets-json-url-field" class="text_pole widthUnset flex1">
|
||||
<i id="assets-connect-button" class="menu_button fa-solid fa-plug-circle-exclamation fa-xl redOverlayGlow"></i>
|
||||
@@ -16,7 +16,7 @@
|
||||
<input id="assets_search" class="text_pole flex1" placeholder="Search" type="search">
|
||||
<div id="assets-characters-button" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-image-portrait"></i>
|
||||
Characters
|
||||
<span data-i18n="Characters">Characters</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer-content" id="assets_menu">
|
||||
|
4
public/scripts/extensions/attachments/attach-button.html
Normal file
4
public/scripts/extensions/attachments/attach-button.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">
|
||||
<div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
|
||||
<span data-i18n="Attach a File">Attach a File</span>
|
||||
</div>
|
@@ -1,15 +1,360 @@
|
||||
import { renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { deleteAttachment, getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment, uploadFileAttachmentToServer } from '../../chats.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
|
||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from '../../slash-commands/SlashCommandExecutor.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
|
||||
jQuery(async () => {
|
||||
const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {});
|
||||
$('#extensionsMenu').prepend(buttons);
|
||||
/**
|
||||
* List of attachment sources
|
||||
* @type {string[]}
|
||||
*/
|
||||
const TYPES = ['global', 'character', 'chat'];
|
||||
const FIELDS = ['name', 'url'];
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'db',
|
||||
callback: () => document.getElementById('manageAttachments')?.click(),
|
||||
/**
|
||||
* Get attachments from the data bank. Includes disabled attachments.
|
||||
* @param {string} [source] Source for the attachments
|
||||
* @returns {import('../../chats').FileAttachment[]} List of attachments
|
||||
*/
|
||||
function getAttachments(source) {
|
||||
if (!source || !TYPES.includes(source)) {
|
||||
return getDataBankAttachments(true);
|
||||
}
|
||||
|
||||
return getDataBankAttachmentsForSource(source, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment by a single name or URL.
|
||||
* @param {import('../../chats').FileAttachment[]} attachments List of attachments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {import('../../chats').FileAttachment} Attachment
|
||||
*/
|
||||
function getAttachmentByField(attachments, value) {
|
||||
const match = (a) => String(a).trim().toLowerCase() === String(value).trim().toLowerCase();
|
||||
const fullMatchByURL = attachments.find(it => match(it.url));
|
||||
const fullMatchByName = attachments.find(it => match(it.name));
|
||||
return fullMatchByURL || fullMatchByName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment by multiple fields.
|
||||
* @param {import('../../chats').FileAttachment[]} attachments List of attachments
|
||||
* @param {string[]} values Name and URL of the attachment to search for
|
||||
* @returns
|
||||
*/
|
||||
function getAttachmentByFields(attachments, values) {
|
||||
for (const value of values) {
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
if (attachment) {
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for listing attachments in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @returns {string} JSON string of the list of attachments
|
||||
*/
|
||||
function listDataBankAttachments(args) {
|
||||
const attachments = getAttachments(args?.source);
|
||||
const field = args?.field;
|
||||
return JSON.stringify(attachments.map(a => FIELDS.includes(field) ? a[field] : a.url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for getting text from an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Content of the attachment
|
||||
*/
|
||||
async function getDataBankText(args, value) {
|
||||
if (!value) {
|
||||
toastr.warning('No attachment name or URL provided.');
|
||||
return;
|
||||
}
|
||||
|
||||
const attachments = getAttachments(args?.source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await getFileAttachment(attachment.url);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for adding an attachment to the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Content of the attachment
|
||||
* @returns {Promise<string>} URL of the attachment
|
||||
*/
|
||||
async function uploadDataBankAttachment(args, value) {
|
||||
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
||||
const name = args?.name || new Date().toLocaleString();
|
||||
const file = new File([value], name, { type: 'text/plain' });
|
||||
const url = await uploadFileAttachmentToServer(file, source);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for updating an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Content of the attachment
|
||||
* @returns {Promise<string>} URL of the attachment
|
||||
*/
|
||||
async function updateDataBankAttachment(args, value) {
|
||||
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
||||
const attachments = getAttachments(source);
|
||||
const attachment = getAttachmentByFields(attachments, [args?.url, args?.name]);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
await deleteAttachment(attachment, source, () => { }, false);
|
||||
const file = new File([value], attachment.name, { type: 'text/plain' });
|
||||
const url = await uploadFileAttachmentToServer(file, source);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for deleting an attachment from the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Empty string
|
||||
*/
|
||||
async function deleteDataBankAttachment(args, value) {
|
||||
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
||||
const attachments = getAttachments(source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
await deleteAttachment(attachment, source, () => { }, false);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for disabling an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Empty string
|
||||
*/
|
||||
async function disableDataBankAttachment(args, value) {
|
||||
const attachments = getAttachments(args?.source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (extension_settings.disabled_attachments.includes(attachment.url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
extension_settings.disabled_attachments.push(attachment.url);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for enabling an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Empty string
|
||||
*/
|
||||
async function enableDataBankAttachment(args, value) {
|
||||
const attachments = getAttachments(args?.source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
const index = extension_settings.disabled_attachments.indexOf(attachment.url);
|
||||
if (index === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
extension_settings.disabled_attachments.splice(index, 1);
|
||||
return '';
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});
|
||||
const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});
|
||||
$('#data_bank_wand_container').append(manageButton);
|
||||
$('#attach_file_wand_container').append(attachButton);
|
||||
|
||||
/** A collection of local enum providers for this context of data bank */
|
||||
const localEnumProviders = {
|
||||
/**
|
||||
* All attachments in the data bank based on the source argument. If not provided, defaults to 'chat'.
|
||||
* @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url'
|
||||
* @param {'chat' | 'character' | 'global' | ''} fallbackSource - The source to use if the source argument is not provided. Empty string to use all sources.
|
||||
* */
|
||||
attachments: (returnField = 'name', fallbackSource = 'chat') => (/** @type {SlashCommandExecutor} */ executor) => {
|
||||
const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? fallbackSource;
|
||||
if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures');
|
||||
const attachments = getAttachments(source);
|
||||
|
||||
return attachments.map(attachment => new SlashCommandEnumValue(
|
||||
returnField === 'name' ? attachment.name : attachment.url,
|
||||
`${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`,
|
||||
enumTypes.enum, enumIcons.file));
|
||||
},
|
||||
};
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db',
|
||||
callback: () => {
|
||||
document.getElementById('manageAttachments')?.click();
|
||||
return '';
|
||||
},
|
||||
aliases: ['databank', 'data-bank'],
|
||||
helpString: 'Open the data bank',
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-list',
|
||||
callback: listDataBankAttachments,
|
||||
aliases: ['databank-list', 'data-bank-list'],
|
||||
helpString: 'List attachments in the Data Bank as a JSON-serialized array. Optionally, provide the source of the attachments and the field to list by.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachments.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
new SlashCommandNamedArgument('field', 'The field to list by.', ARGUMENT_TYPE.STRING, false, false, 'url', FIELDS),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.LIST,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-get',
|
||||
callback: getDataBankText,
|
||||
aliases: ['databank-get', 'data-bank-get'],
|
||||
helpString: 'Get attachment text from the Data Bank. Either provide the name or URL of the attachment. Optionally, provide the source of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
acceptsMultiple: false,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-add',
|
||||
callback: uploadDataBankAttachment,
|
||||
aliases: ['databank-add', 'data-bank-add'],
|
||||
helpString: 'Add an attachment to the Data Bank. If name is not provided, it will be generated automatically. Returns the URL of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
new SlashCommandNamedArgument('name', 'The name of the attachment.', ARGUMENT_TYPE.STRING, false, false),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-update',
|
||||
callback: updateDataBankAttachment,
|
||||
aliases: ['databank-update', 'data-bank-update'],
|
||||
helpString: 'Update an attachment in the Data Bank, preserving its name. Returns a new URL of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'The name of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.attachments('name'),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'url',
|
||||
description: 'The URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.attachments('url'),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-disable',
|
||||
callback: disableDataBankAttachment,
|
||||
aliases: ['databank-disable', 'data-bank-disable'],
|
||||
helpString: 'Disable an attachment in the Data Bank by its name or URL. Optionally, provide the source of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-enable',
|
||||
callback: enableDataBankAttachment,
|
||||
aliases: ['databank-enable', 'data-bank-enable'],
|
||||
helpString: 'Enable an attachment in the Data Bank by its name or URL. Optionally, provide the source of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-delete',
|
||||
callback: deleteDataBankAttachment,
|
||||
aliases: ['databank-delete', 'data-bank-delete'],
|
||||
helpString: 'Delete an attachment from the Data Bank.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments(),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
@@ -1,7 +1,4 @@
|
||||
<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">
|
||||
<div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
|
||||
<span data-i18n="Attach a File">Attach a File</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="manageAttachments" class="list-group-item flex-container flexGap5" title="View global, character, or data files.">
|
||||
<div class="fa-fw fa-solid fa-book-open-reader extensionsMenuExtensionButton"></div>
|
@@ -1,4 +1,4 @@
|
||||
<div class="wide100p padding5">
|
||||
<div class="wide100p padding5 dataBankAttachments">
|
||||
<h2 class="marginBot5">
|
||||
<span data-i18n="Data Bank">
|
||||
Data Bank
|
||||
@@ -37,7 +37,35 @@
|
||||
Size (Largest First)
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label class="margin0 menu_button menu_button_icon attachmentsBulkEditButton">
|
||||
<i class="fa-solid fa-edit"></i>
|
||||
<span data-i18n="Bulk Edit">Bulk Edit</span>
|
||||
<input type="checkbox" class="displayNone attachmentsBulkEditCheckbox" hidden>
|
||||
</label>
|
||||
</div>
|
||||
<div class="attachmentBulkActionsContainer flex-container marginTopBot5 alignItemsBaseline">
|
||||
<div class="flex-container">
|
||||
<div class="menu_button menu_button_icon bulkActionSelectAll" title="Select all *visible* attachments">
|
||||
<i class="fa-solid fa-check-square"></i>
|
||||
<span data-i18n="Select All">Select All</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionSelectNone" title="Deselect all *visible* attachments">
|
||||
<i class="fa-solid fa-square"></i>
|
||||
<span data-i18n="Select None">Select None</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionDisable" title="Disable selected attachments">
|
||||
<i class="fa-solid fa-comment-slash"></i>
|
||||
<span data-i18n="Disable">Disable</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionEnable" title="Enable selected attachments">
|
||||
<i class="fa-solid fa-comment"></i>
|
||||
<span data-i18n="Enable">Enable</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionDelete" title="Delete selected attachments">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
<span data-i18n="Delete">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justifyLeft globalAttachmentsBlock marginBot10">
|
||||
<h3 class="globalAttachmentsTitle margin0 title_restorable">
|
||||
@@ -68,8 +96,8 @@
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<strong><small class="characterAttachmentsName"></small></strong>
|
||||
<small>
|
||||
<span data-i18n="These files are available the current character in all chats they are in.">
|
||||
These files are available the current character in all chats they are in.
|
||||
<span data-i18n="These files are available for the current character in all chats they are in.">
|
||||
These files are available for the current character in all chats they are in.
|
||||
</span>
|
||||
<span>
|
||||
<span data-i18n="Saved locally. Not exported.">
|
||||
@@ -93,8 +121,8 @@
|
||||
</h3>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<strong><small class="chatAttachmentsName"></small></strong>
|
||||
<small data-i18n="These files are available to all characters in the current chat.">
|
||||
These files are available to all characters in the current chat.
|
||||
<small data-i18n="These files are available for all characters in the current chat.">
|
||||
These files are available for all characters in the current chat.
|
||||
</small>
|
||||
</div>
|
||||
<div class="chatAttachmentsList attachmentsList"></div>
|
||||
@@ -102,6 +130,7 @@
|
||||
|
||||
<div class="attachmentListItemTemplate template_element">
|
||||
<div class="attachmentListItem flex-container alignItemsCenter flexGap10">
|
||||
<div class="attachmentListItemCheckboxContainer"><input type="checkbox" class="attachmentListItemCheckbox"></div>
|
||||
<div class="attachmentFileIcon fa-solid fa-file-alt"></div>
|
||||
<div class="attachmentListItemName flex1"></div>
|
||||
<small class="attachmentListItemCreated"></small>
|
||||
|
@@ -37,3 +37,27 @@
|
||||
.attachmentListItemCreated {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.attachmentListItemCheckboxContainer,
|
||||
.attachmentBulkActionsContainer,
|
||||
.attachmentsBulkEditCheckbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@supports selector(:has(*)) {
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentsBulkEditButton {
|
||||
color: var(--golden);
|
||||
}
|
||||
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentBulkActionsContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentListItemCheckboxContainer {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentFileIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules } from '../../extensions.js';
|
||||
import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParams } from '../../../script.js';
|
||||
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
|
||||
import { getMessageTimeStamp } from '../../RossAscends-mods.js';
|
||||
import { SECRET_KEYS, secret_state } from '../../secrets.js';
|
||||
import { getMultimodalCaption } from '../shared.js';
|
||||
@@ -8,6 +8,8 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'caption';
|
||||
@@ -82,12 +84,11 @@ async function setSpinnerIcon() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a captioned message to the chat.
|
||||
* @param {string} caption Caption text
|
||||
* @param {string} image Image URL
|
||||
* Wraps a caption with a message template.
|
||||
* @param {string} caption Raw caption
|
||||
* @returns {Promise<string>} Wrapped caption
|
||||
*/
|
||||
async function sendCaptionedMessage(caption, image) {
|
||||
const context = getContext();
|
||||
async function wrapCaptionTemplate(caption) {
|
||||
let template = extension_settings.caption.template || TEMPLATE_DEFAULT;
|
||||
|
||||
if (!/{{caption}}/i.test(template)) {
|
||||
@@ -95,11 +96,11 @@ async function sendCaptionedMessage(caption, image) {
|
||||
template += ' {{caption}}';
|
||||
}
|
||||
|
||||
let messageText = substituteParams(template).replace(/{{caption}}/i, caption);
|
||||
let messageText = substituteParamsExtended(template, { caption: caption });
|
||||
|
||||
if (extension_settings.caption.refine_mode) {
|
||||
messageText = await callPopup(
|
||||
'<h3>Review and edit the generated message:</h3>Press "Cancel" to abort the caption sending.',
|
||||
'<h3>Review and edit the generated caption:</h3>Press "Cancel" to abort the caption sending.',
|
||||
'input',
|
||||
messageText,
|
||||
{ rows: 5, okButton: 'Send' });
|
||||
@@ -109,6 +110,55 @@ async function sendCaptionedMessage(caption, image) {
|
||||
}
|
||||
}
|
||||
|
||||
return messageText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends caption to an existing message.
|
||||
* @param {Object} data Message data
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function captionExistingMessage(data) {
|
||||
if (!(data?.extra?.image)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageData = await fetch(data.extra.image);
|
||||
const blob = await imageData.blob();
|
||||
const type = imageData.headers.get('Content-Type');
|
||||
const file = new File([blob], 'image.png', { type });
|
||||
const caption = await getCaptionForFile(file, null, true);
|
||||
|
||||
if (!caption) {
|
||||
console.warn('Failed to generate a caption for the image.');
|
||||
return;
|
||||
}
|
||||
|
||||
const wrappedCaption = await wrapCaptionTemplate(caption);
|
||||
|
||||
const messageText = String(data.mes).trim();
|
||||
|
||||
if (!messageText) {
|
||||
data.extra.inline_image = false;
|
||||
data.mes = wrappedCaption;
|
||||
data.extra.title = wrappedCaption;
|
||||
}
|
||||
else {
|
||||
data.extra.inline_image = true;
|
||||
data.extra.append_title = true;
|
||||
data.extra.title = wrappedCaption;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a captioned message to the chat.
|
||||
* @param {string} caption Caption text
|
||||
* @param {string} image Image URL
|
||||
*/
|
||||
async function sendCaptionedMessage(caption, image) {
|
||||
const messageText = await wrapCaptionTemplate(caption);
|
||||
|
||||
const context = getContext();
|
||||
const message = {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
@@ -272,7 +322,7 @@ async function getCaptionForFile(file, prompt, quiet) {
|
||||
try {
|
||||
setSpinnerIcon();
|
||||
const context = getContext();
|
||||
const fileData = await getBase64Async(file);
|
||||
const fileData = await getBase64Async(await ensureImageFormatSupported(file));
|
||||
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
|
||||
const base64Data = fileData.split(',')[1];
|
||||
const { caption } = await doCaptionRequest(base64Data, fileData, prompt);
|
||||
@@ -334,7 +384,7 @@ async function captionCommandCallback(args, prompt) {
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
jQuery(async function () {
|
||||
function addSendPictureButton() {
|
||||
const sendButton = $(`
|
||||
<div id="send_picture" class="list-group-item flex-container flexGap5">
|
||||
@@ -342,18 +392,19 @@ jQuery(function () {
|
||||
Generate Caption
|
||||
</div>`);
|
||||
|
||||
$('#extensionsMenu').prepend(sendButton);
|
||||
$('#caption_wand_container').append(sendButton);
|
||||
$(sendButton).on('click', () => {
|
||||
const hasCaptionModule =
|
||||
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && secret_state[SECRET_KEYS.MAKERSUITE]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && secret_state[SECRET_KEYS.CLAUDE]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'koboldcpp' && textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'vllm' && textgenerationwebui_settings.server_urls[textgen_types.VLLM]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'custom') ||
|
||||
extension_settings.caption.source === 'local' ||
|
||||
extension_settings.caption.source === 'horde';
|
||||
@@ -377,6 +428,12 @@ jQuery(function () {
|
||||
}
|
||||
function switchMultimodalBlocks() {
|
||||
const isMultimodal = extension_settings.caption.source === 'multimodal';
|
||||
$('#caption_ollama_pull').on('click', (e) => {
|
||||
const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : '';
|
||||
e.preventDefault();
|
||||
$('#ollama_download_model').trigger('click');
|
||||
$('#dialogue_popup_input').val(presetModel);
|
||||
});
|
||||
$('#caption_multimodal_block').toggle(isMultimodal);
|
||||
$('#caption_prompt_block').toggle(isMultimodal);
|
||||
$('#caption_multimodal_api').val(extension_settings.caption.multimodal_api);
|
||||
@@ -399,102 +456,12 @@ jQuery(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
}
|
||||
function addSettings() {
|
||||
const html = `
|
||||
<div class="caption_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Image Captioning</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="caption_source">Source</label>
|
||||
<select id="caption_source" class="text_pole">
|
||||
<option value="local">Local</option>
|
||||
<option value="multimodal">Multimodal (OpenAI / Anthropic / llama / Google)</option>
|
||||
<option value="extras">Extras</option>
|
||||
<option value="horde">Horde</option>
|
||||
</select>
|
||||
<div id="caption_multimodal_block" class="flex-container wide100p">
|
||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||
<label for="caption_multimodal_api">API</label>
|
||||
<select id="caption_multimodal_api" class="flex1 text_pole">
|
||||
<option value="anthropic">Anthropic</option>
|
||||
<option value="custom">Custom (OpenAI-compatible)</option>
|
||||
<option value="google">Google MakerSuite</option>
|
||||
<option value="koboldcpp">KoboldCpp</option>
|
||||
<option value="llamacpp">llama.cpp</option>
|
||||
<option value="ollama">Ollama</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="ooba">Text Generation WebUI (oobabooga)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||
<label for="caption_multimodal_model">Model</label>
|
||||
<select id="caption_multimodal_model" class="flex1 text_pole">
|
||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option>
|
||||
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
|
||||
<option data-type="openrouter" value="fireworks/firellava-13b">fireworks/firellava-13b</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-haiku">anthropic/claude-3-haiku</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-sonnet">anthropic/claude-3-sonnet</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-opus">anthropic/claude-3-opus</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-haiku:beta">anthropic/claude-3-haiku:beta</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-sonnet:beta">anthropic/claude-3-sonnet:beta</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-opus:beta">anthropic/claude-3-opus:beta</option>
|
||||
<option data-type="openrouter" value="nousresearch/nous-hermes-2-vision-7b">nousresearch/nous-hermes-2-vision-7b</option>
|
||||
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
|
||||
<option data-type="openrouter" value="google/gemini-flash-1.5">google/gemini-flash-1.5</option>
|
||||
<option data-type="openrouter" value="liuhaotian/llava-yi-34b">liuhaotian/llava-yi-34b</option>
|
||||
<option data-type="ollama" value="ollama_current">[Currently selected]</option>
|
||||
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
|
||||
<option data-type="ollama" value="llava:latest">llava:latest</option>
|
||||
<option data-type="llamacpp" value="llamacpp_current">[Currently loaded]</option>
|
||||
<option data-type="ooba" value="ooba_current">[Currently loaded]</option>
|
||||
<option data-type="koboldcpp" value="koboldcpp_current">[Currently loaded]</option>
|
||||
<option data-type="custom" value="custom_current">[Currently selected]</option>
|
||||
</select>
|
||||
</div>
|
||||
<label data-type="openai,anthropic" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
|
||||
<input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
|
||||
Allow reverse proxy
|
||||
</label>
|
||||
<div class="flexBasis100p m-b-1">
|
||||
<small><b>Hint:</b> Set your API keys and endpoints in the 'API Connections' tab first.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div id="caption_prompt_block">
|
||||
<label for="caption_prompt">Caption Prompt</label>
|
||||
<textarea id="caption_prompt" class="text_pole" rows="1" placeholder="< Use default >">${PROMPT_DEFAULT}</textarea>
|
||||
<label class="checkbox_label margin-bot-10px" for="caption_prompt_ask" title="Ask for a custom prompt every time an image is captioned.">
|
||||
<input id="caption_prompt_ask" type="checkbox" class="checkbox">
|
||||
Ask every time
|
||||
</label>
|
||||
</div>
|
||||
<label for="caption_template">Message Template <small>(use <code>{{caption}}</code> macro)</small></label>
|
||||
<textarea id="caption_template" class="text_pole" rows="2" placeholder="< Use default >">${TEMPLATE_DEFAULT}</textarea>
|
||||
<label class="checkbox_label margin-bot-10px" for="caption_refine_mode">
|
||||
<input id="caption_refine_mode" type="checkbox" class="checkbox">
|
||||
Edit captions before saving
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$('#extensions_settings2').append(html);
|
||||
async function addSettings() {
|
||||
const html = await renderExtensionTemplateAsync('caption', 'settings', { TEMPLATE_DEFAULT, PROMPT_DEFAULT });
|
||||
$('#caption_container').append(html);
|
||||
}
|
||||
|
||||
addSettings();
|
||||
await addSettings();
|
||||
addPictureSendForm();
|
||||
addSendPictureButton();
|
||||
setImageIcon();
|
||||
@@ -504,6 +471,7 @@ jQuery(function () {
|
||||
$('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
|
||||
$('#caption_allow_reverse_proxy').prop('checked', !!(extension_settings.caption.allow_reverse_proxy));
|
||||
$('#caption_prompt_ask').prop('checked', !!(extension_settings.caption.prompt_ask));
|
||||
$('#caption_auto_mode').prop('checked', !!(extension_settings.caption.auto_mode));
|
||||
$('#caption_source').val(extension_settings.caption.source);
|
||||
$('#caption_prompt').val(extension_settings.caption.prompt);
|
||||
$('#caption_template').val(extension_settings.caption.template);
|
||||
@@ -529,17 +497,50 @@ jQuery(function () {
|
||||
extension_settings.caption.prompt_ask = $('#caption_prompt_ask').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#caption_auto_mode').on('input', () => {
|
||||
extension_settings.caption.auto_mode = !!$('#caption_auto_mode').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
const onMessageEvent = async (index) => {
|
||||
if (!extension_settings.caption.auto_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getContext().chat[index];
|
||||
await captionExistingMessage(data);
|
||||
};
|
||||
|
||||
eventSource.on(event_types.MESSAGE_SENT, onMessageEvent);
|
||||
eventSource.on(event_types.MESSAGE_FILE_EMBEDDED, onMessageEvent);
|
||||
|
||||
$(document).on('click', '.mes_img_caption', async function () {
|
||||
const animationClass = 'fa-fade';
|
||||
const messageBlock = $(this).closest('.mes');
|
||||
const messageImg = messageBlock.find('.mes_img');
|
||||
if (messageImg.hasClass(animationClass)) return;
|
||||
messageImg.addClass(animationClass);
|
||||
const index = Number(messageBlock.attr('mesid'));
|
||||
const data = getContext().chat[index];
|
||||
await captionExistingMessage(data);
|
||||
appendMediaToMessage(data, messageBlock, false);
|
||||
await saveChatConditional();
|
||||
messageImg.removeClass(animationClass);
|
||||
});
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption',
|
||||
callback: captionCommandCallback,
|
||||
returns: 'caption',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'id', 'get image from a message with this ID', [ARGUMENT_TYPE.NUMBER], false, false,
|
||||
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'id',
|
||||
description: 'get image from a message with this ID',
|
||||
typeList: [ARGUMENT_TYPE.NUMBER],
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@@ -561,4 +562,6 @@ jQuery(function () {
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
|
||||
document.body.classList.add('caption');
|
||||
});
|
||||
|
106
public/scripts/extensions/caption/settings.html
Normal file
106
public/scripts/extensions/caption/settings.html
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
<div class="caption_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b data-i18n="Image Captioning">Image Captioning</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="caption_source" data-i18n="Source">Source</label>
|
||||
<select id="caption_source" class="text_pole">
|
||||
<option value="local" data-i18n="Local">Local</option>
|
||||
<option value="multimodal" data-i18n="Multimodal (OpenAI / Anthropic / llama / Google)">Multimodal (OpenAI / Anthropic / llama / Google)</option>
|
||||
<option value="extras" data-i18n="Extras">Extras</option>
|
||||
<option value="horde" data-i18n="Horde">Horde</option>
|
||||
</select>
|
||||
<div id="caption_multimodal_block" class="flex-container wide100p">
|
||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||
<label for="caption_multimodal_api" data-i18n="API">API</label>
|
||||
<select id="caption_multimodal_api" class="flex1 text_pole">
|
||||
<option value="anthropic">Anthropic</option>
|
||||
<option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
|
||||
<option value="google">Google MakerSuite</option>
|
||||
<option value="koboldcpp">KoboldCpp</option>
|
||||
<option value="llamacpp">llama.cpp</option>
|
||||
<option value="ollama">Ollama</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="openrouter">OpenRouter</option>
|
||||
<option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
|
||||
<option value="vllm">vLLM</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex1 flex-container flexFlowColumn flexNoGap">
|
||||
<label for="caption_multimodal_model" data-i18n="Model">Model</label>
|
||||
<select id="caption_multimodal_model" class="flex1 text_pole">
|
||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
||||
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
|
||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
||||
<option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
|
||||
<option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option>
|
||||
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
|
||||
<option data-type="openrouter" value="fireworks/firellava-13b">fireworks/firellava-13b</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3.5-sonnet">anthropic/claude-3.5-sonnet</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-haiku">anthropic/claude-3-haiku</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-sonnet">anthropic/claude-3-sonnet</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-opus">anthropic/claude-3-opus</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3.5-sonnet:beta">anthropic/claude-3.5-sonnet:beta</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-haiku:beta">anthropic/claude-3-haiku:beta</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-sonnet:beta">anthropic/claude-3-sonnet:beta</option>
|
||||
<option data-type="openrouter" value="anthropic/claude-3-opus:beta">anthropic/claude-3-opus:beta</option>
|
||||
<option data-type="openrouter" value="nousresearch/nous-hermes-2-vision-7b">nousresearch/nous-hermes-2-vision-7b</option>
|
||||
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
|
||||
<option data-type="openrouter" value="google/gemini-flash-1.5">google/gemini-flash-1.5</option>
|
||||
<option data-type="openrouter" value="liuhaotian/llava-yi-34b">liuhaotian/llava-yi-34b</option>
|
||||
<option data-type="ollama" value="ollama_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
<option data-type="ollama" value="bakllava">bakllava</option>
|
||||
<option data-type="ollama" value="llava">llava</option>
|
||||
<option data-type="ollama" value="llava-llama3">llava-llama3</option>
|
||||
<option data-type="ollama" value="llava-phi3">llava-phi3</option>
|
||||
<option data-type="ollama" value="moondream">moondream</option>
|
||||
<option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
|
||||
<option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
<option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
|
||||
</select>
|
||||
</div>
|
||||
<div data-type="ollama">
|
||||
The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.
|
||||
</div>
|
||||
<label data-type="openai,anthropic,google" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
|
||||
<input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
|
||||
<span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
|
||||
</label>
|
||||
<div class="flexBasis100p m-b-1">
|
||||
<small><b data-i18n="Hint:">Hint:</b> <span data-i18n="Set your API keys and endpoints in the 'API Connections' tab first.">Set your API keys and endpoints in the 'API Connections' tab first.</span></small>
|
||||
</div>
|
||||
</div>
|
||||
<div id="caption_prompt_block">
|
||||
<label for="caption_prompt" data-i18n="Caption Prompt">Caption Prompt</label>
|
||||
<textarea id="caption_prompt" class="text_pole" rows="1" placeholder="< Use default >">{{PROMPT_DEFAULT}}</textarea>
|
||||
<label class="checkbox_label margin-bot-10px" for="caption_prompt_ask" title="Ask for a custom prompt every time an image is captioned.">
|
||||
<input id="caption_prompt_ask" type="checkbox" class="checkbox">
|
||||
<span data-i18n="Ask every time">Ask every time</span>
|
||||
</label>
|
||||
</div>
|
||||
<label for="caption_template"><span data-i18n="Message Template">Message Template</span> <small><span data-i18n="(use _space">(use </span> <code>{{caption}}</code> <span data-i18n="macro)">macro)</span></small></label>
|
||||
<textarea id="caption_template" class="text_pole" rows="2" placeholder="< Use default >">{{TEMPLATE_DEFAULT}}</textarea>
|
||||
<label class="checkbox_label" for="caption_auto_mode">
|
||||
<input id="caption_auto_mode" type="checkbox" class="checkbox">
|
||||
<span data-i18n="Automatically caption images">Automatically caption images</span>
|
||||
<i class="fa-solid fa-info-circle" title="Automatically caption images when they are pasted into the chat or attached to messages."></i>
|
||||
</label>
|
||||
<label class="checkbox_label margin-bot-10px" for="caption_refine_mode">
|
||||
<input id="caption_refine_mode" type="checkbox" class="checkbox">
|
||||
<span data-i18n="Edit captions before saving">Edit captions before saving</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -1,14 +1,17 @@
|
||||
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, saveSettingsDebounced, substituteParams } from '../../../script.js';
|
||||
import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types } from '../../../script.js';
|
||||
import { dragElement, isMobile } from '../../RossAscends-mods.js';
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { loadMovingUIState, power_user } from '../../power-user.js';
|
||||
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence } from '../../utils.js';
|
||||
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition } from '../../utils.js';
|
||||
import { hideMutedSprites } from '../../group-chats.js';
|
||||
import { isJsonSchemaSupported } from '../../textgen-settings.js';
|
||||
import { debounce_timeout } from '../../constants.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { isFunctionCallingSupported } from '../../openai.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'expressions';
|
||||
@@ -16,6 +19,7 @@ const UPDATE_INTERVAL = 2000;
|
||||
const STREAMING_UPDATE_INTERVAL = 10000;
|
||||
const TALKINGCHECK_UPDATE_INTERVAL = 500;
|
||||
const DEFAULT_FALLBACK_EXPRESSION = 'joy';
|
||||
const FUNCTION_NAME = 'set_emotion';
|
||||
const DEFAULT_LLM_PROMPT = 'Pause your roleplay. Classify the emotion of the last message. Output just one word, e.g. "joy" or "anger". Choose only one of the following labels: {{labels}}';
|
||||
const DEFAULT_EXPRESSIONS = [
|
||||
'talkinghead',
|
||||
@@ -85,6 +89,7 @@ function getFallbackExpression() {
|
||||
*/
|
||||
function toggleTalkingHeadCommand(_) {
|
||||
setTalkingHeadState(!extension_settings.expressions.talkinghead);
|
||||
return String(extension_settings.expressions.talkinghead);
|
||||
}
|
||||
|
||||
function isVisualNovelMode() {
|
||||
@@ -912,6 +917,7 @@ async function setSpriteSetCommand(_, folder) {
|
||||
// moduleWorker();
|
||||
const vnMode = isVisualNovelMode();
|
||||
await sendExpressionCall(folder, lastExpression, true, vnMode);
|
||||
return '';
|
||||
}
|
||||
|
||||
async function classifyCommand(_, text) {
|
||||
@@ -933,7 +939,7 @@ async function classifyCommand(_, text) {
|
||||
async function setSpriteSlashCommand(_, spriteId) {
|
||||
if (!spriteId) {
|
||||
console.log('No sprite id provided');
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
spriteId = spriteId.trim().toLowerCase();
|
||||
@@ -953,7 +959,7 @@ async function setSpriteSlashCommand(_, spriteId) {
|
||||
|
||||
if (!spriteItem) {
|
||||
console.log('No sprite found for search term ' + spriteId);
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
|
||||
label = spriteItem.label;
|
||||
@@ -961,6 +967,7 @@ async function setSpriteSlashCommand(_, spriteId) {
|
||||
|
||||
const vnMode = isVisualNovelMode();
|
||||
await sendExpressionCall(spriteFolderName, label, true, vnMode);
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1001,9 +1008,12 @@ async function getLlmPrompt(labels) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isFunctionCallingSupported()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const labelsString = labels.map(x => `"${x}"`).join(', ');
|
||||
const prompt = substituteParams(String(extension_settings.expressions.llmPrompt))
|
||||
.replace(/{{labels}}/gi, labelsString);
|
||||
const prompt = substituteParamsExtended(String(extension_settings.expressions.llmPrompt), { labels: labelsString });
|
||||
return prompt;
|
||||
}
|
||||
|
||||
@@ -1014,11 +1024,16 @@ async function getLlmPrompt(labels) {
|
||||
* @returns {string} The parsed emotion or the fallback expression.
|
||||
*/
|
||||
function parseLlmResponse(emotionResponse, labels) {
|
||||
const fallbackExpression = getFallbackExpression();
|
||||
|
||||
try {
|
||||
const parsedEmotion = JSON.parse(emotionResponse);
|
||||
return parsedEmotion?.emotion ?? fallbackExpression;
|
||||
const response = parsedEmotion?.emotion?.trim()?.toLowerCase();
|
||||
|
||||
if (!response || !labels.includes(response)) {
|
||||
console.debug(`Parsed emotion response: ${response} not in labels: ${labels}`);
|
||||
throw new Error('Emotion not in labels');
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch {
|
||||
const fuse = new Fuse(labels, { includeScore: true });
|
||||
console.debug('Using fuzzy search in labels:', labels);
|
||||
@@ -1032,6 +1047,41 @@ function parseLlmResponse(emotionResponse, labels) {
|
||||
throw new Error('Could not parse emotion response ' + emotionResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the function tool for the LLM API.
|
||||
* @param {FunctionToolRegister} args Function tool register arguments.
|
||||
*/
|
||||
function onFunctionToolRegister(args) {
|
||||
if (inApiCall && extension_settings.expressions.api === EXPRESSION_API.llm && isFunctionCallingSupported()) {
|
||||
// Only trigger on quiet mode
|
||||
if (args.type !== 'quiet') {
|
||||
return;
|
||||
}
|
||||
|
||||
const emotions = DEFAULT_EXPRESSIONS.filter((e) => e != 'talkinghead');
|
||||
const jsonSchema = {
|
||||
$schema: 'http://json-schema.org/draft-04/schema#',
|
||||
type: 'object',
|
||||
properties: {
|
||||
emotion: {
|
||||
type: 'string',
|
||||
enum: emotions,
|
||||
description: `One of the following: ${JSON.stringify(emotions)}`,
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'emotion',
|
||||
],
|
||||
};
|
||||
args.registerFunctionTool(
|
||||
FUNCTION_NAME,
|
||||
substituteParams('Sets the label that best describes the current emotional state of {{char}}. Only select one of the enumerated values.'),
|
||||
jsonSchema,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function onTextGenSettingsReady(args) {
|
||||
// Only call if inside an API call
|
||||
if (inApiCall && extension_settings.expressions.api === EXPRESSION_API.llm && isJsonSchemaSupported()) {
|
||||
@@ -1087,11 +1137,27 @@ async function getExpressionLabel(text) {
|
||||
} break;
|
||||
// Using LLM
|
||||
case EXPRESSION_API.llm: {
|
||||
try {
|
||||
await waitUntilCondition(() => online_status !== 'no_connection', 3000, 250);
|
||||
} catch (error) {
|
||||
console.warn('No LLM connection. Using fallback expression', error);
|
||||
return getFallbackExpression();
|
||||
}
|
||||
|
||||
const expressionsList = await getExpressionsList();
|
||||
const prompt = await getLlmPrompt(expressionsList);
|
||||
let functionResult = null;
|
||||
eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
|
||||
eventSource.once(event_types.LLM_FUNCTION_TOOL_REGISTER, onFunctionToolRegister);
|
||||
eventSource.once(event_types.LLM_FUNCTION_TOOL_CALL, (/** @type {FunctionToolCall} */ args) => {
|
||||
if (args.name !== FUNCTION_NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
functionResult = args?.arguments;
|
||||
});
|
||||
const emotionResponse = await generateQuietPrompt(prompt, false, false);
|
||||
return parseLlmResponse(emotionResponse, expressionsList);
|
||||
return parseLlmResponse(functionResult || emotionResponse, expressionsList);
|
||||
}
|
||||
// Extras
|
||||
default: {
|
||||
@@ -1125,7 +1191,7 @@ function getLastCharacterMessage() {
|
||||
const reversedChat = context.chat.slice().reverse();
|
||||
|
||||
for (let mes of reversedChat) {
|
||||
if (mes.is_user || mes.is_system) {
|
||||
if (mes.is_user || mes.is_system || mes.extra?.type === system_message_types.NARRATOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1264,10 +1330,18 @@ async function renderFallbackExpressionPicker() {
|
||||
}
|
||||
}
|
||||
|
||||
function getCachedExpressions() {
|
||||
if (!Array.isArray(expressionsList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
|
||||
}
|
||||
|
||||
async function getExpressionsList() {
|
||||
// Return cached list if available
|
||||
if (Array.isArray(expressionsList)) {
|
||||
return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
|
||||
return getCachedExpressions();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1855,7 +1929,7 @@ function migrateSettings() {
|
||||
}
|
||||
async function addSettings() {
|
||||
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'settings');
|
||||
$('#extensions_settings').append(template);
|
||||
$('#expressions_container').append(template);
|
||||
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
|
||||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
|
||||
@@ -1971,17 +2045,31 @@ function migrateSettings() {
|
||||
});
|
||||
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
|
||||
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite',
|
||||
|
||||
const localEnumProviders = {
|
||||
expressions: () => getCachedExpressions().map(expression => {
|
||||
const isCustom = extension_settings.expressions.custom?.includes(expression);
|
||||
return new SlashCommandEnumValue(expression, null, isCustom ? enumTypes.name : enumTypes.enum, isCustom ? 'C' : 'D');
|
||||
}),
|
||||
};
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'sprite',
|
||||
aliases: ['emote'],
|
||||
callback: setSpriteSlashCommand,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'spriteId', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'spriteId',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.expressions,
|
||||
}),
|
||||
],
|
||||
helpString: 'Force sets the sprite for the current character.',
|
||||
returns: 'label',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'spriteoverride',
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'spriteoverride',
|
||||
aliases: ['costume'],
|
||||
callback: setSpriteSetCommand,
|
||||
unnamedArgumentList: [
|
||||
@@ -1991,22 +2079,29 @@ function migrateSettings() {
|
||||
],
|
||||
helpString: 'Sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lastsprite',
|
||||
callback: (_, value) => lastExpression[value.trim()] ?? '',
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'lastsprite',
|
||||
callback: (_, value) => lastExpression[String(value).trim()] ?? '',
|
||||
returns: 'sprite',
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'charName', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
],
|
||||
helpString: 'Returns the last set sprite / expression for the named character.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'th',
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'th',
|
||||
callback: toggleTalkingHeadCommand,
|
||||
aliases: ['talkinghead'],
|
||||
helpString: 'Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.',
|
||||
returns: ARGUMENT_TYPE.BOOLEAN,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'classify',
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'classify',
|
||||
callback: classifyCommand,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
|
@@ -1,65 +1,65 @@
|
||||
<div class="expression_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character Expressions</b>
|
||||
<b data-i18n="Character Expressions">Character Expressions</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label" for="expression_translate" title="Use the selected API from Chat Translation extension settings.">
|
||||
<input id="expression_translate" type="checkbox">
|
||||
<span>Translate text to English before classification</span>
|
||||
<span data-i18n="Translate text to English before classification">Translate text to English before classification</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="expressions_show_default">
|
||||
<input id="expressions_show_default" type="checkbox">
|
||||
<span>Show default images (emojis) if sprite missing</span>
|
||||
<span data-i18n="Show default images (emojis) if sprite missing">Show default images (emojis) if sprite missing</span>
|
||||
</label>
|
||||
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span>Image Type - talkinghead (extras)</span>
|
||||
<span data-i18n="Image Type - talkinghead (extras)">Image Type - talkinghead (extras)</span>
|
||||
</label>
|
||||
<div class="expression_api_block m-b-1 m-t-1">
|
||||
<label for="expression_api">Classifier API</label>
|
||||
<small>Select the API for classifying expressions.</small>
|
||||
<select id="expression_api" class="flex1 margin0" data-i18n="Expression API" placeholder="Expression API">
|
||||
<option value="0">Local</option>
|
||||
<option value="1">Extras</option>
|
||||
<option value="2">LLM</option>
|
||||
<label for="expression_api" data-i18n="Classifier API">Classifier API</label>
|
||||
<small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small>
|
||||
<select id="expression_api" class="flex1 margin0">
|
||||
<option value="0" data-i18n="Local">Local</option>
|
||||
<option value="1" data-i18n="Extras">Extras</option>
|
||||
<option value="2" data-i18n="LLM">LLM</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="expression_llm_prompt_block m-b-1 m-t-1">
|
||||
<label for="expression_llm_prompt" class="title_restorable">
|
||||
<span>LLM Prompt</span>
|
||||
<span data-i18n="LLM Prompt">LLM Prompt</span>
|
||||
<div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
|
||||
<i class="fa-solid fa-clock-rotate-left fa-sm"></i>
|
||||
</div>
|
||||
</label>
|
||||
<small>Will be used if the API doesn't support JSON schemas.</small>
|
||||
<small data-i18n="Will be used if the API doesn't support JSON schemas or function calling.">Will be used if the API doesn't support JSON schemas or function calling.</small>
|
||||
<textarea id="expression_llm_prompt" type="text" class="text_pole textarea_compact" rows="2" placeholder="Use {{labels}} special macro."></textarea>
|
||||
</div>
|
||||
<div class="expression_fallback_block m-b-1 m-t-1">
|
||||
<label for="expression_fallback">Default / Fallback Expression</label>
|
||||
<small>Set the default and fallback expression being used when no matching expression is found.</small>
|
||||
<label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>
|
||||
<small data-i18n="Set the default and fallback expression being used when no matching expression is found.">Set the default and fallback expression being used when no matching expression is found.</small>
|
||||
<select id="expression_fallback" class="flex1 margin0" data-i18n="Fallback Expression" placeholder="Fallback Expression"></select>
|
||||
</div>
|
||||
<div class="expression_custom_block m-b-1 m-t-1">
|
||||
<label for="expression_custom">Custom Expressions</label>
|
||||
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
|
||||
<label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>
|
||||
<small><span data-i18n="Can be set manually or with an _space">Can be set manually or with an </span><tt>/emote</tt><span data-i18n="space_ slash command."> slash command.</span></small>
|
||||
<div class="flex-container">
|
||||
<select id="expression_custom" class="flex1 margin0"><select>
|
||||
<i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
|
||||
<i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="no_chat_expressions">
|
||||
<div id="no_chat_expressions" data-i18n="Open a chat to see the character expressions.">
|
||||
Open a chat to see the character expressions.
|
||||
</div>
|
||||
<div id="open_chat_expressions">
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
<small data-i18n="You are in offline mode. Click on the image below to set the expression.">You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<label for="expression_override">Sprite Folder Override</label>
|
||||
<small>Use a forward slash to specify a subfolder. Example: <tt>Bob/formal</tt></small>
|
||||
<label for="expression_override" data-i18n="Sprite Folder Override">Sprite Folder Override</label>
|
||||
<small><span data-i18n="Use a forward slash to specify a subfolder. Example: _space">Use a forward slash to specify a subfolder. Example: </span><tt>Bob/formal</tt></small>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
@@ -67,17 +67,17 @@
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
<span data-i18n="Upload sprite pack (ZIP)">Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
<span data-i18n="Remove all image overrides">Remove all image overrides</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>/characters/</b> folder of your user data directory and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
<p class="hint"><b data-i18n="Hint:">Hint:</b> <i><span data-i18n="Create new folder in the _space">Create new folder in the </span><b>/characters/</b> <span data-i18n="folder of your user data directory and name it as the name of the character.">folder of your user data directory and name it as the name of the character.</span>
|
||||
<span data-i18n="Put images with expressions there. File names should follow the pattern:">Put images with expressions there. File names should follow the pattern: </span><tt data-i18n="expression_label_pattern">[expression_label].[image_format]</tt></i></p>
|
||||
<h3 id="image_list_header">
|
||||
<strong>Sprite set:</strong> <span id="image_list_header_name"></span>
|
||||
<strong data-i18n="Sprite set:">Sprite set:</strong> <span id="image_list_header_name"></span>
|
||||
</h3>
|
||||
<div id="image_list"></div>
|
||||
|
||||
|
@@ -11,6 +11,8 @@ import { dragElement } from '../../RossAscends-mods.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { DragAndDropHandler } from '../../dragdrop.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
|
||||
const extensionName = 'gallery';
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
||||
@@ -56,7 +58,8 @@ async function getGalleryItems(url) {
|
||||
* @returns {Promise<void>} - Promise representing the completion of the gallery initialization.
|
||||
*/
|
||||
async function initGallery(items, url) {
|
||||
$('#dragGallery').nanogallery2({
|
||||
const gallery = $('#dragGallery');
|
||||
gallery.nanogallery2({
|
||||
'items': items,
|
||||
thumbnailWidth: 'auto',
|
||||
thumbnailHeight: thumbnailHeight,
|
||||
@@ -80,44 +83,24 @@ async function initGallery(items, url) {
|
||||
|
||||
|
||||
eventSource.on('resizeUI', function (elmntName) {
|
||||
jQuery('#dragGallery').nanogallery2('resize');
|
||||
gallery.nanogallery2('resize');
|
||||
});
|
||||
|
||||
const dropZone = $('#dragGallery');
|
||||
//remove any existing handlers
|
||||
dropZone.off('dragover');
|
||||
dropZone.off('dragleave');
|
||||
dropZone.off('drop');
|
||||
|
||||
// Set dropzone height to be the same as the parent
|
||||
dropZone.css('height', dropZone.parent().css('height'));
|
||||
|
||||
// Initialize dropzone handlers
|
||||
dropZone.on('dragover', function (e) {
|
||||
e.stopPropagation(); // Ensure this event doesn't propagate
|
||||
e.preventDefault();
|
||||
$(this).addClass('dragging'); // Add a CSS class to change appearance during drag-over
|
||||
});
|
||||
|
||||
dropZone.on('dragleave', function (e) {
|
||||
e.stopPropagation(); // Ensure this event doesn't propagate
|
||||
$(this).removeClass('dragging');
|
||||
});
|
||||
|
||||
dropZone.on('drop', function (e) {
|
||||
e.stopPropagation(); // Ensure this event doesn't propagate
|
||||
e.preventDefault();
|
||||
$(this).removeClass('dragging');
|
||||
let file = e.originalEvent.dataTransfer.files[0];
|
||||
const dragDropHandler = new DragAndDropHandler('#dragGallery', async (files, event) => {
|
||||
let file = files[0];
|
||||
uploadFile(file, url); // Added url parameter to know where to upload
|
||||
});
|
||||
|
||||
|
||||
// Set dropzone height to be the same as the parent
|
||||
gallery.css('height', gallery.parent().css('height'));
|
||||
|
||||
//let images populate first
|
||||
await delay(100);
|
||||
//unset the height (which must be getting set by the gallery library at some point)
|
||||
$('#dragGallery').css('height', 'unset');
|
||||
gallery.css('height', 'unset');
|
||||
//force a resize to make images display correctly
|
||||
jQuery('#dragGallery').nanogallery2('resize');
|
||||
gallery.nanogallery2('resize');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,7 +402,10 @@ function viewWithDragbox(items) {
|
||||
// Registers a simple command for opening the char gallery.
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery',
|
||||
aliases: ['sg'],
|
||||
callback: showGalleryCommand,
|
||||
callback: () => {
|
||||
showCharGallery();
|
||||
return '';
|
||||
},
|
||||
helpString: 'Shows the gallery.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery',
|
||||
@@ -427,21 +413,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery
|
||||
callback: listGalleryCommand,
|
||||
returns: 'list of images',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'char', 'character name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'group', 'group name', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'char',
|
||||
description: 'character name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.characters('character'),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'group',
|
||||
description: 'group name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: commonEnumProviders.characters('group'),
|
||||
}),
|
||||
],
|
||||
helpString: 'List images in the gallery of the current char / group or a specified char / group.',
|
||||
}));
|
||||
|
||||
|
||||
function showGalleryCommand(args) {
|
||||
showCharGallery();
|
||||
}
|
||||
|
||||
async function listGalleryCommand(args) {
|
||||
try {
|
||||
let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid);
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
generateQuietPrompt,
|
||||
is_send_press,
|
||||
saveSettingsDebounced,
|
||||
substituteParams,
|
||||
substituteParamsExtended,
|
||||
generateRaw,
|
||||
getMaxContextSize,
|
||||
} from '../../../script.js';
|
||||
@@ -24,6 +24,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { resolveVariable } from '../../variables.js';
|
||||
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '1_memory';
|
||||
@@ -43,8 +44,7 @@ const formatMemoryValue = function (value) {
|
||||
value = value.trim();
|
||||
|
||||
if (extension_settings.memory.template) {
|
||||
let result = extension_settings.memory.template.replace(/{{summary}}/i, value);
|
||||
return substituteParams(result);
|
||||
return substituteParamsExtended(extension_settings.memory.template, { summary: value });
|
||||
} else {
|
||||
return `Summary: ${value}`;
|
||||
}
|
||||
@@ -447,7 +447,7 @@ async function summarizeCallback(args, text) {
|
||||
}
|
||||
|
||||
const source = args.source || extension_settings.memory.source;
|
||||
const prompt = substituteParams((resolveVariable(args.prompt) || extension_settings.memory.prompt)?.replace(/{{words}}/gi, extension_settings.memory.promptWords));
|
||||
const prompt = substituteParamsExtended((args.prompt || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords });
|
||||
|
||||
try {
|
||||
switch (source) {
|
||||
@@ -534,7 +534,7 @@ async function summarizeChatMain(context, force, skipWIAN) {
|
||||
}
|
||||
|
||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
|
||||
const prompt = extension_settings.memory.prompt?.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
const prompt = substituteParamsExtended(extension_settings.memory.prompt, { words: extension_settings.memory.promptWords });
|
||||
|
||||
if (!prompt) {
|
||||
console.debug('Summarization prompt is empty. Skipping summarization.');
|
||||
@@ -900,7 +900,7 @@ function setupListeners() {
|
||||
jQuery(async function () {
|
||||
async function addExtensionControls() {
|
||||
const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings });
|
||||
$('#extensions_settings2').append(settingsHtml);
|
||||
$('#summarize_container').append(settingsHtml);
|
||||
setupListeners();
|
||||
$('#summaryExtensionPopoutButton').off('click').on('click', function (e) {
|
||||
doPopout(e);
|
||||
@@ -920,11 +920,17 @@ jQuery(async function () {
|
||||
callback: summarizeCallback,
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', ['main', 'extras']),
|
||||
new SlashCommandNamedArgument('prompt', 'prompt to use for summarization', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, ''),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'prompt',
|
||||
description: 'prompt to use for summarization',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: '',
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''),
|
||||
],
|
||||
helpString: 'Summarizes the given text. If no text is provided, the current chat will be summarized. Can specify the source and the prompt to use.',
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<div class="flex-container alignitemscenter margin0">
|
||||
<b>Summarize</b>
|
||||
<b data-i18n="ext_sum_title">Summarize</b>
|
||||
<i id="summaryExtensionPopoutButton" class="fa-solid fa-window-restore menu_button margin0"></i>
|
||||
</div>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_memory_placeholder" placeholder="Summary will be generated here..."></textarea>
|
||||
<div class="memory_contents_controls">
|
||||
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" data-i18n="[title]ext_sum_force_tip" title="Trigger a summary update right now." data-i18n="Trigger a summary update right now.">
|
||||
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" title="Trigger a summary update right now." data-i18n="[title]ext_sum_force_tip">
|
||||
<i class="fa-solid fa-database"></i>
|
||||
<span data-i18n="ext_sum_force_text">Summarize now</span>
|
||||
</div>
|
||||
|
@@ -1,38 +1,42 @@
|
||||
<div id="qr--modalEditor">
|
||||
<div id="qr--main">
|
||||
<h3>Labels and Message</h3>
|
||||
<h3 data-i18n="Labels and Message">Labels and Message</h3>
|
||||
<div class="qr--labels">
|
||||
<label>
|
||||
<span class="qr--labelText">Label</span>
|
||||
<span class="qr--labelText" data-i18n="Label">Label</span>
|
||||
<input type="text" class="text_pole" id="qr--modal-label">
|
||||
</label>
|
||||
<label>
|
||||
<span class="qr--labelText">Title</span>
|
||||
<small class="qr--labelHint">(tooltip, leave empty to show message or /command)</small>
|
||||
<span class="qr--labelText" data-i18n="Title">Title</span>
|
||||
<small class="qr--labelHint" data-i18n="(tooltip, leave empty to show message or /command)">(tooltip, leave empty to show message or /command)</small>
|
||||
<input type="text" class="text_pole" id="qr--modal-title">
|
||||
</label>
|
||||
</div>
|
||||
<div class="qr--modal-messageContainer">
|
||||
<label for="qr--modal-message">
|
||||
<label for="qr--modal-message" data-i18n="Message / Command:">
|
||||
Message / Command:
|
||||
</label>
|
||||
<div class="qr--modal-editorSettings">
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-wrap">
|
||||
<span>Word wrap</span>
|
||||
<span data-i18n="Word wrap">Word wrap</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<span>Tab size:</span>
|
||||
<span data-i18n="Tab size:">Tab size:</span>
|
||||
<input type="number" min="1" max="9" id="qr--modal-tabSize" class="text_pole">
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-executeShortcut">
|
||||
<span>Ctrl+Enter to execute</span>
|
||||
<span data-i18n="Ctrl+Enter to execute">Ctrl+Enter to execute</span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-syntax">
|
||||
<span>Syntax highlight</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="qr--modal-messageHolder">
|
||||
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
||||
<textarea class="monospace" id="qr--modal-message" spellcheck="false"></textarea>
|
||||
<textarea id="qr--modal-message" spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,14 +44,14 @@
|
||||
|
||||
|
||||
<div id="qr--qrOptions">
|
||||
<h3>Context Menu</h3>
|
||||
<h3 data-i18n="Context Menu">Context Menu</h3>
|
||||
<div id="qr--ctxEditor">
|
||||
<template id="qr--ctxItem">
|
||||
<div class="qr--ctxItem" data-order="0">
|
||||
<div class="drag-handle ui-sortable-handle">☰</div>
|
||||
<select class="qr--set"></select>
|
||||
<label class="qr--isChainedLabel checkbox_label" title="When enabled, the current Quick Reply will be sent together with (before) the clicked QR from the context menu.">
|
||||
Chaining:
|
||||
<span data-i18n="Chaining:">Chaining:</span>
|
||||
<input type="checkbox" class="qr--isChained">
|
||||
</label>
|
||||
<div class="qr--delete menu_button menu_button_icon fa-solid fa-trash-can" title="Remove entry"></div>
|
||||
@@ -59,48 +63,48 @@
|
||||
</div>
|
||||
|
||||
|
||||
<h3>Auto-Execute</h3>
|
||||
<h3 data-i18n="Auto-Execute">Auto-Execute</h3>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label class="checkbox_label" title="Prevent this quick reply from triggering other auto-executed quick replies while auto-executing (i.e., prevent recursive auto-execution)">
|
||||
<input type="checkbox" id="qr--preventAutoExecute" >
|
||||
<span><i class="fa-solid fa-fw fa-plane-slash"></i> Don't trigger auto-execute</span>
|
||||
<span><i class="fa-solid fa-fw fa-plane-slash"></i><span data-i18n="Don't trigger auto-execute">Don't trigger auto-execute</span></span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--isHidden" >
|
||||
<span><i class="fa-solid fa-fw fa-eye-slash"></i> Invisible (auto-execute only)</span>
|
||||
<span><i class="fa-solid fa-fw fa-eye-slash"></i><span data-i18n="Invisible (auto-execute only)">Invisible (auto-execute only)</span></span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--executeOnStartup" >
|
||||
<span><i class="fa-solid fa-fw fa-rocket"></i> Execute on app startup</span>
|
||||
<span><i class="fa-solid fa-fw fa-rocket"></i><span data-i18n="Execute on startup">Execute on startup</span></span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--executeOnUser" >
|
||||
<span><i class="fa-solid fa-fw fa-user"></i> Execute on user message</span>
|
||||
<span><i class="fa-solid fa-fw fa-user"></i><span data-i18n="Execute on user message">Execute on user message</span></span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--executeOnAi" >
|
||||
<span><i class="fa-solid fa-fw fa-robot"></i> Execute on AI message</span>
|
||||
<span><i class="fa-solid fa-fw fa-robot"></i><span data-i18n="Execute on AI message">Execute on AI message</span></span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--executeOnChatChange" >
|
||||
<span><i class="fa-solid fa-fw fa-message"></i> Execute on opening chat</span>
|
||||
<span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
|
||||
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
|
||||
<span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>
|
||||
</label>
|
||||
<div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
|
||||
<small>Automation ID</small>
|
||||
<small data-i18n="Automation ID:">Automation ID</small>
|
||||
<input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h3>Testing</h3>
|
||||
<h3 data-i18n="Testing">Testing</h3>
|
||||
<div id="qr--modal-executeButtons">
|
||||
<div id="qr--modal-execute" class="qr--modal-executeButton menu_button" title="Execute the quick reply now">
|
||||
<i class="fa-solid fa-play"></i>
|
||||
Execute
|
||||
<span data-i18n="Execute">Execute</span>
|
||||
</div>
|
||||
<div id="qr--modal-pause" class="qr--modal-executeButton menu_button" title="Pause / continue execution">
|
||||
<span class="qr--modal-executeComboIcon">
|
||||
@@ -115,7 +119,7 @@
|
||||
<div id="qr--modal-executeProgress"></div>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-executeHide">
|
||||
<span> Hide editor while executing</span>
|
||||
<span title="Hide editor while executing"> Hide editor while executing</span>
|
||||
</label>
|
||||
<div id="qr--modal-executeErrors"></div>
|
||||
<div id="qr--modal-executeResult"></div>
|
||||
|
@@ -1,22 +1,22 @@
|
||||
<div id="qr--settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<strong>Quick Reply</strong>
|
||||
<strong data-i18n="Quick Reply">Quick Reply</strong>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="flex-container">
|
||||
<input type="checkbox" id="qr--isEnabled"> Enable Quick Replies
|
||||
<input type="checkbox" id="qr--isEnabled"><span data-i18n="Enable Quick Replies">Enable Quick Replies</span>
|
||||
</label>
|
||||
<label class="flex-container">
|
||||
<input type="checkbox" id="qr--isCombined"> Combine buttons from all active sets
|
||||
<input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span>
|
||||
</label>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id="qr--global">
|
||||
<div class="qr--head">
|
||||
<div class="qr--title">Global Quick Reply Sets</div>
|
||||
<div class="qr--title" data-i18n="Global Quick Reply Sets">Global Quick Reply Sets</div>
|
||||
<div class="qr--actions">
|
||||
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--global-setListAdd" title="Add quick reply set"></div>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<div id="qr--chat">
|
||||
<div class="qr--head">
|
||||
<div class="qr--title">Chat Quick Reply Sets</div>
|
||||
<div class="qr--title" data-i18n="Chat Quick Reply Sets">Chat Quick Reply Sets</div>
|
||||
<div class="qr--actions">
|
||||
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--chat-setListAdd" title="Add quick reply set"></div>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<div id="qr--editor">
|
||||
<div class="qr--head">
|
||||
<div class="qr--title">Edit Quick Replies</div>
|
||||
<div class="qr--title" data-i18n="Edit Quick Replies">Edit Quick Replies</div>
|
||||
<div class="qr--actions">
|
||||
<select id="qr--set" class="text_pole"></select>
|
||||
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
|
||||
@@ -52,13 +52,13 @@
|
||||
</div>
|
||||
<div id="qr--set-settings">
|
||||
<label class="flex-container">
|
||||
<input type="checkbox" id="qr--disableSend"> <span>Disable send (insert into input field)</span>
|
||||
<input type="checkbox" id="qr--disableSend"> <span data-i18n="Disable Send (Insert Into Input Field)">Disable send (insert into input field)</span>
|
||||
</label>
|
||||
<label class="flex-container">
|
||||
<input type="checkbox" id="qr--placeBeforeInput"> <span>Place quick reply before input</span>
|
||||
<input type="checkbox" id="qr--placeBeforeInput"> <span data-i18n="Place Quick Reply Before Input">Place quick reply before input</span>
|
||||
</label>
|
||||
<label class="flex-container" id="qr--injectInputContainer">
|
||||
<input type="checkbox" id="qr--injectInput"> <span>Inject user input automatically <small>(if disabled, use <code>{{input}}</code> macro for manual injection)</small></span>
|
||||
<input type="checkbox" id="qr--injectInput"> <span><span data-i18n="Inject user input automatically">Inject user input automatically</span> <small><span data-i18n="(if disabled, use ">(if disabled, use</span><code>{{input}}</code> <span data-i18n="macro for manual injection)">macro for manual injection)</span></small></span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="qr--set-qrList" class="qr--qrList"></div>
|
||||
|
@@ -169,7 +169,7 @@ const init = async () => {
|
||||
log('settings: ', settings);
|
||||
|
||||
manager = new SettingsUi(settings);
|
||||
document.querySelector('#extensions_settings2').append(await manager.render());
|
||||
document.querySelector('#qr_container').append(await manager.render());
|
||||
|
||||
buttons = new ButtonUi(settings);
|
||||
buttons.show();
|
||||
|
@@ -264,6 +264,13 @@ export class QuickReply {
|
||||
const updateSyntax = ()=>{
|
||||
messageSyntaxInner.innerHTML = hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value;
|
||||
};
|
||||
const updateSyntaxEnabled = ()=>{
|
||||
if (JSON.parse(localStorage.getItem('qr--syntax'))) {
|
||||
dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax');
|
||||
} else {
|
||||
dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax');
|
||||
}
|
||||
};
|
||||
/**@type {HTMLInputElement}*/
|
||||
const tabSize = dom.querySelector('#qr--modal-tabSize');
|
||||
tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4');
|
||||
@@ -282,6 +289,13 @@ export class QuickReply {
|
||||
executeShortcut.addEventListener('click', () => {
|
||||
localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked));
|
||||
});
|
||||
/**@type {HTMLInputElement}*/
|
||||
const syntax = dom.querySelector('#qr--modal-syntax');
|
||||
syntax.checked = JSON.parse(localStorage.getItem('qr--syntax') ?? 'true');
|
||||
syntax.addEventListener('click', () => {
|
||||
localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
|
||||
updateSyntaxEnabled();
|
||||
});
|
||||
/**@type {HTMLTextAreaElement}*/
|
||||
const message = dom.querySelector('#qr--modal-message');
|
||||
message.value = this.message;
|
||||
@@ -352,8 +366,7 @@ export class QuickReply {
|
||||
}
|
||||
});
|
||||
window.addEventListener('resize', resizeListener);
|
||||
message.style.color = 'transparent';
|
||||
message.style.background = 'transparent';
|
||||
updateSyntaxEnabled();
|
||||
message.style.setProperty('text-shadow', 'none', 'important');
|
||||
/**@type {HTMLElement}*/
|
||||
const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner');
|
||||
@@ -544,7 +557,7 @@ export class QuickReply {
|
||||
this.editorExecuteErrors.innerHTML = '';
|
||||
this.editorExecuteResult.innerHTML = '';
|
||||
if (this.editorExecuteHide.checked) {
|
||||
this.editorPopup.dom.classList.add('qr--hide');
|
||||
this.editorPopup.dlg.classList.add('qr--hide');
|
||||
}
|
||||
try {
|
||||
this.editorExecutePromise = this.execute({}, true);
|
||||
@@ -575,7 +588,7 @@ export class QuickReply {
|
||||
}
|
||||
this.editorExecutePromise = null;
|
||||
this.editorExecuteBtn.classList.remove('qr--busy');
|
||||
this.editorPopup.dom.classList.remove('qr--hide');
|
||||
this.editorPopup.dlg.classList.remove('qr--hide');
|
||||
}
|
||||
|
||||
updateEditorProgress(done, total) {
|
||||
|
@@ -231,6 +231,7 @@ export class QuickReplySet {
|
||||
this.rerender();
|
||||
} else {
|
||||
warn(`Failed to save Quick Reply Set: ${this.name}`);
|
||||
console.error('QR could not be saved', response);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,13 @@
|
||||
import { SlashCommand } from '../../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js';
|
||||
import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
|
||||
import { isTrueBoolean } from '../../../utils.js';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { QuickReplyApi } from '../api/QuickReplyApi.js';
|
||||
import { QuickReply } from './QuickReply.js';
|
||||
import { QuickReplySet } from './QuickReplySet.js';
|
||||
|
||||
export class SlashCommandHandler {
|
||||
/**@type {QuickReplyApi}*/ api;
|
||||
@@ -19,6 +23,50 @@ export class SlashCommandHandler {
|
||||
|
||||
|
||||
init() {
|
||||
function getExecutionIcons(/**@type {QuickReply} */ qr) {
|
||||
let icons = '';
|
||||
if (qr.preventAutoExecute) icons += '🚫';
|
||||
if (qr.isHidden) icons += '👁️';
|
||||
if (qr.executeOnStartup) icons += '🚀';
|
||||
if (qr.executeOnUser) icons += enumIcons.user;
|
||||
if (qr.executeOnAi) icons += enumIcons.assistant;
|
||||
if (qr.executeOnChatChange) icons += '💬';
|
||||
if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
|
||||
return icons;
|
||||
}
|
||||
|
||||
const localEnumProviders = {
|
||||
/** All quick reply sets, optionally filtering out sets that wer already used in the "set" named argument */
|
||||
qrSets: (executor) => QuickReplySet.list.filter(qrSet => qrSet.name != String(executor.namedArgumentList.find(x => x.name == 'set')?.value))
|
||||
.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')),
|
||||
|
||||
/** All QRs inside a set, utilizing the "set" named argument */
|
||||
qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => {
|
||||
const icons = getExecutionIcons(qr);
|
||||
const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim();
|
||||
return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr);
|
||||
}) ?? [],
|
||||
|
||||
/** All QRs as a set.name string, to be able to execute, for example via the /run command */
|
||||
qrExecutables: () => {
|
||||
const globalSetList = this.api.settings.config.setList;
|
||||
const chatSetList = this.api.settings.chatConfig?.setList;
|
||||
|
||||
const globalQrs = globalSetList.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat();
|
||||
const chatQrs = chatSetList?.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat() ?? [];
|
||||
const otherQrs = QuickReplySet.list.filter(set => !globalSetList.some(link => link.set.name === set.name && !chatSetList?.some(link => link.set.name === set.name)))
|
||||
.map(set => set.qrList.map(qr => ({ set, qr }))).flat();
|
||||
|
||||
return [
|
||||
...globalQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[global] ${x.qr.title || x.qr.message}`, enumTypes.name, enumIcons.qr)),
|
||||
...chatQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[chat] ${x.qr.title || x.qr.message}`, enumTypes.enum, enumIcons.qr)),
|
||||
...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)),
|
||||
];
|
||||
},
|
||||
}
|
||||
|
||||
window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr',
|
||||
callback: (_, value) => this.executeQuickReplyByIndex(Number(value)),
|
||||
unnamedArgumentList: [
|
||||
@@ -29,110 +77,166 @@ export class SlashCommandHandler {
|
||||
helpString: 'Activates the specified Quick Reply',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qrset',
|
||||
callback: () => toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.'),
|
||||
callback: () => {
|
||||
toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.');
|
||||
return '';
|
||||
},
|
||||
helpString: '<strong>DEPRECATED</strong> – The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set',
|
||||
callback: (args, value) => this.toggleGlobalSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.toggleGlobalSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Toggle global QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-on',
|
||||
callback: (args, value) => this.addGlobalSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.addGlobalSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Activate global QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-off',
|
||||
callback: (_, value) => this.removeGlobalSet(value),
|
||||
callback: (_, value) => {
|
||||
this.removeGlobalSet(value);
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Deactivate global QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set',
|
||||
callback: (args, value) => this.toggleChatSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.toggleChatSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Toggle chat QR set',
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-on',
|
||||
callback: (args, value) => this.addChatSet(value, args),
|
||||
callback: (args, value) => {
|
||||
this.addChatSet(value, args);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ['true', 'false'],
|
||||
'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Activate chat QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-off',
|
||||
callback: (_, value) => this.removeChatSet(value),
|
||||
callback: (_, value) => {
|
||||
this.removeChatSet(value);
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'QR set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Deactivate chat QR set',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-list',
|
||||
callback: (_, value) => this.listSets(value ?? 'all'),
|
||||
callback: (_, value) => JSON.stringify(this.listSets(value ?? 'all')),
|
||||
returns: 'list of QR sets',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'set type', [ARGUMENT_TYPE.STRING], false, false, null, ['all', 'global', 'chat'],
|
||||
'set type', [ARGUMENT_TYPE.STRING], false, false, 'all', ['all', 'global', 'chat'],
|
||||
),
|
||||
],
|
||||
helpString: 'Gets a list of the names of all quick reply sets.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-list',
|
||||
callback: (_, value) => this.listQuickReplies(value),
|
||||
callback: (_, value) => {
|
||||
return JSON.stringify(this.listQuickReplies(value));
|
||||
},
|
||||
returns: 'list of QRs',
|
||||
namedArgumentList: [],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'set name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: 'Gets a list of the names of all quick replies in this quick reply set.',
|
||||
}));
|
||||
|
||||
const qrArgs = [
|
||||
new SlashCommandNamedArgument('label', 'text on the button, e.g., label=MyButton', [ARGUMENT_TYPE.STRING]),
|
||||
new SlashCommandNamedArgument('set', 'name of the QR set, e.g., set=PresetName1', [ARGUMENT_TYPE.STRING]),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'name of the QR set, e.g., set=PresetName1',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'text on the button, e.g., label=MyButton',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrLabels,
|
||||
}),
|
||||
new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
|
||||
@@ -144,8 +248,12 @@ export class SlashCommandHandler {
|
||||
const qrUpdateArgs = [
|
||||
new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false),
|
||||
];
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create',
|
||||
callback: (args, message) => this.createQuickReply(args, message),
|
||||
callback: (args, message) => {
|
||||
this.createQuickReply(args, message);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: qrArgs,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@@ -165,9 +273,15 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update',
|
||||
callback: (args, message) => this.updateQuickReply(args, message),
|
||||
callback: (args, message) => {
|
||||
this.updateQuickReply(args, message);
|
||||
return '';
|
||||
},
|
||||
returns: 'updated quick reply',
|
||||
namedArgumentList: [...qrUpdateArgs, ...qrArgs],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Updates Quick Reply.
|
||||
@@ -183,34 +297,57 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-delete',
|
||||
callback: (args, name) => this.deleteQuickReply(args, name),
|
||||
callback: (args, name) => {
|
||||
this.deleteQuickReply(args, name);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'Quick Reply set', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'label', 'Quick Reply label', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
],
|
||||
helpString: 'Deletes a Quick Reply from the specified set. If no label is provided, the entire set is deleted.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextadd',
|
||||
callback: (args, name) => this.createContextItem(args, name),
|
||||
callback: (args, name) => {
|
||||
this.createContextItem(args, name);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'label', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'preset name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@@ -227,19 +364,32 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextdel',
|
||||
callback: (args, name) => this.deleteContextItem(args, name),
|
||||
callback: (args, name) => {
|
||||
this.deleteContextItem(args, name);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'label', 'string', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'label',
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'preset name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@@ -256,16 +406,25 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextclear',
|
||||
callback: (args, label) => this.clearContextMenu(args, label),
|
||||
callback: (args, label) => {
|
||||
this.clearContextMenu(args, label);
|
||||
return '';
|
||||
},
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'set', 'context menu preset name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'set',
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'label', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Quick Reply label',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.qrEntries,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@@ -288,13 +447,20 @@ export class SlashCommandHandler {
|
||||
new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false),
|
||||
];
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create',
|
||||
callback: (args, name) => this.createSet(name, args),
|
||||
callback: (args, name) => {
|
||||
this.createSet(name, args);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetadd'],
|
||||
namedArgumentList: presetArgs,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
forceEnum: false,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@@ -312,11 +478,19 @@ export class SlashCommandHandler {
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update',
|
||||
callback: (args, name) => this.updateSet(name, args),
|
||||
callback: (args, name) => {
|
||||
this.updateSet(name, args);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetupdate'],
|
||||
namedArgumentList: presetArgs,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@@ -329,10 +503,18 @@ export class SlashCommandHandler {
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete',
|
||||
callback: (args, name) => this.deleteSet(name),
|
||||
callback: (_, name) => {
|
||||
this.deleteSet(name);
|
||||
return '';
|
||||
},
|
||||
aliases: ['qr-presetdelete'],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true),
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'QR set name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.qrSets,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
@@ -468,7 +650,7 @@ export class SlashCommandHandler {
|
||||
}
|
||||
deleteQuickReply(args, label) {
|
||||
try {
|
||||
this.api.deleteQuickReply(args.set, label);
|
||||
this.api.deleteQuickReply(args.set, args.label ?? label);
|
||||
} catch (ex) {
|
||||
toastr.error(ex.message);
|
||||
}
|
||||
|
@@ -148,7 +148,7 @@ export class SettingsUi {
|
||||
this.onQrSetChange();
|
||||
}
|
||||
onQrSetChange() {
|
||||
this.currentQrSet = QuickReplySet.get(this.currentSet.value);
|
||||
this.currentQrSet = QuickReplySet.get(this.currentSet.value) ?? new QuickReplySet();
|
||||
this.disableSend.checked = this.currentQrSet.disableSend;
|
||||
this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput;
|
||||
this.injectInput.checked = this.currentQrSet.injectInput;
|
||||
@@ -357,6 +357,7 @@ export class SettingsUi {
|
||||
a.download = `${this.currentQrSet.name}.json`;
|
||||
a.click();
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
selectQrSet(qrs) {
|
||||
|
@@ -220,68 +220,68 @@
|
||||
align-items: baseline;
|
||||
}
|
||||
@media screen and (max-width: 750px) {
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
|
||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main {
|
||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
flex-direction: column;
|
||||
}
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
|
||||
body .popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
|
||||
min-height: 50svh;
|
||||
height: 50svh;
|
||||
}
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) {
|
||||
.popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text {
|
||||
.popup:has(#qr--modalEditor) .popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label {
|
||||
flex: 1 1 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label > input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
@@ -289,19 +289,35 @@
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
|
||||
font-size: inherit;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-messageSyntax {
|
||||
display: none;
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message {
|
||||
background-color: var(--ac-style-color-background);
|
||||
color: var(--ac-style-color-text);
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||
color: unset;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
}
|
||||
@supports (color: rgb(from white r g b / 0.25)) {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder.qr--noSyntax > #qr--modal-message::selection {
|
||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||
}
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
padding: 0;
|
||||
@@ -311,22 +327,34 @@
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax > #qr--modal-messageSyntaxInner {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-messageSyntax > #qr--modal-messageSyntaxInner {
|
||||
height: 100%;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
caret-color: white;
|
||||
mix-blend-mode: difference;
|
||||
caret-color: var(--ac-style-color-text);
|
||||
overflow: auto;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar,
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar-thumb {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar,
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-message,
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-messageSyntaxInner {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
|
||||
color: transparent;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
}
|
||||
@supports (color: rgb(from white r g b / 0.25)) {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message::selection {
|
||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||
}
|
||||
}
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-message,
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder #qr--modal-messageSyntaxInner {
|
||||
font-family: var(--monoFontFamily);
|
||||
padding: 0.75em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
@@ -335,11 +363,11 @@
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
display: flex;
|
||||
@@ -347,42 +375,42 @@
|
||||
gap: 0.5em;
|
||||
padding: 0.5em 0.75em;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton .qr--modal-executeComboIcon {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--modal-executeButton .qr--modal-executeComboIcon {
|
||||
display: flex;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
|
||||
transition: 200ms;
|
||||
filter: grayscale(0);
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
|
||||
cursor: wait;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
|
||||
border-color: #51a351;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause,
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause,
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
pointer-events: none;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-pause,
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-stop {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-pause,
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons .qr--busy ~ #qr--modal-stop {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
filter: grayscale(0);
|
||||
pointer-events: all;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-pause {
|
||||
border-color: #92befc;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-stop {
|
||||
border-color: #d78872;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress {
|
||||
--prog: 0;
|
||||
--progColor: #92befc;
|
||||
--progFlashColor: #d78872;
|
||||
@@ -393,7 +421,7 @@
|
||||
background-color: var(--black50a);
|
||||
position: relative;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress:after {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress:after {
|
||||
content: '';
|
||||
background-color: var(--progColor);
|
||||
position: absolute;
|
||||
@@ -401,23 +429,23 @@
|
||||
right: calc(100% - var(--prog) * 1%);
|
||||
transition: 200ms;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
|
||||
animation-name: qr--progressPulse;
|
||||
animation-duration: 1500ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--aborted:after {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--aborted:after {
|
||||
background-color: var(--progAbortedColor);
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--success:after {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--success:after {
|
||||
background-color: var(--progSuccessColor);
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeProgress.qr--error:after {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--error:after {
|
||||
background-color: var(--progErrorColor);
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeErrors {
|
||||
display: none;
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
@@ -428,10 +456,10 @@
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeErrors.qr--hasErrors {
|
||||
display: block;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult {
|
||||
display: none;
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
@@ -442,10 +470,10 @@
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult.qr--hasResult {
|
||||
display: block;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-executeResult:before {
|
||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeResult:before {
|
||||
content: 'Result: ';
|
||||
}
|
||||
@keyframes qr--progressPulse {
|
||||
@@ -457,6 +485,9 @@
|
||||
background-color: var(--progFlashColor);
|
||||
}
|
||||
}
|
||||
.shadow_popup.qr--hide {
|
||||
.popup.qr--hide {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
.popup.qr--hide::backdrop {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#qr--bar {
|
||||
outline: none;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
@@ -10,88 +10,99 @@
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
order: 1;
|
||||
padding-right: 2.5em;
|
||||
padding-right: 2.5em;
|
||||
position: relative;
|
||||
> #qr--popoutTrigger {
|
||||
position: absolute;
|
||||
right: 0.25em;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
>#qr--popoutTrigger {
|
||||
position: absolute;
|
||||
right: 0.25em;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#qr--popout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
z-index: 31;
|
||||
> .qr--header {
|
||||
flex: 0 0 auto;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
> .qr--controls {
|
||||
> .qr--close {
|
||||
height: 15px;
|
||||
aspect-ratio: 1 / 1;
|
||||
font-size: 20px;
|
||||
opacity: 0.5;
|
||||
transition: all 250ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .qr--body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
z-index: 31;
|
||||
|
||||
>.qr--header {
|
||||
flex: 0 0 auto;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
|
||||
>.qr--controls {
|
||||
>.qr--close {
|
||||
height: 15px;
|
||||
aspect-ratio: 1 / 1;
|
||||
font-size: 20px;
|
||||
opacity: 0.5;
|
||||
transition: all 250ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.qr--body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
#qr--bar, #qr--popout > .qr--body {
|
||||
> .qr--buttons {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
|
||||
> .qr--buttons {
|
||||
display: contents;
|
||||
}
|
||||
#qr--bar,
|
||||
#qr--popout>.qr--body {
|
||||
>.qr--buttons {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
|
||||
.qr--button {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
// background-color: var(--black50a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 3px 5px;
|
||||
margin: 3px 0;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
> .qr--button-expander {
|
||||
display: none;
|
||||
}
|
||||
&.qr--hasCtx {
|
||||
> .qr--button-expander {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>.qr--buttons {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.qr--button {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
// background-color: var(--black50a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 3px 5px;
|
||||
margin: 3px 0;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
>.qr--button-expander {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.qr--hasCtx {
|
||||
>.qr--button-expander {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qr--button-expander {
|
||||
border-left: 1px solid;
|
||||
margin-left: 1em;
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
border-left: 1px solid;
|
||||
margin-left: 1em;
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.ctx-blocker {
|
||||
@@ -153,75 +164,103 @@
|
||||
|
||||
|
||||
#qr--settings {
|
||||
.qr--head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1em;
|
||||
> .qr--title {
|
||||
font-weight: bold;
|
||||
}
|
||||
> .qr--actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 0.5em;
|
||||
}
|
||||
}
|
||||
.qr--setList {
|
||||
> .qr--item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
padding: 0 0.5em;
|
||||
> .drag-handle {
|
||||
padding: 0.75em;
|
||||
}
|
||||
> .qr--visible {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
#qr--set-settings {
|
||||
#qr--injectInputContainer {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
#qr--set-qrList {
|
||||
.qr--set-qrListContents > {
|
||||
padding: 0 0.5em;
|
||||
> .qr--set-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
padding: 0.25em 0;
|
||||
> :nth-child(1) { flex: 0 0 auto; }
|
||||
> :nth-child(2) { flex: 1 1 25%; }
|
||||
> :nth-child(3) { flex: 0 0 auto; }
|
||||
> :nth-child(4) { flex: 1 1 75%; }
|
||||
> :nth-child(5) { flex: 0 0 auto; }
|
||||
> .drag-handle {
|
||||
padding: 0.75em;
|
||||
}
|
||||
.qr--set-itemLabel, .qr--action {
|
||||
margin: 0;
|
||||
}
|
||||
.qr--set-itemMessage {
|
||||
font-size: smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.qr--set-qrListActions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
justify-content: center;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
.qr--head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1em;
|
||||
|
||||
>.qr--title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
>.qr--actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.qr--setList {
|
||||
>.qr--item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
padding: 0 0.5em;
|
||||
|
||||
>.drag-handle {
|
||||
padding: 0.75em;
|
||||
}
|
||||
|
||||
>.qr--visible {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#qr--set-settings {
|
||||
#qr--injectInputContainer {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
#qr--set-qrList {
|
||||
.qr--set-qrListContents> {
|
||||
padding: 0 0.5em;
|
||||
|
||||
>.qr--set-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
padding: 0.25em 0;
|
||||
|
||||
> :nth-child(1) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
> :nth-child(2) {
|
||||
flex: 1 1 25%;
|
||||
}
|
||||
|
||||
> :nth-child(3) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
> :nth-child(4) {
|
||||
flex: 1 1 75%;
|
||||
}
|
||||
|
||||
> :nth-child(5) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
>.drag-handle {
|
||||
padding: 0.75em;
|
||||
}
|
||||
|
||||
.qr--set-itemLabel,
|
||||
.qr--action {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.qr--set-itemMessage {
|
||||
font-size: smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qr--set-qrListActions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
justify-content: center;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,258 +268,347 @@
|
||||
|
||||
|
||||
#qr--qrOptions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> #qr--ctxEditor {
|
||||
.qr--ctxItem {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
>#qr--ctxEditor {
|
||||
.qr--ctxItem {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
> #qr--main {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
> #qr--main > .qr--labels {
|
||||
flex-direction: column;
|
||||
}
|
||||
> #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder {
|
||||
min-height: 50svh;
|
||||
height: 50svh;
|
||||
}
|
||||
}
|
||||
body .popup:has(#qr--modalEditor) .popup-content>#qr--modalEditor {
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
>#qr--main {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
>#qr--main>.qr--labels {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
>#qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder {
|
||||
min-height: 50svh;
|
||||
height: 50svh;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
|
||||
.dialogue_popup_text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
|
||||
> #qr--modalEditor {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
overflow: hidden;
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> #qr--main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
> .qr--labels {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
> label {
|
||||
flex: 1 1 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .qr--labelText {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
> .qr--labelHint {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
> input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .qr--modal-messageContainer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
> .qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
> .checkbox_label {
|
||||
white-space: nowrap;
|
||||
> input {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
> #qr--modal-messageHolder {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
> #qr--modal-messageSyntax {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
> #qr--modal-messageSyntaxInner {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
> #qr--modal-message {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
caret-color: white;
|
||||
mix-blend-mode: difference;
|
||||
&::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
#qr--modal-message, #qr--modal-messageSyntaxInner {
|
||||
padding: 0.75em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
resize: none;
|
||||
line-height: 1.2;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
>#qr--modalEditor {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
overflow: hidden;
|
||||
|
||||
#qr--modal-executeButtons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
.qr--modal-executeButton {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em 0.75em;
|
||||
.qr--modal-executeComboIcon {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
#qr--modal-execute {
|
||||
transition: 200ms;
|
||||
filter: grayscale(0);
|
||||
&.qr--busy {
|
||||
cursor: wait;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
#qr--modal-execute {
|
||||
border-color: rgb(81, 163, 81);
|
||||
}
|
||||
#qr--modal-pause, #qr--modal-stop {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
pointer-events: none;
|
||||
}
|
||||
.qr--busy {
|
||||
~ #qr--modal-pause, ~ #qr--modal-stop {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
filter: grayscale(0);
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
#qr--modal-pause {
|
||||
border-color: rgb(146, 190, 252);
|
||||
}
|
||||
#qr--modal-stop {
|
||||
border-color: rgb(215, 136, 114);
|
||||
}
|
||||
}
|
||||
#qr--modal-executeProgress {
|
||||
--prog: 0;
|
||||
--progColor: rgb(146, 190, 252);
|
||||
--progFlashColor: rgb(215, 136, 114);
|
||||
--progSuccessColor: rgb(81, 163, 81);
|
||||
--progErrorColor: rgb(189, 54, 47);
|
||||
--progAbortedColor: rgb(215, 136, 114);
|
||||
height: 0.5em;
|
||||
background-color: var(--black50a);
|
||||
position: relative;
|
||||
&:after {
|
||||
content: '';
|
||||
background-color: var(--progColor);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
right: calc(100% - var(--prog) * 1%);
|
||||
transition: 200ms;
|
||||
}
|
||||
&.qr--paused:after {
|
||||
animation-name: qr--progressPulse;
|
||||
animation-duration: 1500ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
&.qr--aborted:after {
|
||||
background-color: var(--progAbortedColor);
|
||||
}
|
||||
&.qr--success:after {
|
||||
background-color: var(--progSuccessColor);
|
||||
}
|
||||
&.qr--error:after {
|
||||
background-color: var(--progErrorColor);
|
||||
}
|
||||
}
|
||||
#qr--modal-executeErrors {
|
||||
display: none;
|
||||
&.qr--hasErrors {
|
||||
display: block;
|
||||
}
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
background-color: rgb(189, 54, 47);
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
#qr--modal-executeResult {
|
||||
display: none;
|
||||
&.qr--hasResult {
|
||||
display: block;
|
||||
}
|
||||
&:before { content: 'Result: '; }
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
background-color: rgb(81, 163, 81);
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
>#qr--main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
>.qr--labels {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
|
||||
>label {
|
||||
flex: 1 1 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
>.qr--labelText {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
>.qr--labelHint {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
>input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.qr--modal-messageContainer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
>.qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
|
||||
>.checkbox_label {
|
||||
white-space: nowrap;
|
||||
|
||||
>input {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>#qr--modal-messageHolder {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
|
||||
&.qr--noSyntax {
|
||||
>#qr--modal-messageSyntax {
|
||||
display: none;
|
||||
}
|
||||
|
||||
>#qr--modal-message {
|
||||
background-color: var(--ac-style-color-background);
|
||||
color: var(--ac-style-color-text);
|
||||
|
||||
&::selection {
|
||||
color: unset;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
|
||||
@supports (color: rgb(from white r g b / 0.25)) {
|
||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>#qr--modal-messageSyntax {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
|
||||
>#qr--modal-messageSyntaxInner {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
>#qr--modal-message {
|
||||
background-color: transparent;
|
||||
color: transparent;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
caret-color: var(--ac-style-color-text);
|
||||
overflow: auto;
|
||||
|
||||
&::-webkit-scrollbar,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&::selection {
|
||||
color: transparent;
|
||||
background-color: rgba(108 171 251 / 0.25);
|
||||
|
||||
@supports (color: rgb(from white r g b / 0.25)) {
|
||||
background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-message,
|
||||
#qr--modal-messageSyntaxInner {
|
||||
font-family: var(--monoFontFamily);
|
||||
padding: 0.75em;
|
||||
margin: 0;
|
||||
border: none;
|
||||
resize: none;
|
||||
line-height: 1.2;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-executeButtons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
||||
.qr--modal-executeButton {
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em 0.75em;
|
||||
|
||||
.qr--modal-executeComboIcon {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-execute {
|
||||
transition: 200ms;
|
||||
filter: grayscale(0);
|
||||
|
||||
&.qr--busy {
|
||||
cursor: wait;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-execute {
|
||||
border-color: rgb(81, 163, 81);
|
||||
}
|
||||
|
||||
#qr--modal-pause,
|
||||
#qr--modal-stop {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(1);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qr--busy {
|
||||
|
||||
~#qr--modal-pause,
|
||||
~#qr--modal-stop {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
filter: grayscale(0);
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-pause {
|
||||
border-color: rgb(146, 190, 252);
|
||||
}
|
||||
|
||||
#qr--modal-stop {
|
||||
border-color: rgb(215, 136, 114);
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-executeProgress {
|
||||
--prog: 0;
|
||||
--progColor: rgb(146, 190, 252);
|
||||
--progFlashColor: rgb(215, 136, 114);
|
||||
--progSuccessColor: rgb(81, 163, 81);
|
||||
--progErrorColor: rgb(189, 54, 47);
|
||||
--progAbortedColor: rgb(215, 136, 114);
|
||||
height: 0.5em;
|
||||
background-color: var(--black50a);
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
background-color: var(--progColor);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
right: calc(100% - var(--prog) * 1%);
|
||||
transition: 200ms;
|
||||
}
|
||||
|
||||
&.qr--paused:after {
|
||||
animation-name: qr--progressPulse;
|
||||
animation-duration: 1500ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
&.qr--aborted:after {
|
||||
background-color: var(--progAbortedColor);
|
||||
}
|
||||
|
||||
&.qr--success:after {
|
||||
background-color: var(--progSuccessColor);
|
||||
}
|
||||
|
||||
&.qr--error:after {
|
||||
background-color: var(--progErrorColor);
|
||||
}
|
||||
}
|
||||
|
||||
#qr--modal-executeErrors {
|
||||
display: none;
|
||||
|
||||
&.qr--hasErrors {
|
||||
display: block;
|
||||
}
|
||||
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
background-color: rgb(189, 54, 47);
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#qr--modal-executeResult {
|
||||
display: none;
|
||||
|
||||
&.qr--hasResult {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: 'Result: ';
|
||||
}
|
||||
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
background-color: rgb(81, 163, 81);
|
||||
color: white;
|
||||
padding: 0.5em;
|
||||
overflow: auto;
|
||||
min-width: 100%;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes qr--progressPulse {
|
||||
0%, 100% {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
background-color: var(--progColor);
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: var(--progFlashColor);
|
||||
}
|
||||
}
|
||||
|
||||
.shadow_popup.qr--hide {
|
||||
opacity: 0 !important;
|
||||
.popup.qr--hide {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
.popup.qr--hide::backdrop {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
@@ -1,24 +1,52 @@
|
||||
<div class="regex_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Regex</b>
|
||||
<b data-i18n="ext_regex_title">
|
||||
Regex
|
||||
</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div class="flex-container">
|
||||
<div id="open_regex_editor" class="menu_button">
|
||||
<div id="open_regex_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_global_script_desc" title="New global regex script">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<span data-i18n="ext_regex_open_editor">Open Editor</span>
|
||||
<small data-i18n="ext_regex_new_global_script">+ Global</small>
|
||||
</div>
|
||||
<div id="import_regex" class="menu_button">
|
||||
<div id="open_scoped_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_scoped_script_desc" title="New scoped regex script">
|
||||
<i class="fa-solid fa-address-card"></i>
|
||||
<small data-i18n="ext_regex_new_scoped_script">+ Scoped</small>
|
||||
</div>
|
||||
<div id="import_regex" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-file-import"></i>
|
||||
<span data-i18n="ext_regex_import_script">Import Script</span>
|
||||
<small data-i18n="ext_regex_import_script">Import</small>
|
||||
</div>
|
||||
<input type="file" id="import_regex_file" hidden accept="*.json" multiple />
|
||||
</div>
|
||||
<hr />
|
||||
<label data-i18n="ext_regex_saved_scripts">Saved Scripts</label>
|
||||
<div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>
|
||||
<div id="global_scripts_block" class="padding5">
|
||||
<div>
|
||||
<strong data-i18n="ext_regex_global_scripts">Global Scripts</strong>
|
||||
</div>
|
||||
<small data-i18n="ext_regex_global_scripts_desc">
|
||||
Available for all characters. Saved to local settings.
|
||||
</small>
|
||||
<div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>
|
||||
</div>
|
||||
<hr />
|
||||
<div id="scoped_scripts_block" class="padding5">
|
||||
<div class="flex-container alignItemsBaseline">
|
||||
<strong class="flex1" data-i18n="ext_regex_scoped_scripts">Scoped Scripts</strong>
|
||||
<label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">
|
||||
<input type="checkbox" id="regex_scoped_toggle" class="enable_scoped" />
|
||||
<span class="regex-toggle-on fa-solid fa-toggle-on fa-lg" title="Disallow using scoped regex"></span>
|
||||
<span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" data-i18n="[title]ext_regex_allow_scoped" title="Allow using scoped regex"></span>
|
||||
</label>
|
||||
</div>
|
||||
<small data-i18n="ext_regex_scoped_scripts_desc">
|
||||
Only available for this character. Saved to the card data.
|
||||
</small>
|
||||
<div id="saved_scoped_scripts" class="flex-container regex-script-container flexFlowColumn"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user