mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
426 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
565327fe1e | ||
|
2e5bbf0445 | ||
|
ec6b6ab8d4 | ||
|
654a34f932 | ||
|
3d1312c13a | ||
|
52cf684444 | ||
|
54c37e945b | ||
|
ecab8a6cb4 | ||
|
b1ab1451ec | ||
|
b84fbed800 | ||
|
ccf66e6343 | ||
|
5e8fc39735 | ||
|
cd1a8c9224 | ||
|
ae4a9a7b14 | ||
|
6ae1b7a72b | ||
|
d8380a390a | ||
|
5fbb232d69 | ||
|
25d818ecbd | ||
|
5dd9a87dc9 | ||
|
857ce2c577 | ||
|
902acc44a2 | ||
|
22a0bf9451 | ||
|
ad95be2500 | ||
|
071b901f87 | ||
|
ad9382a98c | ||
|
49c26f3810 | ||
|
86c7a7a058 | ||
|
0178c95f6f | ||
|
440ecfc991 | ||
|
e0b5df97c4 | ||
|
e3f760a9dd | ||
|
c9783640c0 | ||
|
f4d1e2a46e | ||
|
b52f71dcce | ||
|
158aa79aed | ||
|
3fe2b21686 | ||
|
dfbeb41afa | ||
|
3d4054f10e | ||
|
6c6f914655 | ||
|
3de5cdd7e8 | ||
|
eb6e987f55 | ||
|
10f27f41d1 | ||
|
3d83d1d5b7 | ||
|
cfd6a26881 | ||
|
e92d4a3dbf | ||
|
1189734c62 | ||
|
a78bb82b44 | ||
|
3a8383ab79 | ||
|
dc1c477d62 | ||
|
e9c459690f | ||
|
43de36b331 | ||
|
39567cf278 | ||
|
0033090a93 | ||
|
ef8c347a95 | ||
|
535ec8c42d | ||
|
66911160c0 | ||
|
d861c59f27 | ||
|
938f89cd1a | ||
|
acbd01407d | ||
|
254339af34 | ||
|
6c9cabfb57 | ||
|
359277deb5 | ||
|
11e7ca76e1 | ||
|
dc1121b72a | ||
|
323493962a | ||
|
751c0723dc | ||
|
23b08173ff | ||
|
9f15e67856 | ||
|
2c84c93f3d | ||
|
bfdd071001 | ||
|
ab9aa28fe4 | ||
|
61995bb33f | ||
|
38b63b07f5 | ||
|
d185e143a8 | ||
|
4e1630c17d | ||
|
2214f284fa | ||
|
6e562bd1ff | ||
|
2d774f32b2 | ||
|
5ab449d8a1 | ||
|
124658a006 | ||
|
57de6229f9 | ||
|
e162df67fa | ||
|
dae09d58d7 | ||
|
6dd09858d4 | ||
|
985c2bcfb1 | ||
|
fc7a4538e9 | ||
|
72c672c2c2 | ||
|
77c8bc8eb5 | ||
|
1edc2b08f2 | ||
|
d34f7d3e1a | ||
|
eaca6ddaf0 | ||
|
aa89a74901 | ||
|
599904d589 | ||
|
ba302e4aa0 | ||
|
0f1a0963fd | ||
|
2b3055a84a | ||
|
1fed8ba4f7 | ||
|
5827f9638f | ||
|
6ad786f348 | ||
|
0cc048cb64 | ||
|
cb8d9ac71b | ||
|
b24509ef43 | ||
|
7553efc308 | ||
|
9fb4b3425e | ||
|
182216e711 | ||
|
52891898d2 | ||
|
f6c29c61df | ||
|
17a5d629ea | ||
|
688551ffa6 | ||
|
ece34dc337 | ||
|
179de92231 | ||
|
c4c962aeb9 | ||
|
d0182c47de | ||
|
0d30d8244f | ||
|
2e67ebd881 | ||
|
d62cdffcc0 | ||
|
77a28c7131 | ||
|
c60d4e5bb9 | ||
|
990e08ba2d | ||
|
bf0cf10403 | ||
|
7a3869c476 | ||
|
e74090139c | ||
|
09fc42a787 | ||
|
c0e5d7efae | ||
|
66ec17620f | ||
|
51e2a3afcf | ||
|
abc1555c19 | ||
|
6c29879f12 | ||
|
2f8f6844fe | ||
|
dc4a6e862b | ||
|
4bf91c7772 | ||
|
004baf7b87 | ||
|
6c8bd06308 | ||
|
65b4551864 | ||
|
7f55d108cf | ||
|
c9a9dab523 | ||
|
f149fc9aaa | ||
|
7aeb098212 | ||
|
c76c76410c | ||
|
5cc6a2dca6 | ||
|
0bdd350b8d | ||
|
9a5e667674 | ||
|
c9d8d7ba64 | ||
|
17367f2b17 | ||
|
6362f76812 | ||
|
74d627f674 | ||
|
599261dc31 | ||
|
d19c151669 | ||
|
b30d7ad51c | ||
|
4fdc533bd7 | ||
|
d17ac770e6 | ||
|
70071312d3 | ||
|
ec23356c99 | ||
|
98cc969d18 | ||
|
dc5deaf47c | ||
|
d81c94de0b | ||
|
e2e32da4e6 | ||
|
0480acebcd | ||
|
dbac2704f3 | ||
|
f8d90c1933 | ||
|
3dbdd1258e | ||
|
f92249790f | ||
|
23951b8c8a | ||
|
af38971a01 | ||
|
ed6417ebcd | ||
|
2fa038f91d | ||
|
31beb05aa1 | ||
|
4cf6a1f7da | ||
|
180dcefe40 | ||
|
307e666c27 | ||
|
b605b940eb | ||
|
967a084aad | ||
|
4d08e3e9be | ||
|
da34517943 | ||
|
7ffe3d21f8 | ||
|
ea01247bcf | ||
|
786b87952e | ||
|
26ddfd1a08 | ||
|
6f3947226f | ||
|
42fd317188 | ||
|
2411b17279 | ||
|
ab460199ab | ||
|
3a3ff89047 | ||
|
a5acc7872d | ||
|
2688d980c1 | ||
|
3abee9e37a | ||
|
5b63d0ff40 | ||
|
a96aad6073 | ||
|
02bdd56e20 | ||
|
b5a6257352 | ||
|
408a1fe846 | ||
|
44ba1cba59 | ||
|
96df705409 | ||
|
40f95bf842 | ||
|
9d45c0a018 | ||
|
470da71b3b | ||
|
92ab17b58b | ||
|
8c00f38a1f | ||
|
89705391d1 | ||
|
bbe52886da | ||
|
ef68dd07ac | ||
|
f2cae64b0d | ||
|
868778b079 | ||
|
e681f1f36f | ||
|
1832145645 | ||
|
af4f60a4af | ||
|
fa147f71a3 | ||
|
3830347d81 | ||
|
902676262a | ||
|
fe813d5469 | ||
|
500994e051 | ||
|
5409b3dc9b | ||
|
33c34eacb7 | ||
|
7dbfa292ed | ||
|
853736fa93 | ||
|
9f79e11bb5 | ||
|
322511caa9 | ||
|
29124df66b | ||
|
9c26e324ac | ||
|
f5beae517e | ||
|
05215218d9 | ||
|
ff9b474df7 | ||
|
fe64597a23 | ||
|
889a1b5323 | ||
|
bbed147ce5 | ||
|
5ef79bd64d | ||
|
3909310a4f | ||
|
06825331d6 | ||
|
56b78a8227 | ||
|
7b3f242454 | ||
|
ab8fb98f5d | ||
|
5d0a4fa940 | ||
|
6e6bb47718 | ||
|
8636987d8a | ||
|
01e38be408 | ||
|
ded1e3a859 | ||
|
e616ab5ced | ||
|
0888cff254 | ||
|
47893b9a14 | ||
|
94ce06b257 | ||
|
c110ebe02b | ||
|
f468a33d60 | ||
|
1b405335d4 | ||
|
f69aa07ec1 | ||
|
67c8476cdf | ||
|
80e286fed2 | ||
|
529c461318 | ||
|
49b60e8dfb | ||
|
2bef2d602e | ||
|
3e1815f599 | ||
|
6d2caf94bf | ||
|
e8545db9a5 | ||
|
d4688d22d3 | ||
|
a26e8ef455 | ||
|
ce2c2b0dac | ||
|
8b13e29702 | ||
|
802149380d | ||
|
1e24d97fd8 | ||
|
aebf173720 | ||
|
69b085e911 | ||
|
e4e02c69f3 | ||
|
ef69dcd502 | ||
|
5f0220d90e | ||
|
e3e6fa2218 | ||
|
942f343915 | ||
|
08e1b5bb5e | ||
|
b7176cb53e | ||
|
f91e1dfd86 | ||
|
4e366a8e9e | ||
|
2d933f835c | ||
|
242600a5a3 | ||
|
37eb074652 | ||
|
838cd81f8e | ||
|
4a6705cea8 | ||
|
428c851c9b | ||
|
6c097560ca | ||
|
79dcfa6c51 | ||
|
7a325f03ea | ||
|
02f9aded4b | ||
|
52ae038297 | ||
|
83ff2e6edc | ||
|
9bf132034f | ||
|
267d0eb16f | ||
|
6a98701e61 | ||
|
8f20c87a7d | ||
|
5cbd5c8210 | ||
|
1f10acdf17 | ||
|
e9de47615a | ||
|
d6b694cc9d | ||
|
bc1d745209 | ||
|
53f8667782 | ||
|
deeedad19e | ||
|
4f1130260c | ||
|
c19eb3146f | ||
|
dce959f72d | ||
|
f4bdf127f7 | ||
|
019c47adc6 | ||
|
8a74715d88 | ||
|
5f5407777f | ||
|
05f3a5d8a1 | ||
|
5057aab739 | ||
|
fce57b41dd | ||
|
4e553cf6ab | ||
|
eb815b906c | ||
|
ab440a3d7b | ||
|
7d6ff2ee3c | ||
|
056fef6831 | ||
|
4ff3b337e2 | ||
|
d5409a5fea | ||
|
218cfb43d8 | ||
|
194278d171 | ||
|
7cb896ddd7 | ||
|
fcfd8b8a53 | ||
|
7f99ae5705 | ||
|
c6bbbf1c25 | ||
|
34c698972c | ||
|
326c7d8841 | ||
|
9dfe42a7c3 | ||
|
2e3ddd16d5 | ||
|
77c9744878 | ||
|
8db38f3f8d | ||
|
49aff85472 | ||
|
c1b76b5f48 | ||
|
f7576750fc | ||
|
691e9ee754 | ||
|
ab9594fe62 | ||
|
8211e67ed7 | ||
|
aa901883d2 | ||
|
cd8027aeea | ||
|
3feeaaa09f | ||
|
113ea1d0ec | ||
|
98a2a2b989 | ||
|
a5aadd936c | ||
|
7ea426ba79 | ||
|
1f6e2ad74c | ||
|
86c0974a6e | ||
|
dfd6961c2a | ||
|
b190035224 | ||
|
100ba3e89b | ||
|
220d19d3ec | ||
|
f5e52eab05 | ||
|
ce46e1a4ca | ||
|
a757532c3e | ||
|
02f7c9ab64 | ||
|
27b6fe1f76 | ||
|
1c46d2740a | ||
|
52dbb916c0 | ||
|
938e244987 | ||
|
f46f2f6901 | ||
|
8b38015fab | ||
|
271a429a62 | ||
|
f3f9efb163 | ||
|
398544407d | ||
|
b648d9bd87 | ||
|
79e6d4c297 | ||
|
94c6b453c0 | ||
|
11cef1b234 | ||
|
6ca678e137 | ||
|
e1df933368 | ||
|
b91bc21d60 | ||
|
baac38f888 | ||
|
3422b3e963 | ||
|
4e78c3ec79 | ||
|
2928c79446 | ||
|
20f807c5c9 | ||
|
37e20f6fc5 | ||
|
6bd77bac7a | ||
|
c703b0b25e | ||
|
274b2e5009 | ||
|
210d76a621 | ||
|
feb19a603c | ||
|
796905e93c | ||
|
39c96122be | ||
|
10148167ba | ||
|
56fc92daca | ||
|
085e92a43e | ||
|
66bc15edc0 | ||
|
189cbcc58e | ||
|
165d4b3b75 | ||
|
1cb86034b5 | ||
|
de5bc45060 | ||
|
bce346c7b6 | ||
|
31ab7a86de | ||
|
87d4c17a9c | ||
|
18afe590df | ||
|
129e675024 | ||
|
7eebbca3dd | ||
|
1ce7131c1b | ||
|
403546e514 | ||
|
7bf72beed7 | ||
|
d07779e5da | ||
|
26e008e907 | ||
|
9087736835 | ||
|
c0b1ea5f4c | ||
|
fc59b20f36 | ||
|
918aba3eb6 | ||
|
288378919a | ||
|
8d5eb062e6 | ||
|
2bd645e271 | ||
|
4d270d94fa | ||
|
01ff9604bd | ||
|
9dc908c9aa | ||
|
8d2c84c6ef | ||
|
a3c6a760a6 | ||
|
b8c501c932 | ||
|
7b51d94e1b | ||
|
6a028cc828 | ||
|
2b768cc151 | ||
|
78512b33ad | ||
|
e820245fcf | ||
|
3d8761f077 | ||
|
79448f5fe7 | ||
|
39eae80f6e | ||
|
91434a3ba8 | ||
|
3737f58072 | ||
|
cf796af950 | ||
|
189895bd01 | ||
|
c7d9eb39f5 | ||
|
7177fec50c | ||
|
3f04a5bfa0 | ||
|
82adc4c780 | ||
|
18e6d3ad17 | ||
|
0d7d68d9dc | ||
|
bbc476b839 | ||
|
ba8997beea | ||
|
e31c87c471 |
90
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
90
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
name: Bug Report 🐛
|
||||
description: Report something that's not working the way it's (probably) intended to. PAY ATTENTION, Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!"
|
||||
title: '[BUG] <title>'
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: dropdown
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Where are you running SillyTavern?
|
||||
options:
|
||||
- Self-Hosted (Bare Metal)
|
||||
- Self-Hosted (Docker)
|
||||
- Android (Termux)
|
||||
- Cloud Service (Static)
|
||||
- Other (Specify below)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: system
|
||||
attributes:
|
||||
label: System
|
||||
description: >-
|
||||
For deployment issues, specify your [distro or OS](https://whatsmyos.com/) and/ or Docker version.
|
||||
For client-side issues, include your [browser version](https://www.whatsmybrowser.org/)
|
||||
placeholder: e.g. Firefox 101, Manjaro Linux 21.3.0, Docker 20.10.16
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of SillyTavern are you running?
|
||||
placeholder: (check User Settings to see the version)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: desktop
|
||||
attributes:
|
||||
label: Desktop Information
|
||||
description: Please provide details about your desktop environment.
|
||||
placeholder: |
|
||||
- Node.js version (if applicable): [run `node --version` in cmd]
|
||||
- Generation API [e.g. KoboldAI, OpenAI]
|
||||
- Branch [staging, release]
|
||||
- Model [e.g. Pygmalion 6b, LLaMa 13b]
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Describe the problem
|
||||
description: Please describe exactly what is not working, include the steps to reproduce, actual result and expected result
|
||||
placeholder: When doing ABC then DEF, I expect to see XYZ, but I actually see ZYX
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Logs? Screenshots? Yes, please.
|
||||
placeholder: If the issue happens during build-time, include terminal logs. For run-time errors, include browser logs which you can view in the Dev Tools (F12), under the Console tab. Take care to blank out any personal info.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: idiot-check
|
||||
attributes:
|
||||
label: Please tick the boxes
|
||||
description: Before submitting, please ensure that
|
||||
options:
|
||||
- label: You have explained the issue clearly, and included all relevant info
|
||||
required: true
|
||||
- label: You've checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue)
|
||||
required: true
|
||||
- label: You've checked the [docs](https://docs.sillytavern.app/) 
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |-
|
||||
## Thanks 🙏
|
||||
Thank you for raising this ticket - in doing so you are helping to make SillyTavern better for everyone.
|
||||
validations:
|
||||
required: false
|
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,45 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: "Create a report to help us improve. PAY ATTENTION: Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!"
|
||||
title: "[BUG]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
> **Warning**. Complete **all** the fields below. Otherwise, your bug report will be **ignored**!
|
||||
|
||||
**Have you searched for similar [bugs](https://github.com/SillyTavern/SillyTavern/issues?q=)?**
|
||||
Yes/No
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
|
||||
Providing the logs from the browser DevTools console (opened by pressing the F12 key) or SillyTavern command line window will be highly appreciated.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS/Device: [e.g. Windows 11]
|
||||
- Environment: [cloud, local]
|
||||
- Node.js version (if applicable): [run `node --version` in cmd]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Generation API [e.g. KoboldAI, OpenAI]
|
||||
- Branch [staging, release]
|
||||
- Model [e.g. Pygmalion 6b, LLaMa 13b]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
91
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
91
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: Feature Request ✨
|
||||
description: Suggest an idea for future development of this project
|
||||
title: '[FEATURE_REQUEST] <title>'
|
||||
labels: ['enhancement']
|
||||
|
||||
body:
|
||||
|
||||
# Field 1 - Did the user searched for similar requests
|
||||
- type: dropdown
|
||||
id: similarRequest
|
||||
attributes:
|
||||
label: Have you searched for similar [requests](https://github.com/SillyTavern/SillyTavern/issues?q=)?
|
||||
description:
|
||||
options:
|
||||
- 'No'
|
||||
- 'Yes'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 2 - Is it bug-related
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? If so, please describe.
|
||||
description:
|
||||
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 3 - Describe feature
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
placeholder: An outline of how you would like this to be implemented, include as much details as possible
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# Field 4 - Describe alternatives
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
placeholder: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 5 - Additional context
|
||||
- type: textarea
|
||||
id: addcontext
|
||||
attributes:
|
||||
label: Additional context
|
||||
placeholder: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Field 6 - Priority
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: Priority
|
||||
description: How urgent is the development of this feature
|
||||
options:
|
||||
- Low (Nice-to-have)
|
||||
- Medium (Would be very useful)
|
||||
- High (The app does not function without it)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
# Field 7 - Can the user implement
|
||||
- type: dropdown
|
||||
id: canImplement
|
||||
attributes:
|
||||
label: Is this something you would be keen to implement?
|
||||
description: Are you raising this ticket in order to get an issue number for your PR?
|
||||
options:
|
||||
- 'No'
|
||||
- 'Maybe'
|
||||
- 'Yes!'
|
||||
validations:
|
||||
required: false
|
||||
|
||||
# Final text
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |-
|
||||
## Thanks 🙏
|
||||
Thank you for your feature suggestion.
|
||||
Please note that there is no guarantee that your idea will be implemented.
|
||||
validations:
|
||||
required: false
|
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature Request] "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Have you searched for similar [requests](https://github.com/SillyTavern/SillyTavern/issues?q=)?**
|
||||
Yes/No
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
2
.github/readme-zh_cn.md
vendored
2
.github/readme-zh_cn.md
vendored
@@ -41,8 +41,6 @@ SillyTavern 本身并无用处,因为它只是一个用户聊天界面。你
|
||||
|
||||
<https://rentry.org/STAI-Termux>
|
||||
|
||||
Termux 不支持**.Webp 字符卡的导入/导出。请使用 JSON 或 PNG 格式**。
|
||||
|
||||
## 有问题或建议?
|
||||
|
||||
### 我们现在有了 Discord 社区
|
||||
|
4
.github/readme.md
vendored
4
.github/readme.md
vendored
@@ -41,8 +41,6 @@ Since Tavern is only a user interface, it has tiny hardware requirements, it wil
|
||||
|
||||
<https://rentry.org/STAI-Termux>
|
||||
|
||||
**.webp character cards import/export is not supported in Termux. Use either JSON or PNG formats instead.**
|
||||
|
||||
## Questions or suggestions?
|
||||
|
||||
### We now have a community Discord server
|
||||
@@ -71,7 +69,6 @@ Get in touch with the developers directly:
|
||||
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
|
||||
* [AI Horde](https://horde.koboldai.net/) connection
|
||||
* Prompt generation formatting tweaking
|
||||
* webp character card interoperability (PNG is still an internal format)
|
||||
|
||||
## Extensions
|
||||
|
||||
@@ -295,6 +292,7 @@ GNU Affero General Public License for more details.**
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
|
||||
* city_unit's extensions and various QoL features (<https://github.com/city-unit>)
|
||||
* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
|
45
.github/workflows/docker-publish.yml
vendored
Normal file
45
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# This workflow will publish a docker image for every full release to the GitHub package repository
|
||||
|
||||
name: Create Docker Image on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
# Only runs on full releases not pre releases
|
||||
types: [released]
|
||||
|
||||
env:
|
||||
# This should allow creation of docker images even in forked repositories
|
||||
# Image name may not contain uppercase characters, so we can not use the repository name
|
||||
# Creates a string like: ghcr.io/SillyTavern/sillytavern
|
||||
image_name: ghcr.io/${{ github.repository_owner }}/sillytavern
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Build docker image using dockerfile and tag it with branch name
|
||||
# Assumes branch name is the version number
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
docker build . --file Dockerfile --tag $image_name:${{ github.ref_name }}
|
||||
|
||||
# Login into package repository as the person who created the release
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Assumes release is the latest and marks image as such
|
||||
- name: Docker Tag and Push
|
||||
run: |
|
||||
docker tag $image_name:${{ github.ref_name }} $image_name:latest
|
||||
docker push $image_name:${{ github.ref_name }}
|
||||
docker push $image_name:latest
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,7 @@ public/settings.json
|
||||
/thumbnails
|
||||
whitelist.txt
|
||||
.vscode
|
||||
.idea/
|
||||
secrets.json
|
||||
/dist
|
||||
/backups/
|
||||
@@ -35,3 +36,5 @@ content.log
|
||||
cloudflared.exe
|
||||
public/assets/
|
||||
access.log
|
||||
/vectors/
|
||||
/cache/
|
||||
|
@@ -13,7 +13,7 @@ ENTRYPOINT [ "tini", "--" ]
|
||||
WORKDIR ${APP_HOME}
|
||||
|
||||
# Install app dependencies
|
||||
COPY package*.json ./
|
||||
COPY package*.json post-install.js ./
|
||||
RUN \
|
||||
echo "*** Install npm packages ***" && \
|
||||
npm install && npm cache clean --force
|
||||
|
25
SECURITY.md
Normal file
25
SECURITY.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Security Policy
|
||||
|
||||
We take the security of this project seriously. If you discover any security vulnerabilities or have concerns regarding the security of this repository, please reach out to us immediately. We appreciate your efforts in responsibly disclosing the issue and will make every effort to address it promptly.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability, please follow these steps:
|
||||
|
||||
1. Go to the **Security** tab of this repository on GitHub.
|
||||
2. Click on **"Report a vulnerability"**.
|
||||
3. Provide a clear description of the vulnerability and its potential impact. Be as detailed as possible.
|
||||
4. If applicable, include steps or a PoC (Proof of Concept) to reproduce the vulnerability.
|
||||
5. Submit the report.
|
||||
|
||||
Once we receive the private report notification, we will promptly investigate and assess the reported vulnerability.
|
||||
|
||||
Please do not disclose any potential vulnerabilities in public repositories, issue trackers, or forums until we have had a chance to review and address the issue.
|
||||
|
||||
## Scope
|
||||
|
||||
This security policy applies to all the code and files within this repository and its dependencies actively maintained by us. If you encounter a security issue in a dependency that is not directly maintained by us, please follow responsible disclosure practices and report it to the respective project.
|
||||
|
||||
While we strive to ensure the security of this project, please note that there may be limitations on resources, response times, and mitigations.
|
||||
|
||||
Thank you for your help in making this project more secure.
|
@@ -73,6 +73,7 @@
|
||||
"#@markdown Enables ChromaDB for Infinity Context plugin\n",
|
||||
"\n",
|
||||
"import subprocess\n",
|
||||
"import secrets\n",
|
||||
"\n",
|
||||
"# ---\n",
|
||||
"# SillyTavern extras\n",
|
||||
@@ -116,9 +117,28 @@
|
||||
"!npm install -g localtunnel\n",
|
||||
"!pip install -r requirements-complete.txt\n",
|
||||
"!pip install tensorflow==2.12\n",
|
||||
"!pip install colorama\n",
|
||||
"!pip install Flask-Cors\n",
|
||||
"!pip install Flask-Compress\n",
|
||||
"!pip install transformers\n",
|
||||
"!pip install Flask_Cloudflared\n",
|
||||
"!pip install webuiapi\n",
|
||||
"!pip install diffusers\n",
|
||||
"!pip install accelerate\n",
|
||||
"!pip install silero_api_server\n",
|
||||
"!pip install edge_tts\n",
|
||||
"!pip install chromadb\n",
|
||||
"!pip install sentence_transformers\n",
|
||||
"!wget https://github.com/cloudflare/cloudflared/releases/download/2023.5.0/cloudflared-linux-amd64 -O /tmp/cloudflared-linux-amd64\n",
|
||||
"!chmod +x /tmp/cloudflared-linux-amd64\n",
|
||||
"\n",
|
||||
"# Generate a random API key\n",
|
||||
"api_key = secrets.token_hex(5)\n",
|
||||
"\n",
|
||||
"# Write the API key to api_key.txt\n",
|
||||
"with open('./api_key.txt', 'w') as f:\n",
|
||||
" f.write(api_key)\n",
|
||||
"print(f\"API Key generated: {api_key}\")\n",
|
||||
"\n",
|
||||
"cmd = f\"python server.py {' '.join(params)}\"\n",
|
||||
"print(cmd)\n",
|
||||
|
@@ -9,23 +9,44 @@ const enableExtensions = true; //Enables support for TavernAI-extras project
|
||||
const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine.
|
||||
const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend.
|
||||
const skipContentCheck = false; // If true, no new default content will be delivered to you.
|
||||
|
||||
const thumbnailsQuality = 95; // Quality of thumbnails. 0-100
|
||||
|
||||
// If true, Allows insecure settings for listen, whitelist, and authentication.
|
||||
// Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting.
|
||||
const securityOverride = false;
|
||||
|
||||
module.exports = {
|
||||
port,
|
||||
whitelist,
|
||||
whitelistMode,
|
||||
basicAuthMode,
|
||||
basicAuthUser,
|
||||
autorun,
|
||||
enableExtensions,
|
||||
listen,
|
||||
disableThumbnails,
|
||||
allowKeysExposure,
|
||||
securityOverride,
|
||||
skipContentCheck,
|
||||
// Additional settings for extra modules / extensions
|
||||
const extras = {
|
||||
// Disables auto-download of models from the HuggingFace Hub.
|
||||
// You will need to manually download the models and put them into the /cache folder.
|
||||
disableAutoDownload: false,
|
||||
// Text classification model for sentiment analysis. HuggingFace ID of a model in ONNX format.
|
||||
classificationModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx',
|
||||
// Image captioning model. HuggingFace ID of a model in ONNX format.
|
||||
captioningModel: 'Xenova/vit-gpt2-image-captioning',
|
||||
// Feature extraction model. HuggingFace ID of a model in ONNX format.
|
||||
embeddingModel: 'Xenova/all-mpnet-base-v2',
|
||||
};
|
||||
|
||||
// Request overrides for additional headers
|
||||
// Format is an array of objects:
|
||||
// { hosts: [ "<url>" ], headers: { <header>: "<value>" } }
|
||||
const requestOverrides = [];
|
||||
|
||||
module.exports = {
|
||||
port,
|
||||
whitelist,
|
||||
whitelistMode,
|
||||
basicAuthMode,
|
||||
basicAuthUser,
|
||||
autorun,
|
||||
enableExtensions,
|
||||
listen,
|
||||
disableThumbnails,
|
||||
allowKeysExposure,
|
||||
securityOverride,
|
||||
skipContentCheck,
|
||||
requestOverrides,
|
||||
thumbnailsQuality,
|
||||
extras,
|
||||
};
|
||||
|
@@ -68,7 +68,6 @@
|
||||
"tokenizer": 99,
|
||||
"token_padding": 64,
|
||||
"collapse_newlines": false,
|
||||
"pygmalion_formatting": 0,
|
||||
"pin_examples": false,
|
||||
"strip_examples": false,
|
||||
"trim_sentences": false,
|
||||
@@ -76,9 +75,6 @@
|
||||
"always_force_name2": true,
|
||||
"user_prompt_bias": "",
|
||||
"show_user_prompt_bias": true,
|
||||
"multigen": false,
|
||||
"multigen_first_chunk": 50,
|
||||
"multigen_next_chunks": 30,
|
||||
"markdown_escape_strings": "",
|
||||
"fast_ui_mode": false,
|
||||
"avatar_style": 0,
|
||||
@@ -168,7 +164,6 @@
|
||||
"custom_stopping_strings_macro": true,
|
||||
"fuzzy_search": true,
|
||||
"encode_tags": false,
|
||||
"lazy_load": 100,
|
||||
"ui_mode": 1
|
||||
},
|
||||
"extension_settings": {
|
||||
@@ -604,7 +599,6 @@
|
||||
"proxy_password": "",
|
||||
"assistant_prefill": "",
|
||||
"use_ai21_tokenizer": false,
|
||||
"exclude_assistant": false,
|
||||
"nsfw_avoidance_prompt": "Avoid writing a NSFW/Smut reply. Creatively write around it NSFW/Smut scenarios in character."
|
||||
"exclude_assistant": false
|
||||
}
|
||||
}
|
||||
|
@@ -9,4 +9,5 @@ services:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- "./config:/home/node/app/config"
|
||||
- "./user:/home/node/app/public/user"
|
||||
restart: unless-stopped
|
||||
|
17
jsconfig.json
Normal file
17
jsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"checkJs": true,
|
||||
"allowUmdGlobalAccess": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
915
package-lock.json
generated
915
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -3,37 +3,32 @@
|
||||
"@agnai/sentencepiece-js": "^1.1.1",
|
||||
"@agnai/web-tokenizers": "^0.1.3",
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"axios": "^1.4.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"csrf-csrf": "^2.2.3",
|
||||
"device-detector-js": "^3.0.3",
|
||||
"exifreader": "^4.12.0",
|
||||
"express": "^4.18.2",
|
||||
"google-translate-api-browser": "^3.0.1",
|
||||
"gpt3-tokenizer": "^1.1.5",
|
||||
"ip-matching": "^2.1.2",
|
||||
"ipaddr.js": "^2.0.1",
|
||||
"jimp": "^0.22.7",
|
||||
"jquery": "^3.6.4",
|
||||
"jimp": "^0.22.10",
|
||||
"json5": "^2.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-fetch": "^2.6.11",
|
||||
"node-rest-client": "^3.1.1",
|
||||
"open": "^8.4.0",
|
||||
"piexifjs": "^1.0.6",
|
||||
"open": "^8.4.2",
|
||||
"png-chunk-text": "^1.0.0",
|
||||
"png-chunks-encode": "^1.0.0",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"response-time": "^2.3.2",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"sillytavern-transformers": "^2.7.3",
|
||||
"simple-git": "^3.19.1",
|
||||
"uniqolor": "^1.1.0",
|
||||
"webp-converter": "2.3.2",
|
||||
"vectra": "^0.2.2",
|
||||
"write-file-atomic": "^5.0.1",
|
||||
"ws": "^8.13.0",
|
||||
"yargs": "^17.7.1",
|
||||
@@ -51,11 +46,12 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.10.0",
|
||||
"version": "1.10.4",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"start-multi": "node server.js --disableCsrf",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public .",
|
||||
"postinstall": "node post-install.js"
|
||||
},
|
||||
"bin": {
|
||||
"sillytavern": "./server.js"
|
||||
@@ -80,6 +76,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"jquery": "^3.6.4",
|
||||
"pkg": "^5.8.1",
|
||||
"pkg-fetch": "^3.5.2"
|
||||
}
|
||||
|
81
post-install.js
Normal file
81
post-install.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Scripts to be done before starting the server for the first time.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Creates the default config files if they don't exist yet.
|
||||
*/
|
||||
function createDefaultFiles() {
|
||||
const files = {
|
||||
settings: './public/settings.json',
|
||||
bg_load: './public/css/bg_load.css',
|
||||
config: './config.conf',
|
||||
};
|
||||
|
||||
for (const file of Object.values(files)) {
|
||||
try {
|
||||
if (!fs.existsSync(file)) {
|
||||
const defaultFilePath = path.join('./default', path.parse(file).base);
|
||||
fs.copyFileSync(defaultFilePath, file);
|
||||
console.log(`Created default file: ${file}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`FATAL: Could not write default file: ${file}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MD5 hash of the given data.
|
||||
* @param {Buffer} data Input data
|
||||
* @returns {string} MD5 hash of the input data
|
||||
*/
|
||||
function getMd5Hash(data) {
|
||||
return crypto
|
||||
.createHash('md5')
|
||||
.update(data)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the WASM binaries from the sillytavern-transformers package to the dist folder.
|
||||
*/
|
||||
function copyWasmFiles() {
|
||||
if (!fs.existsSync('./dist')) {
|
||||
fs.mkdirSync('./dist');
|
||||
}
|
||||
|
||||
const listDir = fs.readdirSync('./node_modules/sillytavern-transformers/dist');
|
||||
|
||||
for (const file of listDir) {
|
||||
if (file.endsWith('.wasm')) {
|
||||
const sourcePath = `./node_modules/sillytavern-transformers/dist/${file}`;
|
||||
const targetPath = `./dist/${file}`;
|
||||
|
||||
// Don't copy if the file already exists and is the same checksum
|
||||
if (fs.existsSync(targetPath)) {
|
||||
const sourceChecksum = getMd5Hash(fs.readFileSync(sourcePath));
|
||||
const targetChecksum = getMd5Hash(fs.readFileSync(targetPath));
|
||||
|
||||
if (sourceChecksum === targetChecksum) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
console.log(`${file} successfully copied to ./dist/${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Create default config files
|
||||
createDefaultFiles();
|
||||
// 2. Copy transformers WASM binaries from node_modules
|
||||
copyWasmFiles();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"temp": 0.8,
|
||||
"top_k": 28,
|
||||
"top_p": 0.94,
|
||||
"top_a": 0.00,
|
||||
"tfs": 0.96,
|
||||
"typical": 0.98,
|
||||
"rep_pen": 1.03,
|
||||
"rep_pen_slope": 0.8,
|
||||
"rep_pen_range": 120.0,
|
||||
"ikgen": 200,
|
||||
"sampler_order": [
|
||||
6,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 1.0,
|
||||
"top_p": 0.9,
|
||||
"top_k": 40,
|
||||
"top_a": 0.0,
|
||||
"tfs": 0.9,
|
||||
"typical": 1.0,
|
||||
"rep_pen": 1.01,
|
||||
"rep_pen_slope": 0.9,
|
||||
"rep_pen_range": 1024,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 0.43,
|
||||
"top_p": 0.96,
|
||||
"top_k": 0,
|
||||
"top_a": 0.0,
|
||||
"tfs": 0.68,
|
||||
"typical": 1.0,
|
||||
"rep_pen": 1.17,
|
||||
"rep_pen_slope": 0.2,
|
||||
"rep_pen_range": 1024,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 0.65,
|
||||
"top_p": 0.9,
|
||||
"top_k": 0,
|
||||
"top_a": 0.0,
|
||||
"tfs": 0.9,
|
||||
"typical": 1.0,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"rep_pen_range": 1024,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 0.79,
|
||||
"top_k": 0,
|
||||
"top_p": 0.9,
|
||||
"top_a": 0,
|
||||
"typical": 1,
|
||||
"tfs": 0.95,
|
||||
"rep_pen": 1.19,
|
||||
"rep_pen_range": 1024,
|
||||
"rep_pen_slope": 0.9,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 0.79,
|
||||
"top_p": 0.9,
|
||||
"top_k": 0,
|
||||
"top_a": 0.0,
|
||||
"tfs": 0.95,
|
||||
"typical": 1.0,
|
||||
"rep_pen": 1.19,
|
||||
"rep_pen_slope": 0.9,
|
||||
"rep_pen_range": 1024,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 0.65,
|
||||
"top_p": 0.9,
|
||||
"top_k": 0,
|
||||
"top_a": 0.0,
|
||||
"tfs": 0.9,
|
||||
"typical": 1.0,
|
||||
"rep_pen": 1.08,
|
||||
"rep_pen_slope": 0.9,
|
||||
"rep_pen_range": 1024,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 0.8,
|
||||
"top_p": 0.94,
|
||||
"top_k": 15,
|
||||
"tfs": 0.96,
|
||||
"typical": 0.98,
|
||||
"top_a": 0.01,
|
||||
"rep_pen": 1.02,
|
||||
"rep_pen_slope": 0.8,
|
||||
"rep_pen_range": 256.0,
|
||||
"sampler_order": [
|
||||
6,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
5
|
||||
]
|
||||
}
|
25
public/KoboldAI Settings/Miro Bronze.settings
Normal file
25
public/KoboldAI Settings/Miro Bronze.settings
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"temp": 1.06,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
],
|
||||
"mirostat": 2,
|
||||
"mirostat_tau": 9.61,
|
||||
"mirostat_eta": 1,
|
||||
"use_default_badwordsids": true
|
||||
}
|
25
public/KoboldAI Settings/Miro Gold.settings
Normal file
25
public/KoboldAI Settings/Miro Gold.settings
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"temp": 1.17,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
],
|
||||
"mirostat": 2,
|
||||
"mirostat_tau": 9.91,
|
||||
"mirostat_eta": 1,
|
||||
"use_default_badwordsids": true
|
||||
}
|
25
public/KoboldAI Settings/Miro Silver.settings
Normal file
25
public/KoboldAI Settings/Miro Silver.settings
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"temp": 1.17,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"top_k": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen_slope": 0.9,
|
||||
"single_line": false,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
2,
|
||||
5
|
||||
],
|
||||
"mirostat": 2,
|
||||
"mirostat_tau": 9.62,
|
||||
"mirostat_eta": 1,
|
||||
"use_default_badwordsids": true
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"temp": 1,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"top_a": 0.0,
|
||||
"tfs": 0.97,
|
||||
"typical": 1.0,
|
||||
"rep_pen": 1.04,
|
||||
"rep_pen_slope": 0.0,
|
||||
"rep_pen_range": 1400,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [3, 2, 1, 0],
|
||||
"temperature": 1.15,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 0,
|
||||
"top_p": 0.95,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.8,
|
||||
"repetition_penalty": 2.75,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 7.02,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [1, 0, 3],
|
||||
"temperature": 1.33,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 13,
|
||||
"top_p": 1,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.836,
|
||||
"repetition_penalty": 2.366,
|
||||
"repetition_penalty_range": 400,
|
||||
"repetition_penalty_slope": 0.33,
|
||||
"repetition_penalty_frequency": 0.01,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [0, 1, 2, 3],
|
||||
"temperature": 0.585,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.87,
|
||||
"repetition_penalty": 3.05,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0.33,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [2, 1, 3, 0],
|
||||
"temperature": 0.63,
|
||||
"max_length": 90,
|
||||
"min_length": 1,
|
||||
"tail_free_sampling": 0.975,
|
||||
"repetition_penalty": 1.148125,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"repetition_penalty_slope": 0.09,
|
||||
"max_context":2048,
|
||||
"top_p": 0.975,
|
||||
"top_k": 0,
|
||||
"top_a": 1,
|
||||
"typical_p": 1
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [3, 4, 5, 2, 0],
|
||||
"temperature": 1.33,
|
||||
"max_length": 90,
|
||||
"min_length": 1,
|
||||
"tail_free_sampling": 0.937,
|
||||
"repetition_penalty": 1.05,
|
||||
"repetition_penalty_range": 560,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"repetition_penalty_slope": 0.18,
|
||||
"max_context": 2048,
|
||||
"top_p": 0.88,
|
||||
"top_k": 0,
|
||||
"top_a": 0.085,
|
||||
"typical_p": 0.985
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [2, 1, 3, 0],
|
||||
"temperature": 0.86,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 20,
|
||||
"top_p": 0.95,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 1,
|
||||
"repetition_penalty": 2.25,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0.09,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [2, 1, 3, 0],
|
||||
"temperature": 0.63,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 0,
|
||||
"top_p": 0.975,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.975,
|
||||
"repetition_penalty": 2.975,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0.09,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context":2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [2, 1, 3, 0],
|
||||
"temperature": 0.94,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 12,
|
||||
"top_p": 1,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.94,
|
||||
"repetition_penalty": 2.66,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0.18,
|
||||
"repetition_penalty_frequency": 0.013,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [1, 5, 4, 3, 0],
|
||||
"temperature": 1.25,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 300,
|
||||
"top_p": 1,
|
||||
"top_a": 0.782,
|
||||
"typical_p": 0.95,
|
||||
"tail_free_sampling": 0.802,
|
||||
"repetition_penalty": 2.075,
|
||||
"repetition_penalty_range": 512,
|
||||
"repetition_penalty_slope": 0.36,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [0],
|
||||
"temperature": 0.6889,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 1,
|
||||
"repetition_penalty": 1,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0,
|
||||
"repetition_penalty_frequency": 0.1,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [1, 0, 3],
|
||||
"temperature": 1.07,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 264,
|
||||
"top_p": 1,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.925,
|
||||
"repetition_penalty": 2.165,
|
||||
"repetition_penalty_range": 404,
|
||||
"repetition_penalty_slope": 0.84,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context":2048
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"order": [3, 0],
|
||||
"temperature": 1.348,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
"top_k": 64,
|
||||
"top_p": 0.909,
|
||||
"top_a": 1,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.688,
|
||||
"repetition_penalty": 4.967,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0.09,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context": 2048
|
||||
}
|
@@ -1,11 +1,13 @@
|
||||
{
|
||||
"temp": 0.5,
|
||||
"top_p": 0.9,
|
||||
"temp": 1.06,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"rep_pen": 1.1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
@@ -15,7 +17,8 @@
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 0,
|
||||
"mirostat_tau": 5,
|
||||
"mirostat_eta": 0.1
|
||||
"mirostat_mode": 2,
|
||||
"mirostat_tau": 9.61,
|
||||
"mirostat_eta": 1,
|
||||
"rep_pen_size": 0
|
||||
}
|
24
public/TextGen Settings/Miro Gold.settings
Normal file
24
public/TextGen Settings/Miro Gold.settings
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"temp": 1.17,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 2,
|
||||
"mirostat_tau": 9.91,
|
||||
"mirostat_eta": 1,
|
||||
"rep_pen_size": 0
|
||||
}
|
24
public/TextGen Settings/Miro Silver.settings
Normal file
24
public/TextGen Settings/Miro Silver.settings
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"temp": 1.17,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false,
|
||||
"mirostat_mode": 2,
|
||||
"mirostat_tau": 9.62,
|
||||
"mirostat_eta": 1,
|
||||
"rep_pen_size": 0
|
||||
}
|
6
public/context/OldDefault.json
Normal file
6
public/context/OldDefault.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Circumstances and context of the dialogue: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "\nThen the roleplay chat between {{user}} and {{char}} begins.\n",
|
||||
"example_separator": "This is how {{char}} should talk",
|
||||
"name": "OldDefault"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Pygmalion",
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{{char}}}'s Persona: {{description}}\n{{/if}}{{#if personality}}Personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "<START>",
|
||||
"example_separator": "<START>"
|
||||
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
|
||||
"chat_start": "",
|
||||
"example_separator": ""
|
||||
}
|
||||
|
@@ -94,3 +94,117 @@ input.extension_missing[type="checkbox"] {
|
||||
margin-right: 10px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* Fixes order of settings for extensions */
|
||||
#extensions_settings,
|
||||
#extensions_settings2 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/** LEFT COLUMN **/
|
||||
#extensions_settings>.expression_settings {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensions_settings>.background_settings {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensions_settings>.sd_settings {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensions_settings>#tts_settings {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensions_settings>#rvc_settings {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensions_settings>.objective-settings {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensions_settings>#speech_recognition_settings {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensions_settings>#audio_settings {
|
||||
order: 8;
|
||||
}
|
||||
|
||||
#extensions_settings>#assets_ui {
|
||||
order: 9;
|
||||
}
|
||||
|
||||
/** RIGHT COLUMN **/
|
||||
#extensions_settings2>.translation_settings {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensions_settings2>.caption_settings {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensions_settings2>.quickReplySettings {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensions_settings2>.idle-settings {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensions_settings2>#memory_settings {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensions_settings2>.hypebot_settings {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensions_settings2>.regex_settings {
|
||||
order: 7;
|
||||
}
|
||||
|
||||
#extensions_settings2>.vectors_settings {
|
||||
order: 8;
|
||||
}
|
||||
|
||||
#extensions_settings2>.chromadb_settings {
|
||||
order: 9;
|
||||
}
|
||||
|
||||
#extensions_settings2>.randomizer_settings {
|
||||
order: 10;
|
||||
}
|
||||
|
||||
/** WAND MENU **/
|
||||
#extensionsMenu>#ttsExtensionMenuItem {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
#extensionsMenu>#sd_gen {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
#extensionsMenu>#send_picture {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
#extensionsMenu>#token_counter {
|
||||
order: 4;
|
||||
}
|
||||
|
||||
#extensionsMenu>#objective-task-manual-check-menu-item {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
#extensionsMenu>#roll_dice {
|
||||
order: 6;
|
||||
}
|
||||
|
||||
#extensionsMenu>#translate_chat {
|
||||
order: 7;
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 5px;
|
||||
border: 1px solid var(--grey30);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#select_chat_popup {
|
||||
@@ -91,7 +91,6 @@
|
||||
#top-settings-holder,
|
||||
#top-bar {
|
||||
position: fixed;
|
||||
padding-top: 3px;
|
||||
width: 100vw;
|
||||
width: 100svw;
|
||||
}
|
||||
@@ -114,14 +113,14 @@
|
||||
/* ,
|
||||
#world_popup */
|
||||
{
|
||||
max-height: calc(100vh - 36px);
|
||||
max-height: calc(100svh - 36px);
|
||||
/*max-height: calc(100vh - 36px);
|
||||
max-height: calc(100svh - 36px);*/
|
||||
width: 100% !important;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
left: 0 !important;
|
||||
resize: none !important;
|
||||
top: 36px;
|
||||
top: var(--topBarBlockSize);
|
||||
}
|
||||
|
||||
.wi-settings {
|
||||
@@ -135,15 +134,15 @@
|
||||
|
||||
#character_popup,
|
||||
#send_form {
|
||||
border: 1px solid var(--grey30);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
max-width: 100dvw;
|
||||
}
|
||||
|
||||
#chat {
|
||||
border-left: 1px solid var(--grey30);
|
||||
border-right: 1px solid var(--grey30);
|
||||
border-bottom: 1px solid var(--grey30);
|
||||
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
align-items: start;
|
||||
align-content: start;
|
||||
overflow-y: auto;
|
||||
@@ -161,6 +160,7 @@
|
||||
}
|
||||
|
||||
#showRawPrompt,
|
||||
#copyPromptToClipboard,
|
||||
#groupCurrentMemberPopoutButton {
|
||||
display: none;
|
||||
}
|
||||
@@ -175,11 +175,11 @@
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
overflow-y: hidden;
|
||||
border-left: 1px solid var(--grey30);
|
||||
border-right: 1px solid var(--grey30);
|
||||
border-bottom: 1px solid var(--grey30);
|
||||
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 0 0 20px 20px;
|
||||
top: 36px !important;
|
||||
top: var(--topBarBlockSize) !important;
|
||||
left: 0 !important;
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
}
|
||||
@@ -270,6 +270,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1001px) {
|
||||
|
||||
#PygOverrides,
|
||||
#ContextFormatting,
|
||||
#UI-Theme-Block,
|
||||
#UI-Customization,
|
||||
#power-user-options-block {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*landscape mode phones and ipads*/
|
||||
@media screen and (max-width: 1000px) and (orientation: landscape) {
|
||||
body.waifuMode img.expression {
|
||||
|
@@ -13,7 +13,7 @@
|
||||
grid-column-end: 4;
|
||||
width: 100%;
|
||||
margin: 0.5em 0;
|
||||
background-image: linear-gradient(90deg, var(--transparent), var(--white30a), var(--transparent));
|
||||
background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBorderColor), var(--transparent));
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
@@ -54,12 +54,13 @@
|
||||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt {
|
||||
align-items: center;
|
||||
padding: 0.5em;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt .prompt_manager_prompt_controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: calc(var(--mainFontSize)*1.2);
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt .prompt_manager_prompt_controls span {
|
||||
@@ -77,7 +78,7 @@
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
filter: drop-shadow(0px 0px 2px black);
|
||||
opacity: 0.2;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt span span:hover {
|
||||
@@ -108,7 +109,7 @@
|
||||
#completion_prompt_manager_popup .completion_prompt_manager_prompt {
|
||||
margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
#completion_prompt_manager_popup .completion_prompt_manager_popup_header {
|
||||
@@ -171,6 +172,10 @@
|
||||
color: var(--white30a);
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt:not(.completion_prompt_manager_prompt_disabled) .prompt-manager-toggle-action {
|
||||
color: var(--SmartThemeQuoteColor);
|
||||
}
|
||||
|
||||
#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt.completion_prompt_manager_prompt_disabled {
|
||||
border: 1px solid var(--white20a);
|
||||
}
|
||||
@@ -260,7 +265,7 @@
|
||||
top: var(--topBarBlockSize);
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
padding: 1em;
|
||||
border: 1px solid #333333;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
flex-direction: column;
|
||||
z-index: 3010 !important;
|
||||
border-radius: 0 0 20px 20px;
|
||||
|
@@ -14,7 +14,8 @@
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
color: rgb(188, 193, 200, 1);
|
||||
border: 1px solid #333;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
@@ -61,7 +62,8 @@
|
||||
#rm_group_add_members {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border: 1px solid grey;
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
;
|
||||
border-radius: 10px;
|
||||
background-color: var(--black30a);
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
/* Customize the dropdown */
|
||||
.select2-dropdown {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border: 1px solid var(--white30a) !important;
|
||||
border: 1px solid var(--SmartThemeBorderColor) !important;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 5px black;
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
@@ -19,11 +19,24 @@
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-search__field {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
line-height: revert;
|
||||
padding-left: unset;
|
||||
}
|
||||
|
||||
.select2-container .select2-results>.select2-results__options {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
|
||||
padding: revert;
|
||||
border-right: 1px solid var(--white30a);
|
||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
font-size: 1.1em;
|
||||
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__display {
|
||||
@@ -34,7 +47,7 @@
|
||||
.select2-search__field {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
@@ -58,27 +71,30 @@
|
||||
background-color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple {
|
||||
.select2-container .select2-selection--multiple,
|
||||
.select2-container .select2-selection--single {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.select2-container.select2-container--focus .select2-selection--multiple {
|
||||
border: 1px solid var(--white30a);
|
||||
.select2-container.select2-container--focus .select2-selection--multiple,
|
||||
.select2-container.select2-container--focus .select2-selection--single {
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice {
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice,
|
||||
.select2-container .select2-selection--single .select2-selection__choice {
|
||||
border-radius: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
box-sizing: border-box;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black30a);
|
||||
border-color: var(--white30a);
|
||||
border-color: var(--SmartThemeBorderColor);
|
||||
font-size: calc(var(--mainFontSize) - 5%);
|
||||
text-shadow: none !important;
|
||||
}
|
||||
@@ -114,12 +130,13 @@
|
||||
margin-top: -7px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
|
||||
.select2-container .select2-selection--multiple .select2-selection__choice__remove,
|
||||
.select2-container .select2-selection--single .select2-selection__choice__remove {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
}
|
||||
|
||||
|
@@ -262,6 +262,10 @@
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.flexFlowRow {
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.wideMinContent {
|
||||
width: min-content;
|
||||
}
|
||||
@@ -404,6 +408,7 @@
|
||||
|
||||
.widthFitContent {
|
||||
width: fit-content;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.flexGap5 {
|
||||
|
@@ -109,6 +109,7 @@
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.tags_inline .tag {
|
||||
@@ -128,6 +129,13 @@
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
filter: brightness(0.8);
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
||||
.rm_tag_filter .tag:hover {
|
||||
|
||||
opacity: 1;
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
.tags_view,
|
||||
|
@@ -8,15 +8,10 @@ body.tts .mes_narrate {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
body.no-hotswap .hotswap {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.no-timer .mes_timer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.no-hotswap .hotswap,
|
||||
body.no-timer .mes_timer,
|
||||
body.no-timestamps .timestamp,
|
||||
body.no-tokenCount .tokenCounterDisplay,
|
||||
body.no-mesIDDisplay .mesIDDisplay,
|
||||
body.no-modelIcons .icon-svg {
|
||||
display: none !important;
|
||||
@@ -122,7 +117,7 @@ body.big-avatars .avatar img {
|
||||
height: 90px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
border: 1px solid var(--black30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@@ -196,7 +191,7 @@ body.bubblechat .mes {
|
||||
border-radius: 10px;
|
||||
background-color: var(--SmartThemeBotMesBlurTintColor);
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
body.bubblechat .mes[is_user="true"] {
|
||||
@@ -262,31 +257,16 @@ body.no-blur #bg_custom {
|
||||
|
||||
}
|
||||
|
||||
body:not(.bubblechat).no-blur #chat,
|
||||
body.no-blur #top-bar,
|
||||
body.no-blur #send_form {
|
||||
background-color: var(--SmartThemeBlurTintColor) !important;
|
||||
}
|
||||
|
||||
body.no-blur #options,
|
||||
body.no-blur .ui-widget-content,
|
||||
body.no-blur #floatingPrompt,
|
||||
body.no-blur #extensionsMenu,
|
||||
body.no-blur .list-group,
|
||||
body.no-blur #character_popup,
|
||||
body.no-blur #world_popup,
|
||||
body.no-blur #dialogue_popup,
|
||||
body.no-blur #select_chat_popup,
|
||||
body.no-blur .drawer-content,
|
||||
body.no-blur .select2-results__options {
|
||||
background-color: black !important;
|
||||
}
|
||||
|
||||
/* wAIfu mode*/
|
||||
|
||||
body.waifuMode #top-bar {
|
||||
border-radius: 0 0 20px 20px;
|
||||
border: 1px solid var(--grey30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
}
|
||||
|
||||
body.waifuMode #sheld {
|
||||
@@ -297,7 +277,7 @@ body.waifuMode #sheld {
|
||||
}
|
||||
|
||||
body.waifuMode #chat {
|
||||
border-top: 1px solid var(--grey30a);
|
||||
border-top: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
|
||||
@@ -347,6 +327,7 @@ body.movingUI #sheld,
|
||||
body.movingUI .drawer-content,
|
||||
body.movingUI #expression-holder,
|
||||
body.movingUI .zoomed_avatar,
|
||||
body.movingUI .draggable,
|
||||
body.movingUI #floatingPrompt,
|
||||
body.movingUI #groupMemberListPopout {
|
||||
resize: both;
|
||||
@@ -357,6 +338,7 @@ body.movingUI #groupMemberListPopout {
|
||||
height: 120px;
|
||||
margin-top: 0;
|
||||
top: 50px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/*No Text Shadows Mode*/
|
||||
@@ -364,3 +346,11 @@ body.movingUI #groupMemberListPopout {
|
||||
body.noShadows * {
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
body.expandMessageActions .mes .mes_buttons .extraMesButtons {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
|
||||
display: none !important;
|
||||
}
|
592
public/i18n.json
592
public/i18n.json
@@ -4,8 +4,9 @@
|
||||
"ja-jp",
|
||||
"ko-kr",
|
||||
"ru-ru",
|
||||
"it-it",
|
||||
"nl-nl"
|
||||
"it-it",
|
||||
"nl-nl",
|
||||
"es-spa"
|
||||
],
|
||||
"zh-cn": {
|
||||
"clickslidertips": "点击滑块右侧数字可手动输入",
|
||||
@@ -113,8 +114,6 @@
|
||||
"to get your NovelAI API key.": "以获取您的 NovelAI API 密钥。",
|
||||
"Enter it in the box below": "将其输入到下面的输入框中",
|
||||
"Novel AI Model": "NovelAI 模型",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "无连接",
|
||||
"oobabooga/text-generation-webui": "",
|
||||
"Make sure you run it with": "确保启动时包含 --api 参数",
|
||||
@@ -156,13 +155,9 @@
|
||||
"Always add character's name to prompt": "始终将角色名称添加到提示符中",
|
||||
"Keep Example Messages in Prompt": "保持示例消息提示",
|
||||
"Remove Empty New Lines from Output": "从输出中删除空的新行",
|
||||
"Pygmalion Formatting": "Pygmalion 格式",
|
||||
"Disabled for all models": "对所有模型禁用",
|
||||
"Automatic (based on model name)": "自动(基于型号名称)",
|
||||
"Enabled for all models": "所有模型启用",
|
||||
"Multigen": "Multigen",
|
||||
"First chunk (tokens)": "第一个区块(Tokens)",
|
||||
"Next chunks (tokens)": "接下来的区块(Tokens)",
|
||||
"Anchors Order": "锚点顺序",
|
||||
"Character then Style": "字符然后样式",
|
||||
"Style then Character": "样式然后字符",
|
||||
@@ -286,7 +281,6 @@
|
||||
"Regenerate": "重新生成",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "预设",
|
||||
"Message Sound": "AI 消息提示音",
|
||||
"Author's Note": "作者注释",
|
||||
@@ -670,8 +664,6 @@
|
||||
"to get your NovelAI API key.": "あなたの NovelAI API キーを取得するために。",
|
||||
"Enter it in the box below": "以下のボックスに入力してください",
|
||||
"Novel AI Model": "NovelAI モデル",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "接続なし",
|
||||
"oobabooga/text-generation-webui": "",
|
||||
"Make sure you run it with": "必ず --api の引数を含めて起動してください",
|
||||
@@ -712,13 +704,9 @@
|
||||
"Always add character's name to prompt": "常にキャラクター名をプロンプトに追加",
|
||||
"Keep Example Messages in Prompt": "プロンプトに例示メッセージを保持",
|
||||
"Remove Empty New Lines from Output": "出力から空の改行を削除",
|
||||
"Pygmalion Formatting": "ピグマリオンフォーマット",
|
||||
"Disabled for all models": "すべてのモデルで無効",
|
||||
"Automatic (based on model name)": "自動(モデル名に基づく)",
|
||||
"Enabled for all models": "すべてのモデルで有効",
|
||||
"Multigen": "マルチジェン",
|
||||
"First chunk (tokens)": "最初のチャンク(トークン)",
|
||||
"Next chunks (tokens)": "次のチャンク(トークン)",
|
||||
"Anchors Order": "アンカーオーダー",
|
||||
"Character then Style": "キャラクター、次にスタイル",
|
||||
"Style then Character": "スタイル、次にキャラクター",
|
||||
@@ -841,7 +829,6 @@
|
||||
"Regenerate": "再生成",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "プリセット",
|
||||
"Message Sound": "メッセージ音",
|
||||
"Author's Note": "作者の注記",
|
||||
@@ -1229,8 +1216,6 @@
|
||||
"to get your NovelAI API key.": "자세히 읽어주세요.",
|
||||
"Enter it in the box below": "밑 입력창에 입력하세요.",
|
||||
"Novel AI Model": "NovelAI 모델",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "접속 실패",
|
||||
"oobabooga/text-generation-webui": "oobabooga/text-generation-webui",
|
||||
"Make sure you run it with": "--api 인수를 반드시 사용해야 합니다.",
|
||||
@@ -1270,13 +1255,9 @@
|
||||
"Always add character's name to prompt": "프롬프트에 항상 캐릭터 이름 삽입",
|
||||
"Keep Example Messages in Prompt": "예사 답변을 프롬프트에 유지",
|
||||
"Remove Empty New Lines from Output": "출력에서 빈줄 삭제",
|
||||
"Pygmalion Formatting": "Pygmalion 서식",
|
||||
"Disabled for all models": "모든 모델에 비활성화",
|
||||
"Automatic (based on model name)": "모델 서식 자동탐지",
|
||||
"Enabled for all models": "모든 모델에 활성화",
|
||||
"Multigen": "다수답변 생성",
|
||||
"First chunk (tokens)": "첫 말뭉치(토큰수)",
|
||||
"Next chunks (tokens)": "다음 말뭉치(토큰수)",
|
||||
"Anchors Order": "Anchors Order",
|
||||
"Character then Style": "캐릭터 다음 스타일",
|
||||
"Style then Character": "스타일 다음 캐릭터",
|
||||
@@ -1400,7 +1381,6 @@
|
||||
"Regenerate": "재생성",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "기본설정",
|
||||
"Message Sound": "메시지 효과음",
|
||||
"Author's Note": "글쓴이 쪽지",
|
||||
@@ -1712,19 +1692,19 @@
|
||||
"Enable this if the streaming doesn't work with your proxy": "Включите это, если потоковый вывод текста не работает с вашим прокси",
|
||||
"Context Size (tokens)": "Размер контекста (в токенах)",
|
||||
"Max Response Length (tokens)": "Максимальная длина ответа (в токенах)",
|
||||
"Temperature": "Temperature",
|
||||
"Frequency Penalty": "Frequency Penalty",
|
||||
"Presence Penalty": "Presence Penalty",
|
||||
"Temperature": "Температура",
|
||||
"Frequency Penalty": "Штраф за частоту",
|
||||
"Presence Penalty": "Штраф за присутствие",
|
||||
"Top-p": "Top-p",
|
||||
"Display bot response text chunks as they are generated": "Отображать ответ ИИ по мере генерации текста",
|
||||
"Top A": "Top-a",
|
||||
"Typical Sampling": "Typical Sampling",
|
||||
"Tail Free Sampling": "Tail Free Sampling",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Slope",
|
||||
"Typical Sampling": "Типичная выборка",
|
||||
"Tail Free Sampling": "Бесхвостовая выборка",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Склон",
|
||||
"Single-line mode": "Режим одной строки",
|
||||
"Top K": "Top-k",
|
||||
"Top P": "Top-p",
|
||||
"Do Sample": "Do Sample",
|
||||
"Do Sample": "Сделать образец",
|
||||
"Add BOS Token": "Добавить BOS-токен",
|
||||
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Добавлять BOS-токен в начале инструкции. Выключение этого может сделать ответы более креативными. ",
|
||||
"Ban EOS Token": "Заблокировать EOS-токен",
|
||||
@@ -1732,11 +1712,24 @@
|
||||
"Skip Special Tokens": "Пропускать специальные токены",
|
||||
"Beam search": "Поиск Beam",
|
||||
"Number of Beams": "Количество Beam",
|
||||
"Length Penalty": "Length Penalty",
|
||||
"Length Penalty": "Штраф за длину",
|
||||
"Early Stopping": "Преждевременная остановка",
|
||||
"Contrastive search": "Contrastive search",
|
||||
"Penalty Alpha": "Penalty Alpha",
|
||||
"Contrastive search": "Контрастный поиск",
|
||||
"Penalty Alpha": "Штраф Альфа",
|
||||
"Seed": "Зерно",
|
||||
"Epsilon Cutoff": "Отсечение эпсилона",
|
||||
"Eta Cutoff": "Отсечка Eta",
|
||||
"Negative Prompt": "Отрицательная подсказка",
|
||||
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (режим = 1 только для llama.cpp)",
|
||||
"Add text here that would make the AI generate things you don't want in your outputs.": "Добавьте сюда текст, который заставит ИИ генерировать то, что вы не хотите видеть в своих выводах",
|
||||
"Phrase Repetition Penalty": "Штраф за повторение фразы",
|
||||
"Preamble": "Преамбула",
|
||||
"Use style tags to modify the writing style of the output.": "Используйте теги стиля, чтобы изменить стиль написания вывода.",
|
||||
"Banned Tokens": "Запрещенные токены",
|
||||
"Sequences you don't want to appear in the output. One per line.": "Последовательности, которые вы не хотите отображать в выводе. По одному на строку.",
|
||||
"AI Module": "Модуль ИИ",
|
||||
"Changes the style of the generated text.": "Изменяет стиль создаваемого текста.",
|
||||
"Used if CFG Scale is unset globally, per chat or character": "Используется, если масштаб CFG не установлен глобально, для каждого чата или персонажа.",
|
||||
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
|
||||
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
|
||||
"NSFW Encouraged": "Поощрять NSFW",
|
||||
@@ -1759,7 +1752,7 @@
|
||||
"Prompt that is used when the Jailbreak toggle is on": "Инструкция, отправляемая ИИ при включенном JailBreak.",
|
||||
"Impersonation prompt": "Инструкция для перевоплощения",
|
||||
"Prompt that is used for Impersonation function": "Инструкция, отправляемая ИИ для генерации действий за пользователя",
|
||||
"Logit Bias": "Logit Bias",
|
||||
"Logit Bias": "Ошибка логита",
|
||||
"Helps to ban or reenforce the usage of certain words": "Позволяет запретить или поощрять использование определенных слов",
|
||||
"View / Edit bias preset": "Посмотреть/Настроить предустановку для bias",
|
||||
"Add bias entry": "Добавить инструкцию в Bias",
|
||||
@@ -1769,42 +1762,70 @@
|
||||
"Bot must send this back to confirm jailbreak": "Это сообщение будет отправлено ИИ при успешном включении JailBreak.",
|
||||
"Character Note": "Заметки о персонаже",
|
||||
"Influences bot behavior in its responses": "Влияет на поведение ИИ и его ответы.",
|
||||
"Connect": "Подключить",
|
||||
"Test Message": "Тестовое сообщение",
|
||||
"API": "API",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Use Horde": "Использовать Horde",
|
||||
"API url": "API URL",
|
||||
"Register a Horde account for faster queue times": "Заведите учетную запись Horde для ускорения генерации",
|
||||
"Learn how to contribute your idle GPU cycles to the Hord": "Узнайте подробнее о том, как использовать время простоя GPU для Hord",
|
||||
"Adjust context size to worker capabilities": "Уточнить размер контекста для возможностей рабочих",
|
||||
"Adjust response length to worker capabilities": "Уточнить длинну ответа для возможностей рабочий",
|
||||
"Adjust context size to worker capabilities": "Уточнить размер контекста в соответствии с возможностями рабочих машин",
|
||||
"Adjust response length to worker capabilities": "Уточнить длинну ответа в соответствии с возможностями рабочих машин",
|
||||
"API key": "API-ключ",
|
||||
"Register": "Регист",
|
||||
"Get it here:": "Получить здесь:",
|
||||
"Register": "Регистрация",
|
||||
"View my Kudos": "Посмотреть мой рейтинг(Kudos)",
|
||||
"Enter": "Вставьте",
|
||||
"to use anonymous mode.": "чтобы использовать анонимный режим.",
|
||||
"For privacy reasons": "В целях конфиденциальности API-ключ будет скрыт после перезагрузки страницы",
|
||||
"Model": "Модель",
|
||||
"Models": "Модели",
|
||||
"Hold Control / Command key to select multiple models.": "Удерживайте Control / Command для выбора нескольких моделей.",
|
||||
"Horde models not loaded": "Модели Horde не загружены",
|
||||
"Not connected": "Нет подключения",
|
||||
"Not connected...": "Не подключено...",
|
||||
"Novel API key": "API-ключ для NovelAI",
|
||||
"Follow": "Следуйте",
|
||||
"these directions": "данным инструкциям",
|
||||
"to get your NovelAI API key.": "чтобы получить свой API-ключ от NovelAI",
|
||||
"Enter it in the box below": "Введите это в окошко ниже",
|
||||
"Novel AI Model": "Модель NovelAI",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "Нет подключения",
|
||||
"If you are using:": "Если вы используете:",
|
||||
"oobabooga/text-generation-webui": "",
|
||||
"Make sure you run it with": "Убедитесь, что при запуске указали аргумент --api",
|
||||
"Blocking API url": "Блокирующий API URL",
|
||||
"Mancer AI": "",
|
||||
"Use API key (Only required for Mancer)": "Нажмите на ячейку (и добавьте свой API ключ!):",
|
||||
"Blocking API url": "Блокирующий API url",
|
||||
"Example: http://127.0.0.1:5000/api": "Пример: http://127.0.0.1:5000/api",
|
||||
"Streaming API url": "Потоковый API URL",
|
||||
"Example: ws://127.0.0.1:5005/api/v1/stream": "Пример: ws://127.0.0.1:5005/api/v1/stream",
|
||||
"Mancer API key": "Mancer API ключ",
|
||||
"Example: https://neuro.mancer.tech/webui/MODEL/api": "Пример: https://neuro.mancer.tech/webui/MODEL/api",
|
||||
"to get your OpenAI API key.": "для получения API-ключа OpenAI",
|
||||
"Window AI Model": "Модель Window AI",
|
||||
"OpenAI Model": "Модель OpenAI",
|
||||
"Claude API Key": "Claude API ключ",
|
||||
"Get your key from": "Получить ключ из",
|
||||
"Anthropic's developer console": "Консоли разработчика Anthropic",
|
||||
"Slack and Poe cookies will not work here, do not bother trying.": "Файлы cookie Slack и Poe здесь не подойдут, не пытайтесь.",
|
||||
"Claude Model": "Модель Claude",
|
||||
"Scale API Key": "Scale API ключ",
|
||||
"Alt Method": "Альтернативный метод",
|
||||
"AI21 API Key": "AI21 API ключ",
|
||||
"AI21 Model": "Модель AI21",
|
||||
"View API Usage Metrics": "Посмотреть статистику использования API",
|
||||
"Show External models (provided by API)": "Показать \"сторонние\" модели (предоставленные API)",
|
||||
"Bot": "Бот:",
|
||||
"Allow fallback routes": "Разрешить резервные маршруты",
|
||||
"Allow fallback routes Description": "Автоматически выбирает альтернативную модель, если выбранная модель не может удовлетворить ваш запрос.",
|
||||
"OpenRouter API Key": "OpenRouter API ключ",
|
||||
"Connect to the API": "Соединение с API",
|
||||
"OpenRouter Model": "Модель OpenRouter",
|
||||
"View Remaining Credits": "Посмотреть оставшиеся кредиты",
|
||||
"Click Authorize below or get the key from": "Нажмите «Авторизовать» ниже или получите ключ от",
|
||||
"Auto-connect to Last Server": "Автоматическое подключение к последнему серверу",
|
||||
"View hidden API keys": "Посмотреть скрытые API-ключи",
|
||||
"Advanced Formatting": "Расширенное форматирование",
|
||||
"Context Template": "Шаблон контекста",
|
||||
"AutoFormat Overrides": "Замена АвтоФормата",
|
||||
"Disable description formatting": "Отключить форматирование описания",
|
||||
"Disable personality formatting": "Отключить форматирование личности",
|
||||
@@ -1812,18 +1833,26 @@
|
||||
"Disable example chats formatting": "Отключить форматирование примеров чата",
|
||||
"Disable chat start formatting": "Отключить форматирование начала чата",
|
||||
"Custom Chat Separator": "Пользовательское разделение чата",
|
||||
"Instruct Mode": "Режим Instruct",
|
||||
"Replace Macro in Custom Stopping Strings": "Заменить макрос в пользовательских стоп-строках",
|
||||
"Strip Example Messages from Prompt": "Удалить примеры сообщений из подсказки",
|
||||
"Story String": "Строка истории",
|
||||
"Example Separator": "Пример разделителя",
|
||||
"Chat Start": "Начало чата",
|
||||
"Activation Regex": "Активация Regex",
|
||||
"Instruct Mode": "Режим \"Инструктаж\"",
|
||||
"Enabled": "Включен",
|
||||
"Wrap Sequences with Newline": "Отделять последовательности красной строкой",
|
||||
"Include Names": "Показывать имена",
|
||||
"Force for Groups and Personas": "Усилия для Групп и Персон",
|
||||
"System Prompt": "Системная инструкция",
|
||||
"Instruct Mode Sequences": "Последовательности режима обучения",
|
||||
"Input Sequence": "Input Sequence",
|
||||
"Input Sequence": "Входная последовательность",
|
||||
"Output Sequence": "Выходная последовательность",
|
||||
"First Output Sequence": "Первая выходная последовательность",
|
||||
"Last Output Sequence": "Последняя выходная последовательность",
|
||||
"System Sequence Prefix": "Префикс системной последовательности",
|
||||
"System Sequence Suffix": "Суффикс системной последовательности",
|
||||
"Stop Sequence": "Stop Sequence",
|
||||
"Stop Sequence": "Последовательность остановки",
|
||||
"Context Formatting": "Форматирование контекста",
|
||||
"Tokenizer": "Токенайзер",
|
||||
"None / Estimated": "Отсутствует/Приблизительно",
|
||||
@@ -1832,13 +1861,9 @@
|
||||
"Always add character's name to prompt": "Всегда добавлять имя персонажа в инструкции",
|
||||
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в инструкции",
|
||||
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
|
||||
"Pygmalion Formatting": "Форматирование Pygmalion",
|
||||
"Disabled for all models": "Выключено для всех моделей",
|
||||
"Automatic (based on model name)": "Автоматически (выбор по названию модели)",
|
||||
"Enabled for all models": "Включить для всех моделей",
|
||||
"Multigen": "Мултиген",
|
||||
"First chunk (tokens)": "Первый отрезок (в токенах)",
|
||||
"Next chunks (tokens)": "Следующий отрезок (в токенах)",
|
||||
"Anchors Order": "Порядок Anchors",
|
||||
"Character then Style": "Персонаж после Стиля",
|
||||
"Style then Character": "Стиль после Персонажа",
|
||||
@@ -1846,6 +1871,9 @@
|
||||
"Style Anchor": "Стиль Anchors",
|
||||
"World Info": "Информация о мире",
|
||||
"Scan Depth": "Глубина сканирования",
|
||||
"Context %": "Процент контекста",
|
||||
"Budget Cap": "Бюджетный лимит",
|
||||
"(0 = disabled)": "(0 = отключено)",
|
||||
"depth": "глубина",
|
||||
"Token Budget": "Объем токенов",
|
||||
"budget": "объем",
|
||||
@@ -1854,7 +1882,10 @@
|
||||
"About soft prompts": "О мягких инструкциях",
|
||||
"None": "Отсутствует",
|
||||
"User Settings": "Настройки пользователя",
|
||||
"UI Customization": "Настройки UI",
|
||||
"UI Mode": "Режим интерфейса",
|
||||
"UI Language": "Язык интерфейса",
|
||||
"MovingUI Preset": "Предустановка MovingUI",
|
||||
"UI Customization": "Настройки интерфейса",
|
||||
"Avatar Style": "Стиль аватаров",
|
||||
"Circle": "Круглые",
|
||||
"Rectangle": "Прямоугольные",
|
||||
@@ -1866,6 +1897,13 @@
|
||||
"No Text Shadows": "Отключить тень текста",
|
||||
"Waifu Mode": "!!!РЕЖИМ ВАЙФУ!!!",
|
||||
"Message Timer": "Таймер сообщений",
|
||||
"Model Icon": "Показать значки модели",
|
||||
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
|
||||
"Advanced Character Search": "Расширенный поиск персонажей",
|
||||
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
|
||||
"Allow {{user}}: in bot messages": "Показать {{user}}: в ответах",
|
||||
"Show tags in responses": "Показывать <теги> в ответах",
|
||||
"Relaxed API URLS": "Смягченные URL-адреса API",
|
||||
"Characters Hotswap": "Смена персонажей на лету",
|
||||
"Movable UI Panels": "Перемещение панелей интерфейса",
|
||||
"Reset Panels": "Сбросить панели",
|
||||
@@ -1893,6 +1931,7 @@
|
||||
"Always disabled": "Всегда выключена",
|
||||
"Automatic (desktop)": "Автоматически (системные настройки)",
|
||||
"Always enabled": "Всегда включена",
|
||||
"Debug Menu": "Меню отладки",
|
||||
"Name": "Имя",
|
||||
"Your Avatar": "Ваш Аватар",
|
||||
"Extensions API:": "API для расширений",
|
||||
@@ -1961,7 +2000,6 @@
|
||||
"Regenerate": "Повторная генерация",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "Предустановки",
|
||||
"Message Sound": "Звук сообщения",
|
||||
"Author's Note": "Авторские заметки",
|
||||
@@ -1976,9 +2014,9 @@
|
||||
"Unrestricted maximum value for the context slider": "Неограниченное максимальное значение для ползунка с размером контекста",
|
||||
"Chat Completion Source": "Источник для Chat Completion",
|
||||
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
|
||||
"Review the Privacy statement": "Посмотреть Privacy statement",
|
||||
"Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",
|
||||
"Learn how to contribute your idel GPU cycles to the Horde": "Изучите, как использовать GPU в состоянии простоя на благо Horde",
|
||||
"Trusted workers only": "Только доверенные рабочие",
|
||||
"Trusted workers only": "Только доверенные рабочие машины",
|
||||
"For privacy reasons, your API key will be hidden after you reload the page.": "По причинам безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
|
||||
"-- Horde models not loaded --": "--Модель Horde не загружена--",
|
||||
"Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api",
|
||||
@@ -1992,7 +2030,7 @@
|
||||
"Trim spaces": "Обрезать пробелы",
|
||||
"Trim Incomplete Sentences": "Обрезать неоконченные предложения",
|
||||
"Include Newline": "Использовать красную строку",
|
||||
"Non-markdown strings": "Неподчеркиваемые Strings",
|
||||
"Non-markdown strings": "Строки без разметки",
|
||||
"Replace Macro in Sequences": "Заменить макросы в последовательности",
|
||||
"Presets": "Предустановки",
|
||||
"Separator": "Разделитель",
|
||||
@@ -2002,12 +2040,16 @@
|
||||
"Active World(s)": "Активные миры",
|
||||
"Character Lore Insertion Strategy": "Порядок включения сведений",
|
||||
"Sorted Evenly": "Равномерная сортировка",
|
||||
"Active World(s) for all chats": "Активные миры для всех чатов",
|
||||
"-- World Info not found --": "-- Информация о мире не найдена --",
|
||||
"--- Pick to Edit ---": "Редактировать",
|
||||
"or": "или",
|
||||
"Character Lore First": "Сначала сведения о персонаже",
|
||||
"Global Lore First": "Сначала общие сведения",
|
||||
"-- World Info not found --": "Информация о Мире не найдена",
|
||||
"Recursive Scan": "Рекурсивное сканирование",
|
||||
"Case Sensitive": "Учитывать регистр",
|
||||
"Match whole words": "Только полное совпадение",
|
||||
"Alert On Overflow": "Оповещение о переполнении",
|
||||
"World/Lore Editor": "Редактировать Мир/Сведения",
|
||||
"--- None ---": "---Отсутствует---",
|
||||
"Comma seperated (ignored if empty)": "Разделение запятыми (не используется, если оставлено пустым)",
|
||||
@@ -2044,8 +2086,12 @@
|
||||
"Not Connected": "Не подключено",
|
||||
"Persona Management": "Управление Персоной",
|
||||
"Persona Description": "Описание Персоны",
|
||||
"Your Persona": "Ваша Персона",
|
||||
"Show notifications on switching personas": "Показывать уведомления о смене персоны",
|
||||
"Blank": "Пустой",
|
||||
"In Story String / Chat Completion: Before Character Card": "В строке истории / Дополнение диалога: Перед Карточкой Персонажа",
|
||||
"In Story String / Chat Completion: After Character Card": "В строке истории / Дополнение диалога: После Карточки Персонажа",
|
||||
"In Story String / Prompt Manager": "В строке истории/Менеджер подсказок",
|
||||
"Top of Author's Note": "Перед Авторскими Заметками",
|
||||
"Bottom of Author's Note": "После Авторских Заметок",
|
||||
"How do I use this?": "Как мне это использовать?",
|
||||
@@ -2080,8 +2126,6 @@
|
||||
"Samplers Order": "Порядок семплирования",
|
||||
"Samplers will be applied in a top-down order. Use with caution.": "Семплирование будет применено в порядке сверху-вниз. Используйте с осторожностью.",
|
||||
"Repetition Penalty": "Наказание за повторы",
|
||||
"Epsilon Cutoff": "Epsilon Cutoff",
|
||||
"Eta Cutoff": "Eta Cutoff",
|
||||
"Rep. Pen. Range.": "Размер наказания за повторы",
|
||||
"Rep. Pen. Freq.": "Частота наказания за повторы",
|
||||
"Rep. Pen. Presence": "Наличие наказания за повторы",
|
||||
@@ -2092,7 +2136,8 @@
|
||||
"Show suggested replies. Not all bots support this.": "Показывать предлагаемые ответы. Не все боты поддерживают это.",
|
||||
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Безлимитный контекст' для активации кусочной генерации",
|
||||
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
|
||||
"Continue": "Пролдолжить",
|
||||
"Continue": "Продолжить",
|
||||
"CFG Scale": "Масштаб CFG",
|
||||
"Editing:": "Изменения",
|
||||
"AI reply prefix": "Префикс Ответ ИИ",
|
||||
"Custom Stopping Strings": "Настройка ограничивающий нитей",
|
||||
@@ -2243,7 +2288,7 @@
|
||||
"Change persona image": "Сменить изображение личности",
|
||||
"Delete persona": "Удалить личность"
|
||||
},
|
||||
"it-it": {
|
||||
"it-it": {
|
||||
"clickslidertips": "consigli per gli slider",
|
||||
"kobldpresets": "Preset Kobold",
|
||||
"guikoboldaisettings": "settaggi KoboldAI",
|
||||
@@ -2349,8 +2394,6 @@
|
||||
"to get your NovelAI API key.": "per acquisire la chiave API di NovelAI.",
|
||||
"Enter it in the box below": "Inserisci la chiave all'interno della casella qui sotto",
|
||||
"Novel AI Model": "Modello di NovelAI",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "Nessuna connessione",
|
||||
"oobabooga/text-generation-webui": "oobabooga/text-generation-webui",
|
||||
"Make sure you run it with": "assicurati di farlo partire con",
|
||||
@@ -2391,13 +2434,9 @@
|
||||
"Always add character's name to prompt": "Aggiungi sempre il nome del personaggio al prompt",
|
||||
"Keep Example Messages in Prompt": "Mantieni i messaggi d'esempio nel Prompt",
|
||||
"Remove Empty New Lines from Output": "Rimuovi le linee di testo vuote dall'output",
|
||||
"Pygmalion Formatting": "Formattazione Pygmalion",
|
||||
"Disabled for all models": "Disabilita per tutti i modelli",
|
||||
"Automatic (based on model name)": "Automatico (basato sul nome del modello)",
|
||||
"Enabled for all models": "Abilita per tutti i modelli",
|
||||
"Multigen": "Multigen",
|
||||
"First chunk (tokens)": "Primo pacchetto (in Token)",
|
||||
"Next chunks (tokens)": "Pacchetto successivo (in Token)",
|
||||
"Anchors Order": "Anchors Order",
|
||||
"Character then Style": "Prima il personaggio, successivamente lo stile",
|
||||
"Style then Character": "Prima lo stile, successivamente il personaggio",
|
||||
@@ -2521,7 +2560,6 @@
|
||||
"Regenerate": "Rigenera",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "preset",
|
||||
"Message Sound": "Suono del messaggio",
|
||||
"Author's Note": "Note d'autore",
|
||||
@@ -2799,133 +2837,130 @@
|
||||
"Select this as default persona for the new chats.": "Seleziona questo alterego come predefinito per tutte le nuove chat",
|
||||
"Change persona image": "Cambia l'immagine del tuo alterego",
|
||||
"Delete persona": "Elimina il tuo alterego",
|
||||
"--- Pick to Edit ---": "--- Scegli per modificare ---",
|
||||
"Add text here that would make the AI generate things you don't want in your outputs.": "Scrivi qui ciò che non vuoi l'IA generi nel suo output.",
|
||||
"write short replies, write replies using past tense": "Scrivi risposte brevi, scrivi risposte usando il passato",
|
||||
"Alert if your world info is greater than the allocated budget.": "Questo avvisa nel momento in cui le 'Info Mondo' consumano più di quanto allocato nel budget.",
|
||||
"Clear your cookie": "Cancella i cookie",
|
||||
"Restore new group chat prompt": "Ripristina il prompt della nuova chat di gruppo",
|
||||
"Save movingUI changes to a new file": "Salva i cambiamenti apportati alla posizione dei pannelli dell'UI (MovingUI) in un nuovo file",
|
||||
"Export all": "Esporta tutto",
|
||||
"Import": "Importa",
|
||||
"Insert": "Inserisci",
|
||||
"New": "Nuovo",
|
||||
"Prompts": "Prompt",
|
||||
"Tokens": "Token",
|
||||
"Reset current character": "Ripristina il personaggio attuale",
|
||||
"(0 = disabled)": "(0 = disabilitato)",
|
||||
"1 = disabled": "1 = disabilitato",
|
||||
"Activation Regex": "Attivazione Regex",
|
||||
"Active World(s) for all chats": "Attiva i Mondi per tutte le chat",
|
||||
"Add character names": "Aggiungi i nomi dei personaggi",
|
||||
"Add Memo": "Aggiungi note",
|
||||
"Advanced Character Search": "Ricerca dei personaggi avanzata",
|
||||
"Aggressive": "Aggressivo",
|
||||
"AI21 Model": "Modello AI21",
|
||||
"Alert On Overflow": "Avviso in caso di Overflow",
|
||||
"Allow fallback routes": "Permetti fallback routes",
|
||||
"Allow fallback routes Description": "Permetti la descrizione di fallback routes",
|
||||
"Alt Method": "Metodo Alt",
|
||||
"Alternate Greetings": "Alterna i saluti",
|
||||
"Alternate Greetings Hint": "Suggerimenti per i saluti alternati",
|
||||
"Alternate Greetings Subtitle": "Sottotitoli per i saluti alternati",
|
||||
"Assistant Prefill": "Assistant Prefill",
|
||||
"Banned Tokens": "Token banditi",
|
||||
"Blank": "In bianco",
|
||||
"Browser default": "Predefinito del browser",
|
||||
"Budget Cap": "Limite budget",
|
||||
"CFG": "CFG",
|
||||
"CFG Scale": "CFG Scale",
|
||||
"Changes the style of the generated text.": "Cambia lo stile del testo generato.",
|
||||
"Character Negatives": "Character Negatives",
|
||||
"Chat Negatives": "Chat Negatives",
|
||||
"Chat Scenario Override": "Sovrascrittura dello scenario della chat",
|
||||
"Chat Start": "Avvio della chat",
|
||||
"Claude Model": "Modello Claude",
|
||||
"Close chat": "Chiudi chat",
|
||||
"Context %": "Context %",
|
||||
"Context Template": "Context Template",
|
||||
"Count Penalty": "Count Penalty",
|
||||
"Example Separator": "Separatore d'esempio",
|
||||
"Exclude Assistant suffix": "Escludi il suffisso assistente",
|
||||
"Exclude the assistant suffix from being added to the end of prompt.": "Esclude il suffisso assistente dall'essere aggiunto alla fine del prompt.",
|
||||
"Force for Groups and Personas": "Forzalo per gruppi e alterego",
|
||||
"Global Negatives": "Global Negatives",
|
||||
"In Story String / Chat Completion: After Character Card": "Nella stringa narrativa / Chat Completion: Dopo la 'Carta Personaggio'",
|
||||
"In Story String / Chat Completion: Before Character Card": "Nella stringa narrativa / Chat Completion: Prima della 'Carta Personaggio",
|
||||
"Instruct": "Instruct",
|
||||
"Instruct Mode": "Modalità Instruct",
|
||||
"Last Sequence": "Ultima sequenza",
|
||||
"Lazy Chat Loading": "Caricamento svogliato della chat",
|
||||
"Least tokens": "Token minimi",
|
||||
"Light": "Leggero",
|
||||
"Load koboldcpp order": "Ripristina l'ordine di koboldcpp",
|
||||
"Main": "Principale",
|
||||
"Mancer API key": "Chiave API di Mancer",
|
||||
"Mancer API url": "Url API di Mancer",
|
||||
"May help the model to understand context. Names must only contain letters or numbers.": "Può aiutare il modello a comprendere meglio il contesto. I nomi devono contenere solo numeri e lettere.",
|
||||
"Medium": "Medium",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (mode=1 è valido solo per llama.cpp)",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat LR": "Mirostat LR",
|
||||
"Mirostat Mode": "Mirostat Mode",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Model Icon": "Icona del modello",
|
||||
"Most tokens": "Token massimi",
|
||||
"MovingUI Preset": "Preset MovingUI",
|
||||
"Negative Prompt": "Prompt negativo",
|
||||
"No Module": "Nessun modulo",
|
||||
"NSFW": "NSFW",
|
||||
"Nucleus Sampling": "Nucleus Sampling",
|
||||
"Off": "Spento",
|
||||
"OpenRouter API Key": "Chiave API di OpenRouter",
|
||||
"OpenRouter Model": "Modello OpenRouter",
|
||||
"or": "o",
|
||||
"Phrase Repetition Penalty": "Phrase Repetition Penalty",
|
||||
"Positive Prompt": "Prompt positivo",
|
||||
"Preamble": "Premessa",
|
||||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "Sovrascrittura del prompt (Per le API di OpenAI/Claude/Scale, Window/OpenRouter, e la Modalità Instruct)",
|
||||
"Prompt that is used when the NSFW toggle is O": "Prompt utilizzato quando l'interruttore NSFW è disattivato.",
|
||||
"Prose Augmenter": "Prose Augmenter",
|
||||
"Proxy Password": "Password proxy",
|
||||
"Quick Edit": "Editing rapido",
|
||||
"Random": "Casuale",
|
||||
"Relaxed API URLS": "URL API sciatto",
|
||||
"Replace Macro in Custom Stopping Strings": "Rimpiazza le macro nelle stringe d'arresto personalizzate",
|
||||
"Scale": "Scale",
|
||||
"Scale": "Scale",
|
||||
"Sequences you don't want to appear in the output. One per line.": "Sequenze che non vuoi appaiano nell'output. Una per linea.",
|
||||
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Impostato all'inizio degli Esempi di dialogo per indicare che un nuovo esempio di chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio degli esempi di dialogo per indicare che una nuova chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che una nuova chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che un nuova chat di gruppo sta per iniziare.",
|
||||
"Show External models (provided by API)": "Mostra modelli esterni (Forniti dall'API)",
|
||||
"Show Notifications Show notifications on switching personas": "Mostra una notifica quando l'alterego viene cambiato",
|
||||
"Show tags in responses": "Mostra i tag nelle risposte",
|
||||
"Story String": "Stringa narrativa",
|
||||
"Text Adventure": "Avventura testuale",
|
||||
"Text Gen WebUI (ooba/Mancer) presets": "Preset Text Gen WebUI (ooba/Mancer)",
|
||||
"Toggle Panels": "Interruttore pannelli",
|
||||
"Top A Sampling": "Top A Sampling",
|
||||
"Top K Sampling": "Top K Sampling",
|
||||
"UI Language": "Linguaggio interfaccia grafica",
|
||||
"Unlocked Context Size": "Sblocca dimensione contesto",
|
||||
"Usage Stats": "Statistiche di utilizzo",
|
||||
"Use AI21 Tokenizer": "Utilizza il Tokenizer di AI21",
|
||||
"Use API key (Only required for Mancer)": "Utilizza la chiave API (Necessario soltanto per Mancer)",
|
||||
"Use character author's note": "Utilizza le note d'autore del personaggio",
|
||||
"Use character CFG scales": "Utilizza CFG scales del personaggio",
|
||||
"Use Proxy password field instead. This input will be ignored.": "Utilizza il campo del password proxy al suo posto. Questo input verrà ignorato.",
|
||||
"Use style tags to modify the writing style of the output": "Utilizza lo stile delle tag per modificare lo stile di scrittura in output",
|
||||
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Utilizza il tokenizer appropiato per i modelli giurassici, visto che è più efficente di quello di GPT.",
|
||||
"Used if CFG Scale is unset globally, per chat or character": "Usato se CFG Scale non è settato globalmente, per le chat o per i personaggi",
|
||||
"Very aggressive": "Esageratamente aggressivo",
|
||||
"Very light": "Esageratamente leggero",
|
||||
"Welcome to SillyTavern!": "Benvenuto in SillyTavern!",
|
||||
"Will be used as a password for the proxy instead of API key.": "Verrà usato come password per il proxy invece che la chiave API.",
|
||||
"Window AI Model": "Modello Window AI",
|
||||
"Your Persona": "Il tuo alterego"
|
||||
"--- Pick to Edit ---": "--- Scegli per modificare ---",
|
||||
"Add text here that would make the AI generate things you don't want in your outputs.": "Scrivi qui ciò che non vuoi l'IA generi nel suo output.",
|
||||
"write short replies, write replies using past tense": "Scrivi risposte brevi, scrivi risposte usando il passato",
|
||||
"Alert if your world info is greater than the allocated budget.": "Questo avvisa nel momento in cui le 'Info Mondo' consumano più di quanto allocato nel budget.",
|
||||
"Clear your cookie": "Cancella i cookie",
|
||||
"Restore new group chat prompt": "Ripristina il prompt della nuova chat di gruppo",
|
||||
"Save movingUI changes to a new file": "Salva i cambiamenti apportati alla posizione dei pannelli dell'UI (MovingUI) in un nuovo file",
|
||||
"Export all": "Esporta tutto",
|
||||
"Import": "Importa",
|
||||
"Insert": "Inserisci",
|
||||
"New": "Nuovo",
|
||||
"Prompts": "Prompt",
|
||||
"Tokens": "Token",
|
||||
"Reset current character": "Ripristina il personaggio attuale",
|
||||
"(0 = disabled)": "(0 = disabilitato)",
|
||||
"1 = disabled": "1 = disabilitato",
|
||||
"Activation Regex": "Attivazione Regex",
|
||||
"Active World(s) for all chats": "Attiva i Mondi per tutte le chat",
|
||||
"Add character names": "Aggiungi i nomi dei personaggi",
|
||||
"Add Memo": "Aggiungi note",
|
||||
"Advanced Character Search": "Ricerca dei personaggi avanzata",
|
||||
"Aggressive": "Aggressivo",
|
||||
"AI21 Model": "Modello AI21",
|
||||
"Alert On Overflow": "Avviso in caso di Overflow",
|
||||
"Allow fallback routes": "Permetti fallback routes",
|
||||
"Allow fallback routes Description": "Permetti la descrizione di fallback routes",
|
||||
"Alt Method": "Metodo Alt",
|
||||
"Alternate Greetings": "Alterna i saluti",
|
||||
"Alternate Greetings Hint": "Suggerimenti per i saluti alternati",
|
||||
"Alternate Greetings Subtitle": "Sottotitoli per i saluti alternati",
|
||||
"Assistant Prefill": "Assistant Prefill",
|
||||
"Banned Tokens": "Token banditi",
|
||||
"Blank": "In bianco",
|
||||
"Browser default": "Predefinito del browser",
|
||||
"Budget Cap": "Limite budget",
|
||||
"CFG": "CFG",
|
||||
"CFG Scale": "CFG Scale",
|
||||
"Changes the style of the generated text.": "Cambia lo stile del testo generato.",
|
||||
"Character Negatives": "Character Negatives",
|
||||
"Chat Negatives": "Chat Negatives",
|
||||
"Chat Scenario Override": "Sovrascrittura dello scenario della chat",
|
||||
"Chat Start": "Avvio della chat",
|
||||
"Claude Model": "Modello Claude",
|
||||
"Close chat": "Chiudi chat",
|
||||
"Context %": "Context %",
|
||||
"Context Template": "Context Template",
|
||||
"Count Penalty": "Count Penalty",
|
||||
"Example Separator": "Separatore d'esempio",
|
||||
"Exclude Assistant suffix": "Escludi il suffisso assistente",
|
||||
"Exclude the assistant suffix from being added to the end of prompt.": "Esclude il suffisso assistente dall'essere aggiunto alla fine del prompt.",
|
||||
"Force for Groups and Personas": "Forzalo per gruppi e alterego",
|
||||
"Global Negatives": "Global Negatives",
|
||||
"In Story String / Chat Completion: After Character Card": "Nella stringa narrativa / Chat Completion: Dopo la 'Carta Personaggio'",
|
||||
"In Story String / Chat Completion: Before Character Card": "Nella stringa narrativa / Chat Completion: Prima della 'Carta Personaggio",
|
||||
"Instruct": "Instruct",
|
||||
"Instruct Mode": "Modalità Instruct",
|
||||
"Last Sequence": "Ultima sequenza",
|
||||
"Least tokens": "Token minimi",
|
||||
"Light": "Leggero",
|
||||
"Load koboldcpp order": "Ripristina l'ordine di koboldcpp",
|
||||
"Main": "Principale",
|
||||
"Mancer API key": "Chiave API di Mancer",
|
||||
"Mancer API url": "Url API di Mancer",
|
||||
"May help the model to understand context. Names must only contain letters or numbers.": "Può aiutare il modello a comprendere meglio il contesto. I nomi devono contenere solo numeri e lettere.",
|
||||
"Medium": "Medium",
|
||||
"Mirostat": "Mirostat",
|
||||
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (mode=1 è valido solo per llama.cpp)",
|
||||
"Mirostat Eta": "Mirostat Eta",
|
||||
"Mirostat LR": "Mirostat LR",
|
||||
"Mirostat Mode": "Mirostat Mode",
|
||||
"Mirostat Tau": "Mirostat Tau",
|
||||
"Model Icon": "Icona del modello",
|
||||
"Most tokens": "Token massimi",
|
||||
"MovingUI Preset": "Preset MovingUI",
|
||||
"Negative Prompt": "Prompt negativo",
|
||||
"No Module": "Nessun modulo",
|
||||
"NSFW": "NSFW",
|
||||
"Nucleus Sampling": "Nucleus Sampling",
|
||||
"Off": "Spento",
|
||||
"OpenRouter API Key": "Chiave API di OpenRouter",
|
||||
"OpenRouter Model": "Modello OpenRouter",
|
||||
"or": "o",
|
||||
"Phrase Repetition Penalty": "Phrase Repetition Penalty",
|
||||
"Positive Prompt": "Prompt positivo",
|
||||
"Preamble": "Premessa",
|
||||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "Sovrascrittura del prompt (Per le API di OpenAI/Claude/Scale, Window/OpenRouter, e la Modalità Instruct)",
|
||||
"Prompt that is used when the NSFW toggle is O": "Prompt utilizzato quando l'interruttore NSFW è disattivato.",
|
||||
"Prose Augmenter": "Prose Augmenter",
|
||||
"Proxy Password": "Password proxy",
|
||||
"Quick Edit": "Editing rapido",
|
||||
"Random": "Casuale",
|
||||
"Relaxed API URLS": "URL API sciatto",
|
||||
"Replace Macro in Custom Stopping Strings": "Rimpiazza le macro nelle stringe d'arresto personalizzate",
|
||||
"Scale": "Scale",
|
||||
"Sequences you don't want to appear in the output. One per line.": "Sequenze che non vuoi appaiano nell'output. Una per linea.",
|
||||
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Impostato all'inizio degli Esempi di dialogo per indicare che un nuovo esempio di chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che una nuova chat sta per iniziare.",
|
||||
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Impostato all'inizio della cronologia chat per indicare che un nuova chat di gruppo sta per iniziare.",
|
||||
"Show External models (provided by API)": "Mostra modelli esterni (Forniti dall'API)",
|
||||
"Show Notifications Show notifications on switching personas": "Mostra una notifica quando l'alterego viene cambiato",
|
||||
"Show tags in responses": "Mostra i tag nelle risposte",
|
||||
"Story String": "Stringa narrativa",
|
||||
"Text Adventure": "Avventura testuale",
|
||||
"Text Gen WebUI (ooba/Mancer) presets": "Preset Text Gen WebUI (ooba/Mancer)",
|
||||
"Toggle Panels": "Interruttore pannelli",
|
||||
"Top A Sampling": "Top A Sampling",
|
||||
"Top K Sampling": "Top K Sampling",
|
||||
"UI Language": "Linguaggio interfaccia grafica",
|
||||
"Unlocked Context Size": "Sblocca dimensione contesto",
|
||||
"Usage Stats": "Statistiche di utilizzo",
|
||||
"Use AI21 Tokenizer": "Utilizza il Tokenizer di AI21",
|
||||
"Use API key (Only required for Mancer)": "Utilizza la chiave API (Necessario soltanto per Mancer)",
|
||||
"Use character author's note": "Utilizza le note d'autore del personaggio",
|
||||
"Use character CFG scales": "Utilizza CFG scales del personaggio",
|
||||
"Use Proxy password field instead. This input will be ignored.": "Utilizza il campo del password proxy al suo posto. Questo input verrà ignorato.",
|
||||
"Use style tags to modify the writing style of the output": "Utilizza lo stile delle tag per modificare lo stile di scrittura in output",
|
||||
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Utilizza il tokenizer appropiato per i modelli giurassici, visto che è più efficente di quello di GPT.",
|
||||
"Used if CFG Scale is unset globally, per chat or character": "Usato se CFG Scale non è settato globalmente, per le chat o per i personaggi",
|
||||
"Very aggressive": "Esageratamente aggressivo",
|
||||
"Very light": "Esageratamente leggero",
|
||||
"Welcome to SillyTavern!": "Benvenuto in SillyTavern!",
|
||||
"Will be used as a password for the proxy instead of API key.": "Verrà usato come password per il proxy invece che la chiave API.",
|
||||
"Window AI Model": "Modello Window AI",
|
||||
"Your Persona": "Il tuo alterego"
|
||||
},
|
||||
"nl-nl": {
|
||||
"clickslidertips": "klikregel tips",
|
||||
@@ -3033,8 +3068,6 @@
|
||||
"to get your NovelAI API key.": "om je NovelAI API-sleutel te verkrijgen.",
|
||||
"Enter it in the box below": "Voer het in in het vak hieronder",
|
||||
"Novel AI Model": "NovelAI-model",
|
||||
"Euterpe": "Euterpe",
|
||||
"Krake": "Krake",
|
||||
"No connection": "Geen verbinding",
|
||||
"oobabooga/text-generation-webui": "oobabooga/text-generation-webui",
|
||||
"Make sure you run it with": "Zorg ervoor dat je het uitvoert met",
|
||||
@@ -3075,13 +3108,9 @@
|
||||
"Always add character's name to prompt": "Voeg altijd de naam van het personage toe aan de prompt",
|
||||
"Keep Example Messages in Prompt": "Behoud voorbeeldberichten in de prompt",
|
||||
"Remove Empty New Lines from Output": "Verwijder lege regels uit de uitvoer",
|
||||
"Pygmalion Formatting": "Pygmalion-opmaak",
|
||||
"Disabled for all models": "Uitgeschakeld voor alle modellen",
|
||||
"Automatic (based on model name)": "Automatisch (op basis van modelnaam)",
|
||||
"Enabled for all models": "Ingeschakeld voor alle modellen",
|
||||
"Multigen": "Multigen",
|
||||
"First chunk (tokens)": "Eerste stuk (tokens)",
|
||||
"Next chunks (tokens)": "Volgende stukken (tokens)",
|
||||
"Anchors Order": "Ankersvolgorde",
|
||||
"Character then Style": "Personage dan Stijl",
|
||||
"Style then Character": "Stijl dan Personage",
|
||||
@@ -3205,7 +3234,6 @@
|
||||
"Regenerate": "Regenereren",
|
||||
"PNG": "PNG",
|
||||
"JSON": "JSON",
|
||||
"WEBP": "WEBP",
|
||||
"presets": "sjablonen",
|
||||
"Message Sound": "Berichtgeluid",
|
||||
"Author's Note": "Notitie van auteur",
|
||||
@@ -3483,5 +3511,165 @@
|
||||
"Select this as default persona for the new chats.": "Selecteer dit als standaard persona voor de nieuwe chats.",
|
||||
"Change persona image": "persona afbeelding wijzigen",
|
||||
"Delete persona": "persona verwijderen"
|
||||
}
|
||||
},
|
||||
"es-spa": {
|
||||
"clickslidertips": "Haz click en el número al lado de la barra \npara seleccionar un número manualmente.",
|
||||
"kobldpresets": "Configuraciones de KoboldAI",
|
||||
"guikoboldaisettings": "Configuración actual de la interfaz de KoboldAI",
|
||||
"novelaipreserts": "Configuraciones de NovelAI",
|
||||
"default": "Predeterminado",
|
||||
"openaipresets": "Configuraciones de OpenAI",
|
||||
"text gen webio(ooba) presets": "Configuraciones de WebUI(ooba)",
|
||||
"response legth(tokens)": "Largo de la respuesta de la IA (en Tokens)",
|
||||
"select": "Seleccionar",
|
||||
"context size(tokens)": "Tamaño del contexto (en Tokens)",
|
||||
"unlocked": "Desbloqueado",
|
||||
"only select modls support context sizes greater than 2048 tokens. proceed only is you know you're doing": "Solo algunos modelos tienen soporte para tamaños de más de 2048 tokens. Procede solo si sabes lo que estás haciendo.",
|
||||
"rep.pen": "Rep. Pen.",
|
||||
"rep.pen range": "Rango de Rep. Pen.",
|
||||
"temperature": "Temperature",
|
||||
"Encoder Rep. Pen.": "Encoder Rep. Pen.",
|
||||
"No Repeat Ngram Size": "No Repeat Ngram Size",
|
||||
"Min Length": "Largo mínimo",
|
||||
"OpenAI Reverse Proxy": "Reverse Proxy de OpenAI",
|
||||
"Alternative server URL (leave empty to use the default value).": "URL del server alternativo (deja vacío para usar el predeterminado)",
|
||||
"Remove your real OAI API Key from the API panel BEFORE typing anything into this box": "Borra tu clave(API) real de OpenAI ANTES de escribir nada en este campo.",
|
||||
"We cannot provide support for problems encountered while using an unofficial OpenAI proxy": "SillyTaven no puede dar soporte por problemas encontrados durante el uso de un proxy no-oficial de OpenAI",
|
||||
"Legacy Streaming Processing": "Processo Streaming Legacy",
|
||||
"Enable this if the streaming doesn't work with your proxy": "Habilita esta opción si el \"streaming\" no está funcionando.",
|
||||
"Context Size (tokens)": "Tamaño del contexto (en Tokens)",
|
||||
"Max Response Length (tokens)": "Tamaño máximo (en Tokens)",
|
||||
"Temperature": "Temperatura",
|
||||
"Frequency Penalty": "Frequency Penalty",
|
||||
"Presence Penalty": "Presence Penalty",
|
||||
"Top-p": "Top-p",
|
||||
"Display bot response text chunks as they are generated": "Muestra el texto poco a poco al mismo tiempo que es generado.",
|
||||
"Top A": "Top-a",
|
||||
"Typical Sampling": "Typical Sampling",
|
||||
"Tail Free Sampling": "Tail Free Sampling",
|
||||
"Rep. Pen. Slope": "Rep. Pen. Slope",
|
||||
"Single-line mode": "Modo \"Solo una línea\"",
|
||||
"Top K": "Top-k",
|
||||
"Top P": "Top-p",
|
||||
"Do Sample": "Do Sample",
|
||||
"Add BOS Token": "Añadir BOS Token",
|
||||
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative.": "Añade el \"bos_token\" al inicio del prompt. Desabilitar esto puede hacer las respuestas de la IA más creativas",
|
||||
"Ban EOS Token": "Prohibir EOS Token",
|
||||
"Ban the eos_token. This forces the model to never end the generation prematurely": "Prohibe el \"eos_token\". Esto obliga a la IA a no terminar su generación de forma prematura",
|
||||
"Skip Special Tokens": "Saltarse Tokens Especiales",
|
||||
"Beam search": "Beam Search",
|
||||
"Number of Beams": "Number of Beams",
|
||||
"Length Penalty": "Length Penalty",
|
||||
"Early Stopping": "Early Stopping",
|
||||
"Contrastive search": "Contrastive search",
|
||||
"Penalty Alpha": "Penalty Alpha",
|
||||
"Seed": "Seed",
|
||||
"Inserts jailbreak as a last system message.": "Inserta el \"jailbreak\" como el último mensaje del Sistema",
|
||||
"This tells the AI to ignore its usual content restrictions.": "Esto ayuda a la IA para ignorar sus restricciones de contenido",
|
||||
"NSFW Encouraged": "Alentar \"NSFW\"",
|
||||
"Tell the AI that NSFW is allowed.": "Le dice a la IA que el contenido NSFW (+18) está permitido",
|
||||
"NSFW Prioritized": "Priorizar NSFW",
|
||||
"NSFW prompt text goes first in the prompt to emphasize its effect.": "El \"prompt NSFW\" va antes para enfatizar su efecto",
|
||||
"Streaming": "Streaming",
|
||||
"Display the response bit by bit as it is generated.": "Enseña el texto poco a poco mientras es generado",
|
||||
"When this is off, responses will be displayed all at once when they are complete.": "Cuando esto está deshabilitado, las respuestas se mostrarán de una vez cuando la generación se haya completado",
|
||||
"Enhance Definitions": "Definiciones Mejoradas",
|
||||
"Use OAI knowledge base to enhance definitions for public figures and known fictional characters": "Usa el conocimiento de OpenAI (GPT 3.5, GPT 4, ChatGPT) para mejorar las definiciones de figuras públicas y personajes ficticios",
|
||||
"Wrap in Quotes": "Envolver En Comillas",
|
||||
"Wrap entire user message in quotes before sending.": "Envuelve todo el mensaje en comillas antes de enviar",
|
||||
"Leave off if you use quotes manually for speech.": "Déjalo deshabilitado si usas comillas manualmente para denotar diálogo",
|
||||
"Main prompt": "Prompt Principal",
|
||||
"The main prompt used to set the model behavior": "El prompt principal usado para definir el comportamiento de la IA",
|
||||
"NSFW prompt": "Prompt NSFW",
|
||||
"Prompt that is used when the NSFW toggle is on": "Prompt que es utilizado cuando \"Alentar NSFW\" está activado",
|
||||
"Jailbreak prompt": "Jailbreak prompt",
|
||||
"Prompt that is used when the Jailbreak toggle is on": "Prompt que es utilizado cuando Jailbreak Prompt está activado",
|
||||
"Impersonation prompt": "Prompt \"Impersonar\"",
|
||||
"Prompt that is used for Impersonation function": "Prompt que es utilizado para la función \"Impersonar\"",
|
||||
"Restore default prompt":"Restaurar el prompt por defecto",
|
||||
"Logit Bias": "Logit Bias",
|
||||
"Helps to ban or reenforce the usage of certain words": "Ayuda a prohibir o alentar el uso de algunas palabras",
|
||||
"View / Edit bias preset": "Ver/Editar configuración de \"Logit Bias\"",
|
||||
"Add bias entry": "Añadir bias",
|
||||
"Jailbreak activation message": "Mensaje de activación de Jailbrak",
|
||||
"Message to send when auto-jailbreak is on.": "Mensaje enviado cuando auto-jailbreak está activado",
|
||||
"Jailbreak confirmation reply": "Mensaje de confirmación de Jailbreak",
|
||||
"Bot must send this back to confirm jailbreak": "La IA debe enviar un mensaje para confirmar el jailbreak",
|
||||
"Character Note": "Nota del personaje",
|
||||
"Influences bot behavior in its responses": "Influencia el comportamiento de la IA y sus respuestas",
|
||||
"API": "API",
|
||||
"KoboldAI": "KoboldAI",
|
||||
"Use Horde": "Usar AI Horde de KoboldAI",
|
||||
"API url": "URL de la API",
|
||||
"Register a Horde account for faster queue times": "Regístrate en KoboldAI para conseguir respuestas más rápido",
|
||||
"Learn how to contribute your idle GPU cycles to the Hord": "Aprende cómo contribuir a AI Horde con tu GPU",
|
||||
"Adjust context size to worker capabilities": "Ajustar tamaño del contexto a las capacidades del trabajador",
|
||||
"Adjust response length to worker capabilities": "Ajustar tamaño de la respuesta a las capacidades del trabajador",
|
||||
"API key": "API key",
|
||||
"Register": "Registrarse",
|
||||
"For privacy reasons": "Por motivos de privacidad, tu API será ocultada cuando se vuelva a cargar la página",
|
||||
"Model": "Modelo IA",
|
||||
"Hold Control / Command key to select multiple models.": "Presiona Ctrl/Command Key para seleccionar multiples modelos",
|
||||
"Horde models not loaded": "Modelos del Horde no cargados",
|
||||
"Not connected": "Desconectado",
|
||||
"Novel API key": "API key de NovelAI",
|
||||
"Follow": "Sigue",
|
||||
"these directions": "estas instrucciones",
|
||||
"to get your NovelAI API key.": "para conseguir tu NovelAI API key",
|
||||
"Enter it in the box below": "Introduce tu clave API de OpenAI en el siguiente campo",
|
||||
"Novel AI Model": "Modelo IA de NovelAI",
|
||||
"No connection": "Desconectado",
|
||||
"oobabooga/text-generation-webui": "oobabooga/text-generation-webui",
|
||||
"Make sure you run it with": "Asegúrate de usar el argumento --api cuando se ejecute",
|
||||
"Blocking API url": "API URL",
|
||||
"Streaming API url": "Streaming API URL",
|
||||
"to get your OpenAI API key.": "para conseguir tu clave API de OpenAI",
|
||||
"OpenAI Model": "Modelo AI de OpenAI",
|
||||
"View API Usage Metrics": "Ver métricas de uso de la API",
|
||||
"Bot": "Bot",
|
||||
"Auto-connect to Last Server": "Auto-conectarse con el último servidor",
|
||||
"View hidden API keys": "Ver claves API ocultas",
|
||||
"Advanced Formatting": "Formateo avanzado",
|
||||
"AutoFormat Overrides": "Autoformateo de overrides",
|
||||
"Samplers Order": "Orden de Samplers",
|
||||
"Samplers will be applied in a top-down order. Use with caution.": "Los Samplers serán aplicados de orden superior a inferior. \nUsa con precaución",
|
||||
"Load koboldcpp order": "Cargar el orden de koboldcpp",
|
||||
"Unlocked Context Size": "Desbloquear Tamaño Del Contexto",
|
||||
"Unrestricted maximum value for the context slider":"Desbloquea el Tamaño máximo del contexto. Solo habilita esto si sabes lo que estás haciendo.",
|
||||
"Quick Edit": "Editor Rápido de Prompts",
|
||||
"Main": "Principal",
|
||||
"Assistant Prefill": "Prefijo del Asistente",
|
||||
"Start Claude's answer with...": "Inicia la respuesta de Claude con...",
|
||||
"Utility Prompts": "Indicaciones Útiles",
|
||||
"World Info Format Template": "Plantilla para formato de World Info",
|
||||
"NSFW avoidance prompt": "Prompt para evitar NSFW",
|
||||
"Prompt that is used when the NSFW toggle is O": "Prompt utilizado para evitar NSFW cuando \"Alentar NSFW\" está deshabilitado",
|
||||
"Wraps activated World Info entries before inserting into the prompt.": "Envuelve las entradas activadas de World Info antes de insertarlas en el prompt.",
|
||||
"New Chat": "Chat Nuevo",
|
||||
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Colocado al inicio del historial de chat para indicar que un nuevo chat va a comenzar.",
|
||||
"New Group Chat": "Nuevo Chat Grupal",
|
||||
"Set at the beginning of the chat history to indicate that a new group chat is about to start.":"Colocado al inicio del historial de chat para indicarle a la IA que un nuevo Chat Grupal va a comenzar",
|
||||
"New Example Chat": "Nuevo Ejemplo De Chat",
|
||||
"Add character names": "Incluír nombre del personaje",
|
||||
"Send names in the ChatML objects.": "Envía los mensajes al objeto ChatML. Ayuda a la IA a asociar mensajes con nombres en un chat grupal.",
|
||||
"Proxy Password": "Contraseña del Proxy",
|
||||
"Will be used as a password for the proxy instead of API key.": "Será utilizado como contraseña del proxy en vez de la clave API.",
|
||||
"Chat Completion Source": "Fuente de Chat",
|
||||
"Use Proxy password field instead. This input will be ignored.": "Utiliza el campo de Contraseña del Proxy. Lo que pongas aquí será ignorado.",
|
||||
"Show External models (provided by API)": "Mostrar modelos externos (Proveídos por la API)",
|
||||
"Connect": "Conectarse",
|
||||
"[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Verifica que tu conexión con la API enviando un mensaje corto. ¡Ten en cuenta que se te cobrará por ello!",
|
||||
"Continue nudge": "Empujuón para continuar",
|
||||
"Replace empty message": "Reemplazar mensaje vacío",
|
||||
"Send this text instead of nothing when the text box is empty.": "Envía este mensaje en vez de nada cuando la barra de chat está vacía",
|
||||
"No connection...": "Sin conexión...",
|
||||
"Avoid sending sensitive information to the Horde.": "No envíes información personal a Horde.",
|
||||
"Review the Privacy statement": "Revisa el aviso de privacidad",
|
||||
"Learn how to contribute your idle GPU cycles to the Horde": "Aprende como contribuír a Horde con tu GPU.",
|
||||
"Trusted workers only": "Solo trabajadores de confianza",
|
||||
"API Key": "Clave API",
|
||||
"Get it here:": "Consíguela aquí:",
|
||||
"View my Kudos": "Ver mis Kudos",
|
||||
"Models": "Modelos IA"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
17
public/instruct/Pygmalion.json
Normal file
17
public/instruct/Pygmalion.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Pygmalion",
|
||||
"system_prompt": "Enter RP mode. You shall reply to {{user}} while staying in character. Your responses must be detailed, creative, immersive, and drive the scenario forward. You will follow {{char}}'s persona.",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"first_output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence_prefix": "<|system|>",
|
||||
"system_sequence_suffix": "",
|
||||
"stop_sequence": "<|user|>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false,
|
||||
"macro": true,
|
||||
"names": true,
|
||||
"names_force_groups": true,
|
||||
"activation_regex": ""
|
||||
}
|
25
public/lib/select2-search-placeholder.js
Normal file
25
public/lib/select2-search-placeholder.js
Normal file
@@ -0,0 +1,25 @@
|
||||
(function($) {
|
||||
|
||||
var Defaults = $.fn.select2.amd.require('select2/defaults');
|
||||
|
||||
$.extend(Defaults.defaults, {
|
||||
searchInputPlaceholder: '',
|
||||
searchInputCssClass: '',
|
||||
});
|
||||
|
||||
var SearchDropdown = $.fn.select2.amd.require('select2/dropdown/search');
|
||||
|
||||
var _renderSearchDropdown = SearchDropdown.prototype.render;
|
||||
|
||||
SearchDropdown.prototype.render = function(decorated) {
|
||||
|
||||
// invoke parent method
|
||||
var $rendered = _renderSearchDropdown.apply(this, Array.prototype.slice.apply(arguments));
|
||||
|
||||
this.$search.attr('placeholder', this.options.get('searchInputPlaceholder'));
|
||||
this.$search.addClass(this.options.get('searchInputCssClass'));
|
||||
|
||||
return $rendered;
|
||||
};
|
||||
|
||||
})(window.jQuery);
|
1131
public/script.js
1131
public/script.js
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { callPopup, event_types, eventSource, is_send_press, main_api, substitut
|
||||
import { is_group_generating } from "./group-chats.js";
|
||||
import { TokenHandler } from "./openai.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { debounce, waitUntilCondition } from "./utils.js";
|
||||
import { debounce, waitUntilCondition, escapeHtml } from "./utils.js";
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
let timeoutId;
|
||||
@@ -53,14 +53,14 @@ const registerPromptManagerMigration = () => {
|
||||
};
|
||||
|
||||
eventSource.on(event_types.SETTINGS_LOADED_BEFORE, settings => migrate(settings));
|
||||
eventSource.on(event_types.OAI_PRESET_CHANGED, event => migrate(event.preset, event.savePreset, event.presetName));
|
||||
eventSource.on(event_types.OAI_PRESET_CHANGED_BEFORE, event => migrate(event.preset, event.savePreset, event.presetName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a prompt.
|
||||
*/
|
||||
class Prompt {
|
||||
identifier; role; content; name; system_prompt;
|
||||
identifier; role; content; name; system_prompt; position;
|
||||
|
||||
/**
|
||||
* Create a new Prompt instance.
|
||||
@@ -71,13 +71,15 @@ class Prompt {
|
||||
* @param {string} param0.content - The content of the prompt.
|
||||
* @param {string} param0.name - The name of the prompt.
|
||||
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
|
||||
* @param {string} param0.position - The position of the prompt in the prompt list.
|
||||
*/
|
||||
constructor({ identifier, role, content, name, system_prompt } = {}) {
|
||||
constructor({ identifier, role, content, name, system_prompt, position } = {}) {
|
||||
this.identifier = identifier;
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
this.name = name;
|
||||
this.system_prompt = system_prompt;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,22 +606,20 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_close_button').addEventListener('click', closeAndClearPopup);
|
||||
|
||||
// Re-render prompt manager on openai preset change
|
||||
eventSource.on(event_types.OAI_PRESET_CHANGED, settings => {
|
||||
// Save configuration and wrap everything up.
|
||||
this.saveServiceSettings().then(() => {
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.renderDebounced();
|
||||
eventSource.on(event_types.OAI_PRESET_CHANGED_AFTER, () => {
|
||||
this.sanitizeServiceSettings();
|
||||
const mainPrompt = this.getPromptById('main');
|
||||
this.updateQuickEdit('main', mainPrompt);
|
||||
|
||||
const mainPrompt = this.getPromptById('main');
|
||||
this.updateQuickEdit('main', mainPrompt);
|
||||
const nsfwPrompt = this.getPromptById('nsfw');
|
||||
this.updateQuickEdit('nsfw', nsfwPrompt);
|
||||
|
||||
const nsfwPrompt = this.getPromptById('nsfw');
|
||||
this.updateQuickEdit('nsfw', nsfwPrompt);
|
||||
const jailbreakPrompt = this.getPromptById('jailbreak');
|
||||
this.updateQuickEdit('jailbreak', jailbreakPrompt);
|
||||
|
||||
const jailbreakPrompt = this.getPromptById('jailbreak');
|
||||
this.updateQuickEdit('jailbreak', jailbreakPrompt);
|
||||
});
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.renderDebounced();
|
||||
});
|
||||
|
||||
// Re-render prompt manager on world settings update
|
||||
@@ -643,19 +643,13 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
if (true === afterTryGenerate) {
|
||||
// Executed during dry-run for determining context composition
|
||||
this.profileStart('filling context');
|
||||
this.tryGenerate().then(() => {
|
||||
this.tryGenerate().finally(() => {
|
||||
this.profileEnd('filling context');
|
||||
this.profileStart('render');
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
this.profileEnd('render');
|
||||
}).catch(error => {
|
||||
this.profileEnd('filling context');
|
||||
this.log('Error caught during render: ' + error);
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
});
|
||||
} else {
|
||||
// Executed during live communication
|
||||
@@ -1291,7 +1285,7 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
const prompts = [...this.serviceSettings.prompts]
|
||||
.filter(prompt => prompt && !prompt?.system_prompt)
|
||||
.sort((promptA, promptB) => promptA.name.localeCompare(promptB.name))
|
||||
.reduce((acc, prompt) => acc + `<option value="${prompt.identifier}">${prompt.name}</option>`, '');
|
||||
.reduce((acc, prompt) => acc + `<option value="${prompt.identifier}">${escapeHtml(prompt.name)}</option>`, '');
|
||||
|
||||
const footerHtml = `
|
||||
<div class="${this.configuration.prefix}prompt_manager_footer">
|
||||
@@ -1440,13 +1434,14 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
toggleSpanHtml = `<span class="fa-solid"></span>`;
|
||||
}
|
||||
|
||||
const encodedName = escapeHtml(prompt.name);
|
||||
listItemHtml += `
|
||||
<li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass}" data-pm-identifier="${prompt.identifier}">
|
||||
<span class="${prefix}prompt_manager_prompt_name" data-pm-name="${prompt.name}">
|
||||
<span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">
|
||||
${prompt.marker ? '<span class="fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
|
||||
${!prompt.marker && prompt.system_prompt ? '<span class="fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
|
||||
${!prompt.marker && !prompt.system_prompt ? '<span class="fa-solid fa-user" title="User Prompt"></span>' : ''}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${encodedName}</a>` : encodedName}
|
||||
</span>
|
||||
<span>
|
||||
<span class="prompt_manager_prompt_controls">
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
getEntitiesList,
|
||||
getThumbnailUrl,
|
||||
selectCharacterById,
|
||||
eventSource,
|
||||
} from "../script.js";
|
||||
|
||||
import {
|
||||
@@ -30,10 +31,11 @@ import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
|
||||
import { debounce, delay, getStringHash, isUrlOrAPIKey, waitUntilCondition } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
import { getTokenCount } from "./tokenizers.js";
|
||||
|
||||
|
||||
var RPanelPin = document.getElementById("rm_button_panel_pin");
|
||||
var LPanelPin = document.getElementById("lm_button_panel_pin");
|
||||
var WIPanelPin = document.getElementById("WI_panel_pin");
|
||||
@@ -208,10 +210,12 @@ $("#character_popup").on("input", function () { countTokensDebounced(); });
|
||||
//function:
|
||||
export function RA_CountCharTokens() {
|
||||
let total_tokens = 0;
|
||||
let permanent_tokens = 0;
|
||||
|
||||
$('[data-token-counter]').each(function () {
|
||||
const counter = $(this);
|
||||
const input = $(document.getElementById(counter.data('token-counter')));
|
||||
const isPermanent = counter.data('token-permanent') === true;
|
||||
const value = String(input.val());
|
||||
|
||||
if (input.length === 0) {
|
||||
@@ -228,10 +232,12 @@ export function RA_CountCharTokens() {
|
||||
|
||||
if (input.data('last-value-hash') === valueHash) {
|
||||
total_tokens += Number(counter.text());
|
||||
permanent_tokens += isPermanent ? Number(counter.text()) : 0;
|
||||
} else {
|
||||
const tokens = getTokenCount(value);
|
||||
counter.text(tokens);
|
||||
total_tokens += tokens;
|
||||
permanent_tokens += isPermanent ? tokens : 0;
|
||||
input.data('last-value-hash', valueHash);
|
||||
}
|
||||
});
|
||||
@@ -240,6 +246,7 @@ export function RA_CountCharTokens() {
|
||||
const tokenLimit = Math.max(((main_api !== 'openai' ? max_context : oai_settings.openai_max_context) / 2), 1024);
|
||||
const showWarning = (total_tokens > tokenLimit);
|
||||
$('#result_info_total_tokens').text(total_tokens);
|
||||
$('#result_info_permanent_tokens').text(permanent_tokens);
|
||||
$('#result_info_text').toggleClass('neutral_warning', showWarning);
|
||||
$('#chartokenwarning').toggle(showWarning);
|
||||
}
|
||||
@@ -272,10 +279,16 @@ export async function favsToHotswap() {
|
||||
const entities = getEntitiesList({ doFilter: false });
|
||||
const container = $('#right-nav-panel .hotswap');
|
||||
const template = $('#hotswap_template .hotswapAvatar');
|
||||
container.empty();
|
||||
const maxCount = 6;
|
||||
const DEFAULT_COUNT = 6;
|
||||
const WIDTH_PER_ITEM = 60; // 50px + 5px gap + 5px padding
|
||||
const containerWidth = container.outerWidth();
|
||||
const maxCount = containerWidth > 0 ? Math.floor(containerWidth / WIDTH_PER_ITEM) : DEFAULT_COUNT;
|
||||
let count = 0;
|
||||
|
||||
const promises = [];
|
||||
const newContainer = container.clone();
|
||||
newContainer.empty();
|
||||
|
||||
for (const entity of entities) {
|
||||
if (count >= maxCount) {
|
||||
break;
|
||||
@@ -308,23 +321,39 @@ export async function favsToHotswap() {
|
||||
}
|
||||
|
||||
if (isCharacter) {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
const imgLoadPromise = new Promise((resolve) => {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl).on('load', resolve);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
});
|
||||
|
||||
// if the image doesn't load in 500ms, resolve the promise anyway
|
||||
promises.push(Promise.race([imgLoadPromise, delay(500)]));
|
||||
}
|
||||
|
||||
$(slot).css('cursor', 'pointer');
|
||||
container.append(slot);
|
||||
newContainer.append(slot);
|
||||
count++;
|
||||
}
|
||||
|
||||
// there are 6 slots in total,
|
||||
if (count < maxCount) { //if any are left over
|
||||
// don't fill leftover spaces with avatar placeholders
|
||||
// just evenly space the selected avatars instead
|
||||
/*
|
||||
if (count < maxCount) { //if any space is left over
|
||||
let leftOverSlots = maxCount - count;
|
||||
for (let i = 1; i <= leftOverSlots; i++) {
|
||||
container.append(template.clone());
|
||||
newContainer.append(template.clone());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
//helpful instruction message if no characters are favorited
|
||||
if (count === 0) { container.html(`<small><span><i class="fa-solid fa-star"></i> Favorite characters to add them to HotSwaps</span></small>`) }
|
||||
//otherwise replace with fav'd characters
|
||||
if (count > 0) {
|
||||
container.replaceWith(newContainer);
|
||||
}
|
||||
}
|
||||
|
||||
//changes input bar and send button display depending on connection status
|
||||
@@ -401,15 +430,6 @@ function RA_autoconnect(PrevApi) {
|
||||
}
|
||||
}
|
||||
|
||||
function isUrlOrAPIKey(string) {
|
||||
try {
|
||||
new URL(string);
|
||||
return true;
|
||||
} catch (_) {
|
||||
// return pattern.test(string);
|
||||
}
|
||||
}
|
||||
|
||||
function OpenNavPanels() {
|
||||
const deviceInfo = getDeviceInfo();
|
||||
if (deviceInfo && deviceInfo.device.type === 'desktop') {
|
||||
@@ -448,8 +468,9 @@ export function dragElement(elmnt) {
|
||||
topbar, topbarWidth, topBarFirstX, topBarLastX, topBarLastY, sheldWidth;
|
||||
|
||||
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) {
|
||||
@@ -554,8 +575,15 @@ export function dragElement(elmnt) {
|
||||
//set a listener for mouseup to save new width/height
|
||||
elmnt.off('mouseup').on('mouseup', () => {
|
||||
console.debug(`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')
|
||||
return
|
||||
}
|
||||
|
||||
power_user.movingUIState[elmntName].width = width;
|
||||
power_user.movingUIState[elmntName].height = height;
|
||||
eventSource.emit('resizeUI', elmntName);
|
||||
saveSettingsDebounced();
|
||||
})
|
||||
}
|
||||
@@ -576,6 +604,7 @@ export function dragElement(elmnt) {
|
||||
}
|
||||
|
||||
//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
|
||||
@@ -584,6 +613,7 @@ export function dragElement(elmnt) {
|
||||
console.debug('topbar hit')
|
||||
elmnt.css('top', top + 1 + "px");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Check if the element header exists and set the listener on the grabber
|
||||
@@ -855,8 +885,12 @@ export function initRossMods() {
|
||||
|
||||
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
|
||||
$('#send_textarea').on('input', function () {
|
||||
const chatBlock = $('#chat');
|
||||
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
|
||||
this.style.height = window.getComputedStyle(this).getPropertyValue('min-height');
|
||||
this.style.height = (this.scrollHeight) + 'px';
|
||||
const newScrollTop = chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom);
|
||||
chatBlock.scrollTop(newScrollTop);
|
||||
});
|
||||
|
||||
//Regenerate if user swipes on the last mesage in chat
|
||||
@@ -894,15 +928,18 @@ export function initRossMods() {
|
||||
}
|
||||
|
||||
$(document).on('keydown', function (event) {
|
||||
processHotkeys(event);
|
||||
processHotkeys(event.originalEvent);
|
||||
});
|
||||
|
||||
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
|
||||
/**
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
function processHotkeys(event) {
|
||||
//Enter to send when send_textarea in focus
|
||||
if ($(':focus').attr('id') === 'send_textarea') {
|
||||
const sendOnEnter = shouldSendOnEnter();
|
||||
if (!event.shiftKey && !event.ctrlKey && event.key == "Enter" && is_send_press == false && sendOnEnter) {
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == "Enter" && is_send_press == false && sendOnEnter) {
|
||||
event.preventDefault();
|
||||
Generate();
|
||||
}
|
||||
@@ -931,6 +968,14 @@ export function initRossMods() {
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Alt+Enter or AltGr+Enter to Continue
|
||||
if ((event.altKey || (event.altKey && event.ctrlKey)) && event.key == "Enter") {
|
||||
if (is_send_press == false) {
|
||||
console.debug("Continuing with Alt+Enter");
|
||||
$('#option_continue').trigger('click');
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
|
||||
if (event.ctrlKey && event.key == "Enter") {
|
||||
const editMesDone = $(".mes_edit_done:visible");
|
||||
@@ -945,13 +990,16 @@ export function initRossMods() {
|
||||
console.debug("Ctrl+Enter ignored");
|
||||
}
|
||||
}
|
||||
//ctrl+left to show all local stored vars (debug)
|
||||
if (event.ctrlKey && event.key == "ArrowLeft") {
|
||||
CheckLocal();
|
||||
|
||||
// Helper function to check if nanogallery2's lightbox is active
|
||||
function isNanogallery2LightboxActive() {
|
||||
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
|
||||
return $('body').hasClass('nGY2_body_scrollbar');
|
||||
}
|
||||
|
||||
if (event.key == "ArrowLeft") { //swipes left
|
||||
if (
|
||||
!isNanogallery2LightboxActive() && // Check if lightbox is NOT active
|
||||
$(".swipe_left:last").css('display') === 'flex' &&
|
||||
$("#send_textarea").val() === '' &&
|
||||
$("#character_popup").css("display") === "none" &&
|
||||
@@ -963,6 +1011,7 @@ export function initRossMods() {
|
||||
}
|
||||
if (event.key == "ArrowRight") { //swipes right
|
||||
if (
|
||||
!isNanogallery2LightboxActive() && // Check if lightbox is NOT active
|
||||
$(".swipe_right:last").css('display') === 'flex' &&
|
||||
$("#send_textarea").val() === '' &&
|
||||
$("#character_popup").css("display") === "none" &&
|
||||
@@ -973,6 +1022,7 @@ export function initRossMods() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
|
||||
if (
|
||||
$("#send_textarea").val() === '' &&
|
||||
@@ -1008,9 +1058,9 @@ export function initRossMods() {
|
||||
}
|
||||
|
||||
if (event.key == "Escape") { //closes various panels
|
||||
|
||||
//dont override Escape hotkey functions from script.js
|
||||
//"close edit box" and "cancel stream generation".
|
||||
|
||||
if ($("#curEditTextarea").is(":visible") || $("#mes_stop").is(":visible")) {
|
||||
console.debug('escape key, but deferring to script.js routines')
|
||||
return
|
||||
@@ -1047,13 +1097,11 @@ export function initRossMods() {
|
||||
.not('#left-nav-panel')
|
||||
.not('#right-nav-panel')
|
||||
.not('#floatingPrompt')
|
||||
console.log(visibleDrawerContent)
|
||||
$(visibleDrawerContent).parent().find('.drawer-icon').trigger('click');
|
||||
return
|
||||
}
|
||||
|
||||
if ($("#floatingPrompt").is(":visible")) {
|
||||
console.log('saw AN visible, trying to close')
|
||||
$("#ANClose").trigger('click');
|
||||
return
|
||||
}
|
||||
@@ -1074,10 +1122,18 @@ export function initRossMods() {
|
||||
$("#rightNavDrawerIcon").trigger('click');
|
||||
return
|
||||
}
|
||||
if ($(".draggable").is(":visible")) {
|
||||
// Remove the first matched element
|
||||
$('.draggable:first').remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (event.ctrlKey && /^[1-9]$/.test(event.key)) {
|
||||
// Your code here
|
||||
// This will eventually be to trigger quick replies
|
||||
event.preventDefault();
|
||||
console.log("Ctrl +" + event.key + " pressed!");
|
||||
}
|
||||
|
@@ -133,6 +133,39 @@ async function saveBookmarkMenu() {
|
||||
return createNewBookmark(chat.length - 1);
|
||||
}
|
||||
|
||||
export async function createBranch(mesId) {
|
||||
if (!chat.length) {
|
||||
toastr.warning('The chat is empty.', 'Branch creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
if (mesId < 0 || mesId >= chat.length) {
|
||||
toastr.warning('Invalid message ID.', 'Branch creation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const lastMes = chat[mesId];
|
||||
const mainChat = selected_group ? groups?.find(x => x.id == selected_group)?.chat_id : characters[this_chid].chat;
|
||||
const newMetadata = { main_chat: mainChat };
|
||||
let name = `Branch #${mesId} - ${humanizedDateTime()}`
|
||||
|
||||
if (selected_group) {
|
||||
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
|
||||
} else {
|
||||
await saveChat(name, newMetadata, mesId);
|
||||
}
|
||||
// append to branches list if it exists
|
||||
// otherwise create it
|
||||
if (typeof lastMes.extra !== 'object') {
|
||||
lastMes.extra = {};
|
||||
}
|
||||
if (typeof lastMes.extra['branches'] !== 'object') {
|
||||
lastMes.extra['branches'] = [];
|
||||
}
|
||||
lastMes.extra['branches'].push(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
async function createNewBookmark(mesId) {
|
||||
if (!chat.length) {
|
||||
toastr.warning('The chat is empty.', 'Bookmark creation failed');
|
||||
@@ -280,7 +313,6 @@ async function convertSoloToGroupChat() {
|
||||
message.name = character.name;
|
||||
message.original_avatar = character.avatar;
|
||||
message.force_avatar = getThumbnailUrl('avatar', character.avatar);
|
||||
message.is_name = true;
|
||||
|
||||
// Allow regens of a single message in group
|
||||
if (typeof message.extra !== 'object') {
|
||||
|
@@ -134,7 +134,10 @@ const extension_settings = {
|
||||
caption: {
|
||||
refine_mode: false,
|
||||
},
|
||||
expressions: {},
|
||||
expressions: {
|
||||
/** @type {string[]} */
|
||||
custom: [],
|
||||
},
|
||||
dice: {},
|
||||
regex: [],
|
||||
tts: {},
|
||||
@@ -153,6 +156,8 @@ const extension_settings = {
|
||||
},
|
||||
speech_recognition: {},
|
||||
rvc: {},
|
||||
hypebot: {},
|
||||
vectors: {},
|
||||
};
|
||||
|
||||
let modules = [];
|
||||
@@ -192,7 +197,6 @@ async function doExtrasFetch(endpoint, args) {
|
||||
}
|
||||
Object.assign(args.headers, {
|
||||
'Authorization': `Bearer ${extension_settings.apiKey}`,
|
||||
'Bypass-Tunnel-Reminder': 'bypass'
|
||||
});
|
||||
|
||||
const response = await fetch(endpoint, args);
|
||||
@@ -201,7 +205,7 @@ async function doExtrasFetch(endpoint, args) {
|
||||
|
||||
async function discoverExtensions() {
|
||||
try {
|
||||
const response = await fetch('/discover_extensions');
|
||||
const response = await fetch('/api/extensions/discover');
|
||||
|
||||
if (response.ok) {
|
||||
const extensions = await response.json();
|
||||
@@ -606,7 +610,7 @@ async function showExtensionsDetails() {
|
||||
async function onUpdateClick() {
|
||||
const extensionName = $(this).data('name');
|
||||
try {
|
||||
const response = await fetch('/update_extension', {
|
||||
const response = await fetch('/api/extensions/update', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ extensionName })
|
||||
@@ -626,7 +630,7 @@ async function onUpdateClick() {
|
||||
|
||||
/**
|
||||
* Handles the click event for the delete button of an extension.
|
||||
* This function makes a POST request to '/delete_extension' with the extension's name.
|
||||
* This function makes a POST request to '/api/extensions/delete' with the extension's name.
|
||||
* If the extension is deleted, it displays a success message.
|
||||
* Creates a popup for the user to confirm before delete.
|
||||
*/
|
||||
@@ -636,7 +640,7 @@ async function onDeleteClick() {
|
||||
const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension');
|
||||
if (confirmation) {
|
||||
try {
|
||||
const response = await fetch('/delete_extension', {
|
||||
const response = await fetch('/api/extensions/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ extensionName })
|
||||
@@ -663,7 +667,7 @@ async function onDeleteClick() {
|
||||
*/
|
||||
async function getExtensionVersion(extensionName) {
|
||||
try {
|
||||
const response = await fetch('/get_extension_version', {
|
||||
const response = await fetch('/api/extensions/version', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ extensionName })
|
||||
|
@@ -150,7 +150,7 @@ async function installAsset(url, assetType, filename) {
|
||||
const category = assetType;
|
||||
try {
|
||||
const body = { url, category, filename };
|
||||
const result = await fetch('/asset_download', {
|
||||
const result = await fetch('/api/assets/download', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
@@ -171,7 +171,7 @@ async function deleteAsset(assetType, filename) {
|
||||
const category = assetType;
|
||||
try {
|
||||
const body = { category, filename };
|
||||
const result = await fetch('/asset_delete', {
|
||||
const result = await fetch('/api/assets/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
@@ -194,7 +194,7 @@ async function deleteAsset(assetType, filename) {
|
||||
async function updateCurrentAssets() {
|
||||
console.debug(DEBUG_PREFIX, "Checking installed assets...")
|
||||
try {
|
||||
const result = await fetch(`/get_assets`, {
|
||||
const result = await fetch(`/api/assets/get`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
@@ -1,6 +1,9 @@
|
||||
/*
|
||||
Ideas:
|
||||
- Clean design of new ui
|
||||
- change select text versus options for playing: audio
|
||||
- cross fading between bgm / start a different time
|
||||
- fading should appear before end when switching randomly
|
||||
- Background based ambient sounds
|
||||
- import option on background UI ?
|
||||
- Allow background music edition using background menu
|
||||
@@ -59,6 +62,8 @@ const DEFAULT_EXPRESSIONS = [
|
||||
];
|
||||
const SPRITE_DOM_ID = "#expression-image";
|
||||
|
||||
let current_chat_id = null
|
||||
|
||||
let fallback_BGMS = null; // Initialized only once with module workers
|
||||
let ambients = null; // Initialized only once with module workers
|
||||
let characterMusics = {}; // Updated with module workers
|
||||
@@ -69,16 +74,27 @@ let currentBackground = null;
|
||||
|
||||
let cooldownBGM = 0;
|
||||
|
||||
let bgmEnded = true;
|
||||
|
||||
//#############################//
|
||||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
const defaultSettings = {
|
||||
enabled: false,
|
||||
dynamic_bgm_enabled: false,
|
||||
//dynamic_ambient_enabled: false,
|
||||
|
||||
bgm_locked: true,
|
||||
bgm_muted: true,
|
||||
ambient_muted: true,
|
||||
bgm_volume: 50,
|
||||
bgm_selected: null,
|
||||
|
||||
ambient_locked: true,
|
||||
ambient_muted: true,
|
||||
ambient_volume: 50,
|
||||
ambient_selected: null,
|
||||
|
||||
bgm_cooldown: 30
|
||||
}
|
||||
|
||||
@@ -90,6 +106,8 @@ function loadSettings() {
|
||||
Object.assign(extension_settings.audio, defaultSettings)
|
||||
}
|
||||
$("#audio_enabled").prop('checked', extension_settings.audio.enabled);
|
||||
$("#audio_dynamic_bgm_enabled").prop('checked', extension_settings.audio.dynamic_bgm_enabled);
|
||||
//$("#audio_dynamic_ambient_enabled").prop('checked', extension_settings.audio.dynamic_ambient_enabled);
|
||||
|
||||
$("#audio_bgm_volume").text(extension_settings.audio.bgm_volume);
|
||||
$("#audio_ambient_volume").text(extension_settings.audio.ambient_volume);
|
||||
@@ -103,20 +121,55 @@ function loadSettings() {
|
||||
$("#audio_bgm_mute").addClass("redOverlayGlow");
|
||||
$("#audio_bgm").prop("muted", true);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
$("#audio_bgm_mute_icon").addClass("fa-volume-high");
|
||||
$("#audio_bgm_mute_icon").removeClass("fa-volume-mute");
|
||||
$("#audio_bgm_mute").removeClass("redOverlayGlow");
|
||||
$("#audio_bgm").prop("muted", false);
|
||||
}
|
||||
|
||||
if (extension_settings.audio.bgm_locked) {
|
||||
//$("#audio_bgm_lock_icon").removeClass("fa-lock-open");
|
||||
//$("#audio_bgm_lock_icon").addClass("fa-lock");
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
$("#audio_bgm_lock").addClass("redOverlayGlow");
|
||||
}
|
||||
else {
|
||||
//$("#audio_bgm_lock_icon").removeClass("fa-lock");
|
||||
//$("#audio_bgm_lock_icon").addClass("fa-lock-open");
|
||||
$("#audio_bgm").attr("loop", false);
|
||||
$("#audio_bgm_lock").removeClass("redOverlayGlow");
|
||||
}
|
||||
|
||||
/*
|
||||
if (extension_settings.audio.bgm_selected !== null) {
|
||||
$("#audio_bgm_select").append(new Option(extension_settings.audio.bgm_selected, extension_settings.audio.bgm_selected));
|
||||
$("#audio_bgm_select").val(extension_settings.audio.bgm_selected);
|
||||
}*/
|
||||
|
||||
if (extension_settings.audio.ambient_locked) {
|
||||
$("#audio_ambient_lock_icon").removeClass("fa-lock-open");
|
||||
$("#audio_ambient_lock_icon").addClass("fa-lock");
|
||||
$("#audio_ambient_lock").addClass("redOverlayGlow");
|
||||
}
|
||||
else {
|
||||
$("#audio_ambient_lock_icon").removeClass("fa-lock");
|
||||
$("#audio_ambient_lock_icon").addClass("fa-lock-open");
|
||||
}
|
||||
|
||||
/*
|
||||
if (extension_settings.audio.ambient_selected !== null) {
|
||||
$("#audio_ambient_select").append(new Option(extension_settings.audio.ambient_selected, extension_settings.audio.ambient_selected));
|
||||
$("#audio_ambient_select").val(extension_settings.audio.ambient_selected);
|
||||
}*/
|
||||
|
||||
if (extension_settings.audio.ambient_muted) {
|
||||
$("#audio_ambient_mute_icon").removeClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").addClass("fa-volume-mute");
|
||||
$("#audio_ambient_mute").addClass("redOverlayGlow");
|
||||
$("#audio_ambient").prop("muted", true);
|
||||
}
|
||||
else{
|
||||
else {
|
||||
$("#audio_ambient_mute_icon").addClass("fa-volume-high");
|
||||
$("#audio_ambient_mute_icon").removeClass("fa-volume-mute");
|
||||
$("#audio_ambient_mute").removeClass("redOverlayGlow");
|
||||
@@ -125,7 +178,7 @@ function loadSettings() {
|
||||
|
||||
$("#audio_bgm_cooldown").val(extension_settings.audio.bgm_cooldown);
|
||||
|
||||
$("#audio_debug_div").hide(); // DBG
|
||||
$("#audio_debug_div").hide(); // DBG: comment to see debug mode
|
||||
}
|
||||
|
||||
async function onEnabledClick() {
|
||||
@@ -142,6 +195,51 @@ async function onEnabledClick() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onDynamicBGMEnabledClick() {
|
||||
extension_settings.audio.dynamic_bgm_enabled = $('#audio_dynamic_bgm_enabled').is(':checked');
|
||||
currentCharacterBGM = null;
|
||||
currentExpressionBGM = null;
|
||||
cooldownBGM = 0;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
/*
|
||||
async function onDynamicAmbientEnabledClick() {
|
||||
extension_settings.audio.dynamic_ambient_enabled = $('#audio_dynamic_ambient_enabled').is(':checked');
|
||||
currentBackground = null;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
*/
|
||||
async function onBGMLockClick() {
|
||||
extension_settings.audio.bgm_locked = !extension_settings.audio.bgm_locked;
|
||||
if (extension_settings.audio.bgm_locked) {
|
||||
extension_settings.audio.bgm_selected = $("#audio_bgm_select").val();
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
}
|
||||
else {
|
||||
$("#audio_bgm").attr("loop", false);
|
||||
}
|
||||
//$("#audio_bgm_lock_icon").toggleClass("fa-lock");
|
||||
//$("#audio_bgm_lock_icon").toggleClass("fa-lock-open");
|
||||
$("#audio_bgm_lock").toggleClass("redOverlayGlow");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onBGMRandomClick() {
|
||||
var select = document.getElementById('audio_bgm_select');
|
||||
var items = select.getElementsByTagName('option');
|
||||
|
||||
if (items.length < 2)
|
||||
return;
|
||||
|
||||
var index;
|
||||
do {
|
||||
index = Math.floor(Math.random() * items.length);
|
||||
} while (index == select.selectedIndex);
|
||||
|
||||
select.selectedIndex = index;
|
||||
onBGMSelectChange();
|
||||
}
|
||||
|
||||
async function onBGMMuteClick() {
|
||||
extension_settings.audio.bgm_muted = !extension_settings.audio.bgm_muted;
|
||||
$("#audio_bgm_mute_icon").toggleClass("fa-volume-high");
|
||||
@@ -151,6 +249,20 @@ async function onBGMMuteClick() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onAmbientLockClick() {
|
||||
extension_settings.audio.ambient_locked = !extension_settings.audio.ambient_locked;
|
||||
if (extension_settings.audio.ambient_locked)
|
||||
extension_settings.audio.ambient_selected = $("#audio_ambient_select").val();
|
||||
else {
|
||||
extension_settings.audio.ambient_selected = null;
|
||||
currentBackground = null;
|
||||
}
|
||||
$("#audio_ambient_lock_icon").toggleClass("fa-lock");
|
||||
$("#audio_ambient_lock_icon").toggleClass("fa-lock-open");
|
||||
$("#audio_ambient_lock").toggleClass("redOverlayGlow");
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onAmbientMuteClick() {
|
||||
extension_settings.audio.ambient_muted = !extension_settings.audio.ambient_muted;
|
||||
$("#audio_ambient_mute_icon").toggleClass("fa-volume-high");
|
||||
@@ -176,6 +288,20 @@ async function onAmbientVolumeChange() {
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED Ambient MAX TO",extension_settings.audio.ambient_volume);
|
||||
}
|
||||
|
||||
async function onBGMSelectChange() {
|
||||
extension_settings.audio.bgm_selected = $("#audio_bgm_select").val();
|
||||
updateBGM(true);
|
||||
saveSettingsDebounced();
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
|
||||
}
|
||||
|
||||
async function onAmbientSelectChange() {
|
||||
extension_settings.audio.ambient_selected = $("#audio_ambient_select").val();
|
||||
updateAmbient(true);
|
||||
saveSettingsDebounced();
|
||||
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
|
||||
}
|
||||
|
||||
async function onBGMCooldownInput() {
|
||||
extension_settings.audio.bgm_cooldown = ~~($("#audio_bgm_cooldown").val());
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
@@ -191,7 +317,7 @@ async function getAssetsList(type) {
|
||||
console.debug(DEBUG_PREFIX, "getting assets of type", type);
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_assets`, {
|
||||
const result = await fetch(`/api/assets/get`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
@@ -209,7 +335,7 @@ async function getCharacterBgmList(name) {
|
||||
console.debug(DEBUG_PREFIX, "getting bgm list for", name);
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_character_assets_list?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
|
||||
const result = await fetch(`/api/assets/character?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
@@ -222,11 +348,42 @@ async function getCharacterBgmList(name) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#############################//
|
||||
// Module Worker //
|
||||
//#############################//
|
||||
|
||||
function fillBGMSelect() {
|
||||
let found_last_selected_bgm = false;
|
||||
// Update bgm list in UI
|
||||
$("#audio_bgm_select")
|
||||
.find('option')
|
||||
.remove();
|
||||
|
||||
for (const file of fallback_BGMS) {
|
||||
$('#audio_bgm_select').append(new Option("asset: " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
|
||||
if (file === extension_settings.audio.bgm_selected) {
|
||||
$('#audio_bgm_select').val(extension_settings.audio.bgm_selected);
|
||||
found_last_selected_bgm = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update bgm list in UI
|
||||
for (const char in characterMusics)
|
||||
for (const e in characterMusics[char])
|
||||
for (const file of characterMusics[char][e]) {
|
||||
$('#audio_bgm_select').append(new Option(char + ": " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
|
||||
if (file === extension_settings.audio.bgm_selected) {
|
||||
$('#audio_bgm_select').val(extension_settings.audio.bgm_selected);
|
||||
found_last_selected_bgm = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_last_selected_bgm) {
|
||||
$('#audio_bgm_select').val($("#audio_bgm_select option:first").val());
|
||||
extension_settings.audio.bgm_selected = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
- Update ambient sound
|
||||
- Update character BGM
|
||||
@@ -246,6 +403,8 @@ async function moduleWorker() {
|
||||
fallback_BGMS = await getAssetsList(ASSETS_BGM_FOLDER);
|
||||
fallback_BGMS = fallback_BGMS.filter((filename) => filename != ".placeholder")
|
||||
console.debug(DEBUG_PREFIX, "Detected assets:", fallback_BGMS);
|
||||
|
||||
fillBGMSelect();
|
||||
}
|
||||
|
||||
if (ambients == null) {
|
||||
@@ -253,10 +412,33 @@ async function moduleWorker() {
|
||||
ambients = await getAssetsList(ASSETS_AMBIENT_FOLDER);
|
||||
ambients = ambients.filter((filename) => filename != ".placeholder")
|
||||
console.debug(DEBUG_PREFIX, "Detected assets:", ambients);
|
||||
|
||||
// Update bgm list in UI
|
||||
$("#audio_ambient_select")
|
||||
.find('option')
|
||||
.remove();
|
||||
|
||||
if (extension_settings.audio.ambient_selected !== null) {
|
||||
let ambient_label = extension_settings.audio.ambient_selected;
|
||||
if (ambient_label.includes("assets"))
|
||||
ambient_label = "asset: " + ambient_label.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, "");
|
||||
else {
|
||||
ambient_label = ambient_label.substring("/characters/".length);
|
||||
ambient_label = ambient_label.substring(0, ambient_label.indexOf("/")) + ": " + ambient_label.substring(ambient_label.indexOf("/") + "/bgm/".length);
|
||||
ambient_label = ambient_label.replace(/\.[^/.]+$/, "");
|
||||
}
|
||||
$('#audio_ambient_select').append(new Option(ambient_label, extension_settings.audio.ambient_selected));
|
||||
}
|
||||
|
||||
for (const file of ambients) {
|
||||
if (file !== extension_settings.audio.ambient_selected)
|
||||
$("#audio_ambient_select").append(new Option("asset: " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
|
||||
}
|
||||
}
|
||||
|
||||
// 1) Update ambient audio
|
||||
// ---------------------------
|
||||
//if (extension_settings.audio.dynamic_ambient_enabled) {
|
||||
let newBackground = $("#bg1").css("background-image");
|
||||
const custom_background = getContext()["chatMetadata"]["custom_background"];
|
||||
|
||||
@@ -275,6 +457,7 @@ async function moduleWorker() {
|
||||
updateAmbient();
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
const context = getContext();
|
||||
//console.debug(DEBUG_PREFIX,context);
|
||||
@@ -288,6 +471,14 @@ async function moduleWorker() {
|
||||
// 1) Update BGM (single chat)
|
||||
// -----------------------------
|
||||
if (!chatIsGroup) {
|
||||
|
||||
// Reset bgm list on new chat
|
||||
if (context.chatId != current_chat_id) {
|
||||
current_chat_id = context.chatId;
|
||||
characterMusics = {};
|
||||
cooldownBGM = 0;
|
||||
}
|
||||
|
||||
newCharacter = context.name2;
|
||||
|
||||
//console.log(DEBUG_PREFIX,"SOLO CHAT MODE"); // DBG
|
||||
@@ -307,7 +498,7 @@ async function moduleWorker() {
|
||||
if (currentCharacterBGM !== newCharacter) {
|
||||
currentCharacterBGM = newCharacter;
|
||||
try {
|
||||
await updateBGM();
|
||||
await updateBGM(false, true);
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
}
|
||||
catch (error) {
|
||||
@@ -329,9 +520,9 @@ async function moduleWorker() {
|
||||
}
|
||||
|
||||
try {
|
||||
currentExpressionBGM = newExpression;
|
||||
await updateBGM();
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
currentExpressionBGM = newExpression;
|
||||
console.debug(DEBUG_PREFIX, "(SOLO) Updated current character expression to", currentExpressionBGM, "cooldown", cooldownBGM);
|
||||
}
|
||||
catch (error) {
|
||||
@@ -346,6 +537,34 @@ async function moduleWorker() {
|
||||
|
||||
// 2) Update BGM (group chat)
|
||||
// -----------------------------
|
||||
|
||||
// Load current chat character bgms
|
||||
// Reset bgm list on new chat
|
||||
if (context.chatId != current_chat_id) {
|
||||
current_chat_id = context.chatId;
|
||||
characterMusics = {};
|
||||
cooldownBGM = 0;
|
||||
for (const message of context.chat) {
|
||||
if (characterMusics[message.name] === undefined)
|
||||
await loadCharacterBGM(message.name);
|
||||
}
|
||||
|
||||
try {
|
||||
newCharacter = context.chat[context.chat.length - 1].name;
|
||||
currentCharacterBGM = newCharacter;
|
||||
await updateBGM(false, true);
|
||||
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
|
||||
currentCharacterBGM = newCharacter;
|
||||
currentExpressionBGM = FALLBACK_EXPRESSION;
|
||||
console.debug(DEBUG_PREFIX, "(GROUP) Updated current character BGM to", currentExpressionBGM, "cooldown", cooldownBGM);
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(DEBUG_PREFIX, "Error while trying to update BGM group, will try again");
|
||||
currentCharacterBGM = null
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
newCharacter = context.chat[context.chat.length - 1].name;
|
||||
const userName = context.name1;
|
||||
|
||||
@@ -353,17 +572,17 @@ async function moduleWorker() {
|
||||
|
||||
//console.log(DEBUG_PREFIX,"GROUP CHAT MODE"); // DBG
|
||||
|
||||
// 2.1) First time character appear
|
||||
// 2.1) New character appear
|
||||
if (characterMusics[newCharacter] === undefined) {
|
||||
await loadCharacterBGM(newCharacter);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.2) Switched chat
|
||||
// 2.2) Switched char
|
||||
if (currentCharacterBGM !== newCharacter) {
|
||||
// Check cooldown
|
||||
if (cooldownBGM > 0) {
|
||||
//console.debug(DEBUG_PREFIX,"(GROUP) BGM switch on cooldown:",cooldownBGM);
|
||||
console.debug(DEBUG_PREFIX, "(GROUP) BGM switch on cooldown:", cooldownBGM);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,6 +648,8 @@ async function loadCharacterBGM(newCharacter) {
|
||||
characterMusics[newCharacter][e].push(i);
|
||||
}
|
||||
console.debug(DEBUG_PREFIX, "Updated BGM map of", newCharacter, "to", characterMusics[newCharacter]);
|
||||
|
||||
fillBGMSelect();
|
||||
}
|
||||
|
||||
function getNewExpression() {
|
||||
@@ -458,24 +679,52 @@ function getNewExpression() {
|
||||
return newExpression;
|
||||
}
|
||||
|
||||
async function updateBGM() {
|
||||
let audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
|
||||
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
|
||||
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
|
||||
audio_files = fallback_BGMS; // ST FALLBACK BGM
|
||||
|
||||
if (audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
async function updateBGM(isUserInput = false, newChat = false) {
|
||||
if (!isUserInput && !extension_settings.audio.dynamic_bgm_enabled && $("#audio_bgm").attr("src") != "" && !bgmEnded && !newChat) {
|
||||
console.debug(DEBUG_PREFIX, "BGM already playing and dynamic switch disabled, no update done");
|
||||
return;
|
||||
}
|
||||
|
||||
let audio_file_path = ""
|
||||
if (isUserInput || (extension_settings.audio.bgm_locked && extension_settings.audio.bgm_selected !== null)) {
|
||||
audio_file_path = extension_settings.audio.bgm_selected;
|
||||
|
||||
if (isUserInput)
|
||||
console.debug(DEBUG_PREFIX, "User selected BGM", audio_file_path);
|
||||
if (extension_settings.audio.bgm_locked)
|
||||
console.debug(DEBUG_PREFIX, "BGM locked keeping current audio", audio_file_path);
|
||||
}
|
||||
else {
|
||||
|
||||
let audio_files = null;
|
||||
|
||||
if (extension_settings.audio.dynamic_bgm_enabled) {
|
||||
extension_settings.audio.bgm_selected = null;
|
||||
saveSettingsDebounced();
|
||||
audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
|
||||
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
|
||||
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
|
||||
if (audio_files === undefined || audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
|
||||
audio_files = fallback_BGMS; // ST FALLBACK BGM
|
||||
|
||||
if (audio_files.length == 0) {
|
||||
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
audio_files = [];
|
||||
$("#audio_bgm_select option").each(function () { audio_files.push($(this).val()); });
|
||||
}
|
||||
|
||||
audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
|
||||
}
|
||||
|
||||
const audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
|
||||
console.log(DEBUG_PREFIX, "Updating BGM");
|
||||
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
|
||||
try {
|
||||
@@ -485,20 +734,30 @@ async function updateBGM() {
|
||||
console.log(DEBUG_PREFIX, "File not found!")
|
||||
}
|
||||
else {
|
||||
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM)
|
||||
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM);
|
||||
$("#audio_bgm_select").val(audio_file_path);
|
||||
const audio = $("#audio_bgm");
|
||||
|
||||
if (audio.attr("src") == audio_file_path) {
|
||||
if (audio.attr("src") == audio_file_path && !bgmEnded) {
|
||||
console.log(DEBUG_PREFIX, "Already playing, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
audio.animate({ volume: 0.0 }, 2000, function () {
|
||||
let fade_time = 2000;
|
||||
bgmEnded = false;
|
||||
|
||||
if (isUserInput || extension_settings.audio.bgm_locked) {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.bgm_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, 2000);
|
||||
})
|
||||
}
|
||||
else {
|
||||
audio.animate({ volume: 0.0 }, fade_time, function () {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.bgm_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, fade_time);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -506,18 +765,30 @@ async function updateBGM() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAmbient() {
|
||||
async function updateAmbient(isUserInput = false) {
|
||||
let audio_file_path = null;
|
||||
for (const i of ambients) {
|
||||
console.debug(i)
|
||||
if (i.includes(currentBackground)) {
|
||||
audio_file_path = i;
|
||||
break;
|
||||
|
||||
if (isUserInput || extension_settings.audio.ambient_locked) {
|
||||
audio_file_path = extension_settings.audio.ambient_selected;
|
||||
|
||||
if (isUserInput)
|
||||
console.debug(DEBUG_PREFIX, "User selected Ambient", audio_file_path);
|
||||
if (extension_settings.audio.bgm_locked)
|
||||
console.debug(DEBUG_PREFIX, "Ambient locked keeping current audio", audio_file_path);
|
||||
}
|
||||
else {
|
||||
extension_settings.audio.ambient_selected = null;
|
||||
for (const i of ambients) {
|
||||
console.debug(i)
|
||||
if (i.includes(currentBackground)) {
|
||||
audio_file_path = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (audio_file_path === null) {
|
||||
console.debug(DEBUG_PREFIX, "No ambient file found for background", currentBackground);
|
||||
console.debug(DEBUG_PREFIX, "No bgm file found for background", currentBackground);
|
||||
const audio = $("#audio_ambient");
|
||||
audio.attr("src", "");
|
||||
audio[0].pause();
|
||||
@@ -527,45 +798,87 @@ async function updateAmbient() {
|
||||
//const audio_file_path = AMBIENT_FOLDER+currentBackground+".mp3";
|
||||
console.log(DEBUG_PREFIX, "Updating ambient");
|
||||
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
|
||||
$("#audio_ambient_select").val(audio_file_path);
|
||||
|
||||
let fade_time = 2000;
|
||||
if (isUserInput)
|
||||
fade_time = 0;
|
||||
|
||||
const audio = $("#audio_ambient");
|
||||
audio.animate({ volume: 0.0 }, 2000, function () {
|
||||
|
||||
if (audio.attr("src") == audio_file_path) {
|
||||
console.log(DEBUG_PREFIX, "Already playing, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
audio.animate({ volume: 0.0 }, fade_time, function () {
|
||||
audio.attr("src", audio_file_path);
|
||||
audio[0].play();
|
||||
audio.volume = extension_settings.audio.ambient_volume * 0.01;
|
||||
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, 2000);
|
||||
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, fade_time);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles wheel events on volume sliders.
|
||||
* @param {WheelEvent} e Event
|
||||
*/
|
||||
function onVolumeSliderWheelEvent(e) {
|
||||
const slider = $(this);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const delta = e.deltaY / 20;
|
||||
const sliderVal = Number(slider.val());
|
||||
|
||||
let newVal = sliderVal - delta;
|
||||
if (newVal < 0) {
|
||||
newVal = 0;
|
||||
} else if (newVal > 100) {
|
||||
newVal = 100;
|
||||
}
|
||||
|
||||
slider.val(newVal).trigger('input');
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// Extension load //
|
||||
//#############################//
|
||||
|
||||
// This function is called when the extension is loaded
|
||||
jQuery(async () => {
|
||||
// This is an example of loading HTML from a file
|
||||
const windowHtml = $(await $.get(`${extensionFolderPath}/window.html`));
|
||||
|
||||
$('#extensions_settings').append(windowHtml);
|
||||
loadSettings();
|
||||
|
||||
$("#audio_bgm").attr("loop", true);
|
||||
$("#audio_enabled").on("click", onEnabledClick);
|
||||
$("#audio_dynamic_bgm_enabled").on("click", onDynamicBGMEnabledClick);
|
||||
//$("#audio_dynamic_ambient_enabled").on("click", onDynamicAmbientEnabledClick);
|
||||
|
||||
//$("#audio_bgm").attr("loop", false);
|
||||
$("#audio_ambient").attr("loop", true);
|
||||
|
||||
$("#audio_bgm").hide();
|
||||
$("#audio_ambient").hide();
|
||||
$("#audio_bgm_lock").on("click", onBGMLockClick);
|
||||
$("#audio_bgm_mute").on("click", onBGMMuteClick);
|
||||
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
|
||||
|
||||
$("#audio_enabled").on("click", onEnabledClick);
|
||||
$("#audio_bgm_volume_slider").on("input", onBGMVolumeChange);
|
||||
$("#audio_bgm_random").on("click", onBGMRandomClick);
|
||||
|
||||
$("#audio_ambient").hide();
|
||||
$("#audio_ambient_lock").on("click", onAmbientLockClick);
|
||||
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
|
||||
$("#audio_ambient_volume_slider").on("input", onAmbientVolumeChange);
|
||||
|
||||
document.getElementById('audio_ambient_volume_slider').addEventListener('wheel', onVolumeSliderWheelEvent, { passive: false });
|
||||
document.getElementById('audio_bgm_volume_slider').addEventListener('wheel', onVolumeSliderWheelEvent, { passive: false });
|
||||
|
||||
$("#audio_bgm_cooldown").on("input", onBGMCooldownInput);
|
||||
|
||||
// Reset assets container, will be redected like if ST restarted
|
||||
$("#audio_refresh_assets").on("click", function(){
|
||||
console.debug(DEBUG_PREFIX,"Refreshing audio assets");
|
||||
$("#audio_refresh_assets").on("click", function () {
|
||||
console.debug(DEBUG_PREFIX, "Refreshing audio assets");
|
||||
current_chat_id = null
|
||||
fallback_BGMS = null;
|
||||
ambients = null;
|
||||
characterMusics = {};
|
||||
@@ -574,6 +887,9 @@ jQuery(async () => {
|
||||
currentBackground = null;
|
||||
})
|
||||
|
||||
$("#audio_bgm_select").on("change", onBGMSelectChange);
|
||||
$("#audio_ambient_select").on("change", onAmbientSelectChange);
|
||||
|
||||
// DBG
|
||||
$("#audio_debug").on("click", function () {
|
||||
if ($("#audio_debug").is(':checked')) {
|
||||
@@ -587,6 +903,14 @@ jQuery(async () => {
|
||||
});
|
||||
//
|
||||
|
||||
$("#audio_bgm").on("ended", function () {
|
||||
console.debug(DEBUG_PREFIX, "END OF BGM")
|
||||
if (!extension_settings.audio.bgm_locked) {
|
||||
bgmEnded = true;
|
||||
updateBGM();
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
|
||||
moduleWorker();
|
||||
|
@@ -1,15 +1,68 @@
|
||||
.mixer-div {
|
||||
.audio-ui-block {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.audio-mixer-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
background-color: rgba(38, 38, 38, 0.5);
|
||||
border: 1px rgb(75, 75, 75) solid;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.audio-mixer-element {
|
||||
height: 60px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
label {
|
||||
text-align: top;
|
||||
}
|
||||
input {
|
||||
margin-top: 15px;
|
||||
}
|
||||
select {
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-label {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-volume-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.audio-lock-button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.audio-random-button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.audio-mute-button {
|
||||
padding: 5px;
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.audio-slider {
|
||||
width: 100% !important;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.audio-mute-button-muted {
|
||||
@@ -20,3 +73,23 @@
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.audio-mixer-mute {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.audio-mixer-volume {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.audio-mixer-playlist {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.audio-mixer-lock {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.audio-mixer-random {
|
||||
width: 10%;
|
||||
}
|
@@ -10,6 +10,20 @@
|
||||
<input type="checkbox" id="audio_enabled" name="audio_enabled">
|
||||
<small>Enabled</small>
|
||||
</label>
|
||||
<div id="audio_bgm_dynamic_enable_div">
|
||||
<label class="checkbox_label" for="audio_dynamic_bgm_enabled">
|
||||
<input type="checkbox" id="audio_dynamic_bgm_enabled" name="audio_dynamic_bgm_enabled">
|
||||
<small>Enable expression BGM switch (req. character expression)</small>
|
||||
</label>
|
||||
</div>
|
||||
<!--
|
||||
<div id="audio_ambient_dynamic_enable_div">
|
||||
<label class="checkbox_label" for="audio_dynamic_ambient_enabled">
|
||||
<input type="checkbox" id="audio_dynamic_ambient_enabled" name="audio_dynamic_ambient_enabled">
|
||||
<small>Enable ambient background switch (req. chat background)</small>
|
||||
</label>
|
||||
</div>
|
||||
-->
|
||||
<div id="audio_debug_div">
|
||||
<label class="checkbox_label" for="audio_debug">
|
||||
<input type="checkbox" id="audio_debug" name="audio_debug">
|
||||
@@ -24,23 +38,63 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="audio_bgm_volume_slider">Music <span id="audio_bgm_volume"></span></label>
|
||||
<div class="mixer-div">
|
||||
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_bgm_mute_icon"></i>
|
||||
<div class="audio-ui-block">
|
||||
<label for="audio_bgm_volume_slider">Music</label>
|
||||
<div class="audio-mixer-div">
|
||||
<div class="audio-mixer-element audio-mixer-mute">
|
||||
<label for="audio_bgm_lock" class="audio-label">Mute</label>
|
||||
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_bgm_mute_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-volume">
|
||||
<label for="audio_bgm_lock" class="audio-label">Vol <span id="audio_bgm_volume"></span></label>
|
||||
<input type="range" class ="audio-slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-playlist">
|
||||
<label for="audio_bgm_lock" class="audio-label">Playlist</label>
|
||||
<select id="audio_bgm_select">
|
||||
</select>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-lock">
|
||||
<label for="audio_bgm_lock" class="audio-label">Loop</label>
|
||||
<div id="audio_bgm_lock" class="menu_button audio-lock-button">
|
||||
<i class="fa-solid fa-repeat fa-lg" id="audio_bgm_lock_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-random">
|
||||
<label for="audio_bgm_random" class="audio-label">Roll</label>
|
||||
<div id="audio_bgm_random" class="menu_button audio-random-button">
|
||||
<i class="fa-solid fa-random fa-lg" id="audio_bgm_random_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" class ="slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<audio id="audio_bgm" controls src="">
|
||||
</div>
|
||||
<div>
|
||||
<label for="audio_ambient_volume_slider">Ambient <span id="audio_ambient_volume"></span></label>
|
||||
<div class="mixer-div">
|
||||
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_ambient_mute_icon"></i>
|
||||
<label for="audio_ambient_volume_slider">Ambient</label>
|
||||
<div class="audio-mixer-div">
|
||||
<div class="audio-mixer-element audio-mixer-mute">
|
||||
<label for="audio_ambient_lock" class="audio-label">Mute</label>
|
||||
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
|
||||
<i class="fa-solid fa-volume-high fa-lg" id="audio_ambient_mute_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-volume">
|
||||
<label for="audio_ambient_lock" class="audio-label">Vol <span id="audio_ambient_volume"></span></label>
|
||||
<input type="range" class ="audio-slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<div class="audio-mixer-element audio-mixer-playlist">
|
||||
<label for="audio_ambient_lock" class="audio-label">Playlist</label>
|
||||
<select id="audio_ambient_select">
|
||||
</select>
|
||||
</div>
|
||||
<div class="audio-mixer-element">
|
||||
<label for="audio_ambient_lock audio-mixer-lock" class="audio-label">Lock</label>
|
||||
<div id="audio_ambient_lock" class="menu_button audio-lock-button">
|
||||
<i class="fa-solid fa-lock-open fa-lg" id="audio_ambient_lock_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<input type="range" class ="slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
|
||||
</div>
|
||||
<audio id="audio_ambient" controls src="">
|
||||
</div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { generateQuietPrompt } from "../../../script.js";
|
||||
import { eventSource, event_types, generateQuietPrompt } from "../../../script.js";
|
||||
import { getContext, saveMetadataDebounced } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { stringFormat } from "../../utils.js";
|
||||
@@ -6,8 +6,10 @@ export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'backgrounds';
|
||||
const METADATA_KEY = 'custom_background';
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
/**
|
||||
* @param {string} background
|
||||
*/
|
||||
function forceSetBackground(background) {
|
||||
saveBackgroundMetadata(background);
|
||||
setCustomBackground();
|
||||
@@ -168,9 +170,9 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
addSettings();
|
||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||
registerSlashCommand('lockbg', onLockBackgroundClick, ['bglock'], " – locks a background for the currently selected chat", true, true);
|
||||
registerSlashCommand('unlockbg', onUnlockBackgroundClick, ['bgunlock'], ' – unlocks a background for the currently selected chat', true, true);
|
||||
registerSlashCommand('autobg', autoBackgroundCommand, ['bgauto'], ' – automatically changes the background based on the chat context using the AI request prompt', true, true);
|
||||
window['forceSetBackground'] = forceSetBackground;
|
||||
eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground);
|
||||
eventSource.on(event_types.CHAT_CHANGED, moduleWorker);
|
||||
});
|
||||
|
@@ -6,7 +6,7 @@
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--black50a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
box-shadow: 0 0 7px var(--black50a);
|
||||
margin: 5px;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { getBase64Async } from "../../utils.js";
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings } from "../../extensions.js";
|
||||
import { callPopup, saveSettingsDebounced } from "../../../script.js";
|
||||
import { getBase64Async, saveBase64AsFile } from "../../utils.js";
|
||||
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules } from "../../extensions.js";
|
||||
import { callPopup, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
|
||||
import { getMessageTimeStamp } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
@@ -8,7 +8,8 @@ const MODULE_NAME = 'caption';
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
async function moduleWorker() {
|
||||
$('#send_picture').toggle(getContext().onlineStatus !== 'no_connection');
|
||||
const hasConnection = getContext().onlineStatus !== 'no_connection';
|
||||
$('#send_picture').toggle(hasConnection);
|
||||
}
|
||||
|
||||
async function setImageIcon() {
|
||||
@@ -52,7 +53,6 @@ async function sendCaptionedMessage(caption, image) {
|
||||
const message = {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: messageText,
|
||||
extra: {
|
||||
@@ -65,16 +65,21 @@ async function sendCaptionedMessage(caption, image) {
|
||||
await context.generate('caption');
|
||||
}
|
||||
|
||||
async function onSelectImage(e) {
|
||||
setSpinnerIcon();
|
||||
const file = e.target.files[0];
|
||||
async function doCaptionRequest(base64Img) {
|
||||
if (extension_settings.caption.local) {
|
||||
const apiResult = await fetch('/api/extra/caption', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ image: base64Img })
|
||||
});
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!apiResult.ok) {
|
||||
throw new Error('Failed to caption image via local pipeline.');
|
||||
}
|
||||
|
||||
try {
|
||||
const base64Img = await getBase64Async(file);
|
||||
const data = await apiResult.json();
|
||||
return data;
|
||||
} else if (modules.includes('caption')) {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/caption';
|
||||
|
||||
@@ -84,17 +89,42 @@ async function onSelectImage(e) {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ image: base64Img.split(',')[1] })
|
||||
body: JSON.stringify({ image: base64Img })
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
const caption = data.caption;
|
||||
const imageToSave = data.thumbnail ? `data:image/jpeg;base64,${data.thumbnail}` : base64Img;
|
||||
await sendCaptionedMessage(caption, imageToSave);
|
||||
if (!apiResult.ok) {
|
||||
throw new Error('Failed to caption image via Extras.');
|
||||
}
|
||||
|
||||
const data = await apiResult.json();
|
||||
return data;
|
||||
} else {
|
||||
throw new Error('No captioning module is available.');
|
||||
}
|
||||
}
|
||||
|
||||
async function onSelectImage(e) {
|
||||
setSpinnerIcon();
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file || !(file instanceof File)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const context = getContext();
|
||||
const fileData = await getBase64Async(file);
|
||||
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
|
||||
const base64Data = fileData.split(',')[1];
|
||||
const data = await doCaptionRequest(base64Data);
|
||||
const caption = data.caption;
|
||||
const imageToSave = data.thumbnail ? data.thumbnail : base64Data;
|
||||
const format = data.thumbnail ? 'jpeg' : base64Format;
|
||||
const imagePath = await saveBase64AsFile(imageToSave, context.name2, '', format);
|
||||
await sendCaptionedMessage(caption, imagePath);
|
||||
}
|
||||
catch (error) {
|
||||
toastr.error('Failed to caption image.');
|
||||
console.log(error);
|
||||
}
|
||||
finally {
|
||||
@@ -113,12 +143,21 @@ jQuery(function () {
|
||||
const sendButton = $(`
|
||||
<div id="send_picture" class="list-group-item flex-container flexGap5">
|
||||
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
|
||||
Send a picture
|
||||
Send a Picture
|
||||
</div>`);
|
||||
|
||||
$('#extensionsMenu').prepend(sendButton);
|
||||
$(sendButton).hide();
|
||||
$(sendButton).on('click', () => $('#img_file').trigger('click'));
|
||||
$(sendButton).on('click', () => {
|
||||
const hasCaptionModule = modules.includes('caption') || extension_settings.caption.local;
|
||||
|
||||
if (!hasCaptionModule) {
|
||||
toastr.error('No captioning module is available. Either enable the local captioning pipeline or connect to Extras.');
|
||||
return;
|
||||
}
|
||||
|
||||
$('#img_file').trigger('click');
|
||||
});
|
||||
}
|
||||
function addPictureSendForm() {
|
||||
const inputHtml = `<input id="img_file" type="file" accept="image/*">`;
|
||||
@@ -131,13 +170,17 @@ jQuery(function () {
|
||||
}
|
||||
function addSettings() {
|
||||
const html = `
|
||||
<div class="background_settings">
|
||||
<div class="caption_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Image Captioning</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label" for="caption_local">
|
||||
<input id="caption_local" type="checkbox" class="checkbox">
|
||||
Use local captioning pipeline
|
||||
</label>
|
||||
<label class="checkbox_label" for="caption_refine_mode">
|
||||
<input id="caption_refine_mode" type="checkbox" class="checkbox">
|
||||
Edit captions before generation
|
||||
@@ -155,6 +198,11 @@ jQuery(function () {
|
||||
setImageIcon();
|
||||
moduleWorker();
|
||||
$('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
|
||||
$('#caption_local').prop('checked', !!(extension_settings.caption.local));
|
||||
$('#caption_refine_mode').on('input', onRefineModeInput);
|
||||
$('#caption_local').on('input', () => {
|
||||
extension_settings.caption.local = !!$('#caption_local').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"display_name": "Image Captioning",
|
||||
"loading_order": 4,
|
||||
"requires": [
|
||||
"requires": [],
|
||||
"optional": [
|
||||
"caption"
|
||||
],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee#1207",
|
||||
|
@@ -385,7 +385,7 @@ jQuery(async () => {
|
||||
|
||||
const buttonHtml = $(await $.get(`${extensionFolderPath}/menuButton.html`));
|
||||
buttonHtml.on('click', onCfgMenuItemClick)
|
||||
buttonHtml.insertAfter("#option_toggle_AN");
|
||||
buttonHtml.appendTo("#options_advanced");
|
||||
|
||||
// Hook events
|
||||
eventSource.on(event_types.CHAT_CHANGED, async () => {
|
||||
|
@@ -21,6 +21,6 @@
|
||||
}
|
||||
|
||||
#dice_dropdown {
|
||||
z-index: 100;
|
||||
z-index: 30000;
|
||||
backdrop-filter: blur(--SmartThemeBlurStrength);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
<h3>
|
||||
Enter a name for the custom expression:
|
||||
</h3>
|
||||
<h4>
|
||||
Requirements:
|
||||
</h4>
|
||||
<ol class="justifyLeft">
|
||||
<li>
|
||||
The name must be unique and not already in use by the default expression.
|
||||
</li>
|
||||
<li>
|
||||
The name must contain only letters, numbers, dashes and underscores. Don't include any file extensions.
|
||||
</li>
|
||||
</ol>
|
@@ -2,11 +2,13 @@ import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDeb
|
||||
import { dragElement, isMobile } from "../../RossAscends-mods.js";
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { loadMovingUIState, power_user } from "../../power-user.js";
|
||||
import { onlyUnique, debounce, getCharaFilename } from "../../utils.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'expressions';
|
||||
const UPDATE_INTERVAL = 2000;
|
||||
const STREAMING_UPDATE_INTERVAL = 6000;
|
||||
const FALLBACK_EXPRESSION = 'joy';
|
||||
const DEFAULT_EXPRESSIONS = [
|
||||
"talkinghead",
|
||||
@@ -45,6 +47,7 @@ let lastCharacter = undefined;
|
||||
let lastMessage = null;
|
||||
let spriteCache = {};
|
||||
let inApiCall = false;
|
||||
let lastServerResponseTime = 0;
|
||||
|
||||
function isVisualNovelMode() {
|
||||
return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
|
||||
@@ -125,15 +128,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let spriteFolderName = character.name;
|
||||
const avatarFileName = getSpriteFolderName({ original_avatar: character.avatar });
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
const spriteFolderName = getSpriteFolderName({ original_avatar: character.avatar }, character.name);
|
||||
|
||||
// download images if not downloaded yet
|
||||
if (spriteCache[spriteFolderName] === undefined) {
|
||||
@@ -270,16 +265,7 @@ async function setLastMessageSprite(img, avatar, labels) {
|
||||
|
||||
if (lastMessage) {
|
||||
const text = lastMessage.mes || '';
|
||||
let spriteFolderName = lastMessage.name;
|
||||
const avatarFileName = getSpriteFolderName(lastMessage);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
|
||||
const spriteFolderName = getSpriteFolderName(lastMessage, lastMessage.name);
|
||||
const sprites = spriteCache[spriteFolderName] || [];
|
||||
const label = await getExpressionLabel(text);
|
||||
const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : '';
|
||||
@@ -365,7 +351,7 @@ async function setImage(img, path) {
|
||||
expressionClone.removeClass('default');
|
||||
expressionClone.off('error');
|
||||
expressionClone.on('error', function () {
|
||||
console.debug('Expression image error', sprite.path);
|
||||
console.debug('Expression image error', path);
|
||||
$(this).attr('src', '');
|
||||
$(this).off('error');
|
||||
resolve();
|
||||
@@ -394,6 +380,11 @@ function onExpressionsShowDefaultInput() {
|
||||
}
|
||||
|
||||
async function unloadLiveChar() {
|
||||
if (!modules.includes('talkinghead')) {
|
||||
console.debug('talkinghead module is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/talkinghead/unload';
|
||||
@@ -414,17 +405,7 @@ async function loadLiveChar() {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
let spriteFolderName = context.name2;
|
||||
const message = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
const spriteFolderName = getSpriteFolderName();
|
||||
|
||||
const talkingheadPath = `/characters/${encodeURIComponent(spriteFolderName)}/talkinghead.png`;
|
||||
|
||||
@@ -463,19 +444,19 @@ async function loadLiveChar() {
|
||||
function handleImageChange() {
|
||||
const imgElement = document.querySelector('img#expression-image.expression');
|
||||
|
||||
if (!imgElement) {
|
||||
if (!imgElement || !(imgElement instanceof HTMLImageElement)) {
|
||||
console.log("Cannot find addExpressionImage()");
|
||||
return;
|
||||
}
|
||||
|
||||
if (extension_settings.expressions.talkinghead) {
|
||||
if (extension_settings.expressions.talkinghead && !extension_settings.expressions.local) {
|
||||
// Method get IP of endpoint
|
||||
const talkingheadResultFeedSrc = `${getApiUrl()}/api/talkinghead/result_feed`;
|
||||
$('#expression-holder').css({ display: '' });
|
||||
if (imgElement.src !== talkingheadResultFeedSrc) {
|
||||
const expressionImageElement = document.querySelector('.expression_list_image');
|
||||
|
||||
if (expressionImageElement) {
|
||||
if (expressionImageElement && expressionImageElement instanceof HTMLImageElement) {
|
||||
doExtrasFetch(expressionImageElement.src, {
|
||||
method: 'HEAD',
|
||||
})
|
||||
@@ -498,6 +479,14 @@ function handleImageChange() {
|
||||
async function moduleWorker() {
|
||||
const context = getContext();
|
||||
|
||||
// Hide and disable talkinghead while in local mode
|
||||
$('#image_type_block').toggle(!extension_settings.expressions.local);
|
||||
|
||||
if (extension_settings.expressions.local && extension_settings.expressions.talkinghead) {
|
||||
$('#image_type_toggle').prop('checked', false);
|
||||
setTalkingHeadState(false);
|
||||
}
|
||||
|
||||
// non-characters not supported
|
||||
if (!context.groupId && (context.characterId === undefined || context.characterId === 'invalid-safety-id')) {
|
||||
removeExpression();
|
||||
@@ -511,12 +500,14 @@ async function moduleWorker() {
|
||||
|
||||
//clear expression
|
||||
let imgElement = document.getElementById('expression-image');
|
||||
imgElement.src = "";
|
||||
if (imgElement && imgElement instanceof HTMLImageElement) {
|
||||
imgElement.src = "";
|
||||
}
|
||||
|
||||
//set checkbox to global var
|
||||
$('#image_type_toggle').prop('checked', extension_settings.expressions.talkinghead);
|
||||
if (extension_settings.expressions.talkinghead) {
|
||||
settalkingheadState(extension_settings.expressions.talkinghead);
|
||||
setTalkingHeadState(extension_settings.expressions.talkinghead);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,15 +531,7 @@ async function moduleWorker() {
|
||||
}
|
||||
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
let spriteFolderName = currentLastMessage.name;
|
||||
const avatarFileName = getSpriteFolderName(currentLastMessage);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
let spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
|
||||
|
||||
// character has no expressions or it is not loaded
|
||||
if (Object.keys(spriteCache).length === 0) {
|
||||
@@ -557,8 +540,9 @@ async function moduleWorker() {
|
||||
}
|
||||
|
||||
const offlineMode = $('.expression_settings .offline_mode');
|
||||
if (!modules.includes('classify')) {
|
||||
$('.expression_settings').show();
|
||||
if (!modules.includes('classify') && !extension_settings.expressions.local) {
|
||||
$('#open_chat_expressions').show();
|
||||
$('#no_chat_expressions').hide();
|
||||
offlineMode.css('display', 'block');
|
||||
lastCharacter = context.groupId || context.characterId;
|
||||
|
||||
@@ -582,6 +566,11 @@ async function moduleWorker() {
|
||||
offlineMode.css('display', 'none');
|
||||
}
|
||||
|
||||
// Don't bother classifying if current char has no sprites and no default expressions are enabled
|
||||
if ((!Array.isArray(spriteCache[spriteFolderName]) || spriteCache[spriteFolderName].length === 0) && !extension_settings.expressions.showDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if last message changed
|
||||
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
|
||||
&& lastMessage === currentLastMessage.mes) {
|
||||
@@ -590,9 +579,21 @@ async function moduleWorker() {
|
||||
|
||||
// API is busy
|
||||
if (inApiCall) {
|
||||
console.debug('Classification API is busy');
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttle classification requests during streaming
|
||||
if (context.streamingProcessor && !context.streamingProcessor.isFinished) {
|
||||
const now = Date.now();
|
||||
const timeSinceLastServerResponse = now - lastServerResponseTime;
|
||||
|
||||
if (timeSinceLastServerResponse < STREAMING_UPDATE_INTERVAL) {
|
||||
console.log('Streaming in progress: throttling expression update. Next update at ' + new Date(lastServerResponseTime + STREAMING_UPDATE_INTERVAL));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
inApiCall = true;
|
||||
let expression = await getExpressionLabel(currentLastMessage.mes);
|
||||
@@ -610,7 +611,6 @@ async function moduleWorker() {
|
||||
}
|
||||
|
||||
await sendExpressionCall(spriteFolderName, expression, force, vnMode);
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
@@ -619,21 +619,12 @@ async function moduleWorker() {
|
||||
inApiCall = false;
|
||||
lastCharacter = context.groupId || context.characterId;
|
||||
lastMessage = currentLastMessage.mes;
|
||||
lastServerResponseTime = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
async function talkingheadcheck() {
|
||||
const context = getContext();
|
||||
let spriteFolderName = context.name2;
|
||||
const message = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
async function talkingHeadCheck() {
|
||||
let spriteFolderName = getSpriteFolderName();
|
||||
|
||||
try {
|
||||
await validateImages(spriteFolderName);
|
||||
@@ -654,11 +645,29 @@ async function talkingheadcheck() {
|
||||
}
|
||||
}
|
||||
|
||||
function settalkingheadState(switch_var) {
|
||||
function getSpriteFolderName(characterMessage = null, characterName = null) {
|
||||
const context = getContext();
|
||||
let spriteFolderName = characterName ?? context.name2;
|
||||
const message = characterMessage ?? getLastCharacterMessage();
|
||||
const avatarFileName = getFolderNameByMessage(message);
|
||||
const expressionOverride = extension_settings.expressionOverrides.find(e => e.name == avatarFileName);
|
||||
|
||||
if (expressionOverride && expressionOverride.path) {
|
||||
spriteFolderName = expressionOverride.path;
|
||||
}
|
||||
|
||||
return spriteFolderName;
|
||||
}
|
||||
|
||||
function setTalkingHeadState(switch_var) {
|
||||
extension_settings.expressions.talkinghead = switch_var; // Store setting
|
||||
saveSettingsDebounced();
|
||||
|
||||
talkingheadcheck().then(result => {
|
||||
if (extension_settings.expressions.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
talkingHeadCheck().then(result => {
|
||||
if (result) {
|
||||
//console.log("talkinghead exists!");
|
||||
|
||||
@@ -667,7 +676,7 @@ function settalkingheadState(switch_var) {
|
||||
} else {
|
||||
unloadLiveChar();
|
||||
}
|
||||
handleImageChange(switch_var); // Change image as needed
|
||||
handleImageChange(); // Change image as needed
|
||||
|
||||
|
||||
} else {
|
||||
@@ -676,7 +685,7 @@ function settalkingheadState(switch_var) {
|
||||
});
|
||||
}
|
||||
|
||||
function getSpriteFolderName(message) {
|
||||
function getFolderNameByMessage(message) {
|
||||
const context = getContext();
|
||||
let avatarPath = '';
|
||||
|
||||
@@ -707,27 +716,122 @@ async function sendExpressionCall(name, expression, force, vnMode) {
|
||||
}
|
||||
}
|
||||
|
||||
async function setSpriteSetCommand(_, folder) {
|
||||
if (!folder) {
|
||||
console.log('Clearing sprite set');
|
||||
folder = '';
|
||||
}
|
||||
|
||||
if (folder.startsWith('/') || folder.startsWith('\\')) {
|
||||
folder = folder.slice(1);
|
||||
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
folder = `${currentLastMessage.name}/${folder}`;
|
||||
}
|
||||
|
||||
$("#expression_override").val(folder.trim());
|
||||
onClickExpressionOverrideButton();
|
||||
removeExpression();
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function setSpriteSlashCommand(_, spriteId) {
|
||||
if (!spriteId) {
|
||||
console.log('No sprite id provided');
|
||||
return;
|
||||
}
|
||||
|
||||
spriteId = spriteId.trim().toLowerCase();
|
||||
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
|
||||
await validateImages(spriteFolderName);
|
||||
|
||||
// Fuzzy search for sprite
|
||||
const fuse = new Fuse(spriteCache[spriteFolderName], { keys: ['label'] });
|
||||
const results = fuse.search(spriteId);
|
||||
const spriteItem = results[0]?.item;
|
||||
|
||||
if (!spriteItem) {
|
||||
console.log('No sprite found for search term ' + spriteId);
|
||||
return;
|
||||
}
|
||||
|
||||
const vnMode = isVisualNovelMode();
|
||||
await sendExpressionCall(spriteFolderName, spriteItem.label, true, vnMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the classification text to reduce the amount of text sent to the API.
|
||||
* Quotes and asterisks are to be removed. If the text is less than 300 characters, it is returned as is.
|
||||
* If the text is more than 300 characters, the first and last 150 characters are returned.
|
||||
* The result is trimmed to the end of sentence.
|
||||
* @param {string} text The text to process.
|
||||
* @returns {string}
|
||||
*/
|
||||
function sampleClassifyText(text) {
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Remove asterisks and quotes
|
||||
let result = text.replace(/[\*\"]/g, '');
|
||||
|
||||
const SAMPLE_THRESHOLD = 300;
|
||||
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;
|
||||
|
||||
if (text.length < SAMPLE_THRESHOLD) {
|
||||
result = trimToEndSentence(result);
|
||||
} else {
|
||||
result = trimToEndSentence(result.slice(0, HALF_SAMPLE_THRESHOLD)) + ' ' + trimToStartSentence(result.slice(-HALF_SAMPLE_THRESHOLD));
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
async function getExpressionLabel(text) {
|
||||
// Return if text is undefined, saving a costly fetch request
|
||||
if (!modules.includes('classify') || !text) {
|
||||
if ((!modules.includes('classify') && !extension_settings.expressions.local) || !text) {
|
||||
return FALLBACK_EXPRESSION;
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify';
|
||||
text = sampleClassifyText(text);
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ text: text }),
|
||||
});
|
||||
try {
|
||||
if (extension_settings.expressions.local) {
|
||||
// Local transformers pipeline
|
||||
const apiResult = await fetch('/api/extra/classify', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text }),
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
return data.classification[0].label;
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
return data.classification[0].label;
|
||||
}
|
||||
} else {
|
||||
// Extras
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify';
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
},
|
||||
body: JSON.stringify({ text: text }),
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
return data.classification[0].label;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return FALLBACK_EXPRESSION;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,7 +855,8 @@ function removeExpression() {
|
||||
$('img.expression').off('error');
|
||||
$('img.expression').prop('src', '');
|
||||
$('img.expression').removeClass('default');
|
||||
$('.expression_settings').hide();
|
||||
$('#open_chat_expressions').hide();
|
||||
$('#no_chat_expressions').show();
|
||||
}
|
||||
|
||||
async function validateImages(character, forceRedrawCached) {
|
||||
@@ -777,9 +882,11 @@ async function validateImages(character, forceRedrawCached) {
|
||||
|
||||
function drawSpritesList(character, labels, sprites) {
|
||||
let validExpressions = [];
|
||||
$('.expression_settings').show();
|
||||
$('#no_chat_expressions').hide();
|
||||
$('#open_chat_expressions').show();
|
||||
$('#image_list').empty();
|
||||
$('#image_list').data('name', character);
|
||||
$('#image_list_header_name').text(character);
|
||||
|
||||
if (!Array.isArray(labels)) {
|
||||
return [];
|
||||
@@ -787,27 +894,36 @@ function drawSpritesList(character, labels, sprites) {
|
||||
|
||||
labels.sort().forEach((item) => {
|
||||
const sprite = sprites.find(x => x.label == item);
|
||||
const isCustom = extension_settings.expressions.custom.includes(item);
|
||||
|
||||
if (sprite) {
|
||||
validExpressions.push(sprite);
|
||||
$('#image_list').append(getListItem(item, sprite.path, 'success'));
|
||||
$('#image_list').append(getListItem(item, sprite.path, 'success', isCustom));
|
||||
}
|
||||
else {
|
||||
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure'));
|
||||
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure', isCustom));
|
||||
}
|
||||
});
|
||||
return validExpressions;
|
||||
}
|
||||
|
||||
function getListItem(item, imageSrc, textClass) {
|
||||
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass });
|
||||
/**
|
||||
* Renders a list item template for the expressions list.
|
||||
* @param {string} item Expression name
|
||||
* @param {string} imageSrc Path to image
|
||||
* @param {'success' | 'failure'} textClass 'success' or 'failure'
|
||||
* @param {boolean} isCustom If expression is added by user
|
||||
* @returns {string} Rendered list item template
|
||||
*/
|
||||
function getListItem(item, imageSrc, textClass, isCustom) {
|
||||
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass, isCustom });
|
||||
}
|
||||
|
||||
async function getSpritesList(name) {
|
||||
console.debug('getting sprites list');
|
||||
|
||||
try {
|
||||
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`);
|
||||
const result = await fetch(`/api/sprites/get?name=${encodeURIComponent(name)}`);
|
||||
let sprites = result.ok ? (await result.json()) : [];
|
||||
return sprites;
|
||||
}
|
||||
@@ -817,40 +933,84 @@ async function getSpritesList(name) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getExpressionsList() {
|
||||
// get something for offline mode (default images)
|
||||
if (!modules.includes('classify')) {
|
||||
return DEFAULT_EXPRESSIONS;
|
||||
function renderCustomExpressions() {
|
||||
if (!Array.isArray(extension_settings.expressions.custom)) {
|
||||
extension_settings.expressions.custom = [];
|
||||
}
|
||||
|
||||
const customExpressions = extension_settings.expressions.custom.sort((a, b) => a.localeCompare(b));
|
||||
$('#expression_custom').empty();
|
||||
|
||||
for (const expression of customExpressions) {
|
||||
const option = document.createElement('option');
|
||||
option.value = expression;
|
||||
option.text = expression;
|
||||
$('#expression_custom').append(option);
|
||||
}
|
||||
|
||||
if (customExpressions.length === 0) {
|
||||
$('#expression_custom').append('<option value="" disabled selected>[ No custom expressions ]</option>');
|
||||
}
|
||||
}
|
||||
|
||||
async function getExpressionsList() {
|
||||
// Return cached list if available
|
||||
if (Array.isArray(expressionsList)) {
|
||||
return expressionsList;
|
||||
}
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify/labels';
|
||||
/**
|
||||
* Returns the list of expressions from the API or fallback in offline mode.
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async function resolveExpressionsList() {
|
||||
// get something for offline mode (default images)
|
||||
if (!modules.includes('classify') && !extension_settings.expressions.local) {
|
||||
return DEFAULT_EXPRESSIONS;
|
||||
}
|
||||
|
||||
try {
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
||||
});
|
||||
try {
|
||||
if (extension_settings.expressions.local) {
|
||||
const apiResult = await fetch('/api/extra/classify/labels', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
if (apiResult.ok) {
|
||||
const data = await apiResult.json();
|
||||
expressionsList = data.labels;
|
||||
return expressionsList;
|
||||
}
|
||||
} else {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/classify/labels';
|
||||
|
||||
const data = await apiResult.json();
|
||||
expressionsList = data.labels;
|
||||
return expressionsList;
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'GET',
|
||||
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
||||
});
|
||||
|
||||
if (apiResult.ok) {
|
||||
|
||||
const data = await apiResult.json();
|
||||
expressionsList = data.labels;
|
||||
return expressionsList;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await resolveExpressionsList();
|
||||
result.push(...extension_settings.expressions.custom);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function setExpression(character, expression, force) {
|
||||
if (!extension_settings.expressions.talkinghead) {
|
||||
if (extension_settings.expressions.local || !extension_settings.expressions.talkinghead) {
|
||||
console.debug('entered setExpressions');
|
||||
await validateImages(character);
|
||||
const img = $('img.expression');
|
||||
@@ -963,12 +1123,12 @@ async function setExpression(character, expression, force) {
|
||||
} else {
|
||||
|
||||
|
||||
talkingheadcheck().then(result => {
|
||||
talkingHeadCheck().then(result => {
|
||||
if (result) {
|
||||
// Find the <img> element with id="expression-image" and class="expression"
|
||||
const imgElement = document.querySelector('img#expression-image.expression');
|
||||
//console.log("searching");
|
||||
if (imgElement) {
|
||||
if (imgElement && imgElement instanceof HTMLImageElement) {
|
||||
//console.log("setting value");
|
||||
imgElement.src = getApiUrl() + '/api/talkinghead/result_feed';
|
||||
}
|
||||
@@ -983,18 +1143,76 @@ async function setExpression(character, expression, force) {
|
||||
}
|
||||
|
||||
function onClickExpressionImage() {
|
||||
// online mode doesn't need force set
|
||||
if (modules.includes('classify')) {
|
||||
const expression = $(this).attr('id');
|
||||
setSpriteSlashCommand({}, expression);
|
||||
}
|
||||
|
||||
async function onClickExpressionAddCustom() {
|
||||
let expressionName = await callPopup(renderExtensionTemplate(MODULE_NAME, 'add-custom-expression'), 'input');
|
||||
|
||||
if (!expressionName) {
|
||||
console.debug('No custom expression name provided');
|
||||
return;
|
||||
}
|
||||
|
||||
const expression = $(this).attr('id');
|
||||
const name = getLastCharacterMessage().name;
|
||||
expressionName = expressionName.trim().toLowerCase();
|
||||
|
||||
if ($(this).find('.failure').length === 0) {
|
||||
setExpression(name, expression, true);
|
||||
// a-z, 0-9, dashes and underscores only
|
||||
if (!/^[a-z0-9-_]+$/.test(expressionName)) {
|
||||
toastr.info('Invalid custom expression name provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if expression name already exists in default expressions
|
||||
if (DEFAULT_EXPRESSIONS.includes(expressionName)) {
|
||||
toastr.info('Expression name already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if expression name already exists in custom expressions
|
||||
if (extension_settings.expressions.custom.includes(expressionName)) {
|
||||
toastr.info('Custom expression already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add custom expression into settings
|
||||
extension_settings.expressions.custom.push(expressionName);
|
||||
renderCustomExpressions();
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Force refresh sprites list
|
||||
expressionsList = null;
|
||||
spriteCache = {};
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function onClickExpressionRemoveCustom() {
|
||||
const selectedExpression = $('#expression_custom').val();
|
||||
|
||||
if (!selectedExpression) {
|
||||
console.debug('No custom expression selected');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmation = await callPopup(renderExtensionTemplate(MODULE_NAME, 'remove-custom-expression', { expression: selectedExpression }), 'confirm');
|
||||
|
||||
if (!confirmation) {
|
||||
console.debug('Custom expression removal cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove custom expression from settings
|
||||
const index = extension_settings.expressions.custom.indexOf(selectedExpression);
|
||||
extension_settings.expressions.custom.splice(index, 1);
|
||||
renderCustomExpressions();
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Force refresh sprites list
|
||||
expressionsList = null;
|
||||
spriteCache = {};
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function handleFileUpload(url, formData) {
|
||||
try {
|
||||
const data = await jQuery.ajax({
|
||||
@@ -1037,7 +1255,7 @@ async function onClickExpressionUpload(event) {
|
||||
formData.append('label', id);
|
||||
formData.append('avatar', file);
|
||||
|
||||
await handleFileUpload('/upload_sprite', formData);
|
||||
await handleFileUpload('/api/sprites/upload', formData);
|
||||
|
||||
// Reset the input
|
||||
e.target.form.reset();
|
||||
@@ -1052,7 +1270,7 @@ async function onClickExpressionUpload(event) {
|
||||
async function onClickExpressionOverrideButton() {
|
||||
const context = getContext();
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(currentLastMessage);
|
||||
const avatarFileName = getFolderNameByMessage(currentLastMessage);
|
||||
|
||||
// If the avatar name couldn't be found, abort.
|
||||
if (!avatarFileName) {
|
||||
@@ -1061,7 +1279,7 @@ async function onClickExpressionOverrideButton() {
|
||||
return;
|
||||
}
|
||||
|
||||
const overridePath = $("#expression_override").val();
|
||||
const overridePath = String($("#expression_override").val());
|
||||
const existingOverrideIndex = extension_settings.expressionOverrides.findIndex((e) =>
|
||||
e.name == avatarFileName
|
||||
);
|
||||
@@ -1143,7 +1361,7 @@ async function onClickExpressionUploadPackButton() {
|
||||
formData.append('name', name);
|
||||
formData.append('avatar', file);
|
||||
|
||||
const { count } = await handleFileUpload('/upload_sprite_pack', formData);
|
||||
const { count } = await handleFileUpload('/api/sprites/upload-zip', formData);
|
||||
toastr.success(`Uploaded ${count} image(s) for ${name}`);
|
||||
|
||||
// Reset the input
|
||||
@@ -1170,7 +1388,7 @@ async function onClickExpressionDelete(event) {
|
||||
const name = $('#image_list').data('name');
|
||||
|
||||
try {
|
||||
await fetch('/delete_sprite', {
|
||||
await fetch('/api/sprites/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name, label: id }),
|
||||
@@ -1186,7 +1404,7 @@ async function onClickExpressionDelete(event) {
|
||||
|
||||
function setExpressionOverrideHtml(forceClear = false) {
|
||||
const currentLastMessage = getLastCharacterMessage();
|
||||
const avatarFileName = getSpriteFolderName(currentLastMessage);
|
||||
const avatarFileName = getFolderNameByMessage(currentLastMessage);
|
||||
if (!avatarFileName) {
|
||||
return;
|
||||
}
|
||||
@@ -1232,6 +1450,11 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
|
||||
$('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
|
||||
$('#expression_local').prop('checked', extension_settings.expressions.local).on('input', function () {
|
||||
extension_settings.expressions.local = !!$(this).prop('checked');
|
||||
moduleWorker();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
|
||||
$(document).on('dragstart', '.expression', (e) => {
|
||||
e.preventDefault()
|
||||
@@ -1241,11 +1464,18 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
$(document).on('click', '.expression_list_upload', onClickExpressionUpload);
|
||||
$(document).on('click', '.expression_list_delete', onClickExpressionDelete);
|
||||
$(window).on("resize", updateVisualNovelModeDebounced);
|
||||
$('.expression_settings').hide();
|
||||
$("#open_chat_expressions").hide();
|
||||
|
||||
$('#image_type_toggle').on('click', function () {
|
||||
settalkingheadState(this.checked);
|
||||
if (this instanceof HTMLInputElement) {
|
||||
setTalkingHeadState(this.checked);
|
||||
}
|
||||
});
|
||||
|
||||
renderCustomExpressions();
|
||||
|
||||
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
|
||||
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
|
||||
}
|
||||
|
||||
addExpressionImage();
|
||||
@@ -1265,4 +1495,6 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
});
|
||||
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
|
||||
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
|
||||
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">spriteId</span> – force sets the sprite for the current character', true, true);
|
||||
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">folder</span> – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
|
||||
})();
|
||||
|
@@ -7,6 +7,11 @@
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="expression_list_title {{textClass}}">{{item}}</span>
|
||||
<div class="expression_list_title {{textClass}}">
|
||||
<span>{{item}}</span>
|
||||
{{#if isCustom}}
|
||||
<small class="expression_list_custom">(custom)</small>
|
||||
{{/if}}
|
||||
</div>
|
||||
<img class="expression_list_image" src="{{imageSrc}}" />
|
||||
</div>
|
||||
|
@@ -0,0 +1,7 @@
|
||||
<h3>
|
||||
Are you sure you want to remove the expression <tt>"{{expression}}"</tt>?
|
||||
</h3>
|
||||
<div>
|
||||
Uploaded images will not be deleted, but will no longer be used by the extension.
|
||||
</div>
|
||||
<br>
|
@@ -6,35 +6,57 @@
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<!-- Toggle button for aituber/static images -->
|
||||
<div class="toggle_button">
|
||||
<label class="switch">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
<label for="image_type_toggle">Image Type - talkinghead (extras)</label>
|
||||
</label>
|
||||
</div>
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
</div>
|
||||
<div id="image_list"></div>
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
<label class="checkbox_label" for="expression_local" title="Use classification model without the Extras server.">
|
||||
<input id="expression_local" type="checkbox" />
|
||||
<span data-i18n="Local server classification">Local server classification</span>
|
||||
</label>
|
||||
<label class="checkbox_label" for="expressions_show_default">
|
||||
<input id="expressions_show_default" type="checkbox">
|
||||
<span>Show default images (emojis) if sprite missing</span>
|
||||
</label>
|
||||
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span>Image Type - talkinghead (extras)</span>
|
||||
</label>
|
||||
<div class="expression_custom_block m-b-1 m-t-1">
|
||||
<label for="expression_custom">Custom Expressions</label>
|
||||
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
|
||||
<div class="flex-container">
|
||||
<select id="expression_custom" class="flex1 margin0"><select>
|
||||
<i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
|
||||
<i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||
<div id="no_chat_expressions">
|
||||
Open a chat to see the character expressions.
|
||||
</div>
|
||||
<div id="open_chat_expressions">
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<label for="expression_override">Sprite Folder Override</label>
|
||||
<small>Use a forward slash to specify a subfolder. Example: <tt>Bob/formal</tt></small>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
</div>
|
||||
<h3 id="image_list_header">
|
||||
<strong>Sprite set:</strong> <span id="image_list_header_name"></span>
|
||||
</h3>
|
||||
<div id="image_list"></div>
|
||||
<div class="expression_buttons flex-container spaceEvenly">
|
||||
<div id="expression_upload_pack_button" class="menu_button">
|
||||
<i class="fa-solid fa-file-zipper"></i>
|
||||
<span>Upload sprite pack (ZIP)</span>
|
||||
</div>
|
||||
<div id="expression_override_cleanup_button" class="menu_button">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
<span>Remove all image overrides</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
|
@@ -49,6 +49,18 @@
|
||||
overflow: hidden;
|
||||
resize: both;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#no_chat_expressions {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
#image_list_header_name {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
img.expression {
|
||||
@@ -112,6 +124,8 @@ img.expression.default {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.expression_list_buttons {
|
||||
|
411
public/scripts/extensions/gallery/index.js
Normal file
411
public/scripts/extensions/gallery/index.js
Normal file
@@ -0,0 +1,411 @@
|
||||
import {
|
||||
eventSource,
|
||||
this_chid,
|
||||
characters,
|
||||
getRequestHeaders,
|
||||
} from "../../../script.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { loadFileToDocument } from "../../utils.js";
|
||||
import { loadMovingUIState } from '../../power-user.js';
|
||||
import { dragElement } from '../../RossAscends-mods.js';
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
|
||||
const extensionName = "gallery";
|
||||
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
|
||||
let firstTime = true;
|
||||
|
||||
// Exposed defaults for future tweaking
|
||||
let thumbnailHeight = 150;
|
||||
let paginationVisiblePages = 10;
|
||||
let paginationMaxLinesPerPage = 2;
|
||||
let galleryMaxRows = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a list of gallery items based on a given URL. This function calls an API endpoint
|
||||
* to get the filenames and then constructs the item list.
|
||||
*
|
||||
* @param {string} url - The base URL to retrieve the list of images.
|
||||
* @returns {Promise<Array>} - Resolves with an array of gallery item objects, rejects on error.
|
||||
*/
|
||||
async function getGalleryItems(url) {
|
||||
const response = await fetch(`/listimgfiles/${url}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const items = data.map((file) => ({
|
||||
src: `user/images/${url}/${file}`,
|
||||
srct: `user/images/${url}/${file}`,
|
||||
title: "", // Optional title for each item
|
||||
}));
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a gallery using the provided items and sets up the drag-and-drop functionality.
|
||||
* It uses the nanogallery2 library to display the items and also initializes
|
||||
* event listeners to handle drag-and-drop of files onto the gallery.
|
||||
*
|
||||
* @param {Array<Object>} items - An array of objects representing the items to display in the gallery.
|
||||
* @param {string} url - The URL to use when a file is dropped onto the gallery for uploading.
|
||||
* @returns {Promise<void>} - Promise representing the completion of the gallery initialization.
|
||||
*/
|
||||
async function initGallery(items, url) {
|
||||
$("#dragGallery").nanogallery2({
|
||||
"items": items,
|
||||
thumbnailWidth: 'auto',
|
||||
thumbnailHeight: thumbnailHeight,
|
||||
paginationVisiblePages: paginationVisiblePages,
|
||||
paginationMaxLinesPerPage: paginationMaxLinesPerPage,
|
||||
galleryMaxRows: galleryMaxRows,
|
||||
galleryPaginationTopButtons: false,
|
||||
galleryNavigationOverlayButtons: true,
|
||||
galleryTheme: {
|
||||
navigationBar: { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
|
||||
navigationBreadcrumb: { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
|
||||
navigationFilter: { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
|
||||
navigationPagination: { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
|
||||
thumbnail: { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', borderRadius: '0px', labelOpacity: 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
|
||||
thumbnailIcon: { padding: '5px', color: '#fff', shadow: '' },
|
||||
pagination: { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid var(--SmartThemeQuoteColor)', shapeColor: '#444', shapeSelectedColor: '#aaa' }
|
||||
},
|
||||
galleryDisplayMode: "pagination",
|
||||
fnThumbnailOpen: viewWithDragbox,
|
||||
});
|
||||
|
||||
|
||||
eventSource.on('resizeUI', function (elmntName) {
|
||||
jQuery("#dragGallery").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];
|
||||
uploadFile(file, url); // Added url parameter to know where to upload
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a character gallery using the nanogallery2 library.
|
||||
*
|
||||
* This function takes care of:
|
||||
* - Loading necessary resources for the gallery on the first invocation.
|
||||
* - Preparing gallery items based on the character or group selection.
|
||||
* - Handling the drag-and-drop functionality for image upload.
|
||||
* - Displaying the gallery in a popup.
|
||||
* - Cleaning up resources when the gallery popup is closed.
|
||||
*
|
||||
* @returns {Promise<void>} - Promise representing the completion of the gallery display process.
|
||||
*/
|
||||
async function showCharGallery() {
|
||||
// Load necessary files if it's the first time calling the function
|
||||
if (firstTime) {
|
||||
await loadFileToDocument(
|
||||
`${extensionFolderPath}nanogallery2.woff.min.css`,
|
||||
"css"
|
||||
);
|
||||
await loadFileToDocument(
|
||||
`${extensionFolderPath}jquery.nanogallery2.min.js`,
|
||||
"js"
|
||||
);
|
||||
firstTime = false;
|
||||
toastr.info("Images can also be found in the folder `user/images`", "Drag and drop images onto the gallery to upload them", { timeOut: 6000 });
|
||||
}
|
||||
|
||||
try {
|
||||
let url = selected_group || this_chid;
|
||||
if (!selected_group && this_chid) {
|
||||
const char = characters[this_chid];
|
||||
url = char.avatar.replace(".png", "");
|
||||
}
|
||||
|
||||
const items = await getGalleryItems(url);
|
||||
// if there already is a gallery, destroy it and place this one in its place
|
||||
if ($(`#dragGallery`).length) {
|
||||
$(`#dragGallery`).nanogallery2("destroy");
|
||||
initGallery(items, url);
|
||||
} else {
|
||||
makeMovable();
|
||||
setTimeout(async () => {
|
||||
await initGallery(items, url);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.trace();
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a given file to a specified URL.
|
||||
* Once the file is uploaded, it provides a success message using toastr,
|
||||
* destroys the existing gallery, fetches the latest items, and reinitializes the gallery.
|
||||
*
|
||||
* @param {File} file - The file object to be uploaded.
|
||||
* @param {string} url - The URL indicating where the file should be uploaded.
|
||||
* @returns {Promise<void>} - Promise representing the completion of the file upload and gallery refresh.
|
||||
*/
|
||||
async function uploadFile(file, url) {
|
||||
// Convert the file to a base64 string
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = async function () {
|
||||
const base64Data = reader.result;
|
||||
|
||||
// Create the payload
|
||||
const payload = {
|
||||
image: base64Data
|
||||
};
|
||||
|
||||
// Add the ch_name from the provided URL (assuming it's the character name)
|
||||
payload.ch_name = url;
|
||||
|
||||
try {
|
||||
const headers = await getRequestHeaders();
|
||||
|
||||
// Merge headers with content-type for JSON
|
||||
Object.assign(headers, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
const response = await fetch('/uploadimage', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
toastr.success('File uploaded successfully. Saved at: ' + result.path);
|
||||
|
||||
// Refresh the gallery
|
||||
$("#dragGallery").nanogallery2("destroy"); // Destroy old gallery
|
||||
const newItems = await getGalleryItems(url); // Fetch the latest items
|
||||
initGallery(newItems, url); // Reinitialize the gallery with new items and pass 'url'
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error("There was an issue uploading the file:", error);
|
||||
|
||||
// Replacing alert with toastr error notification
|
||||
toastr.error('Failed to upload the file.');
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// Register an event listener
|
||||
eventSource.on("charManagementDropdown", (selectedOptionId) => {
|
||||
if (selectedOptionId === "show_char_gallery") {
|
||||
showCharGallery();
|
||||
}
|
||||
});
|
||||
|
||||
// Add an option to the dropdown
|
||||
$("#char-management-dropdown").append(
|
||||
$("<option>", {
|
||||
id: "show_char_gallery",
|
||||
text: "Show Gallery",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates a new draggable container based on a template.
|
||||
* This function takes a template with the ID 'generic_draggable_template' and clones it.
|
||||
* The cloned element has its attributes set, a new child div appended, and is made visible on the body.
|
||||
* Additionally, it sets up the element to prevent dragging on its images.
|
||||
*/
|
||||
function makeMovable(id="gallery"){
|
||||
|
||||
console.debug('making new container from template')
|
||||
const template = $('#generic_draggable_template').html();
|
||||
const newElement = $(template);
|
||||
newElement.attr('forChar', id);
|
||||
newElement.attr('id', `${id}`);
|
||||
newElement.find('.drag-grabber').attr('id', `${id}header`);
|
||||
//add a div for the gallery
|
||||
newElement.append(`<div id="dragGallery"></div>`);
|
||||
// add no-scrollbar class to this element
|
||||
newElement.addClass('no-scrollbar');
|
||||
|
||||
// get the close button and set its id and data-related-id
|
||||
const closeButton = newElement.find('.dragClose');
|
||||
closeButton.attr('id', `${id}close`);
|
||||
closeButton.attr('data-related-id', `${id}`);
|
||||
|
||||
$(`#dragGallery`).css('display', 'block');
|
||||
|
||||
$('body').append(newElement);
|
||||
|
||||
loadMovingUIState();
|
||||
$(`.draggable[forChar="${id}"]`).css('display', 'block');
|
||||
dragElement(newElement);
|
||||
|
||||
$(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => {
|
||||
console.log('saw drag on avatar!');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('body').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
||||
$(`#${relatedId}`).remove(); // Remove the associated draggable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new draggable image based on a template.
|
||||
*
|
||||
* This function clones a provided template with the ID 'generic_draggable_template',
|
||||
* appends the given image URL, ensures the element has a unique ID,
|
||||
* and attaches the element to the body. After appending, it also prevents
|
||||
* dragging on the appended image.
|
||||
*
|
||||
* @param {string} id - A base identifier for the new draggable element.
|
||||
* @param {string} url - The URL of the image to be added to the draggable element.
|
||||
*/
|
||||
function makeDragImg(id, url) {
|
||||
// Step 1: Clone the template content
|
||||
const template = document.getElementById('generic_draggable_template');
|
||||
|
||||
if (!(template instanceof HTMLTemplateElement)) {
|
||||
console.error('The element is not a <template> tag');
|
||||
return;
|
||||
}
|
||||
|
||||
const newElement = document.importNode(template.content, true);
|
||||
|
||||
// Step 2: Append the given image
|
||||
const imgElem = document.createElement('img');
|
||||
imgElem.src = url;
|
||||
let uniqueId = `draggable_${id}`;
|
||||
const draggableElem = newElement.querySelector('.draggable');
|
||||
if (draggableElem) {
|
||||
draggableElem.appendChild(imgElem);
|
||||
|
||||
// Find a unique id for the draggable element
|
||||
|
||||
let counter = 1;
|
||||
while (document.getElementById(uniqueId)) {
|
||||
uniqueId = `draggable_${id}_${counter}`;
|
||||
counter++;
|
||||
}
|
||||
draggableElem.id = uniqueId;
|
||||
|
||||
// Ensure that the newly added element is displayed as block
|
||||
draggableElem.style.display = 'block';
|
||||
|
||||
// Add an id to the close button
|
||||
// If the close button exists, set related-id
|
||||
const closeButton = draggableElem.querySelector('.dragClose');
|
||||
if (closeButton) {
|
||||
closeButton.id = `${uniqueId}close`;
|
||||
closeButton.dataset.relatedId = uniqueId;
|
||||
}
|
||||
|
||||
// Find the .drag-grabber and set its matching unique ID
|
||||
const dragGrabber = draggableElem.querySelector('.drag-grabber');
|
||||
if (dragGrabber) {
|
||||
dragGrabber.id = `${uniqueId}header`; // appending _header to make it match the parent's unique ID
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Attach it to the body
|
||||
document.body.appendChild(newElement);
|
||||
|
||||
// Step 4: Call dragElement and loadMovingUIState
|
||||
const appendedElement = document.getElementById(uniqueId);
|
||||
if (appendedElement) {
|
||||
var elmntName = $(appendedElement);
|
||||
loadMovingUIState();
|
||||
dragElement(elmntName);
|
||||
|
||||
// Prevent dragging the image
|
||||
$(`#${uniqueId} img`).on('dragstart', (e) => {
|
||||
console.log('saw drag on avatar!');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
console.error("Failed to append the template content or retrieve the appended content.");
|
||||
}
|
||||
|
||||
$('body').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
||||
$(`#${relatedId}`).remove(); // Remove the associated draggable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a given ID to ensure it can be used as an HTML ID.
|
||||
* This function replaces spaces and non-word characters with dashes.
|
||||
* It also removes any non-ASCII characters.
|
||||
* @param {string} id - The ID to be sanitized.
|
||||
* @returns {string} - The sanitized ID.
|
||||
*/
|
||||
function sanitizeHTMLId(id){
|
||||
// Replace spaces and non-word characters
|
||||
id = id.replace(/\s+/g, '-')
|
||||
.replace(/[^\x00-\x7F]/g, '-')
|
||||
.replace(/\W/g, '');
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a list of items (containing URLs) and creates a draggable box for the first item.
|
||||
*
|
||||
* If the provided list of items is non-empty, it takes the URL of the first item,
|
||||
* derives an ID from the URL, and uses the makeDragImg function to create
|
||||
* a draggable image element based on that ID and URL.
|
||||
*
|
||||
* @param {Array} items - A list of items where each item has a responsiveURL method that returns a URL.
|
||||
*/
|
||||
function viewWithDragbox(items) {
|
||||
if (items && items.length > 0) {
|
||||
const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
|
||||
// ID should just be the last part of the URL, removing the extension
|
||||
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
|
||||
makeDragImg(id, url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Registers a simple command for opening the char gallery.
|
||||
registerSlashCommand("show-gallery", showGalleryCommand, ["sg"], "Shows the gallery", true, true);
|
||||
|
||||
function showGalleryCommand(args) {
|
||||
showCharGallery();
|
||||
}
|
80
public/scripts/extensions/gallery/jquery.nanogallery2.min.js
vendored
Normal file
80
public/scripts/extensions/gallery/jquery.nanogallery2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
public/scripts/extensions/gallery/manifest.json
Normal file
12
public/scripts/extensions/gallery/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"display_name": "Gallery",
|
||||
"loading_order": 6,
|
||||
"requires": [],
|
||||
"optional": [
|
||||
],
|
||||
"js": "index.js",
|
||||
"css": "",
|
||||
"author": "City-Unit",
|
||||
"version": "1.5.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
1
public/scripts/extensions/gallery/nanogallery2.woff.min.css
vendored
Normal file
1
public/scripts/extensions/gallery/nanogallery2.woff.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -62,11 +62,11 @@ const generateDebounced = debounce(() => generateHypeBot(), 500);
|
||||
* @param {string} text Text to set
|
||||
*/
|
||||
function setHypeBotText(text) {
|
||||
const blockA = $('#chat');
|
||||
var originalScrollBottom = blockA[0].scrollHeight - (blockA.scrollTop() + blockA.outerHeight());
|
||||
const chatBlock = $('#chat');
|
||||
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
|
||||
hypeBotBar.html(DOMPurify.sanitize(text));
|
||||
var newScrollTop = blockA[0].scrollHeight - (blockA.outerHeight() + originalScrollBottom);
|
||||
blockA.scrollTop(newScrollTop);
|
||||
const newScrollTop = chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom);
|
||||
chatBlock.scrollTop(newScrollTop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +159,7 @@ async function generateHypeBot() {
|
||||
|
||||
abortController = new AbortController();
|
||||
|
||||
const response = await fetch('/generate_novelai', {
|
||||
const response = await fetch('/api/novelai/generate', {
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(parameters),
|
||||
method: 'POST',
|
||||
@@ -191,11 +191,13 @@ jQuery(() => {
|
||||
settings.enabled = $('#hypebot_enabled').prop('checked');
|
||||
hypeBotBar.toggle(settings.enabled);
|
||||
abortController?.abort();
|
||||
Object.assign(extension_settings.hypebot, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#hypebot_name').val(settings.name).on('input', () => {
|
||||
settings.name = String($('#hypebot_name').val());
|
||||
Object.assign(extension_settings.hypebot, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
54
public/scripts/extensions/idle/dropdown.html
Normal file
54
public/scripts/extensions/idle/dropdown.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="idle-settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header" title="Indicates the settings for the idle feature.">
|
||||
<b>Idle</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer-content">
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_enabled" type="checkbox" title="Toggle to enable or disable the idle feature." />
|
||||
<label for="idle_enabled">Enabled</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_repeats" class="text_pole widthUnset" type="number" min="0" max="100000" step="1" title="The number of times the idle action will be prompted." />
|
||||
<label for="idle_repeats">Idle Prompt Count</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container" style="display: none;">
|
||||
<input id="idle_timer_min" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The minimum amount of time in seconds before the idle action is triggered." />
|
||||
<label for="idle_timer_min">Idle Timer Minimum (seconds)</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_timer" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The amount of time in seconds before the idle action is triggered." />
|
||||
<label for="idle_timer">Idle Timer (seconds)</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<label for="idle_prompts">Idle Prompts</label>
|
||||
<textarea id="idle_prompts" class="text_pole textarea_compact" rows="6" title="The prompts to be sent to initial the idle reply (newline seperated)."></textarea>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_use_continuation" type="checkbox" title="Indicates whether the idle action will just use the 'Continue' function instead of a prompt." />
|
||||
<label for="idle_use_continuation">Use Continuation</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_random_time" type="checkbox" title="Indicates if the idle time should be randomized between a min/max value." />
|
||||
<label for="idle_random_time">Randomize Time</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<input id="idle_include_prompt" type="checkbox" title="Indicates if the idle prompting should be included in context. (Sends as user)" />
|
||||
<label for="idle_include_prompt">Include Idle Prompt</label>
|
||||
</div>
|
||||
<div class="idle_block flex-container">
|
||||
<label for="idle_sendAs">Send As</label>
|
||||
<select id="idle_sendAs" class="text_pole" title="Determines how the idle message prompting is sent; as a user, character, system, or raw message.">
|
||||
<option value="user">User</option>
|
||||
<option value="char">Character</option>
|
||||
<option value="sys">System</option>
|
||||
<option value="raw">Raw</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="sysHR" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
329
public/scripts/extensions/idle/index.js
Normal file
329
public/scripts/extensions/idle/index.js
Normal file
@@ -0,0 +1,329 @@
|
||||
import {
|
||||
saveSettingsDebounced,
|
||||
substituteParams
|
||||
} from "../../../script.js";
|
||||
import { debounce } from "../../utils.js";
|
||||
import { promptQuietForLoudResponse, sendMessageAs, sendNarratorMessage } from "../../slash-commands.js";
|
||||
import { extension_settings, getContext, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
const extensionName = "idle";
|
||||
|
||||
let idleTimer = null;
|
||||
let repeatCount = 0;
|
||||
|
||||
let defaultSettings = {
|
||||
enabled: false,
|
||||
timer: 120,
|
||||
prompts: [
|
||||
"*stands silently, looking deep in thought*",
|
||||
"*pauses, eyes wandering over the surroundings*",
|
||||
"*hesitates, appearing lost for a moment*",
|
||||
"*takes a deep breath, collecting their thoughts*",
|
||||
"*gazes into the distance, seemingly distracted*",
|
||||
"*remains still, absorbing the ambiance*",
|
||||
"*lingers in silence, a contemplative look on their face*",
|
||||
"*stops, fingers brushing against an old memory*",
|
||||
"*seems to drift into a momentary daydream*",
|
||||
"*waits quietly, allowing the weight of the moment to settle*",
|
||||
],
|
||||
useContinuation: true,
|
||||
repeats: 2, // 0 = infinite
|
||||
sendAs: "user",
|
||||
randomTime: false,
|
||||
timeMin: 60,
|
||||
includePrompt: false,
|
||||
};
|
||||
|
||||
|
||||
//TODO: Can we make this a generic function?
|
||||
/**
|
||||
* Load the extension settings and set defaults if they don't exist.
|
||||
*/
|
||||
async function loadSettings() {
|
||||
if (!extension_settings.idle) {
|
||||
console.log("Creating extension_settings.idle");
|
||||
extension_settings.idle = {};
|
||||
}
|
||||
for (const [key, value] of Object.entries(defaultSettings)) {
|
||||
if (!extension_settings.idle.hasOwnProperty(key)) {
|
||||
console.log(`Setting default for: ${key}`);
|
||||
extension_settings.idle[key] = value;
|
||||
}
|
||||
}
|
||||
populateUIWithSettings();
|
||||
}
|
||||
|
||||
//TODO: Can we make this a generic function too?
|
||||
/**
|
||||
* Populate the UI components with values from the extension settings.
|
||||
*/
|
||||
function populateUIWithSettings() {
|
||||
$("#idle_timer").val(extension_settings.idle.timer).trigger("input");
|
||||
$("#idle_prompts").val(extension_settings.idle.prompts.join("\n")).trigger("input");
|
||||
$("#idle_use_continuation").prop("checked", extension_settings.idle.useContinuation).trigger("input");
|
||||
$("#idle_enabled").prop("checked", extension_settings.idle.enabled).trigger("input");
|
||||
$("#idle_repeats").val(extension_settings.idle.repeats).trigger("input");
|
||||
$("#idle_sendAs").val(extension_settings.idle.sendAs).trigger("input");
|
||||
$("#idle_random_time").prop("checked", extension_settings.idle.randomTime).trigger("input");
|
||||
$("#idle_timer_min").val(extension_settings.idle.timerMin).trigger("input");
|
||||
$("#idle_include_prompt").prop("checked", extension_settings.idle.includePrompt).trigger("input");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the idle timer based on the extension settings and context.
|
||||
*/
|
||||
function resetIdleTimer() {
|
||||
console.debug("Resetting idle timer");
|
||||
if (idleTimer) clearTimeout(idleTimer);
|
||||
let context = getContext();
|
||||
if (!context.characterId && !context.groupID) return;
|
||||
if (!extension_settings.idle.enabled) return;
|
||||
if (extension_settings.idle.randomTime) {
|
||||
// ensure these are ints
|
||||
let min = extension_settings.idle.timerMin;
|
||||
let max = extension_settings.idle.timer;
|
||||
min = parseInt(min);
|
||||
max = parseInt(max);
|
||||
let randomTime = (Math.random() * (max - min + 1)) + min;
|
||||
idleTimer = setTimeout(sendIdlePrompt, 1000 * randomTime);
|
||||
} else {
|
||||
idleTimer = setTimeout(sendIdlePrompt, 1000 * extension_settings.idle.timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a random idle prompt to the AI based on the extension settings.
|
||||
* Checks conditions like if the extension is enabled and repeat conditions.
|
||||
*/
|
||||
async function sendIdlePrompt() {
|
||||
if (!extension_settings.idle.enabled) return;
|
||||
|
||||
// Check repeat conditions and waiting for a response
|
||||
if (repeatCount >= extension_settings.idle.repeats || $('#mes_stop').is(':visible')) {
|
||||
//console.debug("Not sending idle prompt due to repeat conditions or waiting for a response.");
|
||||
resetIdleTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
const randomPrompt = extension_settings.idle.prompts[
|
||||
Math.floor(Math.random() * extension_settings.idle.prompts.length)
|
||||
];
|
||||
|
||||
sendPrompt(randomPrompt);
|
||||
repeatCount++;
|
||||
resetIdleTimer();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add our prompt to the chat and then send the chat to the backend.
|
||||
* @param {string} sendAs - The type of message to send. "user", "char", or "sys".
|
||||
* @param {string} prompt - The prompt text to send to the AI.
|
||||
*/
|
||||
function sendLoud(sendAs, prompt) {
|
||||
if (sendAs === "user") {
|
||||
prompt = substituteParams(prompt);
|
||||
|
||||
$("#send_textarea").val(prompt);
|
||||
|
||||
// Set the focus back to the textarea
|
||||
$("#send_textarea").focus();
|
||||
|
||||
$("#send_but").trigger('click');
|
||||
} else if (sendAs === "char") {
|
||||
sendMessageAs("", `${getContext().name2}\n${prompt}`);
|
||||
promptQuietForLoudResponse(sendAs, "");
|
||||
} else if (sendAs === "sys") {
|
||||
sendNarratorMessage("", prompt);
|
||||
promptQuietForLoudResponse(sendAs, "");
|
||||
}
|
||||
else {
|
||||
console.error(`Unknown sendAs value: ${sendAs}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the provided prompt to the AI. Determines method based on continuation setting.
|
||||
* @param {string} prompt - The prompt text to send to the AI.
|
||||
*/
|
||||
function sendPrompt(prompt) {
|
||||
clearTimeout(idleTimer);
|
||||
$("#send_textarea").off("input");
|
||||
|
||||
if (extension_settings.idle.useContinuation) {
|
||||
$('#option_continue').trigger('click');
|
||||
console.debug("Sending idle prompt with continuation");
|
||||
} else {
|
||||
console.debug("Sending idle prompt");
|
||||
console.log(extension_settings.idle);
|
||||
if (extension_settings.idle.includePrompt) {
|
||||
sendLoud(extension_settings.idle.sendAs, prompt);
|
||||
}
|
||||
else {
|
||||
promptQuietForLoudResponse(extension_settings.idle.sendAs, prompt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings HTML and append to the designated area.
|
||||
*/
|
||||
async function loadSettingsHTML() {
|
||||
const settingsHtml = renderExtensionTemplate(extensionName, "dropdown");
|
||||
$("#extensions_settings2").append(settingsHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific setting based on user input.
|
||||
* @param {string} elementId - The HTML element ID tied to the setting.
|
||||
* @param {string} property - The property name in the settings object.
|
||||
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
|
||||
*/
|
||||
function updateSetting(elementId, property, isCheckbox = false) {
|
||||
let value = $(`#${elementId}`).val();
|
||||
if (isCheckbox) {
|
||||
value = $(`#${elementId}`).prop('checked');
|
||||
}
|
||||
|
||||
if (property === "prompts") {
|
||||
value = value.split("\n");
|
||||
}
|
||||
|
||||
extension_settings.idle[property] = value;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach an input listener to a UI component to update the corresponding setting.
|
||||
* @param {string} elementId - The HTML element ID tied to the setting.
|
||||
* @param {string} property - The property name in the settings object.
|
||||
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
|
||||
*/
|
||||
function attachUpdateListener(elementId, property, isCheckbox = false) {
|
||||
$(`#${elementId}`).on('input', debounce(() => {
|
||||
updateSetting(elementId, property, isCheckbox);
|
||||
}, 250));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the enabling or disabling of the idle extension.
|
||||
* Adds or removes the idle listeners based on the checkbox's state.
|
||||
*/
|
||||
function handleIdleEnabled() {
|
||||
if (!extension_settings.idle.enabled) {
|
||||
clearTimeout(idleTimer);
|
||||
removeIdleListeners();
|
||||
} else {
|
||||
resetIdleTimer();
|
||||
attachIdleListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setup input listeners for the various settings and actions related to the idle extension.
|
||||
*/
|
||||
function setupListeners() {
|
||||
const settingsToWatch = [
|
||||
['idle_timer', 'timer'],
|
||||
['idle_prompts', 'prompts'],
|
||||
['idle_use_continuation', 'useContinuation', true],
|
||||
['idle_enabled', 'enabled', true],
|
||||
['idle_repeats', 'repeats'],
|
||||
['idle_sendAs', 'sendAs'],
|
||||
['idle_random_time', 'randomTime', true],
|
||||
['idle_timer_min', 'timerMin'],
|
||||
['idle_include_prompt', 'includePrompt', true]
|
||||
];
|
||||
settingsToWatch.forEach(setting => {
|
||||
attachUpdateListener(...setting);
|
||||
});
|
||||
|
||||
// Idleness listeners, could be made better
|
||||
$('#idle_enabled').on('input', debounce(handleIdleEnabled, 250));
|
||||
|
||||
// Add the idle listeners initially if the idle feature is enabled
|
||||
if (extension_settings.idle.enabled) {
|
||||
attachIdleListeners();
|
||||
}
|
||||
|
||||
//show/hide timer min parent div
|
||||
$('#idle_random_time').on('input', function () {
|
||||
if ($(this).prop('checked')) {
|
||||
$('#idle_timer_min').parent().show();
|
||||
} else {
|
||||
$('#idle_timer_min').parent().hide();
|
||||
}
|
||||
|
||||
$('#idle_timer').trigger('input');
|
||||
});
|
||||
|
||||
// if we're including the prompt, hide raw from the sendAs dropdown
|
||||
$('#idle_include_prompt').on('input', function () {
|
||||
if ($(this).prop('checked')) {
|
||||
$('#idle_sendAs option[value="raw"]').hide();
|
||||
} else {
|
||||
$('#idle_sendAs option[value="raw"]').show();
|
||||
}
|
||||
});
|
||||
|
||||
//make sure timer min is less than timer
|
||||
$('#idle_timer').on('input', function () {
|
||||
if ($('#idle_random_time').prop('checked')) {
|
||||
if ($(this).val() < $('#idle_timer_min').val()) {
|
||||
$('#idle_timer_min').val($(this).val());
|
||||
$('#idle_timer_min').trigger('input');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const debouncedActivityHandler = debounce((event) => {
|
||||
// Check if the event target (or any of its parents) has the id "option_continue"
|
||||
if ($(event.target).closest('#option_continue').length) {
|
||||
return; // Do not proceed if the click was on (or inside) an element with id "option_continue"
|
||||
}
|
||||
|
||||
console.debug("Activity detected, resetting idle timer");
|
||||
resetIdleTimer();
|
||||
repeatCount = 0;
|
||||
}, 250);
|
||||
|
||||
function attachIdleListeners() {
|
||||
$(document).on("click keypress", debouncedActivityHandler);
|
||||
document.addEventListener('keydown', debouncedActivityHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove idle-specific listeners.
|
||||
*/
|
||||
function removeIdleListeners() {
|
||||
$(document).off("click keypress", debouncedActivityHandler);
|
||||
document.removeEventListener('keydown', debouncedActivityHandler);
|
||||
}
|
||||
|
||||
function toggleIdle() {
|
||||
extension_settings.idle.enabled = !extension_settings.idle.enabled;
|
||||
$('#idle_enabled').prop('checked', extension_settings.idle.enabled);
|
||||
$('#idle_enabled').trigger('input');
|
||||
toastr.info(`Idle mode ${extension_settings.idle.enabled ? "enabled" : "disabled"}.`);
|
||||
resetIdleTimer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
jQuery(async () => {
|
||||
await loadSettingsHTML();
|
||||
loadSettings();
|
||||
setupListeners();
|
||||
if (extension_settings.idle.enabled) {
|
||||
resetIdleTimer();
|
||||
}
|
||||
// once the doc is ready, check if random time is checked and hide/show timer min
|
||||
if ($('#idle_random_time').prop('checked')) {
|
||||
$('#idle_timer_min').parent().show();
|
||||
}
|
||||
registerSlashCommand('idle', toggleIdle, [], ' – toggles idle mode', true, true);
|
||||
});
|
12
public/scripts/extensions/idle/manifest.json
Normal file
12
public/scripts/extensions/idle/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"display_name": "Idle",
|
||||
"loading_order": 6,
|
||||
"requires": [],
|
||||
"optional": [
|
||||
],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "City-Unit",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
3
public/scripts/extensions/idle/style.css
Normal file
3
public/scripts/extensions/idle/style.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.idle_block {
|
||||
align-items: center;
|
||||
}
|
@@ -508,7 +508,6 @@ async function onSelectInjectFile(e) {
|
||||
meta: JSON.stringify({
|
||||
name: file.name,
|
||||
is_user: false,
|
||||
is_name: false,
|
||||
is_system: false,
|
||||
send_date: humanizedDateTime(),
|
||||
mes: m,
|
||||
@@ -686,7 +685,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
const charname = context.name2;
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: `[Use these past chat exchanges to inform ${charname}'s next response:`,
|
||||
name: "system",
|
||||
@@ -696,7 +694,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: `]\n`,
|
||||
name: "system",
|
||||
@@ -739,7 +736,7 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
// No memories? No prompt.
|
||||
const promptBlob = (tokenApprox == 0) ? "" : wrapperMsg.replace('{{memories}}', allMemoryBlob);
|
||||
console.debug("CHROMADB: prompt blob: %o", promptBlob);
|
||||
context.setExtensionPrompt(MODULE_NAME, promptBlob, extension_prompt_types.AFTER_SCENARIO);
|
||||
context.setExtensionPrompt(MODULE_NAME, promptBlob, extension_prompt_types.IN_PROMPT);
|
||||
}
|
||||
if (selectedStrategy === 'custom') {
|
||||
const context = getContext();
|
||||
@@ -752,7 +749,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: recallStart,
|
||||
name: "system",
|
||||
@@ -762,7 +758,6 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: recallEnd + `\n`,
|
||||
name: "system",
|
||||
|
@@ -2,6 +2,7 @@ import { getStringHash, debounce, waitUntilCondition, extractAllWords } from "..
|
||||
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
|
||||
import { is_group_generating, selected_group } from "../../group-chats.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '1_memory';
|
||||
@@ -63,7 +64,7 @@ const defaultSettings = {
|
||||
source: summary_sources.extras,
|
||||
prompt: defaultPrompt,
|
||||
template: defaultTemplate,
|
||||
position: extension_prompt_types.AFTER_SCENARIO,
|
||||
position: extension_prompt_types.IN_PROMPT,
|
||||
depth: 2,
|
||||
promptWords: 200,
|
||||
promptMinWords: 25,
|
||||
@@ -190,18 +191,21 @@ function onMemoryPromptInput() {
|
||||
function onMemoryTemplateInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.template = value;
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryDepthInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.depth = Number(value);
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPositionChange(e) {
|
||||
const value = e.target.value;
|
||||
extension_settings.memory.position = value;
|
||||
reinsertMemory();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
@@ -392,7 +396,7 @@ async function summarizeChatMain(context, force) {
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = await generateQuietPrompt(prompt);
|
||||
const summary = await generateQuietPrompt(prompt, false);
|
||||
const newContext = getContext();
|
||||
|
||||
// something changed during summarization request
|
||||
@@ -517,6 +521,11 @@ function onMemoryContentInput() {
|
||||
setMemoryContext(value, true);
|
||||
}
|
||||
|
||||
function reinsertMemory() {
|
||||
const existingValue = $('#memory_contents').val();
|
||||
setMemoryContext(existingValue, false);
|
||||
}
|
||||
|
||||
function setMemoryContext(value, saveToMessage) {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth);
|
||||
@@ -565,6 +574,10 @@ jQuery(function () {
|
||||
</div>
|
||||
<label for="memory_position">Injection position:</label>
|
||||
<div class="radio_group">
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="2" />
|
||||
Before Main Prompt / Story String
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="0" />
|
||||
After Main Prompt / Story String
|
||||
@@ -637,4 +650,5 @@ jQuery(function () {
|
||||
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatEvent);
|
||||
registerSlashCommand('summarize', forceSummarizeChat, [], ' – forces the summarization of the current chat using the Main API', true, true);
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
|
||||
import { saveSettingsDebounced, callPopup, getRequestHeaders, substituteParams } from "../../../script.js";
|
||||
import { getContext, extension_settings } from "../../extensions.js";
|
||||
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "../../slash-commands.js";
|
||||
@@ -15,6 +15,8 @@ const defaultSettings = {
|
||||
quickReplyEnabled: false,
|
||||
numberOfSlots: 5,
|
||||
quickReplySlots: [],
|
||||
placeBeforePromptEnabled: false,
|
||||
quickActionEnabled: false,
|
||||
}
|
||||
|
||||
//method from worldinfo
|
||||
@@ -75,6 +77,8 @@ async function loadSettings(type) {
|
||||
|
||||
$('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled);
|
||||
$('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots);
|
||||
$('#placeBeforePromptEnabled').prop('checked', extension_settings.quickReply.placeBeforePromptEnabled);
|
||||
$('#quickActionEnabled').prop('checked', extension_settings.quickReply.quickActionEnabled);
|
||||
}
|
||||
|
||||
function onQuickReplyInput(id) {
|
||||
@@ -99,7 +103,19 @@ async function onQuickReplyEnabledInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// New function to handle input on quickActionEnabled
|
||||
async function onQuickActionEnabledInput() {
|
||||
extension_settings.quickReply.quickActionEnabled = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onPlaceBeforePromptEnabledInput() {
|
||||
extension_settings.quickReply.placeBeforePromptEnabled = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function sendQuickReply(index) {
|
||||
const existingText = $("#send_textarea").val();
|
||||
const prompt = extension_settings.quickReply.quickReplySlots[index]?.mes || '';
|
||||
|
||||
if (!prompt) {
|
||||
@@ -107,10 +123,35 @@ async function sendQuickReply(index) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#send_textarea").val(prompt);
|
||||
$("#send_but").trigger('click');
|
||||
let newText;
|
||||
|
||||
if (existingText) {
|
||||
// If existing text, add space after prompt
|
||||
if (extension_settings.quickReply.placeBeforePromptEnabled) {
|
||||
newText = `${prompt} ${existingText} `;
|
||||
} else {
|
||||
newText = `${existingText} ${prompt} `;
|
||||
}
|
||||
} else {
|
||||
// If no existing text, add prompt only (with a trailing space)
|
||||
newText = prompt + ' ';
|
||||
}
|
||||
|
||||
newText = substituteParams(newText);
|
||||
|
||||
$("#send_textarea").val(newText);
|
||||
|
||||
// Set the focus back to the textarea
|
||||
$("#send_textarea").focus();
|
||||
|
||||
// Only trigger send button if quickActionEnabled is not checked or
|
||||
// the prompt starts with '/'
|
||||
if (!extension_settings.quickReply.quickActionEnabled || prompt.startsWith('/')) {
|
||||
$("#send_but").trigger('click');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addQuickReplyBar() {
|
||||
$('#quickReplyBar').remove();
|
||||
let quickReplyButtonHtml = '';
|
||||
@@ -309,6 +350,14 @@ jQuery(async () => {
|
||||
<input id="quickReplyEnabled" type="checkbox" />
|
||||
Enable Quick Replies
|
||||
</label>
|
||||
<label class="checkbox_label marginBot10 wide100p flexnowrap">
|
||||
<input id="quickActionEnabled" type="checkbox" />
|
||||
Disable Send / Insert In User Input
|
||||
</label>
|
||||
<label class="checkbox_label marginBot10 wide100p flexnowrap">
|
||||
<input id="placeBeforePromptEnabled" type="checkbox" />
|
||||
Place Quick-reply before the Prompt
|
||||
</label>
|
||||
<div class="flex-container flexnowrap wide100p">
|
||||
<select id="quickReplyPresets" name="quickreply-preset">
|
||||
</select>
|
||||
@@ -331,6 +380,9 @@ jQuery(async () => {
|
||||
|
||||
$('#extensions_settings2').append(settingsHtml);
|
||||
|
||||
// Add event handler for quickActionEnabled
|
||||
$('#quickActionEnabled').on('input', onQuickActionEnabledInput);
|
||||
$('#placeBeforePromptEnabled').on('input', onPlaceBeforePromptEnabledInput);
|
||||
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
|
||||
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
|
||||
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#quickReplyBar {
|
||||
outline: none;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid var(--black30a);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
margin: 0;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
@@ -28,7 +28,7 @@
|
||||
#quickReplies div {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black50a);
|
||||
border: 1px solid var(--white30a);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 3px 5px;
|
||||
width: min-content;
|
||||
|
@@ -136,7 +136,7 @@ jQuery(() => {
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
$('#extensions_settings').append(html);
|
||||
$('#extensions_settings2').append(html);
|
||||
$('#ai_response_configuration .range-block-counter').each(addRandomizeButton);
|
||||
$('#randomizer_enabled').on('input', onRandomizerEnabled);
|
||||
$('#randomizer_enabled').prop('checked', extension_settings.randomizer.enabled).trigger('input');
|
||||
|
@@ -12,7 +12,7 @@
|
||||
|
||||
.regex-script-label {
|
||||
align-items: center;
|
||||
border: 1px solid rgba(128, 128, 128, 0.5);
|
||||
border: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
margin-top: 1px;
|
||||
|
57
public/scripts/extensions/settingsearch/index.js
Normal file
57
public/scripts/extensions/settingsearch/index.js
Normal file
@@ -0,0 +1,57 @@
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'settingsSearch';
|
||||
async function addSettingsSearchHTML() {
|
||||
|
||||
const html = `
|
||||
<div class="wide100p">
|
||||
<div class="justifyLeft">
|
||||
<textarea id="settingsSearch" class="wide100p textarea_compact" rows="1" placeholder="Search Settings"></textarea>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
$("#user-settings-block").prepend(html);
|
||||
}
|
||||
|
||||
async function searchSettings() {
|
||||
removeHighlighting(); // Remove previous highlights
|
||||
let searchString = $("#settingsSearch").val();
|
||||
let searchableText = $("#user-settings-block-content"); // Get the HTML block
|
||||
if (searchString.trim() !== "") {
|
||||
highlightMatchingElements(searchableText[0], searchString); // Highlight matching elements
|
||||
}
|
||||
}
|
||||
function isParentHeader(element) {
|
||||
return $(element).closest('h4, h3').length > 0;
|
||||
}
|
||||
function highlightMatchingElements(element, searchString) {
|
||||
$(element).contents().each(function () {
|
||||
const isTextNode = this.nodeType === Node.TEXT_NODE;
|
||||
const isElementNode = this.nodeType === Node.ELEMENT_NODE;
|
||||
|
||||
if (isTextNode && this.nodeValue.trim() !== "" && !isParentHeader(this)) {
|
||||
const parentElement = $(this).parent();
|
||||
const elementText = this.nodeValue;
|
||||
|
||||
if (elementText.toLowerCase().includes(searchString.toLowerCase())) {
|
||||
parentElement.addClass('highlighted'); // Add CSS class to highlight matched elements
|
||||
}
|
||||
} else if (isElementNode && !$(this).is("h4")) {
|
||||
highlightMatchingElements(this, searchString);
|
||||
}
|
||||
});
|
||||
}
|
||||
function removeHighlighting() {
|
||||
$(".highlighted").removeClass("highlighted"); // Remove CSS class from previously highlighted elements
|
||||
}
|
||||
jQuery(() => {
|
||||
//addSettingsSearchHTML();
|
||||
$('#settingsSearch').on('input change', searchSettings);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
11
public/scripts/extensions/settingsearch/manifest.json
Normal file
11
public/scripts/extensions/settingsearch/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"display_name": "Settings Search",
|
||||
"loading_order": 15,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "RossAscends",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
5
public/scripts/extensions/settingsearch/style.css
Normal file
5
public/scripts/extensions/settingsearch/style.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.highlighted {
|
||||
color: black;
|
||||
background-color: yellow;
|
||||
text-shadow: none !important;
|
||||
}
|
@@ -165,7 +165,6 @@ async function processTranscript(transcript) {
|
||||
const message = {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: messageText,
|
||||
};
|
||||
|
@@ -10,11 +10,14 @@ import {
|
||||
appendImageToMessage,
|
||||
generateQuietPrompt,
|
||||
this_chid,
|
||||
getCurrentChatId,
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from "../../extensions.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename, saveBase64AsFile } from "../../utils.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile } from "../../utils.js";
|
||||
import { getMessageTimeStamp, humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
import { SECRET_KEYS, secret_state } from "../../secrets.js";
|
||||
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from "../../nai-settings.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
@@ -27,10 +30,12 @@ const p = a => `<p>${a}</p>`
|
||||
const MODULE_NAME = 'sd';
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
const postHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Bypass-Tunnel-Reminder': 'bypass',
|
||||
};
|
||||
const sources = {
|
||||
extras: 'extras',
|
||||
horde: 'horde',
|
||||
auto: 'auto',
|
||||
novel: 'novel',
|
||||
}
|
||||
|
||||
const generationMode = {
|
||||
CHARACTER: 0,
|
||||
@@ -116,6 +121,8 @@ const helpString = [
|
||||
].join('<br>');
|
||||
|
||||
const defaultSettings = {
|
||||
source: sources.extras,
|
||||
|
||||
// CFG Scale
|
||||
scale_min: 1,
|
||||
scale_max: 30,
|
||||
@@ -153,13 +160,55 @@ const defaultSettings = {
|
||||
refine_mode: false,
|
||||
|
||||
prompts: promptTemplates,
|
||||
|
||||
// AUTOMATIC1111 settings
|
||||
auto_url: 'http://localhost:7860',
|
||||
auto_auth: '',
|
||||
|
||||
hr_upscaler: 'Latent',
|
||||
hr_scale: 2.0,
|
||||
hr_scale_min: 1.0,
|
||||
hr_scale_max: 4.0,
|
||||
hr_scale_step: 0.1,
|
||||
denoising_strength: 0.7,
|
||||
denoising_strength_min: 0.0,
|
||||
denoising_strength_max: 1.0,
|
||||
denoising_strength_step: 0.01,
|
||||
hr_second_pass_steps: 0,
|
||||
hr_second_pass_steps_min: 0,
|
||||
hr_second_pass_steps_max: 150,
|
||||
hr_second_pass_steps_step: 1,
|
||||
|
||||
// NovelAI settings
|
||||
novel_upscale_ratio_min: 1.0,
|
||||
novel_upscale_ratio_max: 4.0,
|
||||
novel_upscale_ratio_step: 0.1,
|
||||
novel_upscale_ratio: 1.0,
|
||||
novel_anlas_guard: false,
|
||||
}
|
||||
|
||||
const getAutoRequestBody = () => ({ url: extension_settings.sd.auto_url, auth: extension_settings.sd.auto_auth });
|
||||
|
||||
function toggleSourceControls() {
|
||||
$('.sd_settings [data-sd-source]').each(function () {
|
||||
const source = $(this).data('sd-source');
|
||||
$(this).toggle(source === extension_settings.sd.source);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
// Initialize settings
|
||||
if (Object.keys(extension_settings.sd).length === 0) {
|
||||
Object.assign(extension_settings.sd, defaultSettings);
|
||||
}
|
||||
|
||||
// Insert missing settings
|
||||
for (const [key, value] of Object.entries(defaultSettings)) {
|
||||
if (extension_settings.sd[key] === undefined) {
|
||||
extension_settings.sd[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (extension_settings.sd.prompts === undefined) {
|
||||
extension_settings.sd.prompts = promptTemplates;
|
||||
}
|
||||
@@ -175,19 +224,28 @@ async function loadSettings() {
|
||||
extension_settings.sd.character_prompts = {};
|
||||
}
|
||||
|
||||
$('#sd_source').val(extension_settings.sd.source);
|
||||
$('#sd_scale').val(extension_settings.sd.scale).trigger('input');
|
||||
$('#sd_steps').val(extension_settings.sd.steps).trigger('input');
|
||||
$('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input');
|
||||
$('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input');
|
||||
$('#sd_width').val(extension_settings.sd.width).trigger('input');
|
||||
$('#sd_height').val(extension_settings.sd.height).trigger('input');
|
||||
$('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');
|
||||
$('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');
|
||||
$('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');
|
||||
$('#sd_novel_upscale_ratio').val(extension_settings.sd.novel_upscale_ratio).trigger('input');
|
||||
$('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);
|
||||
$('#sd_horde').prop('checked', extension_settings.sd.horde);
|
||||
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
|
||||
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
|
||||
$('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
|
||||
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
|
||||
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
|
||||
$('#sd_auto_url').val(extension_settings.sd.auto_url);
|
||||
$('#sd_auto_auth').val(extension_settings.sd.auto_auth);
|
||||
|
||||
toggleSourceControls();
|
||||
addPromptTemplates();
|
||||
|
||||
await Promise.all([loadSamplers(), loadModels()]);
|
||||
@@ -332,14 +390,40 @@ function onHeightInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onHordeInput() {
|
||||
async function onSourceChange() {
|
||||
extension_settings.sd.source = $('#sd_source').find(':selected').val();
|
||||
extension_settings.sd.model = null;
|
||||
extension_settings.sd.sampler = null;
|
||||
extension_settings.sd.horde = !!$(this).prop('checked');
|
||||
toggleSourceControls();
|
||||
saveSettingsDebounced();
|
||||
await Promise.all([loadModels(), loadSamplers()]);
|
||||
}
|
||||
|
||||
async function onViewAnlasClick() {
|
||||
const result = await loadNovelSubscriptionData();
|
||||
|
||||
if (!result) {
|
||||
toastr.warning('Are you subscribed?', 'Could not load NovelAI subscription data');
|
||||
return;
|
||||
}
|
||||
|
||||
const anlas = getNovelAnlas();
|
||||
const unlimitedGeneration = getNovelUnlimitedImageGeneration();
|
||||
|
||||
toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);
|
||||
}
|
||||
|
||||
function onNovelUpscaleRatioInput() {
|
||||
extension_settings.sd.novel_upscale_ratio = Number($('#sd_novel_upscale_ratio').val());
|
||||
$('#sd_novel_upscale_ratio_value').text(extension_settings.sd.novel_upscale_ratio.toFixed(1));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onNovelAnlasGuardInput() {
|
||||
extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onHordeNsfwInput() {
|
||||
extension_settings.sd.horde_nsfw = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@@ -360,13 +444,140 @@ function onHighResFixInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onAutoUrlInput() {
|
||||
extension_settings.sd.auto_url = $('#sd_auto_url').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onAutoAuthInput() {
|
||||
extension_settings.sd.auto_auth = $('#sd_auto_auth').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrUpscalerChange() {
|
||||
extension_settings.sd.hr_upscaler = $('#sd_hr_upscaler').find(':selected').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrScaleInput() {
|
||||
extension_settings.sd.hr_scale = Number($('#sd_hr_scale').val());
|
||||
$('#sd_hr_scale_value').text(extension_settings.sd.hr_scale.toFixed(1));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onDenoisingStrengthInput() {
|
||||
extension_settings.sd.denoising_strength = Number($('#sd_denoising_strength').val());
|
||||
$('#sd_denoising_strength_value').text(extension_settings.sd.denoising_strength.toFixed(2));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrSecondPassStepsInput() {
|
||||
extension_settings.sd.hr_second_pass_steps = Number($('#sd_hr_second_pass_steps').val());
|
||||
$('#sd_hr_second_pass_steps_value').text(extension_settings.sd.hr_second_pass_steps);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function validateAutoUrl() {
|
||||
try {
|
||||
if (!extension_settings.sd.auto_url) {
|
||||
throw new Error('URL is not set.');
|
||||
}
|
||||
|
||||
const result = await fetch('/api/sd/ping', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
await loadSamplers();
|
||||
await loadModels();
|
||||
toastr.success('SD WebUI API connected.');
|
||||
} catch (error) {
|
||||
toastr.error(`Could not validate SD WebUI API: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function onModelChange() {
|
||||
extension_settings.sd.model = $('#sd_model').find(':selected').val();
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (!extension_settings.sd.horde) {
|
||||
const cloudSources = [sources.horde, sources.novel];
|
||||
|
||||
if (cloudSources.includes(extension_settings.sd.source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
toastr.info('Updating remote model...', 'Please wait');
|
||||
if (extension_settings.sd.source === sources.extras) {
|
||||
await updateExtrasRemoteModel();
|
||||
}
|
||||
if (extension_settings.sd.source === sources.auto) {
|
||||
await updateAutoRemoteModel();
|
||||
}
|
||||
toastr.success('Model successfully loaded!', 'Stable Diffusion');
|
||||
}
|
||||
|
||||
async function getAutoRemoteModel() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/get-model', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.text();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAutoRemoteUpscalers() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/upscalers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [extension_settings.sd.hr_upscaler];
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAutoRemoteModel() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/set-model', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ ...getAutoRequestBody(), model: extension_settings.sd.model }),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
console.log('Model successfully updated on SD WebUI remote.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toastr.error(`Could not update SD WebUI model: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateExtrasRemoteModel() {
|
||||
@@ -374,7 +585,6 @@ async function updateExtrasRemoteModel() {
|
||||
url.pathname = '/api/image/model';
|
||||
const getCurrentModelResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
body: JSON.stringify({ model: extension_settings.sd.model }),
|
||||
});
|
||||
|
||||
@@ -387,10 +597,19 @@ async function loadSamplers() {
|
||||
$('#sd_sampler').empty();
|
||||
let samplers = [];
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
samplers = await loadHordeSamplers();
|
||||
} else {
|
||||
samplers = await loadExtrasSamplers();
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
samplers = await loadExtrasSamplers();
|
||||
break;
|
||||
case sources.horde:
|
||||
samplers = await loadHordeSamplers();
|
||||
break;
|
||||
case sources.auto:
|
||||
samplers = await loadAutoSamplers();
|
||||
break;
|
||||
case sources.novel:
|
||||
samplers = await loadNovelSamplers();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const sampler of samplers) {
|
||||
@@ -403,7 +622,7 @@ async function loadSamplers() {
|
||||
}
|
||||
|
||||
async function loadHordeSamplers() {
|
||||
const result = await fetch('/horde_samplers', {
|
||||
const result = await fetch('/api/horde/sd-samplers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
@@ -433,14 +652,63 @@ async function loadExtrasSamplers() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadAutoSamplers() {
|
||||
if (!extension_settings.sd.auto_url) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fetch('/api/sd/samplers', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNovelSamplers() {
|
||||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||||
console.debug('NovelAI API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'k_dpmpp_2m',
|
||||
'k_dpmpp_sde',
|
||||
'k_dpmpp_2s_ancestral',
|
||||
'k_euler',
|
||||
'k_euler_ancestral',
|
||||
'k_dpm_fast',
|
||||
'ddim',
|
||||
];
|
||||
}
|
||||
|
||||
async function loadModels() {
|
||||
$('#sd_model').empty();
|
||||
let models = [];
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
models = await loadHordeModels();
|
||||
} else {
|
||||
models = await loadExtrasModels();
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
models = await loadExtrasModels();
|
||||
break;
|
||||
case sources.horde:
|
||||
models = await loadHordeModels();
|
||||
break;
|
||||
case sources.auto:
|
||||
models = await loadAutoModels();
|
||||
break;
|
||||
case sources.novel:
|
||||
models = await loadNovelModels();
|
||||
break;
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
@@ -453,7 +721,7 @@ async function loadModels() {
|
||||
}
|
||||
|
||||
async function loadHordeModels() {
|
||||
const result = await fetch('/horde_models', {
|
||||
const result = await fetch('/api/horde/sd-models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
@@ -495,6 +763,71 @@ async function loadExtrasModels() {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function loadAutoModels() {
|
||||
if (!extension_settings.sd.auto_url) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const currentModel = await getAutoRemoteModel();
|
||||
|
||||
if (currentModel) {
|
||||
extension_settings.sd.model = currentModel;
|
||||
}
|
||||
|
||||
const result = await fetch('/api/sd/models', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getAutoRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD WebUI returned an error.');
|
||||
}
|
||||
|
||||
const upscalers = await getAutoRemoteUpscalers();
|
||||
|
||||
if (Array.isArray(upscalers) && upscalers.length > 0) {
|
||||
$('#sd_hr_upscaler').empty();
|
||||
|
||||
for (const upscaler of upscalers) {
|
||||
const option = document.createElement('option');
|
||||
option.innerText = upscaler;
|
||||
option.value = upscaler;
|
||||
option.selected = upscaler === extension_settings.sd.hr_upscaler;
|
||||
$('#sd_hr_upscaler').append(option);
|
||||
}
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNovelModels() {
|
||||
if (!secret_state[SECRET_KEYS.NOVEL]) {
|
||||
console.debug('NovelAI API key is not set.');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
value: 'nai-diffusion',
|
||||
text: 'Full',
|
||||
},
|
||||
{
|
||||
value: 'safe-diffusion',
|
||||
text: 'Safe',
|
||||
},
|
||||
{
|
||||
value: 'nai-diffusion-furry',
|
||||
text: 'Furry',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getGenerationType(prompt) {
|
||||
for (const [key, values] of Object.entries(triggerWords)) {
|
||||
for (const value of values) {
|
||||
@@ -537,7 +870,6 @@ function processReply(str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
function getRawLastMessage() {
|
||||
const getLastUsableMessage = () => {
|
||||
for (const message of context.chat.slice().reverse()) {
|
||||
@@ -565,7 +897,7 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!modules.includes('sd') && !extension_settings.sd.horde) {
|
||||
if (!isValidState()) {
|
||||
toastr.warning("Extensions API is not connected or doesn't provide SD module. Enable Stable Horde to generate images.");
|
||||
return;
|
||||
}
|
||||
@@ -593,21 +925,17 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
extension_settings.sd.height = Math.round(extension_settings.sd.width * 1.5 / 64) * 64;
|
||||
}
|
||||
|
||||
// Background images are always landscape
|
||||
if (generationType == generationMode.BACKGROUND && aspectRatio <= 1) {
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64;
|
||||
if (generationType == generationMode.BACKGROUND) {
|
||||
// Background images are always landscape
|
||||
if (aspectRatio <= 1) {
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64;
|
||||
}
|
||||
const callbackOriginal = callback;
|
||||
callback = async function (prompt, base64Image) {
|
||||
const imagePath = base64Image;
|
||||
const imgUrl = `url('${encodeURIComponent(base64Image)}')`;
|
||||
|
||||
if ('forceSetBackground' in window) {
|
||||
forceSetBackground(imgUrl);
|
||||
} else {
|
||||
toastr.info('Background image will not be preserved.', '"Chat backgrounds" extension is disabled.');
|
||||
$('#bg_custom').css('background-image', imgUrl);
|
||||
}
|
||||
const imgUrl = `url("${encodeURI(base64Image)}")`;
|
||||
eventSource.emit(event_types.FORCE_SET_BACKGROUND, imgUrl);
|
||||
|
||||
if (typeof callbackOriginal === 'function') {
|
||||
callbackOriginal(prompt, imagePath);
|
||||
@@ -669,32 +997,60 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul
|
||||
? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix())
|
||||
: extension_settings.sd.prompt_prefix;
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
await generateHordeImage(prompt, prefix, characterName, callback);
|
||||
} else {
|
||||
await generateExtrasImage(prompt, prefix, characterName, callback);
|
||||
const prefixedPrompt = combinePrefixes(prefix, prompt);
|
||||
|
||||
let result = { format: '', data: '' };
|
||||
const currentChatId = getCurrentChatId();
|
||||
|
||||
try {
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
result = await generateExtrasImage(prefixedPrompt);
|
||||
break;
|
||||
case sources.horde:
|
||||
result = await generateHordeImage(prefixedPrompt);
|
||||
break;
|
||||
case sources.auto:
|
||||
result = await generateAutoImage(prefixedPrompt);
|
||||
break;
|
||||
case sources.novel:
|
||||
result = await generateNovelImage(prefixedPrompt);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (err) {
|
||||
toastr.error('Image generation failed. Please try again', 'Stable Diffusion');
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentChatId !== getCurrentChatId()) {
|
||||
console.warn('Chat changed, aborting SD result saving');
|
||||
toastr.warning('Chat changed, generated image discarded.', 'Stable Diffusion');
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format);
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an "extras" image using a provided prompt and other settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
* Generates an "extras" image using a provided prompt and other settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
||||
console.debug(extension_settings.sd);
|
||||
async function generateExtrasImage(prompt) {
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
const result = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
headers: postHeaders,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
@@ -702,39 +1058,33 @@ async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
karras: !!extension_settings.sd.horde_karras,
|
||||
hr_upscaler: extension_settings.sd.hr_upscaler,
|
||||
hr_scale: extension_settings.sd.hr_scale,
|
||||
denoising_strength: extension_settings.sd.denoising_strength,
|
||||
hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
//filename should be character name + human readable timestamp + generation mode
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data.image, characterName, filename, "jpg");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
return { format: 'jpg', data: data.image };
|
||||
} else {
|
||||
callPopup('Image generation has failed. Please try again.', 'text');
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a "horde" image using the provided prompt and configuration settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
* Generates a "horde" image using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||
const result = await fetch('/horde_generateimage', {
|
||||
async function generateHordeImage(prompt) {
|
||||
const result = await fetch('/api/horde/generate-image', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
@@ -744,7 +1094,6 @@ async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: prefix,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
model: extension_settings.sd.model,
|
||||
nsfw: extension_settings.sd.horde_nsfw,
|
||||
@@ -755,14 +1104,141 @@ async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data, characterName, filename, "webp");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
return { format: 'webp', data: data };
|
||||
} else {
|
||||
toastr.error('Image generation has failed. Please try again.');
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image in SD WebUI API using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateAutoImage(prompt) {
|
||||
const result = await fetch('/api/sd/generate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
...getAutoRequestBody(),
|
||||
prompt: prompt,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
sampler_name: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
cfg_scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
hr_upscaler: extension_settings.sd.hr_upscaler,
|
||||
hr_scale: extension_settings.sd.hr_scale,
|
||||
denoising_strength: extension_settings.sd.denoising_strength,
|
||||
hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
|
||||
// Ensure generated img is saved to disk
|
||||
save_images: true,
|
||||
send_images: true,
|
||||
do_not_save_grid: false,
|
||||
do_not_save_samples: false,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
return { format: 'png', data: data.images[0] };
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image in NovelAI API using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateNovelImage(prompt) {
|
||||
const { steps, width, height } = getNovelParams();
|
||||
|
||||
const result = await fetch('/api/novelai/generate-image', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
model: extension_settings.sd.model,
|
||||
sampler: extension_settings.sd.sampler,
|
||||
steps: steps,
|
||||
scale: extension_settings.sd.scale,
|
||||
width: width,
|
||||
height: height,
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
upscale_ratio: extension_settings.sd.novel_upscale_ratio,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
return { format: 'png', data: data };
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts extension parameters for NovelAI. Applies Anlas guard if needed.
|
||||
* @returns {{steps: number, width: number, height: number}} - A tuple of parameters for NovelAI API.
|
||||
*/
|
||||
function getNovelParams() {
|
||||
let steps = extension_settings.sd.steps;
|
||||
let width = extension_settings.sd.width;
|
||||
let height = extension_settings.sd.height;
|
||||
|
||||
// Don't apply Anlas guard if it's disabled.d
|
||||
if (!extension_settings.sd.novel_anlas_guard) {
|
||||
return { steps, width, height };
|
||||
}
|
||||
|
||||
const MAX_STEPS = 28;
|
||||
const MAX_PIXELS = 409600;
|
||||
|
||||
if (width * height > MAX_PIXELS) {
|
||||
const ratio = Math.sqrt(MAX_PIXELS / (width * height));
|
||||
|
||||
// Calculate new width and height while maintaining aspect ratio.
|
||||
var newWidth = Math.round(width * ratio);
|
||||
var newHeight = Math.round(height * ratio);
|
||||
|
||||
// Ensure new dimensions are multiples of 64. If not, reduce accordingly.
|
||||
if (newWidth % 64 !== 0) {
|
||||
newWidth = newWidth - newWidth % 64;
|
||||
}
|
||||
|
||||
if (newHeight % 64 !== 0) {
|
||||
newHeight = newHeight - newHeight % 64;
|
||||
}
|
||||
|
||||
// If total pixel count after rounding still exceeds MAX_PIXELS, decrease dimension size by 64 accordingly.
|
||||
while (newWidth * newHeight > MAX_PIXELS) {
|
||||
if (newWidth > newHeight) {
|
||||
newWidth -= 64;
|
||||
} else {
|
||||
newHeight -= 64;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Anlas Guard: Image size (${width}x${height}) > ${MAX_PIXELS}, reducing size to ${newWidth}x${newHeight}`);
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
}
|
||||
|
||||
if (steps > MAX_STEPS) {
|
||||
console.log(`Anlas Guard: Steps (${steps}) > ${MAX_STEPS}, reducing steps to ${MAX_STEPS}`);
|
||||
steps = MAX_STEPS;
|
||||
}
|
||||
|
||||
return { steps, width, height };
|
||||
}
|
||||
|
||||
async function sendMessage(prompt, image) {
|
||||
const context = getContext();
|
||||
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
|
||||
@@ -770,7 +1246,6 @@ async function sendMessage(prompt, image) {
|
||||
name: context.groupId ? systemUserName : context.name2,
|
||||
is_user: false,
|
||||
is_system: true,
|
||||
is_name: true,
|
||||
send_date: getMessageTimeStamp(),
|
||||
mes: context.groupId ? p(messageText) : messageText,
|
||||
extra: {
|
||||
@@ -842,12 +1317,21 @@ function addSDGenButtons() {
|
||||
});
|
||||
}
|
||||
|
||||
function isConnectedToExtras() {
|
||||
return modules.includes('sd');
|
||||
function isValidState() {
|
||||
switch (extension_settings.sd.source) {
|
||||
case sources.extras:
|
||||
return modules.includes('sd');
|
||||
case sources.horde:
|
||||
return true;
|
||||
case sources.auto:
|
||||
return !!extension_settings.sd.auto_url;
|
||||
case sources.novel:
|
||||
return secret_state[SECRET_KEYS.NOVEL];
|
||||
}
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
if (isConnectedToExtras() || extension_settings.sd.horde) {
|
||||
if (isValidState()) {
|
||||
$('#sd_gen').show();
|
||||
$('.sd_message_gen').show();
|
||||
}
|
||||
@@ -873,6 +1357,7 @@ async function sdMessageButton(e) {
|
||||
const message_id = $mes.attr('mesid');
|
||||
const message = context.chat[message_id];
|
||||
const characterName = message?.name || context.name2;
|
||||
const characterFileName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]].id.toString();
|
||||
const messageText = message?.mes;
|
||||
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
||||
|
||||
@@ -888,7 +1373,7 @@ async function sdMessageButton(e) {
|
||||
message.extra.title = prompt;
|
||||
|
||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, characterName, saveGeneratedImage);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, characterFileName, saveGeneratedImage);
|
||||
}
|
||||
else {
|
||||
console.log("doing /sd raw last");
|
||||
@@ -941,82 +1426,8 @@ $("#sd_dropdown [id]").on("click", function () {
|
||||
jQuery(async () => {
|
||||
getContext().registerSlashCommand('sd', generatePicture, [], helpString, true, true);
|
||||
|
||||
const settingsHtml = `
|
||||
<div class="sd_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Stable Diffusion</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<br>
|
||||
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
|
||||
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
|
||||
<input id="sd_refine_mode" type="checkbox" />
|
||||
Edit prompts before generation
|
||||
</label>
|
||||
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde" type="checkbox" />
|
||||
Use Stable Horde
|
||||
</label>
|
||||
<label style="margin-left:1em;" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
Allow NSFW images from Horde
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
|
||||
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
|
||||
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
|
||||
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
|
||||
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
|
||||
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
|
||||
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
|
||||
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
|
||||
<div class="flex-container marginTop10 margin-bot-10px">
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_restore_faces" type="checkbox" />
|
||||
Restore Faces
|
||||
</label>
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_enable_hr" type="checkbox" />
|
||||
Hires. Fix
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_model">Stable Diffusion model</label>
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<div class="flex-container flexGap5 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
Karras (only for Horde, not all samplers supported)
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_prompt_prefix">Common prompt prefix</label>
|
||||
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
<div id="sd_character_prompt_block">
|
||||
<label for="sd_character_prompt">Character-specific prompt prefix</label>
|
||||
<small>Won't be used in groups.</small>
|
||||
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prefix. Example: female, green eyes, brown hair, pink shirt"></textarea>
|
||||
</div>
|
||||
<label for="sd_negative_prompt">Negative prompt</label>
|
||||
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>SD Prompt Templates</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div id="sd_prompt_templates" class="inline-drawer-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
$('#extensions_settings').append(settingsHtml);
|
||||
$('#extensions_settings').append(renderExtensionTemplate('stable-diffusion', 'settings', defaultSettings));
|
||||
$('#sd_source').on('change', onSourceChange);
|
||||
$('#sd_scale').on('input', onScaleInput);
|
||||
$('#sd_steps').on('input', onStepsInput);
|
||||
$('#sd_model').on('change', onModelChange);
|
||||
@@ -1025,13 +1436,22 @@ jQuery(async () => {
|
||||
$('#sd_negative_prompt').on('input', onNegativePromptInput);
|
||||
$('#sd_width').on('input', onWidthInput);
|
||||
$('#sd_height').on('input', onHeightInput);
|
||||
$('#sd_horde').on('input', onHordeInput);
|
||||
$('#sd_horde_nsfw').on('input', onHordeNsfwInput);
|
||||
$('#sd_horde_karras').on('input', onHordeKarrasInput);
|
||||
$('#sd_restore_faces').on('input', onRestoreFacesInput);
|
||||
$('#sd_enable_hr').on('input', onHighResFixInput);
|
||||
$('#sd_refine_mode').on('input', onRefineModeInput);
|
||||
$('#sd_character_prompt').on('input', onCharacterPromptInput);
|
||||
$('#sd_auto_validate').on('click', validateAutoUrl);
|
||||
$('#sd_auto_url').on('input', onAutoUrlInput);
|
||||
$('#sd_auto_auth').on('input', onAutoAuthInput);
|
||||
$('#sd_hr_upscaler').on('change', onHrUpscalerChange);
|
||||
$('#sd_hr_scale').on('input', onHrScaleInput);
|
||||
$('#sd_denoising_strength').on('input', onDenoisingStrengthInput);
|
||||
$('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);
|
||||
$('#sd_novel_upscale_ratio').on('input', onNovelUpscaleRatioInput);
|
||||
$('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);
|
||||
$('#sd_novel_view_anlas').on('click', onViewAnlasClick);
|
||||
$('#sd_character_prompt_block').hide();
|
||||
|
||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user