mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
476 Commits
1.12.0-pre
...
1.12.0-6
Author | SHA1 | Date | |
---|---|---|---|
|
db2c9a9926 | ||
|
1fc88e97f4 | ||
|
3d3914645d | ||
|
68ade7b384 | ||
|
071f232611 | ||
|
dff5ca7e92 | ||
|
ab75680ed3 | ||
|
8c11d7e8e8 | ||
|
8b776491e8 | ||
|
884f26924c | ||
|
5f79579a4d | ||
|
82f56da16b | ||
|
bc2035d362 | ||
|
41f25edb15 | ||
|
cf28d6653c | ||
|
53496d70f1 | ||
|
a17588fb1b | ||
|
dd06fddd17 | ||
|
e9c5618b10 | ||
|
b32330df0f | ||
|
3bd2edf4d2 | ||
|
697949c784 | ||
|
ee913be46b | ||
|
4f5813a6ce | ||
|
4a25821aba | ||
|
c049ff82cd | ||
|
64ee2d77b8 | ||
|
ce4106eb3d | ||
|
974d27ce26 | ||
|
dfe482b37b | ||
|
1f18694aa6 | ||
|
89de668241 | ||
|
963e525e07 | ||
|
94ee7167e0 | ||
|
48c075fb42 | ||
|
581e5f1f04 | ||
|
9637b3fbe1 | ||
|
c7232ae23c | ||
|
01912f5b3e | ||
|
db5e2d95c2 | ||
|
d9b55df883 | ||
|
4e9a113a35 | ||
|
70a35e9b49 | ||
|
bc4a8fbe1d | ||
|
d77a70b25a | ||
|
e49317a73c | ||
|
87cc28ae28 | ||
|
b93131ec7a | ||
|
4219468e20 | ||
|
4227968dfa | ||
|
09790bb994 | ||
|
bd1bfee941 | ||
|
0b3c49da90 | ||
|
c3d6e10795 | ||
|
1eae9bd18b | ||
|
99e24f5588 | ||
|
38e2bf955c | ||
|
0653dad5c5 | ||
|
51af830db8 | ||
|
2bde9d2b15 | ||
|
d9f9da99e6 | ||
|
64b0123acf | ||
|
35d853b851 | ||
|
1966baad84 | ||
|
a398566b33 | ||
|
9f2c473040 | ||
|
a73db4984a | ||
|
3498eb92bb | ||
|
455db18d71 | ||
|
2aeffe4095 | ||
|
c4fe9749d5 | ||
|
a0512585b1 | ||
|
c52bdb9a4a | ||
|
bbd9c89357 | ||
|
fb2190ace1 | ||
|
deb09bf5bf | ||
|
d951beb626 | ||
|
748dd5f2e6 | ||
|
75de4c8fcb | ||
|
432be09583 | ||
|
c822b9e2da | ||
|
c661fea07d | ||
|
782f85e05d | ||
|
96a9f7108c | ||
|
9475147435 | ||
|
909ec4191d | ||
|
59d00cca74 | ||
|
71a3e2c91b | ||
|
719202ba12 | ||
|
8ae4332110 | ||
|
964f53273c | ||
|
9e10022014 | ||
|
2eaabe13e3 | ||
|
3389b5dd16 | ||
|
3832afaeba | ||
|
e026ddf6be | ||
|
116fa673c6 | ||
|
517da9f972 | ||
|
74256dc411 | ||
|
1b23a62c13 | ||
|
97de520f9a | ||
|
a6333f3285 | ||
|
012f70336f | ||
|
7fbed26c26 | ||
|
a94af2678b | ||
|
eb57289b2a | ||
|
068b542c50 | ||
|
912fd36e29 | ||
|
b7a91770dc | ||
|
f0af503b4a | ||
|
3d023a5cf6 | ||
|
1e2d1aa118 | ||
|
ccfd3606dc | ||
|
fe95e09c8b | ||
|
bac90edfad | ||
|
b7043a428f | ||
|
5b47b83fe2 | ||
|
7289ed72f8 | ||
|
c4936ed535 | ||
|
8a5f05fb74 | ||
|
36f7bc4aae | ||
|
d5869e3f90 | ||
|
3b83d081db | ||
|
6861135925 | ||
|
b6f47c9927 | ||
|
796cc3c60c | ||
|
f1a57d76a2 | ||
|
87b61f7cff | ||
|
1999f607d6 | ||
|
bb2f553c46 | ||
|
6c2dc6756b | ||
|
f7c12264e8 | ||
|
9ef3dea884 | ||
|
cd90e252bf | ||
|
ac2475fb26 | ||
|
8f1a959da1 | ||
|
8c55e1b05b | ||
|
a0bbee8b79 | ||
|
caf85ad040 | ||
|
3999bee482 | ||
|
f0016b5368 | ||
|
1dec93de8a | ||
|
caf236d60a | ||
|
c8ed8e06f1 | ||
|
aa845b4727 | ||
|
1ebe5547d4 | ||
|
e2e7d5870a | ||
|
ea45d372f3 | ||
|
3113109f0a | ||
|
6ec51ff086 | ||
|
9eae4d9739 | ||
|
84aa746241 | ||
|
00ce078630 | ||
|
5426431adf | ||
|
726ec0fbfc | ||
|
094fc1f24b | ||
|
49cb8daf7d | ||
|
16660e995e | ||
|
8469f43285 | ||
|
6865f84eb1 | ||
|
036603c9e9 | ||
|
ab0f57aba3 | ||
|
13c755c197 | ||
|
5250d1fcaf | ||
|
492f857012 | ||
|
f1a0462ca3 | ||
|
6254ac6fbf | ||
|
5207b3a7f0 | ||
|
9e968de4e4 | ||
|
e3edb96568 | ||
|
dded42374c | ||
|
c561fb4fab | ||
|
67610b9f7f | ||
|
14aa70eea8 | ||
|
28da838bd1 | ||
|
fd18e0cc78 | ||
|
2a30a74886 | ||
|
6130ebb6d9 | ||
|
64500bfb37 | ||
|
cc077732c4 | ||
|
cd47f3b238 | ||
|
297519c401 | ||
|
09d410ec48 | ||
|
1369025092 | ||
|
5147233391 | ||
|
38585cb6af | ||
|
f53775d3f5 | ||
|
14ba7fc646 | ||
|
5b7bfbaa98 | ||
|
1d75b98393 | ||
|
c7d75b7789 | ||
|
4ccedb939c | ||
|
4bb463dd56 | ||
|
0ba600bb2b | ||
|
9ed6ee2161 | ||
|
c4ade296ae | ||
|
0ed81e3b1a | ||
|
d1933be86a | ||
|
15ff8de45c | ||
|
45a080016e | ||
|
78cf6e9086 | ||
|
7d65a6e264 | ||
|
91945ec77e | ||
|
7b472f13af | ||
|
a93777e3b7 | ||
|
2f310c72fa | ||
|
6a4ee68113 | ||
|
e73b5713fd | ||
|
1f81086a21 | ||
|
3e48f4b805 | ||
|
432be2ee57 | ||
|
f421139402 | ||
|
081223cc8f | ||
|
27ccc6b090 | ||
|
62faddac8d | ||
|
6804e4c679 | ||
|
10ee002091 | ||
|
1430eb26ea | ||
|
e17a18ad5d | ||
|
eeaa52bf5d | ||
|
07a6cb1252 | ||
|
7a2f6fb63f | ||
|
bb3ac095c4 | ||
|
e18d554489 | ||
|
a2625ecec6 | ||
|
0252b21901 | ||
|
f6343436b4 | ||
|
9db2f1cb91 | ||
|
2fd59f5aef | ||
|
18d96bc346 | ||
|
535da63e52 | ||
|
e14c9506b6 | ||
|
88aae5978f | ||
|
b9d72bfdf4 | ||
|
f4bb4fe51e | ||
|
cf77b9e7ee | ||
|
ff1399d1ba | ||
|
eb273a1873 | ||
|
0587931cae | ||
|
7408673e41 | ||
|
3c0664dfb6 | ||
|
d2477bba0c | ||
|
f37e444791 | ||
|
1ae08f49c5 | ||
|
10fda0b220 | ||
|
61d5dde497 | ||
|
edc33584da | ||
|
883da48762 | ||
|
49d0d9f557 | ||
|
852bc15a94 | ||
|
c43ddd9d62 | ||
|
c851961234 | ||
|
01aacb9280 | ||
|
dfa8c6c3d4 | ||
|
fda0e886e4 | ||
|
5a45e64999 | ||
|
70a2f71e33 | ||
|
4a70e68c22 | ||
|
542018cecb | ||
|
5e7c214c89 | ||
|
12eabd167d | ||
|
c73bfbd7b0 | ||
|
7063fce2af | ||
|
694cf6f762 | ||
|
83c77c1f18 | ||
|
d54ccece5c | ||
|
96506947cb | ||
|
afc3071576 | ||
|
10727d9a02 | ||
|
55d31a976f | ||
|
181b5aff97 | ||
|
31f1b34911 | ||
|
ef5499c8dc | ||
|
292ecf580e | ||
|
de1ca9af74 | ||
|
75832c1ad6 | ||
|
39a54d158d | ||
|
2bf9869e5f | ||
|
b13434c505 | ||
|
5197809d6b | ||
|
204a934553 | ||
|
de1bb90c23 | ||
|
478be72659 | ||
|
d972ed5a2b | ||
|
932d3dc10c | ||
|
f610d5930c | ||
|
05db2552b3 | ||
|
7bfd666321 | ||
|
a02f5ead7e | ||
|
d9fae7a02c | ||
|
29e2e8f607 | ||
|
7bc87b6e28 | ||
|
ed65ddf981 | ||
|
3c2017c7b8 | ||
|
6b2b849a26 | ||
|
b69c5bcd17 | ||
|
7b87f44518 | ||
|
5ab1e74c5f | ||
|
2bd239fe81 | ||
|
1a219e32fe | ||
|
203146f7e2 | ||
|
73bea1f454 | ||
|
408151c9cb | ||
|
f0adbc3c28 | ||
|
9acf057aae | ||
|
f796387e7e | ||
|
9f1c306920 | ||
|
2f85e50c6f | ||
|
eb4cae4e6d | ||
|
e4e6882f12 | ||
|
15a288b63d | ||
|
620cd6dfc2 | ||
|
a5475e7752 | ||
|
bddfd5763b | ||
|
21edb655d3 | ||
|
51f0d1f33e | ||
|
da31b6fda8 | ||
|
2b071bed90 | ||
|
1cf935eaf3 | ||
|
b33b5264e5 | ||
|
8ca50098d5 | ||
|
d82ed50fa4 | ||
|
f894237a12 | ||
|
9d8ebd7bd2 | ||
|
5c552a3d53 | ||
|
300b68177b | ||
|
83f79c1466 | ||
|
bc94e3992f | ||
|
1c44df8079 | ||
|
b6b1df6a7c | ||
|
b4aa7831e7 | ||
|
d1cdd60883 | ||
|
a850352eab | ||
|
d9d76ba16d | ||
|
993284f9c1 | ||
|
a7d3130f9a | ||
|
e0df5783f8 | ||
|
e4de6da5b8 | ||
|
87219f897e | ||
|
73cf58826f | ||
|
be4637a3a0 | ||
|
6ac6c7cfda | ||
|
94e9b8f4b1 | ||
|
bc6149deeb | ||
|
a0d975c3c0 | ||
|
d51b155e52 | ||
|
fb1b327f9a | ||
|
754cdc4d58 | ||
|
a73cb9ad3d | ||
|
58ecc0dc0d | ||
|
3821e91be0 | ||
|
de2bb7938a | ||
|
61e2877c4b | ||
|
d7ade487b8 | ||
|
6d04e93f34 | ||
|
d7a7af756a | ||
|
0c5fe3d637 | ||
|
eb0a116cc7 | ||
|
e08a21ebe7 | ||
|
49074effce | ||
|
ffe8b3c909 | ||
|
7856afee92 | ||
|
fe533b7c7f | ||
|
fc158ca176 | ||
|
f632888b4c | ||
|
8324632e4e | ||
|
be4b20af97 | ||
|
5a4e0a06e6 | ||
|
fb71d3b562 | ||
|
b96d1e79a0 | ||
|
0d310c434d | ||
|
b111834122 | ||
|
2847b5ee45 | ||
|
943906d8a3 | ||
|
cbedfa4664 | ||
|
01ccc32274 | ||
|
3b153a6c9b | ||
|
1bcdc2652c | ||
|
ea050b98ef | ||
|
b30d69b2a6 | ||
|
60e099e852 | ||
|
c49b37f968 | ||
|
404d9db359 | ||
|
5ac0390446 | ||
|
6e98fb1c5e | ||
|
053d7f9eaa | ||
|
5dcfda0514 | ||
|
b42125a654 | ||
|
413cec8a9f | ||
|
8e7ffab793 | ||
|
770aee4953 | ||
|
f479901c87 | ||
|
1dbe7897d4 | ||
|
c95956766e | ||
|
e92c0db6a2 | ||
|
3a8b8ed639 | ||
|
3a78d69b5b | ||
|
2e562d187a | ||
|
4521dde455 | ||
|
b64b0e3362 | ||
|
f8ca73265b | ||
|
1f7614af33 | ||
|
a48a9318c1 | ||
|
dcb042681d | ||
|
7df2f7e752 | ||
|
c3578d2cda | ||
|
8db39a58fb | ||
|
bbdbb08301 | ||
|
b06e09c030 | ||
|
bb2bcdbf61 | ||
|
2e278e7323 | ||
|
4c9d52422b | ||
|
f4ba1f68ef | ||
|
12497e8fb1 | ||
|
8153e747ef | ||
|
63b597beb8 | ||
|
cdbb0b21da | ||
|
b2f40e490b | ||
|
a96e1903a3 | ||
|
be7eb8b2b5 | ||
|
3b6372431a | ||
|
389ee7917f | ||
|
212e61d2a1 | ||
|
1b60e4a013 | ||
|
93cd93ada3 | ||
|
babb4cb57b | ||
|
dbcc75471f | ||
|
2a0497ca9e | ||
|
2d0767306e | ||
|
8ca83bb255 | ||
|
80a6406062 | ||
|
ff9345a843 | ||
|
fe663c4f04 | ||
|
9fbb012697 | ||
|
0070950911 | ||
|
62cf611fdc | ||
|
75814433a6 | ||
|
e59a5b4449 | ||
|
161e512805 | ||
|
305afb3713 | ||
|
1acbef1890 | ||
|
f90f370fed | ||
|
d34a0ee20e | ||
|
01e3964232 | ||
|
153638c2cd | ||
|
4bb719359c | ||
|
847eb60806 | ||
|
e799bd3920 | ||
|
2b1aee9e71 | ||
|
d65f068310 | ||
|
b1c199e650 | ||
|
51014e7a8d | ||
|
530bf81940 | ||
|
2bba186c9e | ||
|
61241df0d4 | ||
|
b6b9b542d7 | ||
|
71f41d5233 | ||
|
a421af9ea9 | ||
|
75372ad0cc | ||
|
d1f292f462 | ||
|
890cf81627 | ||
|
d97f0a4c4d | ||
|
4370db6bdc | ||
|
770f3e5da3 | ||
|
0f0895f345 | ||
|
6d1933c8f3 | ||
|
776260c85a | ||
|
5a5463bd5d | ||
|
2f45f50d37 | ||
|
41ad7c5d26 | ||
|
df93d43c36 | ||
|
bc9c70556e | ||
|
f75daba6c0 | ||
|
47b6562605 | ||
|
1c9b89fdcc | ||
|
035dbe4901 |
@@ -44,6 +44,7 @@ module.exports = {
|
|||||||
toastr: 'readonly',
|
toastr: 'readonly',
|
||||||
Readability: 'readonly',
|
Readability: 'readonly',
|
||||||
isProbablyReaderable: 'readonly',
|
isProbablyReaderable: 'readonly',
|
||||||
|
ePub: 'readonly',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
2
.github/close-label.yml
vendored
Normal file
2
.github/close-label.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
🐛 Bug: ✅ Fixed
|
||||||
|
🦄 Feature Request: ✅ Implemented
|
62
.github/issue-auto-comments.yml
vendored
Normal file
62
.github/issue-auto-comments.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
comment:
|
||||||
|
footer: |
|
||||||
|
---
|
||||||
|
> I am a bot, and this is an automated message 🤖
|
||||||
|
labels:
|
||||||
|
- name: ✖️ Invalid
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
action: close
|
||||||
|
body: >
|
||||||
|
Hello @{{ issue.user.login }} your ticket has been marked as invalid.
|
||||||
|
Please ensure you follow the issue template, provide all requested info,
|
||||||
|
and be sure to check the docs + previous issues prior to raising tickets.
|
||||||
|
pr:
|
||||||
|
body: Thank you @{{ pull_request.user.login }} for suggesting this. Please follow the pull request templates.
|
||||||
|
action: close
|
||||||
|
|
||||||
|
- name: 👩💻 Good First Issue
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: >
|
||||||
|
This issue has been marked as a good first issue for first-time contributors to implement!
|
||||||
|
This is a great way to support the project, while also improving your skills, you'll also be credited as a contributor once your PR is merged.
|
||||||
|
If you're new to SillyTavern [here are a collection of resources](https://docs.sillytavern.app/)
|
||||||
|
If you need any support at all, feel free to reach out via [Discord](https://discord.gg/sillytavern).
|
||||||
|
|
||||||
|
- name: ❌ wontfix
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
action: close
|
||||||
|
body: >
|
||||||
|
This ticked has been marked as 'wontfix', which usually means it is out-of-scope, or not feasible at this time.
|
||||||
|
You can still fork the project and make the changes yourself.
|
||||||
|
|
||||||
|
- name: ✅ Fixed
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: >
|
||||||
|
Hello @{{ issue.user.login }}! It looks like all or part of this issue has now been implemented.
|
||||||
|
|
||||||
|
|
||||||
|
- name: ‼️ High Priority
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
body: >
|
||||||
|
This ticket has been marked as high priority, and has been bumped to the top of the priority list.
|
||||||
|
You should expect an implementation to be pushed out soon. Thank you for your patience.
|
||||||
|
|
||||||
|
- name: 💀 Spam
|
||||||
|
labeled:
|
||||||
|
issue:
|
||||||
|
action: close
|
||||||
|
locking: lock
|
||||||
|
lock_reason: spam
|
||||||
|
body: >
|
||||||
|
This issue has been identified as spam, and is now locked.
|
||||||
|
Users who repeatedly raise spam issues may be blocked or reported.
|
||||||
|
|
||||||
|
- name: ⛔ Don't Merge
|
||||||
|
labeled:
|
||||||
|
pr:
|
||||||
|
body: This PR has been temporarily blocked from merging.
|
2
.github/readme.md
vendored
2
.github/readme.md
vendored
@@ -326,7 +326,7 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.**
|
GNU Affero General Public License for more details.**
|
||||||
|
|
||||||
* TAI Base by Humi: Unknown license
|
* TAI Base by Humi: MIT
|
||||||
* Cohee's modifications and derived code: AGPL v3
|
* Cohee's modifications and derived code: AGPL v3
|
||||||
* RossAscends' additions: AGPL v3
|
* RossAscends' additions: AGPL v3
|
||||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||||
|
28
.github/workflows/add-comment-from-tag.yml
vendored
Normal file
28
.github/workflows/add-comment-from-tag.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Based on a label applied to an issue, the bot will add a comment with some additional info
|
||||||
|
|
||||||
|
name: 🎯 Auto-Reply to Labeled Tickets
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Label Commenter
|
||||||
|
uses: peaceiris/actions-label-commenter@v1
|
||||||
|
with:
|
||||||
|
config_file: .github/issue-auto-comments.yml
|
||||||
|
github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
17
.github/workflows/check-merge-conflicts.yml
vendored
Normal file
17
.github/workflows/check-merge-conflicts.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Detect and label pull requests that have merge conflicts
|
||||||
|
name: 🏗️ Check Merge Conflicts
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- staging
|
||||||
|
jobs:
|
||||||
|
check-conflicts:
|
||||||
|
if: github.repository == 'SillyTavern/SillyTavern'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: mschilde/auto-label-merge-conflicts@master
|
||||||
|
with:
|
||||||
|
CONFLICT_LABEL_NAME: "🚫 Merge Conflicts"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
MAX_RETRIES: 5
|
||||||
|
WAIT_MS: 5000
|
82
.github/workflows/close-stale-issues.yml
vendored
Normal file
82
.github/workflows/close-stale-issues.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Closes any issues that no longer have user interaction
|
||||||
|
name: 🎯 Close Stale Issues
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *' # Runs every day at midnight UTC
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Comment on, then close issues that haven't been updated for ages
|
||||||
|
- name: Close Stale Issues
|
||||||
|
uses: actions/stale@v4
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-stale: 360
|
||||||
|
days-before-close: 5
|
||||||
|
operations-per-run: 30
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
enable-statistics: true
|
||||||
|
stale-issue-message: >
|
||||||
|
This issue has gone 3 months without an update. To keep the ticket open, please indicate that it is still relevant in a comment below.
|
||||||
|
Otherwise it will be closed in 5 working days.
|
||||||
|
stale-pr-message: >
|
||||||
|
This PR is stale because it has been open 6 weeks with no activity. Either remove the stale label or comment below with a short update,
|
||||||
|
otherwise this PR will be closed in 5 days.
|
||||||
|
close-issue-message: >
|
||||||
|
This issue was automatically closed because it has been stalled for over 1 year with no activity.
|
||||||
|
close-pr-message: >
|
||||||
|
This pull request was automatically closed because it has been stalled for over 1 year with no activity.
|
||||||
|
stale-issue-label: '⚰️ Stale'
|
||||||
|
close-issue-label: '🕸️ Inactive'
|
||||||
|
stale-pr-label: '⚰️ Stale'
|
||||||
|
close-pr-label: '🕸️ Inactive'
|
||||||
|
exempt-issue-labels: '📌 Keep Open'
|
||||||
|
exempt-pr-labels: '📌 Keep Open'
|
||||||
|
labels-to-add-when-unstale: '📌 Keep Open'
|
||||||
|
|
||||||
|
# Comment on, then close issues that required a response from the user, but didn't get one
|
||||||
|
- name: Close Issues without Response
|
||||||
|
uses: actions/stale@v4
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-stale: 5
|
||||||
|
days-before-close: 3
|
||||||
|
operations-per-run: 30
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
stale-issue-message: >
|
||||||
|
Hi! Looks like additional info is required for this issue to be addressed.
|
||||||
|
Don't forget to provide this within the next few days to keep your ticket open.
|
||||||
|
close-issue-message: 'Issue closed due to no response from user.'
|
||||||
|
only-labels: '🚏 Awaiting User Response'
|
||||||
|
labels-to-remove-when-unstale: '🚏 Awaiting User Response, 🛑 No Response'
|
||||||
|
stale-issue-label: '🛑 No Response'
|
||||||
|
close-issue-label: '🕸️ Inactive'
|
||||||
|
exempt-issue-labels: '📌 Keep Open'
|
||||||
|
exempt-pr-labels: '📌 Keep Open'
|
||||||
|
|
||||||
|
# Comment on issues that we should have replied to
|
||||||
|
- name: Notify Repo Owner to Respond
|
||||||
|
uses: actions/stale@v4
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-stale: 7
|
||||||
|
days-before-close: 365
|
||||||
|
operations-per-run: 30
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
stale-issue-message: Hey SillyTavern, - Don't forget to respond!
|
||||||
|
stale-pr-message: Hey SillyTavern, - Don't forget to respond!
|
||||||
|
only-labels: '👤 Awaiting Maintainer Response'
|
||||||
|
labels-to-remove-when-unstale: '👤 Awaiting Maintainer Response'
|
||||||
|
close-issue-message: 'Closed due to no response from repo author for over a year'
|
||||||
|
close-pr-message: 'Closed due to no response from repo author for over a year'
|
||||||
|
stale-issue-label: '👤 Awaiting Maintainer Response'
|
||||||
|
stale-pr-label: '👤 Awaiting Maintainer Response'
|
||||||
|
close-issue-label: '🕸️ Inactive'
|
||||||
|
close-pr-label: '🕸️ Inactive'
|
||||||
|
exempt-issue-labels: '📌 Keep Open'
|
||||||
|
exempt-pr-labels: '📌 Keep Open'
|
13
.github/workflows/docker-publish.yml
vendored
13
.github/workflows/docker-publish.yml
vendored
@@ -21,6 +21,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
if: github.repository == 'SillyTavern/SillyTavern'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -29,7 +30,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "IMAGE_NAME=${REPO,,}" >> ${GITHUB_ENV}
|
echo "IMAGE_NAME=${REPO,,}" >> ${GITHUB_ENV}
|
||||||
|
|
||||||
# Using the following workaround because currently GitHub Actions
|
# Using the following workaround because currently GitHub Actions
|
||||||
# does not support logical AND/OR operations on triggers
|
# does not support logical AND/OR operations on triggers
|
||||||
# It's currently not possible to have `branches` under the `schedule` trigger
|
# It's currently not possible to have `branches` under the `schedule` trigger
|
||||||
- name: Checkout the release branch (on release)
|
- name: Checkout the release branch (on release)
|
||||||
@@ -64,7 +65,10 @@ jobs:
|
|||||||
id: metadata
|
id: metadata
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
tags: ${{ env.BRANCH_NAME }}
|
# Release version tag if the workflow is triggered by a release
|
||||||
|
# Branch name tag if the workflow is triggered by a push
|
||||||
|
tags: |
|
||||||
|
${{ github.event_name == 'release' && github.ref_name || env.BRANCH_NAME }}
|
||||||
|
|
||||||
# Login into package repository as the person who created the release
|
# Login into package repository as the person who created the release
|
||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
@@ -91,5 +95,6 @@ jobs:
|
|||||||
- name: Docker tag latest and push
|
- name: Docker tag latest and push
|
||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
run: |
|
run: |
|
||||||
docker tag $IMAGE_NAME:${{ github.ref_name }} $IMAGE_NAME:latest
|
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||||
docker push $IMAGE_NAME:latest
|
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
39
.github/workflows/get-pr-size.yml
vendored
Normal file
39
.github/workflows/get-pr-size.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Adds a comment to new PRs, showing the compressed size and size difference of new code
|
||||||
|
# And also labels the PR based on the number of lines changes
|
||||||
|
|
||||||
|
name: 🌈 Check PR Size
|
||||||
|
on: [pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
# Find and comment with compressed size
|
||||||
|
- name: Get Compressed Size
|
||||||
|
uses: preactjs/compressed-size-action@v2
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
pattern: './dist/**/*.{js,css,html}'
|
||||||
|
strip-hash: '\\b\\w{8}\\.'
|
||||||
|
exclude: '**/node_modules/**'
|
||||||
|
minimum-change-threshold: 100
|
||||||
|
# Check number of lines of code added
|
||||||
|
- name: Label based on Lines of Code
|
||||||
|
uses: codelytv/pr-size-labeler@v1
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
xs_max_size: '10'
|
||||||
|
s_max_size: '100'
|
||||||
|
m_max_size: '500'
|
||||||
|
l_max_size: '1000'
|
||||||
|
s_label: '🟩 PR - Small'
|
||||||
|
m_label: '🟨 PR - Medium'
|
||||||
|
l_label: '🟧 PR - Large'
|
||||||
|
xl_label: '🟥 PR - XL'
|
||||||
|
fail_if_xl: 'false'
|
||||||
|
message_if_xl: >
|
||||||
|
It looks like this PR is very large (over 1000 lines).
|
||||||
|
Try to avoid addressing multiple issues in a single PR, and
|
||||||
|
in the future consider breaking large tasks down into smaller steps.
|
||||||
|
This it to make reviewing, testing, reverting and general quality management easier.
|
17
.github/workflows/manage-pending-labels-closed.yml
vendored
Normal file
17
.github/workflows/manage-pending-labels-closed.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# When a new comment is added to an issue, if it had the Stale or Awaiting User Response labels, then those labels will be removed
|
||||||
|
|
||||||
|
name: 🎯 Remove Pending Labels on Close
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [closed]
|
||||||
|
jobs:
|
||||||
|
remove-labels:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Remove Labels when Closed
|
||||||
|
uses: actions-cool/issues-helper@v2
|
||||||
|
with:
|
||||||
|
actions: remove-labels
|
||||||
|
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
labels: '🚏 Awaiting User Response,⚰️ Stale,👤 Awaiting Maintainer Response'
|
42
.github/workflows/manage-pending-labels.yml
vendored
Normal file
42
.github/workflows/manage-pending-labels.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# When a new comment is added to an issue, if it had the Stale or Awaiting User Response labels, then those labels will be removed
|
||||||
|
|
||||||
|
name: 🎯 Add/ Remove Awaiting Response Labels
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
jobs:
|
||||||
|
remove-stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.comment.author_association != 'COLLABORATOR' && github.event.comment.author_association != 'OWNER' }}
|
||||||
|
steps:
|
||||||
|
- name: Remove Stale labels when Updated
|
||||||
|
uses: actions-cool/issues-helper@v2
|
||||||
|
with:
|
||||||
|
actions: remove-labels
|
||||||
|
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
labels: '🚏 Awaiting User Response,⚰️ Stale'
|
||||||
|
|
||||||
|
add-awaiting-author:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{!github.event.issue.pull_request && github.event.comment.author_association != 'COLLABORATOR' && github.event.comment.author_association != 'OWNER' && github.event.issue.state == 'open' }}
|
||||||
|
steps:
|
||||||
|
- name: Add Awaiting Author labels when Updated
|
||||||
|
uses: actions-cool/issues-helper@v2
|
||||||
|
with:
|
||||||
|
actions: add-labels
|
||||||
|
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
labels: '👤 Awaiting Maintainer Response'
|
||||||
|
|
||||||
|
remove-awaiting-author:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.comment.author_association == 'OWNER' }}
|
||||||
|
steps:
|
||||||
|
- name: Remove Awaiting Author labels when Updated
|
||||||
|
uses: actions-cool/issues-helper@v2
|
||||||
|
with:
|
||||||
|
actions: remove-labels
|
||||||
|
token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
labels: '👤 Awaiting Maintainer Response'
|
43
.github/workflows/update-docs.yml
vendored
43
.github/workflows/update-docs.yml
vendored
@@ -1,43 +0,0 @@
|
|||||||
name: Update SillyTavern-Docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update_docs:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout current repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Checkout SillyTavern-Docs repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: SillyTavern/SillyTavern-Docs
|
|
||||||
path: SillyTavern-Docs
|
|
||||||
|
|
||||||
- name: Clone SillyTavern wiki into SillyTavern-Docs/extensions
|
|
||||||
run: rm -rf SillyTavern-Docs/extensions && git clone https://github.com/SillyTavern/SillyTavern.wiki.git SillyTavern-Docs/extensions && rm -rf SillyTavern-Docs/extensions/.git
|
|
||||||
|
|
||||||
- name: Copy files
|
|
||||||
run: |
|
|
||||||
cp public/notes/content.md SillyTavern-Docs/guidebook.md
|
|
||||||
cp faq.md SillyTavern-Docs/faq.md
|
|
||||||
cp readme.md SillyTavern-Docs/readme.md
|
|
||||||
cp public/notes/update.md SillyTavern-Docs/update.md
|
|
||||||
|
|
||||||
- name: Deploy to external repository
|
|
||||||
uses: cpina/github-action-push-to-another-repository@main
|
|
||||||
env:
|
|
||||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
|
||||||
with:
|
|
||||||
# GitHub Action output files
|
|
||||||
source-directory: SillyTavern-Docs/
|
|
||||||
destination-github-username: SillyTavern
|
|
||||||
destination-repository-name: SillyTavern-Docs
|
|
||||||
user-email: github-actions[bot]@users.noreply.github.com
|
|
||||||
user-name: "GitHub Actions"
|
|
||||||
target-branch: "main"
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,3 +47,4 @@ access.log
|
|||||||
public/css/user.css
|
public/css/user.css
|
||||||
/plugins/
|
/plugins/
|
||||||
/data
|
/data
|
||||||
|
/default/scaffold
|
||||||
|
@@ -33,7 +33,14 @@ If you insist on installing via a zip, here is the tedious process for doing the
|
|||||||
2. Unzip it into a folder OUTSIDE of your current ST installation.
|
2. Unzip it into a folder OUTSIDE of your current ST installation.
|
||||||
3. Do the usual setup procedure for your OS to install the NodeJS requirements.
|
3. Do the usual setup procedure for your OS to install the NodeJS requirements.
|
||||||
|
|
||||||
4. Copy the following files/folders as necessary(*) from your old ST installation:
|
4a. Updating 1.12.0 and above
|
||||||
|
|
||||||
|
Copy the user data directory from your data root into the data root of the new install.
|
||||||
|
|
||||||
|
By default: /data/default-user
|
||||||
|
|
||||||
|
4a. Migrating from <1.12.0 to >=1.20.0
|
||||||
|
Copy the following files/folders as necessary(*) from your old ST installation:
|
||||||
|
|
||||||
- Assets
|
- Assets
|
||||||
- Backgrounds
|
- Backgrounds
|
||||||
@@ -54,16 +61,15 @@ If you insist on installing via a zip, here is the tedious process for doing the
|
|||||||
- Worlds
|
- Worlds
|
||||||
- User
|
- User
|
||||||
- settings.json
|
- settings.json
|
||||||
- secrets.json <---- this one is in the base folder, not /public/
|
- secrets.json <---- This one is in the base folder, not /public/
|
||||||
|
|
||||||
(*) 'As necessary' = "If you made any custom content related to those folders".
|
(*) 'As necessary' = "If you made any custom content related to those folders".
|
||||||
None of the folders are mandatory, so only copy what you need.
|
None of the folders are mandatory, so only copy what you need.
|
||||||
|
|
||||||
**NB: DO NOT COPY THE ENTIRE /PUBLIC/ FOLDER.**
|
**NB: DO NOT COPY THE ENTIRE /PUBLIC/ FOLDER.**
|
||||||
Doing so could break the new install and prevent new features from being present.
|
Doing so could break the new install and prevent new features from being present.
|
||||||
|
Paste those items into the /data/default-user folder of the new install.
|
||||||
|
|
||||||
5. Paste those items into the /Public/ folder of the new install.
|
5. Start SillyTavern once again with the method appropriate to your OS, and pray you got it right.
|
||||||
|
|
||||||
6. Start SillyTavern once again with the method appropriate to your OS, and pray you got it right.
|
6. If everything shows up, you can safely delete the old ST folder.
|
||||||
|
|
||||||
7. If everything shows up, you can safely delete the old ST folder.
|
|
||||||
|
@@ -9,6 +9,8 @@ port: 8000
|
|||||||
# -- SECURITY CONFIGURATION --
|
# -- SECURITY CONFIGURATION --
|
||||||
# Toggle whitelist mode
|
# Toggle whitelist mode
|
||||||
whitelistMode: true
|
whitelistMode: true
|
||||||
|
# Whitelist will also verify IP in X-Forwarded-For / X-Real-IP headers
|
||||||
|
enableForwardedWhitelist: true
|
||||||
# Whitelist of allowed IP addresses
|
# Whitelist of allowed IP addresses
|
||||||
whitelist:
|
whitelist:
|
||||||
- 127.0.0.1
|
- 127.0.0.1
|
||||||
@@ -46,6 +48,12 @@ allowKeysExposure: false
|
|||||||
skipContentCheck: false
|
skipContentCheck: false
|
||||||
# Disable automatic chats backup
|
# Disable automatic chats backup
|
||||||
disableChatBackup: false
|
disableChatBackup: false
|
||||||
|
# Allowed hosts for card downloads
|
||||||
|
whitelistImportDomains:
|
||||||
|
- localhost
|
||||||
|
- cdn.discordapp.com
|
||||||
|
- files.catbox.moe
|
||||||
|
- raw.githubusercontent.com
|
||||||
# API request overrides (for KoboldAI and Text Completion APIs)
|
# API request overrides (for KoboldAI and Text Completion APIs)
|
||||||
## Note: host includes the port number if it's not the default (80 or 443)
|
## Note: host includes the port number if it's not the default (80 or 443)
|
||||||
## Format is an array of objects:
|
## Format is an array of objects:
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 338 KiB |
Binary file not shown.
Before Width: | Height: | Size: 598 KiB |
@@ -107,14 +107,6 @@
|
|||||||
"filename": "default_Seraphina.png",
|
"filename": "default_Seraphina.png",
|
||||||
"type": "character"
|
"type": "character"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"filename": "default_CodingSensei.png",
|
|
||||||
"type": "character"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": "default_FluxTheCat.png",
|
|
||||||
"type": "character"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"filename": "Seraphina",
|
"filename": "Seraphina",
|
||||||
"type": "sprites"
|
"type": "sprites"
|
||||||
@@ -539,6 +531,10 @@
|
|||||||
"filename": "presets/context/Llama 3 Instruct.json",
|
"filename": "presets/context/Llama 3 Instruct.json",
|
||||||
"type": "context"
|
"type": "context"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"filename": "presets/context/Phi.json",
|
||||||
|
"type": "context"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "presets/instruct/Adventure.json",
|
"filename": "presets/instruct/Adventure.json",
|
||||||
"type": "instruct"
|
"type": "instruct"
|
||||||
@@ -631,6 +627,10 @@
|
|||||||
"filename": "presets/instruct/Llama 3 Instruct.json",
|
"filename": "presets/instruct/Llama 3 Instruct.json",
|
||||||
"type": "instruct"
|
"type": "instruct"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"filename": "presets/instruct/Phi.json",
|
||||||
|
"type": "instruct"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "presets/moving-ui/Default.json",
|
"filename": "presets/moving-ui/Default.json",
|
||||||
"type": "moving_ui"
|
"type": "moving_ui"
|
||||||
@@ -642,5 +642,21 @@
|
|||||||
{
|
{
|
||||||
"filename": "presets/quick-replies/Default.json",
|
"filename": "presets/quick-replies/Default.json",
|
||||||
"type": "quick_replies"
|
"type": "quick_replies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "presets/instruct/Llama-3-Instruct-Names.json",
|
||||||
|
"type": "instruct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "presets/instruct/ChatML-Names.json",
|
||||||
|
"type": "instruct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "presets/context/Llama-3-Instruct-Names.json",
|
||||||
|
"type": "context"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "presets/context/ChatML-Names.json",
|
||||||
|
"type": "context"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
12
default/content/presets/context/ChatML-Names.json
Normal file
12
default/content/presets/context/ChatML-Names.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"story_string": "<|im_start|>system\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|im_end|>",
|
||||||
|
"example_separator": "",
|
||||||
|
"chat_start": "",
|
||||||
|
"use_stop_strings": false,
|
||||||
|
"allow_jailbreak": false,
|
||||||
|
"always_force_name2": true,
|
||||||
|
"trim_sentences": false,
|
||||||
|
"include_newline": false,
|
||||||
|
"single_line": false,
|
||||||
|
"name": "ChatML-Names"
|
||||||
|
}
|
12
default/content/presets/context/Llama-3-Instruct-Names.json
Normal file
12
default/content/presets/context/Llama-3-Instruct-Names.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"story_string": "<|start_header_id|>system<|end_header_id|>\n\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|eot_id|>",
|
||||||
|
"example_separator": "",
|
||||||
|
"chat_start": "",
|
||||||
|
"use_stop_strings": false,
|
||||||
|
"allow_jailbreak": false,
|
||||||
|
"always_force_name2": true,
|
||||||
|
"trim_sentences": false,
|
||||||
|
"include_newline": false,
|
||||||
|
"single_line": false,
|
||||||
|
"name": "Llama-3-Instruct-Names"
|
||||||
|
}
|
12
default/content/presets/context/Phi.json
Normal file
12
default/content/presets/context/Phi.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"story_string": "<|system|>\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|end|>\n",
|
||||||
|
"example_separator": "",
|
||||||
|
"chat_start": "",
|
||||||
|
"use_stop_strings": false,
|
||||||
|
"allow_jailbreak": false,
|
||||||
|
"always_force_name2": true,
|
||||||
|
"trim_sentences": false,
|
||||||
|
"include_newline": false,
|
||||||
|
"single_line": false,
|
||||||
|
"name": "Phi"
|
||||||
|
}
|
24
default/content/presets/instruct/ChatML-Names.json
Normal file
24
default/content/presets/instruct/ChatML-Names.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
|
||||||
|
"input_sequence": "<|im_start|>[{{name}}]",
|
||||||
|
"output_sequence": "<|im_start|>[{{name}}]",
|
||||||
|
"last_output_sequence": "",
|
||||||
|
"system_sequence": "<|im_start|>system",
|
||||||
|
"stop_sequence": "<|im_end|>",
|
||||||
|
"wrap": true,
|
||||||
|
"macro": true,
|
||||||
|
"names": false,
|
||||||
|
"names_force_groups": false,
|
||||||
|
"activation_regex": "",
|
||||||
|
"system_sequence_prefix": "",
|
||||||
|
"system_sequence_suffix": "",
|
||||||
|
"first_output_sequence": "",
|
||||||
|
"skip_examples": false,
|
||||||
|
"output_suffix": "<|im_end|>\n",
|
||||||
|
"input_suffix": "<|im_end|>\n",
|
||||||
|
"system_suffix": "<|im_end|>\n",
|
||||||
|
"user_alignment_message": "",
|
||||||
|
"system_same_as_user": false,
|
||||||
|
"last_system_sequence": "",
|
||||||
|
"name": "ChatML-Names"
|
||||||
|
}
|
24
default/content/presets/instruct/Llama-3-Instruct-Names.json
Normal file
24
default/content/presets/instruct/Llama-3-Instruct-Names.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"system_prompt": "You are an expert actor that can fully immerse yourself into any role given. You do not break character for any reason, even if someone tries addressing you as an AI or language model. Currently your role is {{char}}, which is described in detail below. As {{char}}, continue the exchange with {{user}}.",
|
||||||
|
"input_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
|
||||||
|
"output_sequence": "<|start_header_id|>[{{name}}]<|end_header_id|>\n\n",
|
||||||
|
"last_output_sequence": "",
|
||||||
|
"system_sequence": "<|start_header_id|>system<|end_header_id|>\n\n",
|
||||||
|
"stop_sequence": "<|eot_id|>",
|
||||||
|
"wrap": false,
|
||||||
|
"macro": true,
|
||||||
|
"names": false,
|
||||||
|
"names_force_groups": false,
|
||||||
|
"activation_regex": "",
|
||||||
|
"system_sequence_prefix": "",
|
||||||
|
"system_sequence_suffix": "",
|
||||||
|
"first_output_sequence": "",
|
||||||
|
"skip_examples": false,
|
||||||
|
"output_suffix": "<|eot_id|>",
|
||||||
|
"input_suffix": "<|eot_id|>",
|
||||||
|
"system_suffix": "<|eot_id|>",
|
||||||
|
"user_alignment_message": "",
|
||||||
|
"system_same_as_user": true,
|
||||||
|
"last_system_sequence": "",
|
||||||
|
"name": "Llama-3-Instruct-Names"
|
||||||
|
}
|
24
default/content/presets/instruct/Phi.json
Normal file
24
default/content/presets/instruct/Phi.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
|
||||||
|
"input_sequence": "<|user|>\n",
|
||||||
|
"output_sequence": "<|assistant|>\n",
|
||||||
|
"first_output_sequence": "",
|
||||||
|
"last_output_sequence": "",
|
||||||
|
"system_sequence_prefix": "",
|
||||||
|
"system_sequence_suffix": "",
|
||||||
|
"stop_sequence": "<|end|>",
|
||||||
|
"wrap": false,
|
||||||
|
"macro": true,
|
||||||
|
"names": true,
|
||||||
|
"names_force_groups": true,
|
||||||
|
"activation_regex": "",
|
||||||
|
"skip_examples": false,
|
||||||
|
"output_suffix": "<|end|>\n",
|
||||||
|
"input_suffix": "<|end|>\n",
|
||||||
|
"system_sequence": "<|system|>\n",
|
||||||
|
"system_suffix": "<|end|>\n",
|
||||||
|
"user_alignment_message": "",
|
||||||
|
"last_system_sequence": "",
|
||||||
|
"system_same_as_user": false,
|
||||||
|
"name": "Phi"
|
||||||
|
}
|
@@ -231,6 +231,7 @@
|
|||||||
"api_url_scale": "",
|
"api_url_scale": "",
|
||||||
"show_external_models": false,
|
"show_external_models": false,
|
||||||
"assistant_prefill": "",
|
"assistant_prefill": "",
|
||||||
|
"assistant_impersonation": "",
|
||||||
"human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
|
"human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
|
||||||
"use_ai21_tokenizer": false,
|
"use_ai21_tokenizer": false,
|
||||||
"use_google_tokenizer": false,
|
"use_google_tokenizer": false,
|
||||||
|
@@ -33,8 +33,8 @@
|
|||||||
"negative_prompt": "",
|
"negative_prompt": "",
|
||||||
"grammar_string": "",
|
"grammar_string": "",
|
||||||
"banned_tokens": "",
|
"banned_tokens": "",
|
||||||
"ignore_eos_token_aphrodite": false,
|
"ignore_eos_token": false,
|
||||||
"spaces_between_special_tokens_aphrodite": true,
|
"spaces_between_special_tokens": true,
|
||||||
"type": "ooba",
|
"type": "ooba",
|
||||||
"legacy_api": false,
|
"legacy_api": false,
|
||||||
"sampler_order": [
|
"sampler_order": [
|
||||||
|
@@ -33,8 +33,8 @@
|
|||||||
"negative_prompt": "",
|
"negative_prompt": "",
|
||||||
"grammar_string": "",
|
"grammar_string": "",
|
||||||
"banned_tokens": "",
|
"banned_tokens": "",
|
||||||
"ignore_eos_token_aphrodite": false,
|
"ignore_eos_token": false,
|
||||||
"spaces_between_special_tokens_aphrodite": true,
|
"spaces_between_special_tokens": true,
|
||||||
"type": "ooba",
|
"type": "ooba",
|
||||||
"legacy_api": false,
|
"legacy_api": false,
|
||||||
"sampler_order": [
|
"sampler_order": [
|
||||||
|
@@ -33,8 +33,8 @@
|
|||||||
"negative_prompt": "",
|
"negative_prompt": "",
|
||||||
"grammar_string": "",
|
"grammar_string": "",
|
||||||
"banned_tokens": "",
|
"banned_tokens": "",
|
||||||
"ignore_eos_token_aphrodite": false,
|
"ignore_eos_token": false,
|
||||||
"spaces_between_special_tokens_aphrodite": true,
|
"spaces_between_special_tokens": true,
|
||||||
"type": "ooba",
|
"type": "ooba",
|
||||||
"legacy_api": false,
|
"legacy_api": false,
|
||||||
"sampler_order": [
|
"sampler_order": [
|
||||||
|
@@ -387,14 +387,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tag_map": {
|
"tag_map": {
|
||||||
"default_FluxTheCat.png": [
|
|
||||||
"1345561466591"
|
|
||||||
],
|
|
||||||
"default_Seraphina.png": [
|
"default_Seraphina.png": [
|
||||||
"1345561466591"
|
"1345561466591"
|
||||||
],
|
|
||||||
"default_CodingSensei.png": [
|
|
||||||
"1345561466591"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nai_settings": {
|
"nai_settings": {
|
||||||
@@ -630,6 +624,7 @@
|
|||||||
"show_external_models": false,
|
"show_external_models": false,
|
||||||
"proxy_password": "",
|
"proxy_password": "",
|
||||||
"assistant_prefill": "",
|
"assistant_prefill": "",
|
||||||
|
"assistant_impersonation": "",
|
||||||
"use_ai21_tokenizer": false
|
"use_ai21_tokenizer": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
default/scaffold/README.md
Normal file
26
default/scaffold/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Content Scaffolding
|
||||||
|
|
||||||
|
Content files in this folder will be copied for all users (old and new) on the server startup.
|
||||||
|
|
||||||
|
1. You **must** create an `index.json` file in `/default/scaffold` for it to work. The syntax is the same as for default content.
|
||||||
|
2. All file paths should be relative to `/default/scaffold`, the use of subdirectories is allowed.
|
||||||
|
3. Scaffolded files are copied first, so they override any of the default files (presets/settings/etc.) that have the same file name.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "themes/Midnight.json",
|
||||||
|
"type": "theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "backgrounds/city.png",
|
||||||
|
"type": "background"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "characters/Charlie.png",
|
||||||
|
"type": "character"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
15
package-lock.json
generated
15
package-lock.json
generated
@@ -1,18 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "sillytavern",
|
"name": "sillytavern",
|
||||||
"version": "1.12.0-preview",
|
"version": "1.12.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sillytavern",
|
"name": "sillytavern",
|
||||||
"version": "1.12.0-preview",
|
"version": "1.12.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agnai/sentencepiece-js": "^1.1.1",
|
"@agnai/sentencepiece-js": "^1.1.1",
|
||||||
"@agnai/web-tokenizers": "^0.1.3",
|
"@agnai/web-tokenizers": "^0.1.3",
|
||||||
"@dqbd/tiktoken": "^1.0.13",
|
|
||||||
"@zeldafan0225/ai_horde": "^4.0.1",
|
"@zeldafan0225/ai_horde": "^4.0.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"bing-translate-api": "^2.9.1",
|
"bing-translate-api": "^2.9.1",
|
||||||
@@ -46,6 +45,7 @@
|
|||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "^2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
|
"tiktoken": "^1.0.15",
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
"wavefile": "^11.0.0",
|
"wavefile": "^11.0.0",
|
||||||
"write-file-atomic": "^5.0.1",
|
"write-file-atomic": "^5.0.1",
|
||||||
@@ -82,10 +82,6 @@
|
|||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@dqbd/tiktoken": {
|
|
||||||
"version": "1.0.13",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -4403,6 +4399,11 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tiktoken": {
|
||||||
|
"version": "1.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.15.tgz",
|
||||||
|
"integrity": "sha512-sCsrq/vMWUSEW29CJLNmPvWxlVp7yh2tlkAjpJltIKqp5CKf98ZNpdeHRmAlPVFlGEbswDc6SmI8vz64W/qErw=="
|
||||||
|
},
|
||||||
"node_modules/timm": {
|
"node_modules/timm": {
|
||||||
"version": "1.7.1",
|
"version": "1.7.1",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
10
package.json
10
package.json
@@ -2,7 +2,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agnai/sentencepiece-js": "^1.1.1",
|
"@agnai/sentencepiece-js": "^1.1.1",
|
||||||
"@agnai/web-tokenizers": "^0.1.3",
|
"@agnai/web-tokenizers": "^0.1.3",
|
||||||
"@dqbd/tiktoken": "^1.0.13",
|
|
||||||
"@zeldafan0225/ai_horde": "^4.0.1",
|
"@zeldafan0225/ai_horde": "^4.0.1",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"bing-translate-api": "^2.9.1",
|
"bing-translate-api": "^2.9.1",
|
||||||
@@ -36,6 +35,7 @@
|
|||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "^2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
|
"tiktoken": "^1.0.15",
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
"wavefile": "^11.0.0",
|
"wavefile": "^11.0.0",
|
||||||
"write-file-atomic": "^5.0.1",
|
"write-file-atomic": "^5.0.1",
|
||||||
@@ -68,13 +68,15 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||||
},
|
},
|
||||||
"version": "1.12.0-preview",
|
"version": "1.12.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"start-multi": "node server.js --disableCsrf",
|
"start:no-csrf": "node server.js --disableCsrf",
|
||||||
"postinstall": "node post-install.js",
|
"postinstall": "node post-install.js",
|
||||||
"lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",
|
"lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",
|
||||||
"lint-fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix"
|
"lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix",
|
||||||
|
"plugins:update": "node plugins update",
|
||||||
|
"plugins:install": "node plugins install"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"sillytavern": "./server.js"
|
"sillytavern": "./server.js"
|
||||||
|
75
plugins.js
Normal file
75
plugins.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Plugin manager script.
|
||||||
|
// Usage: node plugins.js update
|
||||||
|
// More operations coming soon.
|
||||||
|
const { default: git } = require('simple-git');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { color } = require('./src/util');
|
||||||
|
|
||||||
|
const pluginsPath = './plugins';
|
||||||
|
|
||||||
|
const command = process.argv[2];
|
||||||
|
|
||||||
|
if (command === 'update') {
|
||||||
|
console.log(color.magenta('Updating all plugins'));
|
||||||
|
updatePlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command === 'install') {
|
||||||
|
const pluginName = process.argv[3];
|
||||||
|
console.log('Installing a new plugin', color.green(pluginName));
|
||||||
|
installPlugin(pluginName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updatePlugins() {
|
||||||
|
const directories = fs.readdirSync(pluginsPath)
|
||||||
|
.filter(file => !file.startsWith('.'))
|
||||||
|
.filter(file => fs.statSync(path.join(pluginsPath, file)).isDirectory());
|
||||||
|
|
||||||
|
console.log(`Found ${color.cyan(directories.length)} directories in ./plugins`);
|
||||||
|
|
||||||
|
for (const directory of directories) {
|
||||||
|
try {
|
||||||
|
console.log(`Updating plugin ${color.green(directory)}...`);
|
||||||
|
const pluginPath = path.join(pluginsPath, directory);
|
||||||
|
const pluginRepo = git(pluginPath);
|
||||||
|
await pluginRepo.fetch();
|
||||||
|
const commitHash = await pluginRepo.revparse(['HEAD']);
|
||||||
|
const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);
|
||||||
|
const log = await pluginRepo.log({
|
||||||
|
from: commitHash,
|
||||||
|
to: trackingBranch,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (log.total === 0) {
|
||||||
|
console.log(`Plugin ${color.blue(directory)} is already up to date`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pluginRepo.pull();
|
||||||
|
const latestCommit = await pluginRepo.revparse(['HEAD']);
|
||||||
|
console.log(`Plugin ${color.green(directory)} updated to commit ${color.cyan(latestCommit)}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(color.red(`Failed to update plugin ${directory}: ${error.message}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(color.magenta('All plugins updated!'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installPlugin(pluginName) {
|
||||||
|
try {
|
||||||
|
const pluginPath = path.join(pluginsPath, path.basename(pluginName, '.git'));
|
||||||
|
|
||||||
|
if (fs.existsSync(pluginPath)) {
|
||||||
|
return console.log(color.yellow(`Directory already exists at ${pluginPath}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
await git().clone(pluginName, pluginPath, { '--depth': 1 });
|
||||||
|
console.log(`Plugin ${color.green(pluginName)} installed to ${color.cyan(pluginPath)}`);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(color.red(`Failed to install plugin ${pluginName}`), error);
|
||||||
|
}
|
||||||
|
}
|
6
public/css/brands.min.css
vendored
Normal file
6
public/css/brands.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -204,3 +204,7 @@ input.extension_missing[type="checkbox"] {
|
|||||||
#extensionsMenu>#translate_chat {
|
#extensionsMenu>#translate_chat {
|
||||||
order: 7;
|
order: 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#extensionsMenu>#translate_input_message {
|
||||||
|
order: 8;
|
||||||
|
}
|
||||||
|
8488
public/css/fontawesome.css
vendored
8488
public/css/fontawesome.css
vendored
File diff suppressed because it is too large
Load Diff
9
public/css/fontawesome.min.css
vendored
Normal file
9
public/css/fontawesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -231,9 +231,11 @@
|
|||||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#right-nav-panel {
|
#right-nav-panel {
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#floatingPrompt,
|
#floatingPrompt,
|
||||||
#cfgConfig,
|
#cfgConfig,
|
||||||
@@ -307,6 +309,10 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.waifuMode .zoomed_avatar_container {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
body.waifuMode .zoomed_avatar {
|
body.waifuMode .zoomed_avatar {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
max-height: calc(60vh - 60px);
|
max-height: calc(60vh - 60px);
|
||||||
|
@@ -171,3 +171,78 @@
|
|||||||
.select2-results__option.select2-results__message::before {
|
.select2-results__option.select2-results__message::before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-selection__choice__display {
|
||||||
|
/* Fix weird alignment on the left side */
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for choice remove icon */
|
||||||
|
span.select2.select2-container .select2-selection__choice__remove {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.select2.select2-container .select2-selection__choice__remove:hover {
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
background-color: var(--white30a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom class to support styling to show clickable choice options */
|
||||||
|
.select2_choice_clickable+span.select2-container .select2-selection__choice__display {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
background-color: var(--black50a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display:hover {
|
||||||
|
background-color: var(--white30a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom class to support same line multi inputs of select2 controls */
|
||||||
|
.select2_multi_sameline+span.select2-container .select2-selection--multiple {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
|
||||||
|
/* Allow search placeholder to take up all space if needed */
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||||
|
/* Fix weird styling choice or huge margin around selected options */
|
||||||
|
margin-block-start: 2px;
|
||||||
|
margin-block-end: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search__field {
|
||||||
|
/* Min height to reserve spacing */
|
||||||
|
min-height: calc(var(--mainFontSize) + 13px);
|
||||||
|
/* Min width to be clickable */
|
||||||
|
min-width: 4em;
|
||||||
|
align-content: center;
|
||||||
|
/* Fix search textarea alignment issue with UL elements */
|
||||||
|
margin-top: 0px;
|
||||||
|
height: unset;
|
||||||
|
/* Prevent height from jumping around when input is focused */
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||||
|
/* Min height to reserve spacing */
|
||||||
|
min-height: calc(var(--mainFontSize) + 13px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make search bar invisible unless the select2 is active, to save space */
|
||||||
|
.select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2_multi_sameline+span.select2-container.select2-container--focus .select2-selection--multiple .select2-search--inline {
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
:root,
|
|
||||||
:host {
|
|
||||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
|
||||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free';
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Font Awesome 6 Free';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 900;
|
|
||||||
font-display: block;
|
|
||||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype");
|
|
||||||
}
|
|
||||||
|
|
||||||
.fas,
|
|
||||||
.fa-solid {
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com
|
|
||||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
|
||||||
* Copyright 2023 Fonticons, Inc.
|
|
||||||
*/
|
|
6
public/css/solid.min.css
vendored
Normal file
6
public/css/solid.min.css
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
* Copyright 2024 Fonticons, Inc.
|
||||||
|
*/
|
||||||
|
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}
|
@@ -360,6 +360,14 @@
|
|||||||
flex: 2 !important;
|
flex: 2 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex3 {
|
||||||
|
flex: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex4 {
|
||||||
|
flex: 4;
|
||||||
|
}
|
||||||
|
|
||||||
.flexFlowColumn {
|
.flexFlowColumn {
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
}
|
}
|
||||||
@@ -563,4 +571,4 @@ textarea:disabled {
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
@@ -103,7 +103,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#bulkTagsList,
|
#bulkTagsList,
|
||||||
#tagList .tag {
|
#tagList .tag,
|
||||||
|
#groupTagList .tag {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +194,8 @@
|
|||||||
filter: brightness(75%) saturate(0.6);
|
filter: brightness(75%) saturate(0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag_as_folder:hover {
|
.tag_as_folder:hover,
|
||||||
|
.tag_as_folder.flash {
|
||||||
filter: brightness(150%) saturate(0.6) !important;
|
filter: brightness(150%) saturate(0.6) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,7 +19,8 @@ body.no-timer .mes_timer,
|
|||||||
body.no-timestamps .timestamp,
|
body.no-timestamps .timestamp,
|
||||||
body.no-tokenCount .tokenCounterDisplay,
|
body.no-tokenCount .tokenCounterDisplay,
|
||||||
body.no-mesIDDisplay .mesIDDisplay,
|
body.no-mesIDDisplay .mesIDDisplay,
|
||||||
body.no-modelIcons .icon-svg {
|
body.no-modelIcons .icon-svg,
|
||||||
|
body.hideChatAvatars .mesAvatarWrapper .avatar {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +124,16 @@ body.charListGrid #rm_print_characters_block .bogus_folder_select_back .avatar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Hack for keeping the spacing */
|
/* Hack for keeping the spacing */
|
||||||
|
/*
|
||||||
body.charListGrid #rm_print_characters_block .ch_add_placeholder {
|
body.charListGrid #rm_print_characters_block .ch_add_placeholder {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
body.charListGrid #rm_print_characters_block .ch_additional_info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/*big avatars mode page-wide changes*/
|
/*big avatars mode page-wide changes*/
|
||||||
|
|
||||||
@@ -433,14 +440,6 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#openai_image_inlining:not(:checked)~#image_inlining_hint {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#openai_image_inlining:checked~#image_inlining_hint {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#smooth_streaming:not(:checked)~#smooth_streaming_speed_control {
|
#smooth_streaming:not(:checked)~#smooth_streaming_speed_control {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@@ -76,6 +76,12 @@
|
|||||||
.world_entry_form_control {
|
.world_entry_form_control {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control .keyprimarytextpole,
|
||||||
|
.world_entry_form_control .keysecondarytextpole {
|
||||||
|
padding-right: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.world_entry_thin_controls {
|
.world_entry_thin_controls {
|
||||||
@@ -101,7 +107,7 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
min-height: calc(var(--mainFontSize) + 13px);
|
min-height: calc(var(--mainFontSize) + 14px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete_entry_button {
|
.delete_entry_button {
|
||||||
@@ -157,7 +163,12 @@
|
|||||||
width: 10em;
|
width: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#world_info_search,
|
#world_info_search {
|
||||||
|
width: 10em;
|
||||||
|
min-width: 10em;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
#world_info_sort_order {
|
#world_info_sort_order {
|
||||||
width: 7em;
|
width: 7em;
|
||||||
}
|
}
|
||||||
@@ -191,3 +202,58 @@
|
|||||||
.WIEntryHeaderTitleMobile {
|
.WIEntryHeaderTitleMobile {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.select2-container .select2-selection__choice__display:has(> .regex_item),
|
||||||
|
span.select2-container .select2-results__option:has(> .result_block .regex_item) {
|
||||||
|
background-color: #D27D2D30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regex_item .regex_icon {
|
||||||
|
background-color: var(--black30a);
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
border-radius: 7px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: calc(var(--mainFontSize) * 0.75);
|
||||||
|
padding: 0px 3px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option .regex_item .regex_icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option .item_count {
|
||||||
|
margin-left: 10px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
select.keyselect+span.select2-container .select2-selection--multiple {
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch_input_type_icon {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
height: 20px;
|
||||||
|
width: fit-content;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: calc(5px + var(--mainFontSize));
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
|
opacity: 0.5;
|
||||||
|
color: var(--SmartThemeBodyColor);
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch_input_type_icon:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
1360
public/global.d.ts
vendored
Normal file
1360
public/global.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
48
public/img/groq.svg
Normal file
48
public/img/groq.svg
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="107.644"
|
||||||
|
height="156.436"
|
||||||
|
viewBox="0 0 107.644 156.436"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
id="svg9"
|
||||||
|
sodipodi:docname="groqcloud_dark_v2.svg"
|
||||||
|
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview9"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="0.667"
|
||||||
|
inkscape:cx="499.25037"
|
||||||
|
inkscape:cy="56.971514"
|
||||||
|
inkscape:window-width="1312"
|
||||||
|
inkscape:window-height="449"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="38"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg9" />
|
||||||
|
<defs
|
||||||
|
id="defs9">
|
||||||
|
<clipPath
|
||||||
|
id="clip0_872_2594">
|
||||||
|
<rect
|
||||||
|
width="1000"
|
||||||
|
height="200.345"
|
||||||
|
id="rect9"
|
||||||
|
x="0"
|
||||||
|
y="0" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
d="M 54.0487,0.00281139 C 24.4736,-0.29748861 0.303066,23.497811 0.00281057,53.072911 -0.297445,82.648011 23.4978,106.89401 53.0729,107.11901 c 0.3003,0 0.6756,0 0.9758,0 H 71.6888 V 87.077011 H 54.0487 c -18.4656,0.225 -33.6285,-14.563 -33.8537,-33.1033 -0.2252,-18.4657 14.5624,-33.6286 33.1031,-33.8538 0.2252,0 0.5255,0 0.7506,0 18.4657,0 33.5536,15.0128 33.5536,33.4784 v 49.316699 c 0,18.316 -14.9377,33.254 -33.2533,33.479 -8.7825,0 -17.1145,-3.603 -23.2698,-9.834 l -14.187,14.187 c 9.8333,9.909 23.1947,15.539 37.1565,15.689 h 0.7507 c 29.1998,-0.451 52.6946,-24.096 52.8446,-53.296 V 52.247211 C 106.894,23.197511 83.1735,0.00281139 54.1238,0.00281139 Z"
|
||||||
|
id="path7" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
40
public/img/infermaticai.svg
Normal file
40
public/img/infermaticai.svg
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
version="1.0"
|
||||||
|
width="70pt"
|
||||||
|
height="70pt"
|
||||||
|
viewBox="0 0 70 70"
|
||||||
|
preserveAspectRatio="xMidYMid"
|
||||||
|
id="svg15"
|
||||||
|
sodipodi:docname="infermatic.svg"
|
||||||
|
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs15" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview15"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="pt"
|
||||||
|
inkscape:zoom="0.75112613"
|
||||||
|
inkscape:cx="306.2069"
|
||||||
|
inkscape:cy="50.590705"
|
||||||
|
inkscape:window-width="1312"
|
||||||
|
inkscape:window-height="449"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="38"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg15" />
|
||||||
|
<path
|
||||||
|
id="path15"
|
||||||
|
d="m 1030,375 v -75 h 75 74 l 6,33 c 4,18 5,51 3,72 l -3,40 -77,3 -78,3 z m 547,619 c -4,-4 -7,-41 -7,-81 v -74 l 78,3 77,3 v 75 75 l -70,3 c -39,1 -74,0 -78,-4 z m -547,-74 v -79 l 133,-3 132,-3 3,-267 2,-268 h 215 215 v 75 75 h -135 -135 l -2,273 -3,272 -212,3 -213,2 z"
|
||||||
|
transform="matrix(0.1,0,0,-0.1,-103,99.999998)" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
1206
public/index.html
1206
public/index.html
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,46 @@ EventEmitter.prototype.on = function (event, listener) {
|
|||||||
this.events[event].push(listener);
|
this.events[event].push(listener);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the listener the last to be called when the event is emitted
|
||||||
|
* @param {string} event Event name
|
||||||
|
* @param {function} listener Event listener
|
||||||
|
*/
|
||||||
|
EventEmitter.prototype.makeLast = function (event, listener) {
|
||||||
|
if (typeof this.events[event] !== 'object') {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = this.events[event];
|
||||||
|
const idx = events.indexOf(listener);
|
||||||
|
|
||||||
|
if (idx > -1) {
|
||||||
|
events.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the listener the first to be called when the event is emitted
|
||||||
|
* @param {string} event Event name
|
||||||
|
* @param {function} listener Event listener
|
||||||
|
*/
|
||||||
|
EventEmitter.prototype.makeFirst = function (event, listener) {
|
||||||
|
if (typeof this.events[event] !== 'object') {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = this.events[event];
|
||||||
|
const idx = events.indexOf(listener);
|
||||||
|
|
||||||
|
if (idx > -1) {
|
||||||
|
events.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
events.unshift(listener);
|
||||||
|
}
|
||||||
|
|
||||||
EventEmitter.prototype.removeListener = function (event, listener) {
|
EventEmitter.prototype.removeListener = function (event, listener) {
|
||||||
var idx;
|
var idx;
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "الإعدادات المسبقة لـ Kobold",
|
"kobldpresets": "الإعدادات المسبقة لـ Kobold",
|
||||||
"guikoboldaisettings": "إعدادات واجهة KoboldAI",
|
"guikoboldaisettings": "إعدادات واجهة KoboldAI",
|
||||||
"novelaipreserts": "الإعدادات المسبقة لـ NovelAI",
|
"novelaipreserts": "الإعدادات المسبقة لـ NovelAI",
|
||||||
"default": "افتراضي",
|
|
||||||
"openaipresets": "الإعدادات المسبقة لـ OpenAI",
|
"openaipresets": "الإعدادات المسبقة لـ OpenAI",
|
||||||
"text gen webio(ooba) presets": "الإعدادات المسبقة لـ WebUI(ooba)",
|
"text gen webio(ooba) presets": "الإعدادات المسبقة لـ WebUI(ooba)",
|
||||||
"response legth(tokens)": "طول الاستجابة (بعدد الاحرف او الرموز)",
|
"response legth(tokens)": "طول الاستجابة (بعدد الاحرف او الرموز)",
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
"Temperature": "درجة الحرارة",
|
"Temperature": "درجة الحرارة",
|
||||||
"Frequency Penalty": "عقوبة التكرار",
|
"Frequency Penalty": "عقوبة التكرار",
|
||||||
"Presence Penalty": "عقوبة الوجود",
|
"Presence Penalty": "عقوبة الوجود",
|
||||||
"Top-p": "أعلى p",
|
"Top-p": "أعلى p",
|
||||||
"Display bot response text chunks as they are generated": "عرض النصوص لجظة بلحظة",
|
"Display bot response text chunks as they are generated": "عرض النصوص لجظة بلحظة",
|
||||||
"Top A": "أعلى A",
|
"Top A": "أعلى A",
|
||||||
"Typical Sampling": "عينة نموذجية",
|
"Typical Sampling": "عينة نموذجية",
|
||||||
@@ -101,7 +100,7 @@
|
|||||||
"Inserts jailbreak as a last system message.": "يدرج كسر الحظر كرسالة نظام أخيرة.",
|
"Inserts jailbreak as a last system message.": "يدرج كسر الحظر كرسالة نظام أخيرة.",
|
||||||
"This tells the AI to ignore its usual content restrictions.": "هذا يخبر الذكاء الاصطناعي بتجاهل القيود المعتادة على المحتوى.",
|
"This tells the AI to ignore its usual content restrictions.": "هذا يخبر الذكاء الاصطناعي بتجاهل القيود المعتادة على المحتوى.",
|
||||||
"NSFW Encouraged": "NSFW مشجع",
|
"NSFW Encouraged": "NSFW مشجع",
|
||||||
"Tell the AI that NSFW is allowed.": "قل للذكاء الاصطناعي أنه يُسمح بـ NSFW",
|
"Tell the AI that NSFW is allowed.": "قل للذكاء الاصطناعي أنه يُسمح بـ NSFW",
|
||||||
"NSFW Prioritized": "الأولوية للمحتوى غير مناسب للعمل",
|
"NSFW Prioritized": "الأولوية للمحتوى غير مناسب للعمل",
|
||||||
"NSFW prompt text goes first in the prompt to emphasize its effect.": "النص الغير مناسب للعمل يأتي أولاً في التعليمات لتأكيد تأثيره.",
|
"NSFW prompt text goes first in the prompt to emphasize its effect.": "النص الغير مناسب للعمل يأتي أولاً في التعليمات لتأكيد تأثيره.",
|
||||||
"Streaming": "البث المباشر ل",
|
"Streaming": "البث المباشر ل",
|
||||||
@@ -141,7 +140,7 @@
|
|||||||
"Influences bot behavior in its responses": "يؤثر على سلوك الروبوت في ردوده.",
|
"Influences bot behavior in its responses": "يؤثر على سلوك الروبوت في ردوده.",
|
||||||
"Connect": "الاتصال",
|
"Connect": "الاتصال",
|
||||||
"Test Message": "رسالة اختبار",
|
"Test Message": "رسالة اختبار",
|
||||||
"API": "واجهة برمجة التطبيقات (API)",
|
"API": "واجهة برمجة التطبيقات (API)",
|
||||||
"KoboldAI": "KoboldAI",
|
"KoboldAI": "KoboldAI",
|
||||||
"Use Horde": "استخدام Horde",
|
"Use Horde": "استخدام Horde",
|
||||||
"API url": "رابط API",
|
"API url": "رابط API",
|
||||||
@@ -206,7 +205,7 @@
|
|||||||
"Scale API Key": "مفتاح API لـ Scale",
|
"Scale API Key": "مفتاح API لـ Scale",
|
||||||
"Alt Method": "طريقة بديلة",
|
"Alt Method": "طريقة بديلة",
|
||||||
"AI21 API Key": "مفتاح API لـ AI21",
|
"AI21 API Key": "مفتاح API لـ AI21",
|
||||||
"AI21 Model": "نموذج AI21",
|
"AI21 Model": "نموذج AI21",
|
||||||
"View API Usage Metrics": "عرض مقاييس استخدام واجهة برمجة التطبيقات",
|
"View API Usage Metrics": "عرض مقاييس استخدام واجهة برمجة التطبيقات",
|
||||||
"Show External models (provided by API)": "عرض النماذج الخارجية (المقدمة من قبل واجهة برمجة التطبيقات)",
|
"Show External models (provided by API)": "عرض النماذج الخارجية (المقدمة من قبل واجهة برمجة التطبيقات)",
|
||||||
"Bot": "روبوت:",
|
"Bot": "روبوت:",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "سرد العالم أولاً",
|
"Global Lore First": "سرد العالم أولاً",
|
||||||
"Recursive Scan": "فحص متكرر",
|
"Recursive Scan": "فحص متكرر",
|
||||||
"Case Sensitive": "حساس لحالة الأحرف",
|
"Case Sensitive": "حساس لحالة الأحرف",
|
||||||
"Match whole words": "تطابق الكلمات الكاملة",
|
|
||||||
"Alert On Overflow": "تنبيه عند التجاوز",
|
"Alert On Overflow": "تنبيه عند التجاوز",
|
||||||
"World/Lore Editor": "محرر العالم/السرد",
|
"World/Lore Editor": "محرر العالم/السرد",
|
||||||
"--- None ---": "--- لا شيء ---",
|
"--- None ---": "--- لا شيء ---",
|
||||||
@@ -915,7 +913,7 @@
|
|||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "استخدم المحلل النحوي المناسب لنماذج Google عبر واجهة برمجة التطبيقات الخاصة بهم. معالجة الإشارات الأولية بطيئة، ولكنها تقدم عداد رمز دقيق جدًا.",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "استخدم المحلل النحوي المناسب لنماذج Google عبر واجهة برمجة التطبيقات الخاصة بهم. معالجة الإشارات الأولية بطيئة، ولكنها تقدم عداد رمز دقيق جدًا.",
|
||||||
"Load koboldcpp order": "تحميل أمر koboldcpp",
|
"Load koboldcpp order": "تحميل أمر koboldcpp",
|
||||||
"Use Google Tokenizer": "استخدم محلل النحوي من Google"
|
"Use Google Tokenizer": "استخدم محلل النحوي من Google"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Kobold-Einstellungen von vorher",
|
"kobldpresets": "Kobold-Einstellungen von vorher",
|
||||||
"guikoboldaisettings": "KoboldAI-Einstellungen für das Menü",
|
"guikoboldaisettings": "KoboldAI-Einstellungen für das Menü",
|
||||||
"novelaipreserts": "NovelAI-Einstellungen von früher",
|
"novelaipreserts": "NovelAI-Einstellungen von früher",
|
||||||
"default": "Normal",
|
|
||||||
"openaipresets": "OpenAI-Einstellungen von vorher",
|
"openaipresets": "OpenAI-Einstellungen von vorher",
|
||||||
"text gen webio(ooba) presets": "WebUI(ooba)-Einstellungen für Texterstellung",
|
"text gen webio(ooba) presets": "WebUI(ooba)-Einstellungen für Texterstellung",
|
||||||
"response legth(tokens)": "Länge der Antwort (Tokens)",
|
"response legth(tokens)": "Länge der Antwort (Tokens)",
|
||||||
@@ -494,7 +493,6 @@
|
|||||||
"Global Lore First": "Globale Lore zuerst",
|
"Global Lore First": "Globale Lore zuerst",
|
||||||
"Recursive Scan": "Rekursive Suche",
|
"Recursive Scan": "Rekursive Suche",
|
||||||
"Case Sensitive": "Groß-/Kleinschreibung beachten",
|
"Case Sensitive": "Groß-/Kleinschreibung beachten",
|
||||||
"Match whole words": "Ganze Wörter abgleichen",
|
|
||||||
"Alert On Overflow": "Warnung bei Überlauf",
|
"Alert On Overflow": "Warnung bei Überlauf",
|
||||||
"World/Lore Editor": "Welt-/Lore-Editor",
|
"World/Lore Editor": "Welt-/Lore-Editor",
|
||||||
"--- None ---": "--- Keine ---",
|
"--- None ---": "--- Keine ---",
|
||||||
@@ -917,5 +915,5 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Preajustes de Kobold",
|
"kobldpresets": "Preajustes de Kobold",
|
||||||
"guikoboldaisettings": "Ajustes de interfaz de KoboldAI",
|
"guikoboldaisettings": "Ajustes de interfaz de KoboldAI",
|
||||||
"novelaipreserts": "Preajustes de NovelAI",
|
"novelaipreserts": "Preajustes de NovelAI",
|
||||||
"default": "Predeterminado",
|
|
||||||
"openaipresets": "Preajustes de OpenAI",
|
"openaipresets": "Preajustes de OpenAI",
|
||||||
"text gen webio(ooba) presets": "Preajustes de Text Gen WebUI(ooba)",
|
"text gen webio(ooba) presets": "Preajustes de Text Gen WebUI(ooba)",
|
||||||
"response legth(tokens)": "Longitud de respuesta (tokens)",
|
"response legth(tokens)": "Longitud de respuesta (tokens)",
|
||||||
@@ -494,7 +493,6 @@
|
|||||||
"Global Lore First": "Historia Global Primero",
|
"Global Lore First": "Historia Global Primero",
|
||||||
"Recursive Scan": "Escaneo Recursiva",
|
"Recursive Scan": "Escaneo Recursiva",
|
||||||
"Case Sensitive": "Sensible a mayúsculas y minúsculas",
|
"Case Sensitive": "Sensible a mayúsculas y minúsculas",
|
||||||
"Match whole words": "Coincidir palabras completas",
|
|
||||||
"Alert On Overflow": "Alerta en Desbordamiento",
|
"Alert On Overflow": "Alerta en Desbordamiento",
|
||||||
"World/Lore Editor": "Editor de Mundo/Historia",
|
"World/Lore Editor": "Editor de Mundo/Historia",
|
||||||
"--- None ---": "--- Ninguno ---",
|
"--- None ---": "--- Ninguno ---",
|
||||||
@@ -891,6 +889,7 @@
|
|||||||
"Chat API": " API de chat",
|
"Chat API": " API de chat",
|
||||||
"and pick a character": "y elige un personaje",
|
"and pick a character": "y elige un personaje",
|
||||||
"in the chat bar": "en la barra de chat",
|
"in the chat bar": "en la barra de chat",
|
||||||
|
"You can browse a list of bundled characters in the Download Extensions & Assets menu within": "Puedes explorar una lista de personajes incluidos en el menú de Download Extensions & Assets dentro de ",
|
||||||
"Confused or lost?": "¿Confundido o perdido?",
|
"Confused or lost?": "¿Confundido o perdido?",
|
||||||
"click these icons!": "¡Haz clic en estos iconos!",
|
"click these icons!": "¡Haz clic en estos iconos!",
|
||||||
"SillyTavern Documentation Site": "Sitio de documentación de SillyTavern",
|
"SillyTavern Documentation Site": "Sitio de documentación de SillyTavern",
|
||||||
@@ -914,7 +913,4 @@
|
|||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Usa el tokenizador apropiado para los modelos de Google a través de su API. Procesamiento de indicaciones más lento, pero ofrece un recuento de tokens mucho más preciso.",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Usa el tokenizador apropiado para los modelos de Google a través de su API. Procesamiento de indicaciones más lento, pero ofrece un recuento de tokens mucho más preciso.",
|
||||||
"Load koboldcpp order": "Cargar orden de koboldcpp",
|
"Load koboldcpp order": "Cargar orden de koboldcpp",
|
||||||
"Use Google Tokenizer": "Usar Tokenizador de Google"
|
"Use Google Tokenizer": "Usar Tokenizador de Google"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Préréglages de Kobold",
|
"kobldpresets": "Préréglages de Kobold",
|
||||||
"guikoboldaisettings": "Paramètres de l'interface utilisateur de KoboldAI",
|
"guikoboldaisettings": "Paramètres de l'interface utilisateur de KoboldAI",
|
||||||
"novelaipreserts": "Préréglages de NovelAI",
|
"novelaipreserts": "Préréglages de NovelAI",
|
||||||
"default": "Par défaut",
|
|
||||||
"openaipresets": "Préréglages d'OpenAI",
|
"openaipresets": "Préréglages d'OpenAI",
|
||||||
"text gen webio(ooba) presets": "Préréglages de WebUI(ooba)",
|
"text gen webio(ooba) presets": "Préréglages de WebUI(ooba)",
|
||||||
"response legth(tokens)": "Longueur de la réponse (en tokens)",
|
"response legth(tokens)": "Longueur de la réponse (en tokens)",
|
||||||
@@ -205,7 +204,7 @@
|
|||||||
"Scale API Key": "Clé API Scale",
|
"Scale API Key": "Clé API Scale",
|
||||||
"Alt Method": "Méthode alternative",
|
"Alt Method": "Méthode alternative",
|
||||||
"AI21 API Key": "Clé API AI21",
|
"AI21 API Key": "Clé API AI21",
|
||||||
"AI21 Model": "Modèle AI21",
|
"AI21 Model": "Modèle AI21",
|
||||||
"View API Usage Metrics": "Afficher les mesures d'utilisation de l'API",
|
"View API Usage Metrics": "Afficher les mesures d'utilisation de l'API",
|
||||||
"Show External models (provided by API)": "Afficher les modèles externes (fournis par l'API)",
|
"Show External models (provided by API)": "Afficher les modèles externes (fournis par l'API)",
|
||||||
"Bot": "Bot",
|
"Bot": "Bot",
|
||||||
@@ -494,7 +493,6 @@
|
|||||||
"Global Lore First": "Lore global d'abord",
|
"Global Lore First": "Lore global d'abord",
|
||||||
"Recursive Scan": "Analyse récursive",
|
"Recursive Scan": "Analyse récursive",
|
||||||
"Case Sensitive": "Sensible à la casse",
|
"Case Sensitive": "Sensible à la casse",
|
||||||
"Match whole words": "Correspondre aux mots entiers",
|
|
||||||
"Alert On Overflow": "Alerte en cas de dépassement",
|
"Alert On Overflow": "Alerte en cas de dépassement",
|
||||||
"World/Lore Editor": "Éditeur de monde/lore",
|
"World/Lore Editor": "Éditeur de monde/lore",
|
||||||
"--- None ---": "--- Aucun ---",
|
"--- None ---": "--- Aucun ---",
|
||||||
@@ -914,5 +912,5 @@
|
|||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Utilisez le tokenizer approprié pour les modèles Google via leur API. Traitement des invitations plus lent, mais offre un décompte de jetons beaucoup plus précis.",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Utilisez le tokenizer approprié pour les modèles Google via leur API. Traitement des invitations plus lent, mais offre un décompte de jetons beaucoup plus précis.",
|
||||||
"Load koboldcpp order": "Charger l'ordre koboldcpp",
|
"Load koboldcpp order": "Charger l'ordre koboldcpp",
|
||||||
"Use Google Tokenizer": "Utiliser le tokenizer Google"
|
"Use Google Tokenizer": "Utiliser le tokenizer Google"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Fyrir stillingar Kobold",
|
"kobldpresets": "Fyrir stillingar Kobold",
|
||||||
"guikoboldaisettings": "Stillingar fyrir KoboldAI viðmót",
|
"guikoboldaisettings": "Stillingar fyrir KoboldAI viðmót",
|
||||||
"novelaipreserts": "Fyrir stillingar NovelAI",
|
"novelaipreserts": "Fyrir stillingar NovelAI",
|
||||||
"default": "Sjálfgefið",
|
|
||||||
"openaipresets": "Fyrir stillingar OpenAI",
|
"openaipresets": "Fyrir stillingar OpenAI",
|
||||||
"text gen webio(ooba) presets": "Fyrir stillingar WebUI(ooba) textagerðar",
|
"text gen webio(ooba) presets": "Fyrir stillingar WebUI(ooba) textagerðar",
|
||||||
"response legth(tokens)": "Lengd svars (í táknum eða stöfum)",
|
"response legth(tokens)": "Lengd svars (í táknum eða stöfum)",
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
"Temperature": "Hitastig",
|
"Temperature": "Hitastig",
|
||||||
"Frequency Penalty": "Tíðnarefning",
|
"Frequency Penalty": "Tíðnarefning",
|
||||||
"Presence Penalty": "Tilkoma refning",
|
"Presence Penalty": "Tilkoma refning",
|
||||||
"Top-p": "Topp-p",
|
"Top-p": "Topp-p",
|
||||||
"Display bot response text chunks as they are generated": "Birta bætir svarborðstextabrot þegar þau eru búnar til",
|
"Display bot response text chunks as they are generated": "Birta bætir svarborðstextabrot þegar þau eru búnar til",
|
||||||
"Top A": "Topp A",
|
"Top A": "Topp A",
|
||||||
"Typical Sampling": "Venjuleg úrtaka",
|
"Typical Sampling": "Venjuleg úrtaka",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "Fyrst heimsfræði",
|
"Global Lore First": "Fyrst heimsfræði",
|
||||||
"Recursive Scan": "Endurkvæm skoðun",
|
"Recursive Scan": "Endurkvæm skoðun",
|
||||||
"Case Sensitive": "Skilgreiningarfræðilegt",
|
"Case Sensitive": "Skilgreiningarfræðilegt",
|
||||||
"Match whole words": "Nákvæm samræmi",
|
|
||||||
"Alert On Overflow": "Viðvörun um flæði",
|
"Alert On Overflow": "Viðvörun um flæði",
|
||||||
"World/Lore Editor": "Heims-/fræðiritari",
|
"World/Lore Editor": "Heims-/fræðiritari",
|
||||||
"--- None ---": "--- Engin ---",
|
"--- None ---": "--- Engin ---",
|
||||||
@@ -915,5 +913,5 @@
|
|||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Notaðu rétta tokenizer fyrir Google módel með þeirra API. Hægri umhvörf fyrir hvöttavinnslu, en býður upp á miklu nákvæmari talningu á táknunum.",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Notaðu rétta tokenizer fyrir Google módel með þeirra API. Hægri umhvörf fyrir hvöttavinnslu, en býður upp á miklu nákvæmari talningu á táknunum.",
|
||||||
"Load koboldcpp order": "Hlaðið inn færslu af koboldcpp",
|
"Load koboldcpp order": "Hlaðið inn færslu af koboldcpp",
|
||||||
"Use Google Tokenizer": "Notaðu Google Tokenizer"
|
"Use Google Tokenizer": "Notaðu Google Tokenizer"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Preimpostazioni Kobold",
|
"kobldpresets": "Preimpostazioni Kobold",
|
||||||
"guikoboldaisettings": "Impostazioni dell'interfaccia KoboldAI",
|
"guikoboldaisettings": "Impostazioni dell'interfaccia KoboldAI",
|
||||||
"novelaipreserts": "Preimpostazioni NovelAI",
|
"novelaipreserts": "Preimpostazioni NovelAI",
|
||||||
"default": "Predefinito",
|
|
||||||
"openaipresets": "Preimpostazioni OpenAI",
|
"openaipresets": "Preimpostazioni OpenAI",
|
||||||
"text gen webio(ooba) presets": "Preimpostazioni WebUI(ooba) per la generazione di testo",
|
"text gen webio(ooba) presets": "Preimpostazioni WebUI(ooba) per la generazione di testo",
|
||||||
"response legth(tokens)": "Lunghezza della risposta (token)",
|
"response legth(tokens)": "Lunghezza della risposta (token)",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "Lore Globale Prima",
|
"Global Lore First": "Lore Globale Prima",
|
||||||
"Recursive Scan": "Scansione Ricorsiva",
|
"Recursive Scan": "Scansione Ricorsiva",
|
||||||
"Case Sensitive": "Sensibile alle Maiuscole",
|
"Case Sensitive": "Sensibile alle Maiuscole",
|
||||||
"Match whole words": "Corrispondi a parole intere",
|
|
||||||
"Alert On Overflow": "Avviso Su Overflow",
|
"Alert On Overflow": "Avviso Su Overflow",
|
||||||
"World/Lore Editor": "Editor di Mondo/Lore",
|
"World/Lore Editor": "Editor di Mondo/Lore",
|
||||||
"--- None ---": "--- Nessuno ---",
|
"--- None ---": "--- Nessuno ---",
|
||||||
@@ -917,5 +915,5 @@
|
|||||||
"Use Google Tokenizer": "Usa il Tokenizer di Google"
|
"Use Google Tokenizer": "Usa il Tokenizer di Google"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Koboldのプリセット",
|
"kobldpresets": "Koboldのプリセット",
|
||||||
"guikoboldaisettings": "KoboldAIのGUI設定",
|
"guikoboldaisettings": "KoboldAIのGUI設定",
|
||||||
"novelaipreserts": "NovelAIのプリセット",
|
"novelaipreserts": "NovelAIのプリセット",
|
||||||
"default": "デフォルト",
|
|
||||||
"openaipresets": "OpenAIのプリセット",
|
"openaipresets": "OpenAIのプリセット",
|
||||||
"text gen webio(ooba) presets": "WebUI(ooba)のプリセット",
|
"text gen webio(ooba) presets": "WebUI(ooba)のプリセット",
|
||||||
"response legth(tokens)": "応答の長さ(トークン数)",
|
"response legth(tokens)": "応答の長さ(トークン数)",
|
||||||
@@ -140,7 +139,7 @@
|
|||||||
"Influences bot behavior in its responses": "返信でボットの動作に影響を与えます",
|
"Influences bot behavior in its responses": "返信でボットの動作に影響を与えます",
|
||||||
"Connect": "接続",
|
"Connect": "接続",
|
||||||
"Test Message": "テストメッセージ",
|
"Test Message": "テストメッセージ",
|
||||||
"API": "API",
|
"API": "API",
|
||||||
"KoboldAI": "KoboldAI",
|
"KoboldAI": "KoboldAI",
|
||||||
"Use Horde": "ホードを使用",
|
"Use Horde": "ホードを使用",
|
||||||
"API url": "API URL",
|
"API url": "API URL",
|
||||||
@@ -494,7 +493,6 @@
|
|||||||
"Global Lore First": "グローバルロアを最初に表示",
|
"Global Lore First": "グローバルロアを最初に表示",
|
||||||
"Recursive Scan": "再帰的スキャン",
|
"Recursive Scan": "再帰的スキャン",
|
||||||
"Case Sensitive": "大文字と小文字を区別する",
|
"Case Sensitive": "大文字と小文字を区別する",
|
||||||
"Match whole words": "完全な単語の一致",
|
|
||||||
"Alert On Overflow": "オーバーフロー時に警告",
|
"Alert On Overflow": "オーバーフロー時に警告",
|
||||||
"World/Lore Editor": "ワールド/ロアの編集",
|
"World/Lore Editor": "ワールド/ロアの編集",
|
||||||
"--- None ---": "--- なし ---",
|
"--- None ---": "--- なし ---",
|
||||||
@@ -914,5 +912,5 @@
|
|||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Googleモデル用の適切なトークナイザーを使用します。 API経由で。 処理が遅くなりますが、トークンの数え上げがはるかに正確になります。",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Googleモデル用の適切なトークナイザーを使用します。 API経由で。 処理が遅くなりますが、トークンの数え上げがはるかに正確になります。",
|
||||||
"Load koboldcpp order": "koboldcppオーダーを読み込む",
|
"Load koboldcpp order": "koboldcppオーダーを読み込む",
|
||||||
"Use Google Tokenizer": "Googleトークナイザーを使用"
|
"Use Google Tokenizer": "Googleトークナイザーを使用"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "코볼드 사전 설정",
|
"kobldpresets": "코볼드 사전 설정",
|
||||||
"guikoboldaisettings": "KoboldAI 인터페이스 설정",
|
"guikoboldaisettings": "KoboldAI 인터페이스 설정",
|
||||||
"novelaipreserts": "NovelAI 사전 설정",
|
"novelaipreserts": "NovelAI 사전 설정",
|
||||||
"default": "기본값",
|
|
||||||
"openaipresets": "OpenAI 사전 설정",
|
"openaipresets": "OpenAI 사전 설정",
|
||||||
"text gen webio(ooba) presets": "텍스트 생성 WebUI(ooba) 사전 설정",
|
"text gen webio(ooba) presets": "텍스트 생성 WebUI(ooba) 사전 설정",
|
||||||
"response legth(tokens)": "응답 길이 (토큰)",
|
"response legth(tokens)": "응답 길이 (토큰)",
|
||||||
@@ -425,7 +424,7 @@
|
|||||||
"Start new chat": "새로운 채팅 시작",
|
"Start new chat": "새로운 채팅 시작",
|
||||||
"View past chats": "과거 채팅 보기",
|
"View past chats": "과거 채팅 보기",
|
||||||
"Delete messages": "메시지 삭제",
|
"Delete messages": "메시지 삭제",
|
||||||
"Impersonate": "사칭",
|
"Impersonate": "대신 말하기",
|
||||||
"Regenerate": "재생성",
|
"Regenerate": "재생성",
|
||||||
"PNG": "PNG",
|
"PNG": "PNG",
|
||||||
"JSON": "JSON",
|
"JSON": "JSON",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "글로벌 로어 우선",
|
"Global Lore First": "글로벌 로어 우선",
|
||||||
"Recursive Scan": "재귀 스캔",
|
"Recursive Scan": "재귀 스캔",
|
||||||
"Case Sensitive": "대소문자 구분",
|
"Case Sensitive": "대소문자 구분",
|
||||||
"Match whole words": "전체 단어 일치",
|
|
||||||
"Alert On Overflow": "오버플로우 알림",
|
"Alert On Overflow": "오버플로우 알림",
|
||||||
"World/Lore Editor": "월드/로어 편집기",
|
"World/Lore Editor": "월드/로어 편집기",
|
||||||
"--- None ---": "--- 없음 ---",
|
"--- None ---": "--- 없음 ---",
|
||||||
@@ -914,7 +912,30 @@
|
|||||||
"Learn how to contribute your idle GPU cycles to the Horde": "여유로운 GPU 주기를 호드에 기여하는 방법 배우기",
|
"Learn how to contribute your idle GPU cycles to the Horde": "여유로운 GPU 주기를 호드에 기여하는 방법 배우기",
|
||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Google 모델용 적절한 토크나이저를 사용하여 API를 통해 제공됩니다. 더 느린 프롬프트 처리지만 훨씬 정확한 토큰 계산을 제공합니다.",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Google 모델용 적절한 토크나이저를 사용하여 API를 통해 제공됩니다. 더 느린 프롬프트 처리지만 훨씬 정확한 토큰 계산을 제공합니다.",
|
||||||
"Load koboldcpp order": "코볼드 CPP 순서로 로드",
|
"Load koboldcpp order": "코볼드 CPP 순서로 로드",
|
||||||
"Use Google Tokenizer": "Google 토크나이저 사용"
|
"Use Google Tokenizer": "구글 토크나이저 사용",
|
||||||
|
"Hide Chat Avatars": "채팅 아바타 숨기기",
|
||||||
|
"Hide avatars in chat messages.": "채팅 메시지에서 아바타 숨김.",
|
||||||
|
"Avatar Hover Magnification": "아바타 마우스오버 시 확대",
|
||||||
|
"Enable magnification for zoomed avatar display.": "마우스 오버 시 아바타가 커지도록 설정하세요.",
|
||||||
|
"AutoComplete Settings": "자동 완성 설정",
|
||||||
|
"Autocomplete Matching": "자동 완성 매칭",
|
||||||
|
"Starts with": "시작하는 단어로",
|
||||||
|
"Autocomplete Style": "자동 완성 스타일",
|
||||||
|
"Includes": "포함하는",
|
||||||
|
"Fuzzy": "퍼지 매칭",
|
||||||
|
"Follow Theme": "테마 적용",
|
||||||
|
"Dark": "다크 모드",
|
||||||
|
"Sets the font size of the autocomplete.": "자동 완성 글꼴 크기 설정",
|
||||||
|
"Autocomplete Width": "자동 완성 너비 조절",
|
||||||
|
"Parser Flags": "파서 플래그 설정",
|
||||||
|
"Sets default flags for the STscript parser.": "STscript 파서 기본 플래그 설정",
|
||||||
|
"Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well.": "모든 구분자를 백슬래시로 이스케이핑하고, 백슬래시 자체도 이스케이프할 수 있도록 엄격한 방식으로 전환합니다.",
|
||||||
|
"STscript Settings": "STscript 설정",
|
||||||
|
"Smooth Streaming": "부드러운 스트리밍",
|
||||||
|
"Experimental feature. May not work for all backends.": "실험적인 기능으로, 모든 백엔드에서 작동이 보장되지는 않을 수 있습니다.",
|
||||||
|
"Char List Subheader": "문자 목록 하위 제목",
|
||||||
|
"Account": "계정",
|
||||||
|
"Theme Colors": "테마 색상",
|
||||||
|
"# Messages to Load": "로딩할 메시지 수"
|
||||||
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
[
|
[
|
||||||
{ "lang": "ar-sa", "display": "عربي (Arabic)" },
|
{ "lang": "ar-sa", "display": "عربي (Arabic)" },
|
||||||
{ "lang": "zh-cn", "display": "简体中文 (Chinese) (Simplified)" },
|
{ "lang": "zh-cn", "display": "简体中文 (Chinese) (Simplified)" },
|
||||||
|
{ "lang": "zh-tw", "display": "繁體中文 (Chinese) (Taiwan)" },
|
||||||
{ "lang": "nl-nl", "display": "Nederlands (Dutch)" },
|
{ "lang": "nl-nl", "display": "Nederlands (Dutch)" },
|
||||||
{ "lang": "de-de", "display": "Deutsch (German)" },
|
{ "lang": "de-de", "display": "Deutsch (German)" },
|
||||||
{ "lang": "fr-fr", "display": "Français (French)" },
|
{ "lang": "fr-fr", "display": "Français (French)" },
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Kobold voorinstellingen",
|
"kobldpresets": "Kobold voorinstellingen",
|
||||||
"guikoboldaisettings": "KoboldAI-interface-instellingen",
|
"guikoboldaisettings": "KoboldAI-interface-instellingen",
|
||||||
"novelaipreserts": "NovelAI-voorinstellingen",
|
"novelaipreserts": "NovelAI-voorinstellingen",
|
||||||
"default": "Standaard",
|
|
||||||
"openaipresets": "OpenAI-voorinstellingen",
|
"openaipresets": "OpenAI-voorinstellingen",
|
||||||
"text gen webio(ooba) presets": "WebUI(ooba)-voorinstellingen voor tekstgeneratie",
|
"text gen webio(ooba) presets": "WebUI(ooba)-voorinstellingen voor tekstgeneratie",
|
||||||
"response legth(tokens)": "Reactielengte (tokens)",
|
"response legth(tokens)": "Reactielengte (tokens)",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "Globale Lore Eerst",
|
"Global Lore First": "Globale Lore Eerst",
|
||||||
"Recursive Scan": "Recursieve Scan",
|
"Recursive Scan": "Recursieve Scan",
|
||||||
"Case Sensitive": "Hoofdlettergevoelig",
|
"Case Sensitive": "Hoofdlettergevoelig",
|
||||||
"Match whole words": "Hele woorden matchen",
|
|
||||||
"Alert On Overflow": "Waarschuwing bij overloop",
|
"Alert On Overflow": "Waarschuwing bij overloop",
|
||||||
"World/Lore Editor": "Wereld/Lore Editor",
|
"World/Lore Editor": "Wereld/Lore Editor",
|
||||||
"--- None ---": "--- Geen ---",
|
"--- None ---": "--- Geen ---",
|
||||||
@@ -917,5 +915,5 @@
|
|||||||
"Use Google Tokenizer": "Google Tokenizer gebruiken"
|
"Use Google Tokenizer": "Google Tokenizer gebruiken"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Configurações predefinidas do Kobold",
|
"kobldpresets": "Configurações predefinidas do Kobold",
|
||||||
"guikoboldaisettings": "Configurações da interface do KoboldAI",
|
"guikoboldaisettings": "Configurações da interface do KoboldAI",
|
||||||
"novelaipreserts": "Configurações predefinidas do NovelAI",
|
"novelaipreserts": "Configurações predefinidas do NovelAI",
|
||||||
"default": "Padrão",
|
|
||||||
"openaipresets": "Configurações predefinidas do OpenAI",
|
"openaipresets": "Configurações predefinidas do OpenAI",
|
||||||
"text gen webio(ooba) presets": "Configurações predefinidas do WebUI(ooba) para geração de texto",
|
"text gen webio(ooba) presets": "Configurações predefinidas do WebUI(ooba) para geração de texto",
|
||||||
"response legth(tokens)": "Comprimento da resposta (tokens)",
|
"response legth(tokens)": "Comprimento da resposta (tokens)",
|
||||||
@@ -493,7 +492,6 @@
|
|||||||
"Global Lore First": "Lore Global Primeiro",
|
"Global Lore First": "Lore Global Primeiro",
|
||||||
"Recursive Scan": "Verificação Recursiva",
|
"Recursive Scan": "Verificação Recursiva",
|
||||||
"Case Sensitive": "Sensível a Maiúsculas",
|
"Case Sensitive": "Sensível a Maiúsculas",
|
||||||
"Match whole words": "Corresponder palavras inteiras",
|
|
||||||
"Alert On Overflow": "Alerta em Overflow",
|
"Alert On Overflow": "Alerta em Overflow",
|
||||||
"World/Lore Editor": "Editor de Mundo/Lore",
|
"World/Lore Editor": "Editor de Mundo/Lore",
|
||||||
"--- None ---": "--- Nenhum ---",
|
"--- None ---": "--- Nenhum ---",
|
||||||
@@ -915,5 +913,5 @@
|
|||||||
"Use Google Tokenizer": "Usar Tokenizer do Google"
|
"Use Google Tokenizer": "Usar Tokenizer do Google"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Пресеты для Kobold",
|
"kobldpresets": "Пресеты для Kobold",
|
||||||
"guikoboldaisettings": "Настройки из интерфейса KoboldAI",
|
"guikoboldaisettings": "Настройки из интерфейса KoboldAI",
|
||||||
"novelaipreserts": "Пресеты для NovelAI",
|
"novelaipreserts": "Пресеты для NovelAI",
|
||||||
"default": "По умолчанию",
|
|
||||||
"openaipresets": "Пресеты для OpenAI",
|
"openaipresets": "Пресеты для OpenAI",
|
||||||
"text gen webio(ooba) presets": "Пресеты для WebUI(ooba)",
|
"text gen webio(ooba) presets": "Пресеты для WebUI(ooba)",
|
||||||
"response legth(tokens)": "Ответ (в токенах)",
|
"response legth(tokens)": "Ответ (в токенах)",
|
||||||
@@ -38,7 +37,7 @@
|
|||||||
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
|
"LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
|
||||||
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
|
"Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
|
||||||
"Classifier Free Guidance. More helpful tip coming soon": "Classifier Free Guidance. Чуть позже опишем более подробно",
|
"Classifier Free Guidance. More helpful tip coming soon": "Classifier Free Guidance. Чуть позже опишем более подробно",
|
||||||
"Scale": "Масштаб",
|
"Scale": "Scale",
|
||||||
"GBNF Grammar": "Грамматика GBNF",
|
"GBNF Grammar": "Грамматика GBNF",
|
||||||
"Usage Stats": "Статистика исп.",
|
"Usage Stats": "Статистика исп.",
|
||||||
"Click for stats!": "Нажмите для получения статистики!",
|
"Click for stats!": "Нажмите для получения статистики!",
|
||||||
@@ -97,7 +96,7 @@
|
|||||||
"Sequences you don't want to appear in the output. One per line.": "Строки, которых не должно быть в выходном тексте. По одной на строчку.",
|
"Sequences you don't want to appear in the output. One per line.": "Строки, которых не должно быть в выходном тексте. По одной на строчку.",
|
||||||
"AI Module": "Модуль ИИ",
|
"AI Module": "Модуль ИИ",
|
||||||
"Changes the style of the generated text.": "Изменяет стиль создаваемого текста.",
|
"Changes the style of the generated text.": "Изменяет стиль создаваемого текста.",
|
||||||
"Used if CFG Scale is unset globally, per chat or character": "Используется, если масштаб CFG не установлен глобально, для каждого чата или персонажа.",
|
"Used if CFG Scale is unset globally, per chat or character": "Используется, если CFG Scale не установлен глобально, для каждого чата или персонажа.",
|
||||||
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
|
"Inserts jailbreak as a last system message.": "Вставлять JailBreak последним системным сообщением.",
|
||||||
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
|
"This tells the AI to ignore its usual content restrictions.": "Сообщает AI о необходимости игнорировать стандартные ограничения контента.",
|
||||||
"NSFW Encouraged": "Поощрять NSFW",
|
"NSFW Encouraged": "Поощрять NSFW",
|
||||||
@@ -262,7 +261,7 @@
|
|||||||
"Auto-Continue": "Авто-продолжение",
|
"Auto-Continue": "Авто-продолжение",
|
||||||
"Collapse Consecutive Newlines": "Сворачивать последовательные новые строки",
|
"Collapse Consecutive Newlines": "Сворачивать последовательные новые строки",
|
||||||
"Allow for Chat Completion APIs": "Разрешить для API Chat Completion",
|
"Allow for Chat Completion APIs": "Разрешить для API Chat Completion",
|
||||||
"Target length (tokens)": "Целевая длина (токены)",
|
"Target length (tokens)": "Целевая длина (в токенах)",
|
||||||
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в промпте",
|
"Keep Example Messages in Prompt": "Сохранять примеры сообщений в промпте",
|
||||||
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
|
"Remove Empty New Lines from Output": "Удалять пустые строчки из вывода",
|
||||||
"Disabled for all models": "Выключено для всех моделей",
|
"Disabled for all models": "Выключено для всех моделей",
|
||||||
@@ -276,7 +275,7 @@
|
|||||||
"World Info": "Информация о мире",
|
"World Info": "Информация о мире",
|
||||||
"Scan Depth": "Глубина сканирования",
|
"Scan Depth": "Глубина сканирования",
|
||||||
"Case-Sensitive": "С учетом регистра",
|
"Case-Sensitive": "С учетом регистра",
|
||||||
"Match Whole Words": "Только целые слова",
|
"Match Whole Words": "Только полное совпадение",
|
||||||
"Use global setting": "Использовать глобальную настройку",
|
"Use global setting": "Использовать глобальную настройку",
|
||||||
"Yes": "Да",
|
"Yes": "Да",
|
||||||
"No": "Нет",
|
"No": "Нет",
|
||||||
@@ -300,11 +299,11 @@
|
|||||||
"Chat Style": "Стиль чата",
|
"Chat Style": "Стиль чата",
|
||||||
"Default": "По умолчанию",
|
"Default": "По умолчанию",
|
||||||
"Bubbles": "Пузыри",
|
"Bubbles": "Пузыри",
|
||||||
"No Blur Effect": "Отключить эффект размытия",
|
"No Blur Effect": "Отключить размытие",
|
||||||
"No Text Shadows": "Отключить тень от текста",
|
"No Text Shadows": "Отключить тень текста",
|
||||||
"Waifu Mode": "Рeжим Вайфу",
|
"Waifu Mode": "Рeжим Вайфу",
|
||||||
"Message Timer": "Таймер сообщений",
|
"Message Timer": "Таймер сообщений",
|
||||||
"Model Icon": "Показать значки модели",
|
"Model Icon": "Значки моделей",
|
||||||
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
|
"# of messages (0 = disabled)": "# сообщений (0 = отключено)",
|
||||||
"Advanced Character Search": "Расширенный поиск по персонажам",
|
"Advanced Character Search": "Расширенный поиск по персонажам",
|
||||||
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
|
"Allow {{char}}: in bot messages": "Показывать {{char}}: в ответах",
|
||||||
@@ -314,7 +313,7 @@
|
|||||||
"Lorebook Import Dialog": "Показывать окно импорта лорбука",
|
"Lorebook Import Dialog": "Показывать окно импорта лорбука",
|
||||||
"MUI Preset": "Пресет MUI:",
|
"MUI Preset": "Пресет MUI:",
|
||||||
"If set in the advanced character definitions, this field will be displayed in the characters list.": "Если это поле задано в расширенных параметрах персонажа, оно будет отображаться в списке персонажей.",
|
"If set in the advanced character definitions, this field will be displayed in the characters list.": "Если это поле задано в расширенных параметрах персонажа, оно будет отображаться в списке персонажей.",
|
||||||
"Relaxed API URLS": "Смягченные URL-адреса API",
|
"Relaxed API URLS": "Смягчённые адреса API",
|
||||||
"Custom CSS": "Пользовательский CSS",
|
"Custom CSS": "Пользовательский CSS",
|
||||||
"Default (oobabooga)": "По умолчанию (oobabooga)",
|
"Default (oobabooga)": "По умолчанию (oobabooga)",
|
||||||
"Mancer Model": "Модель Mancer",
|
"Mancer Model": "Модель Mancer",
|
||||||
@@ -381,7 +380,7 @@
|
|||||||
"text": "текст",
|
"text": "текст",
|
||||||
"Delete": "Удалить",
|
"Delete": "Удалить",
|
||||||
"Cancel": "Отменить",
|
"Cancel": "Отменить",
|
||||||
"Advanced Defininitions": "Продвинутое описание",
|
"Advanced Defininitions": "Расширенное описание",
|
||||||
"Personality summary": "Сводка по личности",
|
"Personality summary": "Сводка по личности",
|
||||||
"A brief description of the personality": "Краткое описание личности",
|
"A brief description of the personality": "Краткое описание личности",
|
||||||
"Scenario": "Сценарий",
|
"Scenario": "Сценарий",
|
||||||
@@ -431,7 +430,7 @@
|
|||||||
"JSON": "JSON",
|
"JSON": "JSON",
|
||||||
"presets": "Пресеты",
|
"presets": "Пресеты",
|
||||||
"Message Sound": "Звук сообщения",
|
"Message Sound": "Звук сообщения",
|
||||||
"Author's Note": "Пометки автора",
|
"Author's Note": "Заметки автора",
|
||||||
"Send Jailbreak": "Отправлять джейлбрейк",
|
"Send Jailbreak": "Отправлять джейлбрейк",
|
||||||
"Replace empty message": "Заменять пустые сообщения",
|
"Replace empty message": "Заменять пустые сообщения",
|
||||||
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
|
"Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
|
||||||
@@ -475,7 +474,7 @@
|
|||||||
"--- Pick to Edit ---": "--- Выберите для редактирования ---",
|
"--- Pick to Edit ---": "--- Выберите для редактирования ---",
|
||||||
"or": "или",
|
"or": "или",
|
||||||
"New": "Новый",
|
"New": "Новый",
|
||||||
"Priority": "Приритет",
|
"Priority": "Приоритет",
|
||||||
"Custom": "Пользовательский",
|
"Custom": "Пользовательский",
|
||||||
"Title A-Z": "Название от A до Z",
|
"Title A-Z": "Название от A до Z",
|
||||||
"Title Z-A": "Название от Z до A",
|
"Title Z-A": "Название от Z до A",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "Сначала глобальный лор",
|
"Global Lore First": "Сначала глобальный лор",
|
||||||
"Recursive Scan": "Рекурсивное сканирование",
|
"Recursive Scan": "Рекурсивное сканирование",
|
||||||
"Case Sensitive": "Учитывать регистр",
|
"Case Sensitive": "Учитывать регистр",
|
||||||
"Match whole words": "Только полное совпадение",
|
|
||||||
"Alert On Overflow": "Оповещение о переполнении",
|
"Alert On Overflow": "Оповещение о переполнении",
|
||||||
"World/Lore Editor": "Редактировать мир или лор",
|
"World/Lore Editor": "Редактировать мир или лор",
|
||||||
"--- None ---": "--- Отсутствует ---",
|
"--- None ---": "--- Отсутствует ---",
|
||||||
@@ -528,7 +526,7 @@
|
|||||||
"UI Border": "Границы UI",
|
"UI Border": "Границы UI",
|
||||||
"Chat Style:": "Стиль чата",
|
"Chat Style:": "Стиль чата",
|
||||||
"Chat Width (PC)": "Ширина чата (для ПК)",
|
"Chat Width (PC)": "Ширина чата (для ПК)",
|
||||||
"Chat Timestamps": "Временные метки в чате",
|
"Chat Timestamps": "Метки времени в чате",
|
||||||
"Tags as Folders": "Теги как папки",
|
"Tags as Folders": "Теги как папки",
|
||||||
"Chat Truncation": "Усечение чата",
|
"Chat Truncation": "Усечение чата",
|
||||||
"(0 = unlimited)": "(0 = неограниченное)",
|
"(0 = unlimited)": "(0 = неограниченное)",
|
||||||
@@ -559,8 +557,8 @@
|
|||||||
"Disables animations and transitions": "Отключение анимаций и переходов.",
|
"Disables animations and transitions": "Отключение анимаций и переходов.",
|
||||||
"removes blur from window backgrounds": "Убрать размытие с фона окон, чтобы ускорить рендеринг.",
|
"removes blur from window backgrounds": "Убрать размытие с фона окон, чтобы ускорить рендеринг.",
|
||||||
"Remove text shadow effect": "Удаление эффекта тени от текста.",
|
"Remove text shadow effect": "Удаление эффекта тени от текста.",
|
||||||
"Reduce chat height, and put a static sprite behind the chat window": "Уменьшитm высоту чата и поместить статичный спрайт за окном чата.",
|
"Reduce chat height, and put a static sprite behind the chat window": "Уменьшить высоту чата и поместить статичный спрайт за окном чата.",
|
||||||
"Always show the full list of the Message Actions context items for chat messages, instead of hiding them behind '...'": "Всегда показывать полный список контекстных элементов 'Действия с сообщением' для сообщений чата, а не прятать их за '...'.",
|
"Always show the full list of the Message Actions context items for chat messages, instead of hiding them behind '...'": "Всегда показывать полный список действий с сообщением, а не прятать их за '...'.",
|
||||||
"Alternative UI for numeric sampling parameters with fewer steps": "Альтернативный пользовательский интерфейс для числовых параметров выборки с меньшим количеством шагов.",
|
"Alternative UI for numeric sampling parameters with fewer steps": "Альтернативный пользовательский интерфейс для числовых параметров выборки с меньшим количеством шагов.",
|
||||||
"Entirely unrestrict all numeric sampling parameters": "Полностью разграничить все числовые параметры выборки.",
|
"Entirely unrestrict all numeric sampling parameters": "Полностью разграничить все числовые параметры выборки.",
|
||||||
"Time the AI's message generation, and show the duration in the chat log": "Время генерации сообщений ИИ и его показ в журнале чата.",
|
"Time the AI's message generation, and show the duration in the chat log": "Время генерации сообщений ИИ и его показ в журнале чата.",
|
||||||
@@ -600,7 +598,7 @@
|
|||||||
"Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Включить авто-свайп. Настройки в этом разделе действуют только при включенном авто-свайпе.",
|
"Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Включить авто-свайп. Настройки в этом разделе действуют только при включенном авто-свайпе.",
|
||||||
"If the generated message is shorter than this, trigger an auto-swipe": "Если сгенерированное сообщение короче этого значения, срабатывает авто-свайп.",
|
"If the generated message is shorter than this, trigger an auto-swipe": "Если сгенерированное сообщение короче этого значения, срабатывает авто-свайп.",
|
||||||
"Reload and redraw the currently open chat": "Перезагрузить и перерисовать открытый в данный момент чат.",
|
"Reload and redraw the currently open chat": "Перезагрузить и перерисовать открытый в данный момент чат.",
|
||||||
"Auto-Expand Message Actions": "Развернуть контекстные элементы",
|
"Auto-Expand Message Actions": "Развернуть действия",
|
||||||
"Not Connected": "Не подключено",
|
"Not Connected": "Не подключено",
|
||||||
"Persona Management": "Управление персоной",
|
"Persona Management": "Управление персоной",
|
||||||
"Persona Description": "Описание персоны",
|
"Persona Description": "Описание персоны",
|
||||||
@@ -629,16 +627,15 @@
|
|||||||
"Most chats": "Больше всего чатов",
|
"Most chats": "Больше всего чатов",
|
||||||
"Least chats": "Меньше всего чатов",
|
"Least chats": "Меньше всего чатов",
|
||||||
"Back": "Назад",
|
"Back": "Назад",
|
||||||
"Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Перезапись промпта (Для OpenAI/Claude/Scale API, Window/OpenRouter, и режима Instruct)",
|
"Prompt Overrides": "Индивидуальный промпт",
|
||||||
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Введите {{original}} в любое поле, чтобы использовать соответствующий промпт из системных настроек",
|
"(For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct Mode)": "(для API OpenAI/Claude/Scale, Window/OpenRouter, а также режима Instruct)",
|
||||||
|
"Insert {{original}} into either box to include the respective default prompt from system settings.": "Введите {{original}} в любое поле, чтобы вставить соответствующий промпт из системных настроек",
|
||||||
"Main Prompt": "Основной промпт",
|
"Main Prompt": "Основной промпт",
|
||||||
"Jailbreak": "Джейлбрейк",
|
"Jailbreak": "Джейлбрейк",
|
||||||
"Creator's Metadata (Not sent with the AI prompt)": "Метаданные (не отправляются ИИ)",
|
|
||||||
"Everything here is optional": "Все поля необязательные",
|
"Everything here is optional": "Все поля необязательные",
|
||||||
"Created by": "Автор",
|
"Created by": "Автор",
|
||||||
"Character Version": "Версия персонажа",
|
"Character Version": "Версия персонажа",
|
||||||
"Tags to Embed": "Встраиваемые теги",
|
"Tags to Embed": "Встраиваемые теги",
|
||||||
"How often the character speaks in group chats!": "Как часто персонаж говорит в групповых чатах",
|
|
||||||
"Important to set the character's writing style.": "Серьёзно влияет на стиль письма персонажа.",
|
"Important to set the character's writing style.": "Серьёзно влияет на стиль письма персонажа.",
|
||||||
"ATTENTION!": "ВНИМАНИЕ!",
|
"ATTENTION!": "ВНИМАНИЕ!",
|
||||||
"Samplers Order": "Порядок сэмплеров",
|
"Samplers Order": "Порядок сэмплеров",
|
||||||
@@ -655,7 +652,7 @@
|
|||||||
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Неограниченный контекст' для активации кусочной генерации",
|
"Use 'Unlocked Context' to enable chunked generation.": "Использовать 'Неограниченный контекст' для активации кусочной генерации",
|
||||||
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
|
"It extends the context window in exchange for reply generation speed.": "Увеличивает размер контекста в обмен на скорость генерации.",
|
||||||
"Continue": "Продолжить",
|
"Continue": "Продолжить",
|
||||||
"CFG Scale": "Масштаб CFG",
|
"CFG Scale": "CFG Scale",
|
||||||
"Editing:": "Изменения",
|
"Editing:": "Изменения",
|
||||||
"AI reply prefix": "Префикс для ответа ИИ",
|
"AI reply prefix": "Префикс для ответа ИИ",
|
||||||
"Custom Stopping Strings": "Стоп-строки",
|
"Custom Stopping Strings": "Стоп-строки",
|
||||||
@@ -671,9 +668,9 @@
|
|||||||
"Chat Name (Optional)": "Название чата (необязательно)",
|
"Chat Name (Optional)": "Название чата (необязательно)",
|
||||||
"Filter...": "Фильтры...",
|
"Filter...": "Фильтры...",
|
||||||
"Search...": "Поиск...",
|
"Search...": "Поиск...",
|
||||||
"Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержание этой ячейки будет заменять стандартный Промт",
|
"Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Все содержимое этого поля будет заменять стандартный промпт",
|
||||||
"Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержание этой ячейки будет заменять стандартный Джейлбрейк",
|
"Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Все содержимое этого поля будет заменять стандартный джейлбрейк",
|
||||||
"(Botmaker's name / Contact Info)": "(Имя автора / Контакты)",
|
"(Botmaker's name / Contact Info)": "(Имя автора, контакты)",
|
||||||
"(If you want to track character versions)": "Если вы хотите отслеживать версии персонажа",
|
"(If you want to track character versions)": "Если вы хотите отслеживать версии персонажа",
|
||||||
"(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "(Описание персонажа, советы по использованию, список моделей, на которых он тестировался. Информация будет отображаться в списке персонажей)",
|
"(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "(Описание персонажа, советы по использованию, список моделей, на которых он тестировался. Информация будет отображаться в списке персонажей)",
|
||||||
"(Write a comma-separated list of tags)": "(Список тегов через запятую)",
|
"(Write a comma-separated list of tags)": "(Список тегов через запятую)",
|
||||||
@@ -713,12 +710,12 @@
|
|||||||
"Restore defaul note": "Восстановить стандартную заметку",
|
"Restore defaul note": "Восстановить стандартную заметку",
|
||||||
"API Connections": "Соединения с API",
|
"API Connections": "Соединения с API",
|
||||||
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.",
|
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.",
|
||||||
"Clear your API key": "Очистите свой ключ от API",
|
"Clear your API key": "Стереть ключ от API",
|
||||||
"Refresh models": "Обновить модели",
|
"Refresh models": "Обновить модели",
|
||||||
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
|
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
|
||||||
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Проверка работоспособности вашего соединения с API. Знайте, что оно будет отправлено от вашего лица.",
|
"Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Проверка работоспособности вашего соединения с API. Знайте, что оно будет отправлено от вашего лица.",
|
||||||
"Create New": "Создать новое",
|
"Create New": "Создать новое",
|
||||||
"Edit": "Изменить",
|
"Edit": "Редактировать",
|
||||||
"Locked = World Editor will stay open": "Закреплено = Редактор мира останется открытым",
|
"Locked = World Editor will stay open": "Закреплено = Редактор мира останется открытым",
|
||||||
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи, если в них содержатся ключевые слова",
|
"Entries can activate other entries by mentioning their keywords": "Записи могут активировать другие записи, если в них содержатся ключевые слова",
|
||||||
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
|
"Lookup for the entry keys in the context will respect the case": "Большая буква имеет значение при активации ключевого слова",
|
||||||
@@ -847,7 +844,7 @@
|
|||||||
"Underlined Text": "Подчёркнутый",
|
"Underlined Text": "Подчёркнутый",
|
||||||
"Token Probabilities": "Вероятности токенов",
|
"Token Probabilities": "Вероятности токенов",
|
||||||
"Close chat": "Закрыть чат",
|
"Close chat": "Закрыть чат",
|
||||||
"Manage chat files": "Управление файлами чата",
|
"Manage chat files": "Управление чатами",
|
||||||
"Import Extension From Git Repo": "Импортировать расширение из Git Repository",
|
"Import Extension From Git Repo": "Импортировать расширение из Git Repository",
|
||||||
"Install extension": "Установить расширение",
|
"Install extension": "Установить расширение",
|
||||||
"Manage extensions": "Управление расширениями",
|
"Manage extensions": "Управление расширениями",
|
||||||
@@ -863,12 +860,12 @@
|
|||||||
"When this is off, responses will be displayed all at once when they are complete.": "Если параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",
|
"When this is off, responses will be displayed all at once when they are complete.": "Если параметр выключен, ответы будут отображаться сразу целиком, и только после полного завершения генерации.",
|
||||||
"Quick Prompts Edit": "Быстрое редактирование промптов",
|
"Quick Prompts Edit": "Быстрое редактирование промптов",
|
||||||
"Enable OpenAI completion streaming": "Включить стриминг OpenAI",
|
"Enable OpenAI completion streaming": "Включить стриминг OpenAI",
|
||||||
"Main": "Главное",
|
"Main": "Основной",
|
||||||
"Utility Prompts": "Служебные промпты",
|
"Utility Prompts": "Служебные промпты",
|
||||||
"Add character names": "Добавить имена персонажей",
|
"Add character names": "Добавить имена персонажей",
|
||||||
"Send names in the message objects. Helps the model to associate messages with characters.": "Отправить имена в объектах сообщений. Помогает модели ассоциировать сообщения с персонажами.",
|
"Send names in the message objects. Helps the model to associate messages with characters.": "Отправить имена в объектах сообщений. Помогает модели ассоциировать сообщения с персонажами.",
|
||||||
"Continue prefill": "Префилл для продолжения",
|
"Continue prefill": "Префилл для продолжения",
|
||||||
"Continue sends the last message as assistant role instead of system message with instruction.": "Продолжение отправляет последнее сообщение в роли ассистента, а не системное сообщение с инструкцией.",
|
"Continue sends the last message as assistant role instead of system message with instruction.": "Продолжение отправляет последнее сообщение в роли ассистента, вместо системного сообщения с инструкцией.",
|
||||||
"Squash system messages": "Склеивать сообщения системыы",
|
"Squash system messages": "Склеивать сообщения системыы",
|
||||||
"Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Объединяет последовательные системные сообщения в одно (за исключением примеров диалогов). Может улучшить согласованность для некоторых моделей.",
|
"Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Объединяет последовательные системные сообщения в одно (за исключением примеров диалогов). Может улучшить согласованность для некоторых моделей.",
|
||||||
"Send inline images": "Отправлять встроенные изображения",
|
"Send inline images": "Отправлять встроенные изображения",
|
||||||
@@ -888,11 +885,11 @@
|
|||||||
"Want to update?": "Хотите обновиться?",
|
"Want to update?": "Хотите обновиться?",
|
||||||
"How to start chatting?": "Как начать общение?",
|
"How to start chatting?": "Как начать общение?",
|
||||||
"Click": "Нажмите",
|
"Click": "Нажмите",
|
||||||
"and select a": " и выберите",
|
"and select a": " и выберите ",
|
||||||
"Chat API": "API чата",
|
"Chat API": "API чата",
|
||||||
"and pick a character": "и выберите персонажа",
|
"and pick a character": "и выберите персонажа",
|
||||||
"in the chat bar": " в поле чата",
|
"in the chat bar": " в поле чата",
|
||||||
"Confused or lost?": "Запутались или потерялись?",
|
"Confused or lost?": "Не можете в чём-то разобраться?",
|
||||||
"click these icons!": "нажмите на эти значки!",
|
"click these icons!": "нажмите на эти значки!",
|
||||||
"SillyTavern Documentation Site": "Сайт документации SillyTavern",
|
"SillyTavern Documentation Site": "Сайт документации SillyTavern",
|
||||||
"Extras Installation Guide": "Руководство по установке Extras",
|
"Extras Installation Guide": "Руководство по установке Extras",
|
||||||
@@ -973,12 +970,209 @@
|
|||||||
"Most tokens have a leading space.": "У большинства токенов в начале пробел.",
|
"Most tokens have a leading space.": "У большинства токенов в начале пробел.",
|
||||||
"Prompts": "Промпты",
|
"Prompts": "Промпты",
|
||||||
"Text or token ids": "Текст или [идентификаторы токенов]",
|
"Text or token ids": "Текст или [идентификаторы токенов]",
|
||||||
"World Info Format Template": "Шаблон форматирования информации о мире",
|
"World Info Format Template": "Шаблон оформления информации о мире",
|
||||||
"Wraps activated World Info entries before inserting into the prompt.": "Дополняет информацию об активном на данный момент мире перед её отправкой в промпт.",
|
"Wraps activated World Info entries before inserting into the prompt.": "Дополняет информацию об активном на данный момент мире перед её отправкой в промпт.",
|
||||||
"Doesn't work? Try adding": "Не работает? Попробуйте добавить в конце",
|
"Doesn't work? Try adding": "Не работает? Попробуйте добавить в конце",
|
||||||
"at the end!": "!",
|
"at the end!": "!",
|
||||||
"Authorize": "Авторизоваться",
|
"Authorize": "Авторизоваться",
|
||||||
"No persona description": "[Нет описания]",
|
"No persona description": "[Нет описания]",
|
||||||
"Not connected to API!": "Нет соединения с API!",
|
"Not connected to API!": "Нет соединения с API!",
|
||||||
"Type a message, or /? for help": "Введите сообщение, или /? для получения справки по командам"
|
"Type a message, or /? for help": "Введите сообщение, или /? для получения справки по командам",
|
||||||
|
"Welcome to SillyTavern!": "Добро пожаловать в SillyTavern!",
|
||||||
|
"Won't be shared with the character card on export.": "Не попадут в карточку персонажа при экспорте.",
|
||||||
|
"Web-search": "Веб-поиск",
|
||||||
|
"Persona Name:": "Имя персоны:",
|
||||||
|
"User first message": "Первое сообщение пользователя",
|
||||||
|
"extension_token_counter": "Токенов:",
|
||||||
|
"Character's Note": "Заметка о персонаже",
|
||||||
|
"(Text to be inserted in-chat @ designated depth and role)": "Этот текст будет вставлен в чат на заданную глубину и с определённой ролью",
|
||||||
|
"@ Depth": "Глубина",
|
||||||
|
"Role": "Роль",
|
||||||
|
"System": "Система",
|
||||||
|
"User": "Пользователь",
|
||||||
|
"Assistant": "Ассистент",
|
||||||
|
"How often the character speaks in": "Как часто персонаж говорит в",
|
||||||
|
"group chats!": "групповых чатах!",
|
||||||
|
"Creator's Metadata": "Метаданные",
|
||||||
|
"(Not sent with the AI Prompt)": "(не отправляются ИИ)",
|
||||||
|
"New Chat": "Новый чат",
|
||||||
|
"Import Chat": "Импорт чата",
|
||||||
|
"Chat Lore": "Лор чата",
|
||||||
|
"Chat Lorebook for": "Лорбук для чата",
|
||||||
|
"A selected World Info will be bound to this chat.": "Выбранный мир будет привязан к этому чату. При генерации ответа ИИ он будет совмещён с записями из глобального лорбука и лорбука персонажа.",
|
||||||
|
"Missing key": "❌ Ключа нет",
|
||||||
|
"Key saved": "✔️ Ключ сохранён",
|
||||||
|
"Use the appropriate tokenizer for Jurassic models, which is more efficient than GPT's.": "Использовать токенайзер для моделей Jurassic, эффективнее GPT-токенайзера",
|
||||||
|
"Use system prompt (Gemini 1.5 pro+ only)": "Использовать системный промпт (только для Gemini 1.5 pro и выше)",
|
||||||
|
"Experimental feature. May not work for all backends.": "Экспериментальная возможность, на некоторых бэкендах может не работать.",
|
||||||
|
"Avatar Hover Magnification": "Зум аватарки по наведению",
|
||||||
|
"Enable magnification for zoomed avatar display.": "Добавляет возможность приближать увеличенную версию аватарки.",
|
||||||
|
"Unique to this chat": "Только для текущего чата",
|
||||||
|
"Checkpoints inherit the Note from their parent, and can be changed individually after that.": "Чекпоинты наследуют заметки от родительского чата, но впоследствие их всегда можно изменить.",
|
||||||
|
"Include in World Info Scanning": "Учитывать при сканировании Информации о мире",
|
||||||
|
"Before Main Prompt / Story String": "Перед основным промптом / строкой истории",
|
||||||
|
"After Main Prompt / Story String": "После основного промпта / строки истории",
|
||||||
|
"In-chat @ Depth": "Встав. на глуб.",
|
||||||
|
"as": "роль:",
|
||||||
|
"Insertion Frequency": "Частота вставки",
|
||||||
|
"(0 = Disable, 1 = Always)": "(0 = никогда, 1 = всегда)",
|
||||||
|
"User inputs until next insertion:": "Ваших сообщений до след. вставки:",
|
||||||
|
"Character Author's Note (Private)": "Заметки автора персонажа (личные)",
|
||||||
|
"Will be automatically added as the author's note for this character. Will be used in groups, but can't be modified when a group chat is open.": "Автоматически применятся к этому персонажу в качестве заметок автора. Будут использоваться в группах, но при активном групповом чате к редактированию недоступны.",
|
||||||
|
"Use character author's note": "Использовать заметки автора персонажа",
|
||||||
|
"Replace Author's Note": "Вместо заметок автора",
|
||||||
|
"Top of Author's Note": "Сверху от заметок автора",
|
||||||
|
"Bottom of Author's Note": "Снизу от заметок автора",
|
||||||
|
"Default Author's Note": "Стандартные заметки автора",
|
||||||
|
"Will be automatically added as the Author's Note for all new chats.": "Будут автоматически добавляться во все новые чаты в качестве Заметок автора",
|
||||||
|
"1 = disabled": "1 = откл.",
|
||||||
|
"write short replies, write replies using past tense": "пиши короткие ответы, пиши в настоящем времени",
|
||||||
|
"Positive Prompt": "Положительный промпт",
|
||||||
|
"Character CFG": "CFG для персонажа",
|
||||||
|
"Will be automatically added as the CFG for this character.": "Автоматически применится к персонажу как его CFG.",
|
||||||
|
"Global CFG": "Глобальный CFG",
|
||||||
|
"Will be used as the default CFG options for every chat unless overridden.": "Будет применяться как стандартный CFG для всех чатов, если не указаны индивидуальные настройки.",
|
||||||
|
"CFG Prompt Cascading": "Совмещение CFG-промптов",
|
||||||
|
"Combine positive/negative prompts from other boxes.": "Комбинировать различные положительные и негативные промпты.",
|
||||||
|
"For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.": "К примеру, если отметить галочки с чатом, персонажем и глобальной настройкой, то все эти негативы соберутся в одну строку, разделённую запятыми.",
|
||||||
|
"Always Include": "Всегда применять",
|
||||||
|
"Chat Negatives": "Негативы от чата",
|
||||||
|
"Character Negatives": "Негативы от персонажа",
|
||||||
|
"Global Negatives": "Глобальные негативы",
|
||||||
|
"Custom Separator:": "Кастомный разделитель:",
|
||||||
|
"Insertion Depth:": "Глубина вставки:",
|
||||||
|
"Chat CFG": "CFG для чата",
|
||||||
|
"Chat backgrounds generated with the": "Здесь будут появляться фоны, сгенерированные расширением",
|
||||||
|
"extension will appear here.": ".",
|
||||||
|
"Prevent further recursion (this entry will not activate others)": "Пресечь дальнейшую рекурсию (эта запись не будет активировать другие)",
|
||||||
|
"Alert if your world info is greater than the allocated budget.": "Оповещать, если ваш мир выходит за выделенный бюджет.",
|
||||||
|
"Convert to Persona": "Преобразовать в персону",
|
||||||
|
"Link to Source": "Ссылка на источник",
|
||||||
|
"Replace / Update": "Заменить / Обновить",
|
||||||
|
"Smoothing Curve": "Кривая сглаживания",
|
||||||
|
"Message Actions": "Действия с сообщением",
|
||||||
|
"SillyTavern is aimed at advanced users.": "SillyTavern рассчитана на продвинутых пользователей.",
|
||||||
|
"If you're new to this, enable the simplified UI mode below.": "Если вы новичок, советуем включить упрощённый UI.",
|
||||||
|
"Enable simple UI mode": "Включить упрощённый UI",
|
||||||
|
"welcome_message_part_1": "Ознакомьтесь с",
|
||||||
|
"welcome_message_part_2": "официальной документацией",
|
||||||
|
"welcome_message_part_3": ".",
|
||||||
|
"welcome_message_part_4": "Введите",
|
||||||
|
"welcome_message_part_5": "в чате, чтобы получить справку по командам и макросам.",
|
||||||
|
"welcome_message_part_6": "Заходите на наш",
|
||||||
|
"Discord server": "Discord-сервер,",
|
||||||
|
"welcome_message_part_7": "там публикуется много разной полезной информации, в том числе анонсы.",
|
||||||
|
"Before you get started, you must select a persona name.": "Для начала вам следует выбрать имя своей персоны.",
|
||||||
|
"welcome_message_part_8": "Его можно будет изменить в любое время через иконку",
|
||||||
|
"welcome_message_part_9": ".",
|
||||||
|
"UI Language:": "Язык интерфейса:",
|
||||||
|
"Ignore EOS Token": "Игнорировать EOS-токен",
|
||||||
|
"Ignore the EOS Token even if it generates.": "Игнорировать EOS-токен, даже если он сгенерировался.",
|
||||||
|
"Hide Muted Member Sprites": "Скрыть спрайты заглушенных участников",
|
||||||
|
"Group generation handling mode": "Генерировать ответы путём...",
|
||||||
|
"Swap character cards": "Подмены карточки персонажа",
|
||||||
|
"Join character cards (exclude muted)": "Совмещения карточек (кроме заглушенных)",
|
||||||
|
"Join character cards (include muted)": "Совмещения карточек (включая заглушенных)",
|
||||||
|
"Click to allow/forbid the use of external media for this group.": "Нажмите, чтобы разрешить/запретить использование внешних медиа в этой группе.",
|
||||||
|
"Scenario Format Template": "Шаблон оформления сценария",
|
||||||
|
"scenario_format_template_part_1": "Используйте",
|
||||||
|
"scenario_format_template_part_2": "чтобы указать, куда именно вставляется основное содержимое.",
|
||||||
|
"Personality Format Template": "Шаблон оформления характера",
|
||||||
|
"Group Nudge Prompt Template": "Шаблон промпта-подсказки для групп",
|
||||||
|
"Sent at the end of the group chat history to force reply from a specific character.": "Добавляется в конец истории сообщений в групповом чате, чтобы запросить ответ от конкретного персонажа.",
|
||||||
|
"Set at the beginning of the chat history to indicate that a new chat is about to start.": "Добавляется в начале истории сообщений в качестве указания на то, что дальше начнётся новый чат.",
|
||||||
|
"New Group Chat": "Новый групповой чат",
|
||||||
|
"Set at the beginning of the chat history to indicate that a new group chat is about to start.": "Добавляется в начале истории сообщений в качестве указания на то, что дальше начнётся новый групповой чат.",
|
||||||
|
"New Example Chat": "Новый образец чата",
|
||||||
|
"Set at the beginning of Dialogue examples to indicate that a new example chat is about to start.": "Добавляется в начале примеров диалогов в качестве указания на то, что дальше начнётся новый чат-пример.",
|
||||||
|
"Continue nudge": "Подсказка для продолжения",
|
||||||
|
"Set at the end of the chat history when the continue button is pressed.": "Добавляется в конец истории чата, когда отправлен запрос на продолжение текущего сообщения.",
|
||||||
|
"Prompts": "Промпты",
|
||||||
|
"Your Persona": "Ваша персона",
|
||||||
|
"Continue Postfix": "Постфикс для продолжения",
|
||||||
|
"Space": "Пробел",
|
||||||
|
"Newline": "Новая строка",
|
||||||
|
"Double Newline": "Две новые строки",
|
||||||
|
"The next chunk of the continued message will be appended using this as a separator.": "Используется в качестве разделителя между уже имеющимся сообщением и его новым отрывком, при генерации продолжения",
|
||||||
|
"Regex Editor": "Редактор рег. выражений",
|
||||||
|
"ext_regex_open_editor": "Открыть редактор",
|
||||||
|
"ext_regex_import_script": "Импорт скрипта",
|
||||||
|
"ext_regex_saved_scripts": "Сохранённые скрипты",
|
||||||
|
"ext_regex_desc": "Regex - это инструмент, позволяющий находить и изменять строки, используя регулярные выражения. Для более подробной информации нажмите ? рядом с заголовком.",
|
||||||
|
"Input": "Поле ввода",
|
||||||
|
"ext_regex_test_input_placeholder": "Введите текст...",
|
||||||
|
"Output": "Результат",
|
||||||
|
"ext_regex_output_placeholder": "Пусто",
|
||||||
|
"Script Name": "Название скрипта",
|
||||||
|
"Find Regex": "Рег. выражение для поиска",
|
||||||
|
"Replace With": "Замена",
|
||||||
|
"ext_regex_replace_string_placeholder": "Чтобы вставить всё вхождение рег. выражения, используйте {{match}}. Чтобы вставить группу символов, используйте $1, $2 и т.д.",
|
||||||
|
"Trim Out": "Усечение",
|
||||||
|
"ext_regex_trim_placeholder": "Удалить перед обработкой ненужные части текста. Каждый элемент с новой строки.",
|
||||||
|
"Slash Commands": "Слэш-команды",
|
||||||
|
"Min Depth": "Мин. глубина",
|
||||||
|
"ext_regex_min_depth_desc": "При форматировании затрагивать только те сообщения, которые находятся как минимум на глубине N. 0 = последнее сообщение, 1 = предпоследнее и т.д. Учитываются только видимые сообщения, т.е. не скрытые и не системные.",
|
||||||
|
"ext_regex_max_depth_desc": "При форматировании затрагивать только те сообщения, которые находятся на глубине не более N. 0 = последнее сообщение, 1 = предпоследнее и т.д. Учитываются только видимые сообщения, т.е. не скрытые и не системные.",
|
||||||
|
"ext_regex_min_depth_placeholder": "Неогранич.",
|
||||||
|
"ext_regex_other_options": "Другие опции",
|
||||||
|
"Only Format Display": "Только визуально",
|
||||||
|
"ext_regex_only_format_prompt_desc": "История чата не изменится, замена будет осуществляться только в промпте (при генерации)",
|
||||||
|
"Only Format Prompt (?)": "Только промпт",
|
||||||
|
"Run On Edit": "Выполнять при редактировании",
|
||||||
|
"Substitute Regex": "Заменить в рег. выражении",
|
||||||
|
"ext_regex_substitute_regex_desc": "Перед выполнением заменять {{макросы}} в рег. выражении",
|
||||||
|
"Test Mode": "Протестировать",
|
||||||
|
"ext_regex_affects": "Затрагивает",
|
||||||
|
"ext_regex_user_input": "Ваши сообщения",
|
||||||
|
"ext_regex_ai_output": "Ответы ИИ",
|
||||||
|
"ext_regex_disable_script": "Отключить скрипт",
|
||||||
|
"ext_regex_enable_script": "Включить скрипт",
|
||||||
|
"ext_regex_edit_script": "Редактировать",
|
||||||
|
"ext_regex_export_script": "Экспортировать",
|
||||||
|
"ext_regex_delete_script": "Удалить",
|
||||||
|
"ext_sum_with": "Для пересказа использовать:",
|
||||||
|
"ext_sum_main_api": "Основное API",
|
||||||
|
"ext_sum_current_summary": "Текущий пересказ:",
|
||||||
|
"ext_sum_restore_previous": "Восстановить предыдущий",
|
||||||
|
"ext_sum_memory_placeholder": "Сгенерированный пересказ будет здесь...",
|
||||||
|
"ext_sum_force_text": "Пересказать сейчас",
|
||||||
|
"ext_sum_force_tip": "Сгенерировать пересказ прямо сейчас.",
|
||||||
|
"Disable automatic summary updates. While paused, the summary remains as-is. You can still force an update by pressing the Summarize now button (which is only available with the Main API).": "Отключить авто-обновление пересказа. Пересказ всё время будет фиксированным. Однако останется возможность принудительно обновить пересказ через кнопку \"Пересказать сейчас\" (доступно только через Основное API)",
|
||||||
|
"ext_sum_pause": "Приостановить",
|
||||||
|
"Omit World Info and Author's Note from text to be summarized. Only has an effect when using the Main API. The Extras API always omits WI/AN.": "Исключать из пересказа Информацию о мире и Заметки автора. Работает только для Основного API. Extras API всегда их исключает.",
|
||||||
|
"ext_sum_no_wi_an": "Без мира и заметок",
|
||||||
|
"ext_sum_settings_tip": "Изменить промпт пересказа, место для инжекта и т.д.",
|
||||||
|
"ext_sum_settings": "Настройки пересказа",
|
||||||
|
"ext_sum_prompt_builder": "Алгоритм формирования промпта",
|
||||||
|
"ext_sum_prompt_builder_1_desc": "Расширение само составит промпт с учётом непересказанных сообщений. Во время генерации чат недоступен.",
|
||||||
|
"ext_sum_prompt_builder_1": "Прямой, блокирующий",
|
||||||
|
"ext_sum_prompt_builder_2_desc": "Расширение само составит промпт с учётом непересказанных сообщений. Во время генерации чат доступен. Может не поддерживаться некоторыми бэкендами.",
|
||||||
|
"ext_sum_prompt_builder_2": "Прямой, неблокирующий",
|
||||||
|
"ext_sum_prompt_builder_3_desc": "Расширение будет использовать стандартные основные настройки промпта, и добавит свой промпт в качестве последнего системного сообщения.",
|
||||||
|
"ext_sum_prompt_builder_3": "Классический, блокирующий",
|
||||||
|
"Summary Prompt": "Промпт для пересказа",
|
||||||
|
"ext_sum_restore_default_prompt_tip": "Восстановить стандартный промпт",
|
||||||
|
"ext_sum_prompt_placeholder": "Этот промпт будет отправлен ИИ при запросе на генерацию пересказа. Макрос {{words}} будет заменён на значение параметра \"Количество слов\".",
|
||||||
|
"ext_sum_target_length_1": "Целевая длина пересказа (слов):",
|
||||||
|
"ext_sum_target_length_2": "",
|
||||||
|
"ext_sum_target_length_3": "",
|
||||||
|
"ext_sum_api_response_length_1": "Длина ответа от API (токенов):",
|
||||||
|
"ext_sum_api_response_length_2": "",
|
||||||
|
"ext_sum_api_response_length_3": " ",
|
||||||
|
"ext_sum_0_default": "по умолчанию = 0",
|
||||||
|
"ext_sum_raw_max_msg": "[Прямое форматирование] Макс. сообщений в запросе",
|
||||||
|
"ext_sum_0_unlimited": "неограничено = 0",
|
||||||
|
"Update frequency": "Частота обновления",
|
||||||
|
"ext_sum_update_every_messages_1": "Интервал обновления (кол-во сообщений):",
|
||||||
|
"ext_sum_update_every_messages_2": "",
|
||||||
|
"ext_sum_pause": "Приостановить",
|
||||||
|
"ext_sum_update_every_words_1": "Интервал обновления (кол-во слов):",
|
||||||
|
"ext_sum_update_every_words_2": "",
|
||||||
|
"ext_sum_0_disable": "для отключения поставьте 0",
|
||||||
|
"ext_sum_auto_adjust_desc": "Попытаться автоматически рассчитать значение интервала, исходя из статистики чата",
|
||||||
|
"ext_sum_both_sliders": "Если оба ползунка отличны от нуля, то оба будут триггерить генерацию пересказа с соответствующей периодичностью.",
|
||||||
|
"ext_sum_injection_template": "Шаблон для инжекта",
|
||||||
|
"ext_sum_memory_template_placeholder": "Макрос {{summary}} будет заменён на содержимое пересказа",
|
||||||
|
"ext_sum_injection_position": "Куда инжектить",
|
||||||
|
"How many messages before the current end of the chat.": "Сколько сообщений от конца чата."
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Налаштування Kobold",
|
"kobldpresets": "Налаштування Kobold",
|
||||||
"guikoboldaisettings": "З інтерфейсу KoboldAI",
|
"guikoboldaisettings": "З інтерфейсу KoboldAI",
|
||||||
"novelaipreserts": "Налаштування NovelAI",
|
"novelaipreserts": "Налаштування NovelAI",
|
||||||
"default": "За замовчуванням",
|
|
||||||
"openaipresets": "Налаштування OpenAI",
|
"openaipresets": "Налаштування OpenAI",
|
||||||
"text gen webio(ooba) presets": "Налаштування Text Completion",
|
"text gen webio(ooba) presets": "Налаштування Text Completion",
|
||||||
"response legth(tokens)": "Відповідь (токени)",
|
"response legth(tokens)": "Відповідь (токени)",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "Глобальна інформація першою",
|
"Global Lore First": "Глобальна інформація першою",
|
||||||
"Recursive Scan": "Рекурсивне сканування",
|
"Recursive Scan": "Рекурсивне сканування",
|
||||||
"Case Sensitive": "Чутливість до регістру",
|
"Case Sensitive": "Чутливість до регістру",
|
||||||
"Match whole words": "Відповідність цілим словам",
|
|
||||||
"Alert On Overflow": "Сповіщення при переповненні",
|
"Alert On Overflow": "Сповіщення при переповненні",
|
||||||
"World/Lore Editor": "Редактор світу/книги",
|
"World/Lore Editor": "Редактор світу/книги",
|
||||||
"--- None ---": "--- Нічого ---",
|
"--- None ---": "--- Нічого ---",
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Cài đặt trước Kobold",
|
"kobldpresets": "Cài đặt trước Kobold",
|
||||||
"guikoboldaisettings": "Cài đặt giao diện KoboldAI",
|
"guikoboldaisettings": "Cài đặt giao diện KoboldAI",
|
||||||
"novelaipreserts": "Cài đặt trước NovelAI",
|
"novelaipreserts": "Cài đặt trước NovelAI",
|
||||||
"default": "Mặc định",
|
|
||||||
"openaipresets": "Cài đặt trước OpenAI",
|
"openaipresets": "Cài đặt trước OpenAI",
|
||||||
"text gen webio(ooba) presets": "Cài đặt trước WebUI(ooba) của máy tạo văn bản",
|
"text gen webio(ooba) presets": "Cài đặt trước WebUI(ooba) của máy tạo văn bản",
|
||||||
"response legth(tokens)": "Độ dài phản hồi (trong các token)",
|
"response legth(tokens)": "Độ dài phản hồi (trong các token)",
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
"Temperature": "Nhiệt độ",
|
"Temperature": "Nhiệt độ",
|
||||||
"Frequency Penalty": "Phạt Tần số",
|
"Frequency Penalty": "Phạt Tần số",
|
||||||
"Presence Penalty": "Phạt Sự hiện",
|
"Presence Penalty": "Phạt Sự hiện",
|
||||||
"Top-p": "Top-p",
|
"Top-p": "Top-p",
|
||||||
"Display bot response text chunks as they are generated": "Hiển thị các phần văn bản phản hồi của bot khi chúng được tạo ra",
|
"Display bot response text chunks as they are generated": "Hiển thị các phần văn bản phản hồi của bot khi chúng được tạo ra",
|
||||||
"Top A": "Top A",
|
"Top A": "Top A",
|
||||||
"Typical Sampling": "Mẫu Đại diện",
|
"Typical Sampling": "Mẫu Đại diện",
|
||||||
@@ -141,7 +140,7 @@
|
|||||||
"Influences bot behavior in its responses": "Ảnh hưởng đến hành vi của bot trong các phản hồi của nó",
|
"Influences bot behavior in its responses": "Ảnh hưởng đến hành vi của bot trong các phản hồi của nó",
|
||||||
"Connect": "Kết nối",
|
"Connect": "Kết nối",
|
||||||
"Test Message": "Tin nhắn kiểm tra",
|
"Test Message": "Tin nhắn kiểm tra",
|
||||||
"API": "Giao diện lập trình ứng dụng (API)",
|
"API": "Giao diện lập trình ứng dụng (API)",
|
||||||
"KoboldAI": "KoboldAI",
|
"KoboldAI": "KoboldAI",
|
||||||
"Use Horde": "Sử dụng Horde",
|
"Use Horde": "Sử dụng Horde",
|
||||||
"API url": "URL API",
|
"API url": "URL API",
|
||||||
@@ -206,7 +205,7 @@
|
|||||||
"Scale API Key": "Khóa API của Scale",
|
"Scale API Key": "Khóa API của Scale",
|
||||||
"Alt Method": "Phương pháp thay thế",
|
"Alt Method": "Phương pháp thay thế",
|
||||||
"AI21 API Key": "Khóa API của AI21",
|
"AI21 API Key": "Khóa API của AI21",
|
||||||
"AI21 Model": "Mô hình AI21",
|
"AI21 Model": "Mô hình AI21",
|
||||||
"View API Usage Metrics": "Xem số liệu sử dụng API",
|
"View API Usage Metrics": "Xem số liệu sử dụng API",
|
||||||
"Show External models (provided by API)": "Hiển thị các mô hình bên ngoài (do API cung cấp)",
|
"Show External models (provided by API)": "Hiển thị các mô hình bên ngoài (do API cung cấp)",
|
||||||
"Bot": "Bot:",
|
"Bot": "Bot:",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "Sử liệu toàn cầu đầu tiên",
|
"Global Lore First": "Sử liệu toàn cầu đầu tiên",
|
||||||
"Recursive Scan": "Quét đệ quy",
|
"Recursive Scan": "Quét đệ quy",
|
||||||
"Case Sensitive": "Phân biệt chữ hoa chữ thường",
|
"Case Sensitive": "Phân biệt chữ hoa chữ thường",
|
||||||
"Match whole words": "Khớp toàn bộ từ",
|
|
||||||
"Alert On Overflow": "Cảnh báo khi tràn",
|
"Alert On Overflow": "Cảnh báo khi tràn",
|
||||||
"World/Lore Editor": "Trình soạn thảo Thế giới/Sử liệu",
|
"World/Lore Editor": "Trình soạn thảo Thế giới/Sử liệu",
|
||||||
"--- None ---": "--- Không ---",
|
"--- None ---": "--- Không ---",
|
||||||
@@ -915,5 +913,5 @@
|
|||||||
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Sử dụng bộ mã hóa phù hợp cho các mô hình của Google thông qua API của họ. Xử lý lời mời chậm hơn, nhưng cung cấp đếm token chính xác hơn nhiều.",
|
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Sử dụng bộ mã hóa phù hợp cho các mô hình của Google thông qua API của họ. Xử lý lời mời chậm hơn, nhưng cung cấp đếm token chính xác hơn nhiều.",
|
||||||
"Load koboldcpp order": "Tải đơn hàng koboldcpp",
|
"Load koboldcpp order": "Tải đơn hàng koboldcpp",
|
||||||
"Use Google Tokenizer": "Sử dụng bộ mã hóa của Google"
|
"Use Google Tokenizer": "Sử dụng bộ mã hóa của Google"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
"kobldpresets": "Kobold 预设",
|
"kobldpresets": "Kobold 预设",
|
||||||
"guikoboldaisettings": "KoboldAI 用户界面设置",
|
"guikoboldaisettings": "KoboldAI 用户界面设置",
|
||||||
"novelaipreserts": "NovelAI 预设",
|
"novelaipreserts": "NovelAI 预设",
|
||||||
"default": "默认",
|
|
||||||
"openaipresets": "对话补全预设",
|
"openaipresets": "对话补全预设",
|
||||||
"text gen webio(ooba) presets": "WebUI(ooba) 预设",
|
"text gen webio(ooba) presets": "WebUI(ooba) 预设",
|
||||||
"response legth(tokens)": "响应长度(以词符数计)",
|
"response legth(tokens)": "响应长度(以词符数计)",
|
||||||
@@ -495,7 +494,6 @@
|
|||||||
"Global Lore First": "全局世界书优先",
|
"Global Lore First": "全局世界书优先",
|
||||||
"Recursive Scan": "递归扫描",
|
"Recursive Scan": "递归扫描",
|
||||||
"Case Sensitive": "区分大小写",
|
"Case Sensitive": "区分大小写",
|
||||||
"Match whole words": "完整匹配单词",
|
|
||||||
"Alert On Overflow": "溢出警报",
|
"Alert On Overflow": "溢出警报",
|
||||||
"World/Lore Editor": "世界书编辑器",
|
"World/Lore Editor": "世界书编辑器",
|
||||||
"--- None ---": "--- 无 ---",
|
"--- None ---": "--- 无 ---",
|
||||||
|
1192
public/locales/zh-tw.json
Normal file
1192
public/locales/zh-tw.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,13 @@
|
|||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="darkreader-lock">
|
<meta name="darkreader-lock">
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<style>
|
||||||
|
/* Put critical CSS here. The rest should go in stylesheets. */
|
||||||
|
body {
|
||||||
|
background-color: rgb(36, 36, 37);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="preload" as="style" href="style.css">
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
|
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="img/apple-icon-114x114.png" />
|
<link rel="apple-touch-icon" sizes="114x114" href="img/apple-icon-114x114.png" />
|
||||||
@@ -20,8 +27,8 @@
|
|||||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
|
<link rel="manifest" crossorigin="use-credentials" href="manifest.json">
|
||||||
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
||||||
<!-- fontawesome webfonts-->
|
<!-- fontawesome webfonts-->
|
||||||
<link href="css/fontawesome.css" rel="stylesheet">
|
<link href="css/fontawesome.min.css" rel="stylesheet">
|
||||||
<link href="css/solid.css" rel="stylesheet">
|
<link href="css/solid.min.css" rel="stylesheet">
|
||||||
<link href="css/user.css" rel="stylesheet">
|
<link href="css/user.css" rel="stylesheet">
|
||||||
<script src="lib/jquery-3.5.1.min.js"></script>
|
<script src="lib/jquery-3.5.1.min.js"></script>
|
||||||
<script src="scripts/login.js"></script>
|
<script src="scripts/login.js"></script>
|
||||||
|
1685
public/script.js
1685
public/script.js
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import { is_group_generating } from './group-chats.js';
|
|||||||
import { Message, TokenHandler } from './openai.js';
|
import { Message, TokenHandler } from './openai.js';
|
||||||
import { power_user } from './power-user.js';
|
import { power_user } from './power-user.js';
|
||||||
import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
|
import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
|
||||||
|
import { debounce_timeout } from './constants.js';
|
||||||
|
|
||||||
function debouncePromise(func, delay) {
|
function debouncePromise(func, delay) {
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
@@ -294,7 +295,7 @@ class PromptManager {
|
|||||||
this.handleCharacterReset = () => { };
|
this.handleCharacterReset = () => { };
|
||||||
|
|
||||||
/** Debounced version of render */
|
/** Debounced version of render */
|
||||||
this.renderDebounced = debounce(this.render.bind(this), 1000);
|
this.renderDebounced = debounce(this.render.bind(this), debounce_timeout.relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -776,7 +777,7 @@ class PromptManager {
|
|||||||
const promptOrder = this.getPromptOrderForCharacter(character);
|
const promptOrder = this.getPromptOrderForCharacter(character);
|
||||||
const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier);
|
const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier);
|
||||||
|
|
||||||
if (-1 === index) promptOrder.push({ identifier: prompt.identifier, enabled: false });
|
if (-1 === index) promptOrder.unshift({ identifier: prompt.identifier, enabled: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1286,7 +1287,7 @@ class PromptManager {
|
|||||||
} else if (!entry.enabled && entry.identifier === 'main') {
|
} else if (!entry.enabled && entry.identifier === 'main') {
|
||||||
// Some extensions require main prompt to be present for relative inserts.
|
// Some extensions require main prompt to be present for relative inserts.
|
||||||
// So we make a GMO-free vegan replacement.
|
// So we make a GMO-free vegan replacement.
|
||||||
const prompt = this.getPromptById(entry.identifier);
|
const prompt = structuredClone(this.getPromptById(entry.identifier));
|
||||||
prompt.content = '';
|
prompt.content = '';
|
||||||
if (prompt) promptCollection.add(this.preparePrompt(prompt));
|
if (prompt) promptCollection.add(this.preparePrompt(prompt));
|
||||||
}
|
}
|
||||||
|
@@ -36,6 +36,7 @@ import { debounce, getStringHash, isValidUrl } from './utils.js';
|
|||||||
import { chat_completion_sources, oai_settings } from './openai.js';
|
import { chat_completion_sources, oai_settings } from './openai.js';
|
||||||
import { getTokenCountAsync } from './tokenizers.js';
|
import { getTokenCountAsync } from './tokenizers.js';
|
||||||
import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer } from './textgen-settings.js';
|
import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer } from './textgen-settings.js';
|
||||||
|
import { debounce_timeout } from './constants.js';
|
||||||
|
|
||||||
import Bowser from '../lib/bowser.min.js';
|
import Bowser from '../lib/bowser.min.js';
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ var retry_delay = 500;
|
|||||||
let counterNonce = Date.now();
|
let counterNonce = Date.now();
|
||||||
|
|
||||||
const observerConfig = { childList: true, subtree: true };
|
const observerConfig = { childList: true, subtree: true };
|
||||||
const countTokensDebounced = debounce(RA_CountCharTokens, 1000);
|
const countTokensDebounced = debounce(RA_CountCharTokens, debounce_timeout.relaxed);
|
||||||
|
|
||||||
const observer = new MutationObserver(function (mutations) {
|
const observer = new MutationObserver(function (mutations) {
|
||||||
mutations.forEach(function (mutation) {
|
mutations.forEach(function (mutation) {
|
||||||
@@ -377,6 +378,7 @@ function RA_autoconnect(PrevApi) {
|
|||||||
|| (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)
|
|| (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)
|
||||||
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|
|| (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
|
||||||
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
|| (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
|
||||||
|
|| (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
|
||||||
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
|
||||||
) {
|
) {
|
||||||
$('#api_button_openai').trigger('click');
|
$('#api_button_openai').trigger('click');
|
||||||
@@ -422,7 +424,7 @@ function restoreUserInput() {
|
|||||||
|
|
||||||
const userInput = LoadLocal('userInput');
|
const userInput = LoadLocal('userInput');
|
||||||
if (userInput) {
|
if (userInput) {
|
||||||
$('#send_textarea').val(userInput).trigger('input');
|
$('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,12 +702,12 @@ const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
|||||||
*/
|
*/
|
||||||
function autoFitSendTextArea() {
|
function autoFitSendTextArea() {
|
||||||
const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight);
|
const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight);
|
||||||
if (sendTextArea.scrollHeight == sendTextArea.offsetHeight) {
|
if (Math.ceil(sendTextArea.scrollHeight + 3) >= Math.floor(sendTextArea.offsetHeight)) {
|
||||||
// Needs to be pulled dynamically because it is affected by font size changes
|
// Needs to be pulled dynamically because it is affected by font size changes
|
||||||
const sendTextAreaMinHeight = window.getComputedStyle(sendTextArea).getPropertyValue('min-height');
|
const sendTextAreaMinHeight = window.getComputedStyle(sendTextArea).getPropertyValue('min-height');
|
||||||
sendTextArea.style.height = sendTextAreaMinHeight;
|
sendTextArea.style.height = sendTextAreaMinHeight;
|
||||||
}
|
}
|
||||||
sendTextArea.style.height = sendTextArea.scrollHeight + 0.3 + 'px';
|
sendTextArea.style.height = sendTextArea.scrollHeight + 3 + 'px';
|
||||||
|
|
||||||
if (!isFirefox) {
|
if (!isFirefox) {
|
||||||
const newScrollTop = Math.round(chatBlock.scrollHeight - (chatBlock.offsetHeight + originalScrollBottom));
|
const newScrollTop = Math.round(chatBlock.scrollHeight - (chatBlock.offsetHeight + originalScrollBottom));
|
||||||
@@ -1131,6 +1133,11 @@ export function initRossMods() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($('#dialogue_del_mes_cancel').is(':visible')) {
|
||||||
|
$('#dialogue_del_mes_cancel').trigger('click');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($('.drawer-content')
|
if ($('.drawer-content')
|
||||||
.not('#WorldInfo')
|
.not('#WorldInfo')
|
||||||
.not('#left-nav-panel')
|
.not('#left-nav-panel')
|
||||||
|
@@ -9,9 +9,12 @@ import {
|
|||||||
} from '../script.js';
|
} from '../script.js';
|
||||||
import { selected_group } from './group-chats.js';
|
import { selected_group } from './group-chats.js';
|
||||||
import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js';
|
import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js';
|
||||||
import { registerSlashCommand } from './slash-commands.js';
|
|
||||||
import { getCharaFilename, debounce, delay } from './utils.js';
|
import { getCharaFilename, debounce, delay } from './utils.js';
|
||||||
import { getTokenCountAsync } from './tokenizers.js';
|
import { getTokenCountAsync } from './tokenizers.js';
|
||||||
|
import { debounce_timeout } from './constants.js';
|
||||||
|
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
|
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
|
||||||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||||||
|
|
||||||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||||
@@ -84,9 +87,9 @@ function updateSettings() {
|
|||||||
setFloatingPrompt();
|
setFloatingPrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
const setMainPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_prompt_token_counter').text(await getTokenCountAsync(value)), 1000);
|
const setMainPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_prompt_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
|
||||||
const setCharaPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_chara_token_counter').text(await getTokenCountAsync(value)), 1000);
|
const setCharaPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_chara_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
|
||||||
const setDefaultPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_default_token_counter').text(await getTokenCountAsync(value)), 1000);
|
const setDefaultPromptTokenCounterDebounced = debounce(async (value) => $('#extension_floating_default_token_counter').text(await getTokenCountAsync(value)), debounce_timeout.relaxed);
|
||||||
|
|
||||||
async function onExtensionFloatingPromptInput() {
|
async function onExtensionFloatingPromptInput() {
|
||||||
chat_metadata[metadata_keys.prompt] = $(this).val();
|
chat_metadata[metadata_keys.prompt] = $(this).val();
|
||||||
@@ -454,9 +457,59 @@ export function initAuthorsNote() {
|
|||||||
});
|
});
|
||||||
$('#option_toggle_AN').on('click', onANMenuItemClick);
|
$('#option_toggle_AN').on('click', onANMenuItemClick);
|
||||||
|
|
||||||
registerSlashCommand('note', setNoteTextCommand, [], '<span class=\'monospace\'>(text)</span> – sets an author\'s note for the currently selected chat', true, true);
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'note',
|
||||||
registerSlashCommand('depth', setNoteDepthCommand, [], '<span class=\'monospace\'>(number)</span> – sets an author\'s note depth for in-chat positioning', true, true);
|
callback: setNoteTextCommand,
|
||||||
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], '<span class=\'monospace\'>(number)</span> – sets an author\'s note insertion frequency', true, true);
|
unnamedArgumentList: [
|
||||||
registerSlashCommand('pos', setNotePositionCommand, ['position'], '(<span class=\'monospace\'>chat</span> or <span class=\'monospace\'>scenario</span>) – sets an author\'s note position', true, true);
|
new SlashCommandArgument(
|
||||||
|
'text', [ARGUMENT_TYPE.STRING], true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Sets an author's note for the currently selected chat.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'depth',
|
||||||
|
callback: setNoteDepthCommand,
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'number', [ARGUMENT_TYPE.NUMBER], true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Sets an author's note depth for in-chat positioning.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'freq',
|
||||||
|
callback: setNoteIntervalCommand,
|
||||||
|
namedArgumentList: [],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'number', [ARGUMENT_TYPE.NUMBER], true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Sets an author's note insertion frequency.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pos',
|
||||||
|
callback: setNotePositionCommand,
|
||||||
|
namedArgumentList: [],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'position', [ARGUMENT_TYPE.STRING], true, false, null, ['chat', 'scenario'],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Sets an author's note position.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||||
}
|
}
|
||||||
|
792
public/scripts/autocomplete/AutoComplete.js
Normal file
792
public/scripts/autocomplete/AutoComplete.js
Normal file
@@ -0,0 +1,792 @@
|
|||||||
|
import { power_user } from '../power-user.js';
|
||||||
|
import { debounce, escapeRegex } from '../utils.js';
|
||||||
|
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||||
|
import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
|
||||||
|
import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
|
||||||
|
import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||||
|
|
||||||
|
/**@readonly*/
|
||||||
|
/**@enum {Number}*/
|
||||||
|
export const AUTOCOMPLETE_WIDTH = {
|
||||||
|
'INPUT': 0,
|
||||||
|
'CHAT': 1,
|
||||||
|
'FULL': 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AutoComplete {
|
||||||
|
/**@type {HTMLTextAreaElement}*/ textarea;
|
||||||
|
/**@type {boolean}*/ isFloating = false;
|
||||||
|
/**@type {()=>boolean}*/ checkIfActivate;
|
||||||
|
/**@type {(text:string, index:number) => Promise<AutoCompleteNameResult>}*/ getNameAt;
|
||||||
|
|
||||||
|
/**@type {boolean}*/ isActive = false;
|
||||||
|
/**@type {boolean}*/ isReplaceable = false;
|
||||||
|
/**@type {boolean}*/ isShowingDetails = false;
|
||||||
|
/**@type {boolean}*/ wasForced = false;
|
||||||
|
/**@type {boolean}*/ isForceHidden = false;
|
||||||
|
/**@type {boolean}*/ canBeAutoHidden = false;
|
||||||
|
|
||||||
|
/**@type {string}*/ text;
|
||||||
|
/**@type {AutoCompleteNameResult}*/ parserResult;
|
||||||
|
/**@type {AutoCompleteSecondaryNameResult}*/ secondaryParserResult;
|
||||||
|
get effectiveParserResult() { return this.secondaryParserResult ?? this.parserResult; }
|
||||||
|
/**@type {string}*/ name;
|
||||||
|
|
||||||
|
/**@type {boolean}*/ startQuote;
|
||||||
|
/**@type {boolean}*/ endQuote;
|
||||||
|
/**@type {number}*/ selectionStart;
|
||||||
|
|
||||||
|
/**@type {RegExp}*/ fuzzyRegex;
|
||||||
|
|
||||||
|
/**@type {AutoCompleteOption[]}*/ result = [];
|
||||||
|
/**@type {AutoCompleteOption}*/ selectedItem = null;
|
||||||
|
|
||||||
|
/**@type {HTMLElement}*/ clone;
|
||||||
|
/**@type {HTMLElement}*/ domWrap;
|
||||||
|
/**@type {HTMLElement}*/ dom;
|
||||||
|
/**@type {HTMLElement}*/ detailsWrap;
|
||||||
|
/**@type {HTMLElement}*/ detailsDom;
|
||||||
|
|
||||||
|
/**@type {function}*/ renderDebounced;
|
||||||
|
/**@type {function}*/ renderDetailsDebounced;
|
||||||
|
/**@type {function}*/ updatePositionDebounced;
|
||||||
|
/**@type {function}*/ updateDetailsPositionDebounced;
|
||||||
|
/**@type {function}*/ updateFloatingPositionDebounced;
|
||||||
|
|
||||||
|
get matchType() {
|
||||||
|
return power_user.stscript.matching ?? 'fuzzy';
|
||||||
|
}
|
||||||
|
|
||||||
|
get autoHide() {
|
||||||
|
return power_user.stscript.autocomplete.autoHide ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLTextAreaElement} textarea The textarea to receive autocomplete.
|
||||||
|
* @param {() => boolean} checkIfActivate Function should return true only if under the current conditions, autocomplete should display (e.g., for slash commands: autoComplete.text[0] == '/')
|
||||||
|
* @param {(text: string, index: number) => Promise<AutoCompleteNameResult>} getNameAt Function should return (unfiltered, matching against input is done in AutoComplete) information about name options at index in text.
|
||||||
|
* @param {boolean} isFloating Whether autocomplete should float at the keyboard cursor.
|
||||||
|
*/
|
||||||
|
constructor(textarea, checkIfActivate, getNameAt, isFloating = false) {
|
||||||
|
this.textarea = textarea;
|
||||||
|
this.checkIfActivate = checkIfActivate;
|
||||||
|
this.getNameAt = getNameAt;
|
||||||
|
this.isFloating = isFloating;
|
||||||
|
|
||||||
|
this.domWrap = document.createElement('div'); {
|
||||||
|
this.domWrap.classList.add('autoComplete-wrap');
|
||||||
|
if (isFloating) this.domWrap.classList.add('isFloating');
|
||||||
|
}
|
||||||
|
this.dom = document.createElement('ul'); {
|
||||||
|
this.dom.classList.add('autoComplete');
|
||||||
|
this.domWrap.append(this.dom);
|
||||||
|
}
|
||||||
|
this.detailsWrap = document.createElement('div'); {
|
||||||
|
this.detailsWrap.classList.add('autoComplete-detailsWrap');
|
||||||
|
if (isFloating) this.detailsWrap.classList.add('isFloating');
|
||||||
|
}
|
||||||
|
this.detailsDom = document.createElement('div'); {
|
||||||
|
this.detailsDom.classList.add('autoComplete-details');
|
||||||
|
this.detailsWrap.append(this.detailsDom);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderDebounced = debounce(this.render.bind(this), 10);
|
||||||
|
this.renderDetailsDebounced = debounce(this.renderDetails.bind(this), 10);
|
||||||
|
this.updatePositionDebounced = debounce(this.updatePosition.bind(this), 10);
|
||||||
|
this.updateDetailsPositionDebounced = debounce(this.updateDetailsPosition.bind(this), 10);
|
||||||
|
this.updateFloatingPositionDebounced = debounce(this.updateFloatingPosition.bind(this), 10);
|
||||||
|
|
||||||
|
textarea.addEventListener('input', ()=>this.text != this.textarea.value && this.show(true, this.wasForced));
|
||||||
|
textarea.addEventListener('keydown', (evt)=>this.handleKeyDown(evt));
|
||||||
|
textarea.addEventListener('click', ()=>this.isActive ? this.show() : null);
|
||||||
|
textarea.addEventListener('selectionchange', ()=>this.show());
|
||||||
|
textarea.addEventListener('blur', ()=>this.hide());
|
||||||
|
if (isFloating) {
|
||||||
|
textarea.addEventListener('scroll', ()=>this.updateFloatingPositionDebounced());
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', ()=>this.updatePositionDebounced());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {AutoCompleteOption} option
|
||||||
|
*/
|
||||||
|
makeItem(option) {
|
||||||
|
const li = option.renderItem();
|
||||||
|
// gotta listen to pointerdown (happens before textarea-blur)
|
||||||
|
li.addEventListener('pointerdown', (evt)=>{
|
||||||
|
evt.preventDefault();
|
||||||
|
this.selectedItem = this.result.find(it=>it.name == li.getAttribute('data-name'));
|
||||||
|
this.select();
|
||||||
|
});
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {AutoCompleteOption} item
|
||||||
|
*/
|
||||||
|
updateName(item) {
|
||||||
|
const chars = Array.from(item.dom.querySelector('.name').children);
|
||||||
|
switch (this.matchType) {
|
||||||
|
case 'strict': {
|
||||||
|
chars.forEach((it, idx)=>{
|
||||||
|
if (idx + item.nameOffset < item.name.length) {
|
||||||
|
it.classList.add('matched');
|
||||||
|
} else {
|
||||||
|
it.classList.remove('matched');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'includes': {
|
||||||
|
const start = item.name.toLowerCase().search(this.name);
|
||||||
|
chars.forEach((it, idx)=>{
|
||||||
|
if (idx + item.nameOffset < start) {
|
||||||
|
it.classList.remove('matched');
|
||||||
|
} else if (idx + item.nameOffset < start + item.name.length) {
|
||||||
|
it.classList.add('matched');
|
||||||
|
} else {
|
||||||
|
it.classList.remove('matched');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'fuzzy': {
|
||||||
|
item.name.replace(this.fuzzyRegex, (_, ...parts)=>{
|
||||||
|
parts.splice(-2, 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
chars.forEach(c=>c.classList.remove('matched'));
|
||||||
|
} else {
|
||||||
|
let cIdx = item.nameOffset;
|
||||||
|
parts.forEach((it, idx)=>{
|
||||||
|
if (it === null || it.length == 0) return '';
|
||||||
|
if (idx % 2 == 1) {
|
||||||
|
chars.slice(cIdx, cIdx + it.length).forEach(c=>c.classList.add('matched'));
|
||||||
|
} else {
|
||||||
|
chars.slice(cIdx, cIdx + it.length).forEach(c=>c.classList.remove('matched'));
|
||||||
|
}
|
||||||
|
cIdx += it.length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate score for the fuzzy match.
|
||||||
|
* @param {AutoCompleteOption} option
|
||||||
|
* @returns The option.
|
||||||
|
*/
|
||||||
|
fuzzyScore(option) {
|
||||||
|
const parts = this.fuzzyRegex.exec(option.name).slice(1, -1);
|
||||||
|
let start = null;
|
||||||
|
let consecutive = [];
|
||||||
|
let current = '';
|
||||||
|
let offset = 0;
|
||||||
|
parts.forEach((part, idx) => {
|
||||||
|
if (idx % 2 == 0) {
|
||||||
|
if (part.length > 0) {
|
||||||
|
if (current.length > 0) {
|
||||||
|
consecutive.push(current);
|
||||||
|
}
|
||||||
|
current = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (start === null) {
|
||||||
|
start = offset;
|
||||||
|
}
|
||||||
|
current += part;
|
||||||
|
}
|
||||||
|
offset += part.length;
|
||||||
|
});
|
||||||
|
if (current.length > 0) {
|
||||||
|
consecutive.push(current);
|
||||||
|
}
|
||||||
|
consecutive.sort((a,b)=>b.length - a.length);
|
||||||
|
option.score = new AutoCompleteFuzzyScore(start, consecutive[0]?.length ?? 0);
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two auto complete options by their fuzzy score.
|
||||||
|
* @param {AutoCompleteOption} a
|
||||||
|
* @param {AutoCompleteOption} b
|
||||||
|
*/
|
||||||
|
fuzzyScoreCompare(a, b) {
|
||||||
|
if (a.score.start < b.score.start) return -1;
|
||||||
|
if (a.score.start > b.score.start) return 1;
|
||||||
|
if (a.score.longestConsecutive > b.score.longestConsecutive) return -1;
|
||||||
|
if (a.score.longestConsecutive < b.score.longestConsecutive) return 1;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
basicAutoHideCheck() {
|
||||||
|
// auto hide only if at least one char has been typed after the name + space
|
||||||
|
return this.textarea.selectionStart > this.parserResult.start
|
||||||
|
+ this.parserResult.name.length
|
||||||
|
+ (this.startQuote ? 1 : 0)
|
||||||
|
+ (this.endQuote ? 1 : 0)
|
||||||
|
+ 1
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the autocomplete.
|
||||||
|
* @param {boolean} isInput Whether triggered by input.
|
||||||
|
* @param {boolean} isForced Whether force-showing (ctrl+space).
|
||||||
|
* @param {boolean} isSelect Whether an autocomplete option was just selected.
|
||||||
|
*/
|
||||||
|
async show(isInput = false, isForced = false, isSelect = false) {
|
||||||
|
//TODO check if isInput and isForced are both required
|
||||||
|
this.text = this.textarea.value;
|
||||||
|
this.isReplaceable = false;
|
||||||
|
|
||||||
|
if (document.activeElement != this.textarea) {
|
||||||
|
// only show with textarea in focus
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
if (!this.checkIfActivate()) {
|
||||||
|
// only show if provider wants to
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable force-hide if trigger was forced
|
||||||
|
if (isForced) this.isForceHidden = false;
|
||||||
|
|
||||||
|
// request provider to get name result (potentially "incomplete", i.e. not an actual existing name) for
|
||||||
|
// cursor position
|
||||||
|
this.parserResult = await this.getNameAt(this.text, this.textarea.selectionStart);
|
||||||
|
this.secondaryParserResult = null;
|
||||||
|
|
||||||
|
if (!this.parserResult) {
|
||||||
|
// don't show if no name result found, e.g., cursor's area is not a command
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to know if name can be inside quotes, and then check if quotes are already there
|
||||||
|
if (this.parserResult.canBeQuoted) {
|
||||||
|
this.startQuote = this.text[this.parserResult.start] == '"';
|
||||||
|
this.endQuote = this.startQuote && this.text[this.parserResult.start + this.parserResult.name.length + 1] == '"';
|
||||||
|
} else {
|
||||||
|
this.startQuote = false;
|
||||||
|
this.endQuote = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use lowercase name for matching
|
||||||
|
this.name = this.parserResult.name.toLowerCase() ?? '';
|
||||||
|
|
||||||
|
const isCursorInNamePart = this.textarea.selectionStart >= this.parserResult.start && this.textarea.selectionStart <= this.parserResult.start + this.parserResult.name.length + (this.startQuote ? 1 : 0);
|
||||||
|
if (isForced || isInput) {
|
||||||
|
// if forced (ctrl+space) or user input...
|
||||||
|
if (isCursorInNamePart) {
|
||||||
|
// ...and cursor is somewhere in the name part (including right behind the final char)
|
||||||
|
// -> show autocomplete for the (partial if cursor in the middle) name
|
||||||
|
this.name = this.name.slice(0, this.textarea.selectionStart - (this.parserResult.start) - (this.startQuote ? 1 : 0));
|
||||||
|
this.parserResult.name = this.name;
|
||||||
|
this.isReplaceable = true;
|
||||||
|
this.isForceHidden = false;
|
||||||
|
this.canBeAutoHidden = false;
|
||||||
|
} else {
|
||||||
|
this.isReplaceable = false;
|
||||||
|
this.canBeAutoHidden = this.basicAutoHideCheck();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if not forced and no user input -> just show details
|
||||||
|
this.isReplaceable = false;
|
||||||
|
this.canBeAutoHidden = this.basicAutoHideCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isForced || isInput || isSelect) {
|
||||||
|
// is forced or user input or just selected autocomplete option...
|
||||||
|
if (!isCursorInNamePart) {
|
||||||
|
// ...and cursor is not somwehere in the main name part -> check for secondary options (e.g., named arguments)
|
||||||
|
const result = this.parserResult.getSecondaryNameAt(this.text, this.textarea.selectionStart, isSelect);
|
||||||
|
if (result && (isForced || result.isRequired)) {
|
||||||
|
this.secondaryParserResult = result;
|
||||||
|
this.name = this.secondaryParserResult.name;
|
||||||
|
this.isReplaceable = isForced || this.secondaryParserResult.isRequired;
|
||||||
|
this.isForceHidden = false;
|
||||||
|
this.canBeAutoHidden = false;
|
||||||
|
} else {
|
||||||
|
this.isReplaceable = false;
|
||||||
|
this.canBeAutoHidden = this.basicAutoHideCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.matchType == 'fuzzy') {
|
||||||
|
// only build the fuzzy regex if match type is set to fuzzy
|
||||||
|
this.fuzzyRegex = new RegExp(`^(.*?)${this.name.split('').map(char=>`(${escapeRegex(char)})`).join('(.*?)')}(.*?)$`, 'i');
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO maybe move the matchers somewhere else; a single match function? matchType is available as property
|
||||||
|
const matchers = {
|
||||||
|
'strict': (name) => name.toLowerCase().startsWith(this.name),
|
||||||
|
'includes': (name) => name.toLowerCase().includes(this.name),
|
||||||
|
'fuzzy': (name) => this.fuzzyRegex.test(name),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.result = this.effectiveParserResult.optionList
|
||||||
|
// filter the list of options by the partial name according to the matching type
|
||||||
|
.filter(it => this.isReplaceable || it.name == '' ? matchers[this.matchType](it.name) : it.name.toLowerCase() == this.name)
|
||||||
|
// remove aliases
|
||||||
|
.filter((it,idx,list) => list.findIndex(opt=>opt.value == it.value) == idx);
|
||||||
|
|
||||||
|
if (this.result.length == 0 && this.effectiveParserResult != this.parserResult && isForced) {
|
||||||
|
// no matching secondary results and forced trigger -> show current command details
|
||||||
|
this.secondaryParserResult = null;
|
||||||
|
this.result = [this.effectiveParserResult.optionList.find(it=>it.name == this.effectiveParserResult.name)];
|
||||||
|
this.name = this.effectiveParserResult.name;
|
||||||
|
this.fuzzyRegex = /(.*)(.*)(.*)/;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.result = this.result
|
||||||
|
// update remaining options
|
||||||
|
.map(option => {
|
||||||
|
// build element
|
||||||
|
option.dom = this.makeItem(option);
|
||||||
|
// update replacer and add quotes if necessary
|
||||||
|
if (this.effectiveParserResult.canBeQuoted) {
|
||||||
|
option.replacer = option.name.includes(' ') || this.startQuote || this.endQuote ? `"${option.name}"` : `${option.name}`;
|
||||||
|
} else {
|
||||||
|
option.replacer = option.name;
|
||||||
|
}
|
||||||
|
// calculate fuzzy score if matching is fuzzy
|
||||||
|
if (this.matchType == 'fuzzy') this.fuzzyScore(option);
|
||||||
|
// update the name to highlight the matched chars
|
||||||
|
this.updateName(option);
|
||||||
|
return option;
|
||||||
|
})
|
||||||
|
// sort by fuzzy score or alphabetical
|
||||||
|
.toSorted(this.matchType == 'fuzzy' ? this.fuzzyScoreCompare : (a, b) => a.name.localeCompare(b.name))
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.isForceHidden) {
|
||||||
|
// hidden with escape
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
if (this.autoHide && this.canBeAutoHidden && !isForced && this.effectiveParserResult == this.parserResult && this.result.length == 1) {
|
||||||
|
// auto hide user setting enabled and somewhere after name part and would usually show command details
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
if (this.result.length == 0) {
|
||||||
|
if (!isInput) {
|
||||||
|
// no result and no input? hide autocomplete
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
// otherwise add "no match" notice
|
||||||
|
const option = new BlankAutoCompleteOption(
|
||||||
|
this.name.length ?
|
||||||
|
this.effectiveParserResult.makeNoMatchText()
|
||||||
|
: this.effectiveParserResult.makeNoOptionstext()
|
||||||
|
,
|
||||||
|
);
|
||||||
|
this.result.push(option);
|
||||||
|
} else if (this.result.length == 1 && this.effectiveParserResult && this.result[0].name == this.effectiveParserResult.name) {
|
||||||
|
// only one result that is exactly the current value? just show hint, no autocomplete
|
||||||
|
this.isReplaceable = false;
|
||||||
|
this.isShowingDetails = false;
|
||||||
|
} else if (!this.isReplaceable && this.result.length > 1) {
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
this.selectedItem = this.result[0];
|
||||||
|
this.isActive = true;
|
||||||
|
this.wasForced = isForced;
|
||||||
|
this.renderDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide autocomplete.
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
this.domWrap?.remove();
|
||||||
|
this.detailsWrap?.remove();
|
||||||
|
this.isActive = false;
|
||||||
|
this.isShowingDetails = false;
|
||||||
|
this.wasForced = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create updated DOM.
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
if (!this.isActive) return this.domWrap.remove();
|
||||||
|
if (this.isReplaceable) {
|
||||||
|
this.dom.innerHTML = '';
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
for (const item of this.result) {
|
||||||
|
if (item == this.selectedItem) {
|
||||||
|
item.dom.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
item.dom.classList.remove('selected');
|
||||||
|
}
|
||||||
|
frag.append(item.dom);
|
||||||
|
}
|
||||||
|
this.dom.append(frag);
|
||||||
|
this.updatePosition();
|
||||||
|
document.body.append(this.domWrap);
|
||||||
|
} else {
|
||||||
|
this.domWrap.remove();
|
||||||
|
}
|
||||||
|
this.renderDetailsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create updated DOM for details.
|
||||||
|
*/
|
||||||
|
renderDetails() {
|
||||||
|
if (!this.isActive) return this.detailsWrap.remove();
|
||||||
|
if (!this.isShowingDetails && this.isReplaceable) return this.detailsWrap.remove();
|
||||||
|
this.detailsDom.innerHTML = '';
|
||||||
|
this.detailsDom.append(this.selectedItem?.renderDetails() ?? 'NO ITEM');
|
||||||
|
document.body.append(this.detailsWrap);
|
||||||
|
this.updateDetailsPositionDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update position of DOM.
|
||||||
|
*/
|
||||||
|
updatePosition() {
|
||||||
|
if (this.isFloating) {
|
||||||
|
this.updateFloatingPosition();
|
||||||
|
} else {
|
||||||
|
const rect = {};
|
||||||
|
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||||
|
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||||
|
rect[AUTOCOMPLETE_WIDTH.FULL] = document.body.getBoundingClientRect();
|
||||||
|
this.domWrap.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||||
|
this.dom.style.setProperty('--bottom', `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`);
|
||||||
|
this.domWrap.style.bottom = `${window.innerHeight - rect[AUTOCOMPLETE_WIDTH.INPUT].top}px`;
|
||||||
|
if (this.isShowingDetails) {
|
||||||
|
this.domWrap.style.setProperty('--leftOffset', '1vw');
|
||||||
|
this.domWrap.style.setProperty('--leftOffset', `max(1vw, ${rect[power_user.stscript.autocomplete.width.left].left}px)`);
|
||||||
|
this.domWrap.style.setProperty('--rightOffset', `calc(100vw - min(${rect[power_user.stscript.autocomplete.width.right].right}px, ${this.isShowingDetails ? 74 : 0}vw)`);
|
||||||
|
} else {
|
||||||
|
this.domWrap.style.setProperty('--leftOffset', `max(1vw, ${rect[power_user.stscript.autocomplete.width.left].left}px)`);
|
||||||
|
this.domWrap.style.setProperty('--rightOffset', `calc(100vw - min(99vw, ${rect[power_user.stscript.autocomplete.width.right].right}px)`);
|
||||||
|
}
|
||||||
|
this.updateDetailsPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update position of details DOM.
|
||||||
|
*/
|
||||||
|
updateDetailsPosition() {
|
||||||
|
if (this.isShowingDetails || !this.isReplaceable) {
|
||||||
|
if (this.isFloating) {
|
||||||
|
this.updateFloatingDetailsPosition();
|
||||||
|
} else {
|
||||||
|
const rect = {};
|
||||||
|
rect[AUTOCOMPLETE_WIDTH.INPUT] = this.textarea.getBoundingClientRect();
|
||||||
|
rect[AUTOCOMPLETE_WIDTH.CHAT] = document.querySelector('#sheld').getBoundingClientRect();
|
||||||
|
rect[AUTOCOMPLETE_WIDTH.FULL] = document.body.getBoundingClientRect();
|
||||||
|
if (this.isReplaceable) {
|
||||||
|
this.detailsWrap.classList.remove('full');
|
||||||
|
const selRect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
||||||
|
this.detailsWrap.style.setProperty('--targetOffset', `${selRect.top}`);
|
||||||
|
this.detailsWrap.style.setProperty('--rightOffset', '1vw');
|
||||||
|
this.detailsWrap.style.setProperty('--bottomOffset', `calc(100vh - ${rect[AUTOCOMPLETE_WIDTH.INPUT].top}px)`);
|
||||||
|
this.detailsWrap.style.setProperty('--leftOffset', `calc(100vw - ${this.domWrap.style.getPropertyValue('--rightOffset')}`);
|
||||||
|
} else {
|
||||||
|
this.detailsWrap.classList.add('full');
|
||||||
|
this.detailsWrap.style.setProperty('--targetOffset', `${rect[AUTOCOMPLETE_WIDTH.INPUT].top}`);
|
||||||
|
this.detailsWrap.style.setProperty('--bottomOffset', `calc(100vh - ${rect[AUTOCOMPLETE_WIDTH.INPUT].top}px)`);
|
||||||
|
this.detailsWrap.style.setProperty('--leftOffset', `${rect[power_user.stscript.autocomplete.width.left].left}px`);
|
||||||
|
this.detailsWrap.style.setProperty('--rightOffset', `calc(100vw - ${rect[power_user.stscript.autocomplete.width.right].right}px)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update position of floating autocomplete.
|
||||||
|
*/
|
||||||
|
updateFloatingPosition() {
|
||||||
|
const location = this.getCursorPosition();
|
||||||
|
const rect = this.textarea.getBoundingClientRect();
|
||||||
|
// cursor is out of view -> hide
|
||||||
|
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
const left = Math.max(rect.left, location.left);
|
||||||
|
this.domWrap.style.setProperty('--targetOffset', `${left}`);
|
||||||
|
if (location.top <= window.innerHeight / 2) {
|
||||||
|
// if cursor is in lower half of window, show list above line
|
||||||
|
this.domWrap.style.top = `${location.bottom}px`;
|
||||||
|
this.domWrap.style.bottom = 'auto';
|
||||||
|
this.domWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
||||||
|
} else {
|
||||||
|
// if cursor is in upper half of window, show list below line
|
||||||
|
this.domWrap.style.top = 'auto';
|
||||||
|
this.domWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
||||||
|
this.domWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFloatingDetailsPosition(location = null) {
|
||||||
|
if (!location) location = this.getCursorPosition();
|
||||||
|
const rect = this.textarea.getBoundingClientRect();
|
||||||
|
if (location.bottom < rect.top || location.top > rect.bottom || location.left < rect.left || location.left > rect.right) {
|
||||||
|
return this.hide();
|
||||||
|
}
|
||||||
|
const left = Math.max(rect.left, location.left);
|
||||||
|
this.detailsWrap.style.setProperty('--targetOffset', `${left}`);
|
||||||
|
if (this.isReplaceable) {
|
||||||
|
this.detailsWrap.classList.remove('full');
|
||||||
|
if (left < window.innerWidth / 4) {
|
||||||
|
// if cursor is in left part of screen, show details on right of list
|
||||||
|
this.detailsWrap.classList.add('right');
|
||||||
|
this.detailsWrap.classList.remove('left');
|
||||||
|
} else {
|
||||||
|
// if cursor is in right part of screen, show details on left of list
|
||||||
|
this.detailsWrap.classList.remove('right');
|
||||||
|
this.detailsWrap.classList.add('left');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.detailsWrap.classList.remove('left');
|
||||||
|
this.detailsWrap.classList.remove('right');
|
||||||
|
this.detailsWrap.classList.add('full');
|
||||||
|
}
|
||||||
|
if (location.top <= window.innerHeight / 2) {
|
||||||
|
// if cursor is in lower half of window, show list above line
|
||||||
|
this.detailsWrap.style.top = `${location.bottom}px`;
|
||||||
|
this.detailsWrap.style.bottom = 'auto';
|
||||||
|
this.detailsWrap.style.maxHeight = `calc(${location.bottom}px - 1vh)`;
|
||||||
|
} else {
|
||||||
|
// if cursor is in upper half of window, show list below line
|
||||||
|
this.detailsWrap.style.top = 'auto';
|
||||||
|
this.detailsWrap.style.bottom = `calc(100vh - ${location.top}px)`;
|
||||||
|
this.detailsWrap.style.maxHeight = `calc(${location.top}px - 1vh)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate (keyboard) cursor coordinates within textarea.
|
||||||
|
* @returns {{left:number, top:number, bottom:number}}
|
||||||
|
*/
|
||||||
|
getCursorPosition() {
|
||||||
|
const inputRect = this.textarea.getBoundingClientRect();
|
||||||
|
const style = window.getComputedStyle(this.textarea);
|
||||||
|
if (!this.clone) {
|
||||||
|
this.clone = document.createElement('div');
|
||||||
|
for (const key of style) {
|
||||||
|
this.clone.style[key] = style[key];
|
||||||
|
}
|
||||||
|
this.clone.style.position = 'fixed';
|
||||||
|
this.clone.style.visibility = 'hidden';
|
||||||
|
document.body.append(this.clone);
|
||||||
|
const mo = new MutationObserver(muts=>{
|
||||||
|
if (muts.find(it=>Array.from(it.removedNodes).includes(this.textarea))) {
|
||||||
|
this.clone.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mo.observe(this.textarea.parentElement, { childList:true });
|
||||||
|
}
|
||||||
|
this.clone.style.height = `${inputRect.height}px`;
|
||||||
|
this.clone.style.left = `${inputRect.left}px`;
|
||||||
|
this.clone.style.top = `${inputRect.top}px`;
|
||||||
|
this.clone.style.whiteSpace = style.whiteSpace;
|
||||||
|
this.clone.style.tabSize = style.tabSize;
|
||||||
|
const text = this.textarea.value;
|
||||||
|
const before = text.slice(0, this.textarea.selectionStart);
|
||||||
|
this.clone.textContent = before;
|
||||||
|
const locator = document.createElement('span');
|
||||||
|
locator.textContent = text[this.textarea.selectionStart];
|
||||||
|
this.clone.append(locator);
|
||||||
|
this.clone.append(text.slice(this.textarea.selectionStart + 1));
|
||||||
|
this.clone.scrollTop = this.textarea.scrollTop;
|
||||||
|
this.clone.scrollLeft = this.textarea.scrollLeft;
|
||||||
|
const locatorRect = locator.getBoundingClientRect();
|
||||||
|
const location = {
|
||||||
|
left: locatorRect.left,
|
||||||
|
top: locatorRect.top,
|
||||||
|
bottom: locatorRect.bottom,
|
||||||
|
};
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle details view alongside autocomplete list.
|
||||||
|
*/
|
||||||
|
toggleDetails() {
|
||||||
|
this.isShowingDetails = !this.isShowingDetails;
|
||||||
|
this.renderDetailsDebounced();
|
||||||
|
this.updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select an item for autocomplete and put text into textarea.
|
||||||
|
*/
|
||||||
|
async select() {
|
||||||
|
if (this.isReplaceable && this.selectedItem.value !== null) {
|
||||||
|
this.textarea.value = `${this.text.slice(0, this.effectiveParserResult.start)}${this.selectedItem.replacer}${this.text.slice(this.effectiveParserResult.start + this.effectiveParserResult.name.length + (this.startQuote ? 1 : 0) + (this.endQuote ? 1 : 0))}`;
|
||||||
|
this.textarea.selectionStart = this.effectiveParserResult.start + this.selectedItem.replacer.length;
|
||||||
|
this.textarea.selectionEnd = this.textarea.selectionStart;
|
||||||
|
this.show(false, false, true);
|
||||||
|
} else {
|
||||||
|
const selectionStart = this.textarea.selectionStart;
|
||||||
|
const selectionEnd = this.textarea.selectionDirection;
|
||||||
|
this.textarea.selectionStart = selectionStart;
|
||||||
|
this.textarea.selectionDirection = selectionEnd;
|
||||||
|
}
|
||||||
|
this.wasForced = false;
|
||||||
|
this.textarea.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the item at newIdx in the autocomplete list as selected.
|
||||||
|
* @param {number} newIdx
|
||||||
|
*/
|
||||||
|
selectItemAtIndex(newIdx) {
|
||||||
|
this.selectedItem.dom.classList.remove('selected');
|
||||||
|
this.selectedItem = this.result[newIdx];
|
||||||
|
this.selectedItem.dom.classList.add('selected');
|
||||||
|
const rect = this.selectedItem.dom.children[0].getBoundingClientRect();
|
||||||
|
const rectParent = this.dom.getBoundingClientRect();
|
||||||
|
if (rect.top < rectParent.top || rect.bottom > rectParent.bottom ) {
|
||||||
|
this.dom.scrollTop += rect.top < rectParent.top ? rect.top - rectParent.top : rect.bottom - rectParent.bottom;
|
||||||
|
}
|
||||||
|
this.renderDetailsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle keyboard events.
|
||||||
|
* @param {KeyboardEvent} evt The event.
|
||||||
|
*/
|
||||||
|
async handleKeyDown(evt) {
|
||||||
|
// autocomplete is shown and cursor at end of current command name (or inside name and typed or forced)
|
||||||
|
if (this.isActive && this.isReplaceable) {
|
||||||
|
// actions in the list
|
||||||
|
switch (evt.key) {
|
||||||
|
case 'ArrowUp': {
|
||||||
|
// select previous item
|
||||||
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey) return;
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
const idx = this.result.indexOf(this.selectedItem);
|
||||||
|
let newIdx;
|
||||||
|
if (idx == 0) newIdx = this.result.length - 1;
|
||||||
|
else newIdx = idx - 1;
|
||||||
|
this.selectItemAtIndex(newIdx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'ArrowDown': {
|
||||||
|
// select next item
|
||||||
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey) return;
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
const idx = this.result.indexOf(this.selectedItem);
|
||||||
|
const newIdx = (idx + 1) % this.result.length;
|
||||||
|
this.selectItemAtIndex(newIdx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'Enter': {
|
||||||
|
// pick the selected item to autocomplete
|
||||||
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
||||||
|
if (this.selectedItem.name == this.name) break;
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
this.select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'Tab': {
|
||||||
|
// pick the selected item to autocomplete
|
||||||
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey || this.selectedItem.value == '') break;
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
this.select();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// details are shown, cursor can be anywhere
|
||||||
|
if (this.isActive) {
|
||||||
|
switch (evt.key) {
|
||||||
|
case 'Escape': {
|
||||||
|
// close autocomplete
|
||||||
|
if (evt.ctrlKey || evt.altKey || evt.shiftKey) return;
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
this.isForceHidden = true;
|
||||||
|
this.wasForced = false;
|
||||||
|
this.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'Enter': {
|
||||||
|
// hide autocomplete on enter (send, execute, ...)
|
||||||
|
if (!evt.shiftKey) {
|
||||||
|
this.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// autocomplete shown or not, cursor anywhere
|
||||||
|
switch (evt.key) {
|
||||||
|
case ' ': {
|
||||||
|
if (evt.ctrlKey) {
|
||||||
|
if (this.isActive && this.isReplaceable) {
|
||||||
|
// ctrl-space to toggle details for selected item
|
||||||
|
this.toggleDetails();
|
||||||
|
} else {
|
||||||
|
// ctrl-space to force show autocomplete
|
||||||
|
this.show(false, true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (['Control', 'Shift', 'Alt'].includes(evt.key)) {
|
||||||
|
// ignore keydown on modifier keys
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (evt.key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'ArrowLeft': {
|
||||||
|
if (this.isActive) {
|
||||||
|
// keyboard navigation, wait for keyup to complete cursor move
|
||||||
|
const oldText = this.textarea.value;
|
||||||
|
await new Promise(resolve=>{
|
||||||
|
window.addEventListener('keyup', resolve, { once:true });
|
||||||
|
});
|
||||||
|
if (this.selectionStart != this.textarea.selectionStart) {
|
||||||
|
this.selectionStart = this.textarea.selectionStart;
|
||||||
|
this.show(this.isReplaceable || oldText != this.textarea.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (this.isActive) {
|
||||||
|
this.text != this.textarea.value && this.show(this.isReplaceable);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
public/scripts/autocomplete/AutoCompleteFuzzyScore.js
Normal file
16
public/scripts/autocomplete/AutoCompleteFuzzyScore.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class AutoCompleteFuzzyScore {
|
||||||
|
/**@type {number}*/ start;
|
||||||
|
/**@type {number}*/ longestConsecutive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} start
|
||||||
|
* @param {number} longestConsecutive
|
||||||
|
*/
|
||||||
|
constructor(start, longestConsecutive) {
|
||||||
|
this.start = start;
|
||||||
|
this.longestConsecutive = longestConsecutive;
|
||||||
|
}
|
||||||
|
}
|
44
public/scripts/autocomplete/AutoCompleteNameResult.js
Normal file
44
public/scripts/autocomplete/AutoCompleteNameResult.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
|
||||||
|
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||||
|
// import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class AutoCompleteNameResult {
|
||||||
|
/**@type {string} */ name;
|
||||||
|
/**@type {number} */ start;
|
||||||
|
/**@type {AutoCompleteOption[]} */ optionList = [];
|
||||||
|
/**@type {boolean} */ canBeQuoted = false;
|
||||||
|
/**@type {()=>string} */ makeNoMatchText = ()=>`No matches found for "${this.name}"`;
|
||||||
|
/**@type {()=>string} */ makeNoOptionstext = ()=>'No options';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name Name (potentially partial) of the name at the requested index.
|
||||||
|
* @param {number} start Index where the name starts.
|
||||||
|
* @param {AutoCompleteOption[]} optionList A list of autocomplete options found in the current scope.
|
||||||
|
* @param {boolean} canBeQuoted Whether the name can be inside quotes.
|
||||||
|
* @param {()=>string} makeNoMatchText Function that returns text to show when no matches where found.
|
||||||
|
* @param {()=>string} makeNoOptionsText Function that returns text to show when no options are available to match against.
|
||||||
|
*/
|
||||||
|
constructor(name, start, optionList = [], canBeQuoted = false, makeNoMatchText = null, makeNoOptionsText = null) {
|
||||||
|
this.name = name;
|
||||||
|
this.start = start;
|
||||||
|
this.optionList = optionList;
|
||||||
|
this.canBeQuoted = canBeQuoted;
|
||||||
|
this.noMatchText = makeNoMatchText ?? this.makeNoMatchText;
|
||||||
|
this.noOptionstext = makeNoOptionsText ?? this.makeNoOptionstext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} text The whole text
|
||||||
|
* @param {number} index Cursor index within text
|
||||||
|
* @param {boolean} isSelect Whether autocomplete was triggered by selecting an autocomplete option
|
||||||
|
* @returns {AutoCompleteSecondaryNameResult}
|
||||||
|
*/
|
||||||
|
getSecondaryNameAt(text, index, isSelect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
206
public/scripts/autocomplete/AutoCompleteOption.js
Normal file
206
public/scripts/autocomplete/AutoCompleteOption.js
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import { SlashCommand } from '../slash-commands/SlashCommand.js';
|
||||||
|
import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class AutoCompleteOption {
|
||||||
|
/**@type {string}*/ name;
|
||||||
|
/**@type {string}*/ typeIcon;
|
||||||
|
/**@type {number}*/ nameOffset = 0;
|
||||||
|
/**@type {AutoCompleteFuzzyScore}*/ score;
|
||||||
|
/**@type {string}*/ replacer;
|
||||||
|
/**@type {HTMLElement}*/ dom;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a comparison value when removing duplicates (e.g., when a SlashCommand has aliases).
|
||||||
|
* @type {any}
|
||||||
|
* */
|
||||||
|
get value() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
constructor(name, typeIcon = ' ') {
|
||||||
|
this.name = name;
|
||||||
|
this.typeIcon = typeIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
makeItem(key, typeIcon, noSlash, namedArguments = [], unnamedArguments = [], returnType = 'void', helpString = '', aliasList = []) {
|
||||||
|
const li = document.createElement('li'); {
|
||||||
|
li.classList.add('item');
|
||||||
|
const type = document.createElement('span'); {
|
||||||
|
type.classList.add('type');
|
||||||
|
type.classList.add('monospace');
|
||||||
|
type.textContent = typeIcon;
|
||||||
|
li.append(type);
|
||||||
|
}
|
||||||
|
const specs = document.createElement('span'); {
|
||||||
|
specs.classList.add('specs');
|
||||||
|
const name = document.createElement('span'); {
|
||||||
|
name.classList.add('name');
|
||||||
|
name.classList.add('monospace');
|
||||||
|
name.textContent = noSlash ? '' : '/';
|
||||||
|
key.split('').forEach(char=>{
|
||||||
|
const span = document.createElement('span'); {
|
||||||
|
span.textContent = char;
|
||||||
|
name.append(span);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
specs.append(name);
|
||||||
|
}
|
||||||
|
const body = document.createElement('span'); {
|
||||||
|
body.classList.add('body');
|
||||||
|
const args = document.createElement('span'); {
|
||||||
|
args.classList.add('arguments');
|
||||||
|
for (const arg of namedArguments) {
|
||||||
|
const argItem = document.createElement('span'); {
|
||||||
|
argItem.classList.add('argument');
|
||||||
|
argItem.classList.add('namedArgument');
|
||||||
|
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
|
||||||
|
if (arg.acceptsMultiple) argItem.classList.add('multiple');
|
||||||
|
const name = document.createElement('span'); {
|
||||||
|
name.classList.add('argument-name');
|
||||||
|
name.textContent = arg.name;
|
||||||
|
argItem.append(name);
|
||||||
|
}
|
||||||
|
if (arg.enumList.length > 0) {
|
||||||
|
const enums = document.createElement('span'); {
|
||||||
|
enums.classList.add('argument-enums');
|
||||||
|
for (const e of arg.enumList) {
|
||||||
|
const enumItem = document.createElement('span'); {
|
||||||
|
enumItem.classList.add('argument-enum');
|
||||||
|
enumItem.textContent = e;
|
||||||
|
enums.append(enumItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argItem.append(enums);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const types = document.createElement('span'); {
|
||||||
|
types.classList.add('argument-types');
|
||||||
|
for (const t of arg.typeList) {
|
||||||
|
const type = document.createElement('span'); {
|
||||||
|
type.classList.add('argument-type');
|
||||||
|
type.textContent = t;
|
||||||
|
types.append(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argItem.append(types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.append(argItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const arg of unnamedArguments) {
|
||||||
|
const argItem = document.createElement('span'); {
|
||||||
|
argItem.classList.add('argument');
|
||||||
|
argItem.classList.add('unnamedArgument');
|
||||||
|
if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
|
||||||
|
if (arg.acceptsMultiple) argItem.classList.add('multiple');
|
||||||
|
if (arg.enumList.length > 0) {
|
||||||
|
const enums = document.createElement('span'); {
|
||||||
|
enums.classList.add('argument-enums');
|
||||||
|
for (const e of arg.enumList) {
|
||||||
|
const enumItem = document.createElement('span'); {
|
||||||
|
enumItem.classList.add('argument-enum');
|
||||||
|
enumItem.textContent = e;
|
||||||
|
enums.append(enumItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argItem.append(enums);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const types = document.createElement('span'); {
|
||||||
|
types.classList.add('argument-types');
|
||||||
|
for (const t of arg.typeList) {
|
||||||
|
const type = document.createElement('span'); {
|
||||||
|
type.classList.add('argument-type');
|
||||||
|
type.textContent = t;
|
||||||
|
types.append(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argItem.append(types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.append(argItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.append(args);
|
||||||
|
}
|
||||||
|
const returns = document.createElement('span'); {
|
||||||
|
returns.classList.add('returns');
|
||||||
|
returns.textContent = returnType ?? 'void';
|
||||||
|
// body.append(returns);
|
||||||
|
}
|
||||||
|
specs.append(body);
|
||||||
|
}
|
||||||
|
li.append(specs);
|
||||||
|
}
|
||||||
|
const help = document.createElement('span'); {
|
||||||
|
help.classList.add('help');
|
||||||
|
const content = document.createElement('span'); {
|
||||||
|
content.classList.add('helpContent');
|
||||||
|
content.innerHTML = helpString;
|
||||||
|
const text = content.textContent;
|
||||||
|
content.innerHTML = '';
|
||||||
|
content.textContent = text;
|
||||||
|
help.append(content);
|
||||||
|
}
|
||||||
|
li.append(help);
|
||||||
|
}
|
||||||
|
if (aliasList.length > 0) {
|
||||||
|
const aliases = document.createElement('span'); {
|
||||||
|
aliases.classList.add('aliases');
|
||||||
|
aliases.append(' (alias: ');
|
||||||
|
for (const aliasName of aliasList) {
|
||||||
|
const alias = document.createElement('span'); {
|
||||||
|
alias.classList.add('monospace');
|
||||||
|
alias.textContent = `/${aliasName}`;
|
||||||
|
aliases.append(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aliases.append(')');
|
||||||
|
// li.append(aliases);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
renderItem() {
|
||||||
|
// throw new Error(`${this.constructor.name}.renderItem() is not implemented`);
|
||||||
|
let li;
|
||||||
|
li = this.makeItem(this.name, this.typeIcon, true);
|
||||||
|
li.setAttribute('data-name', this.name);
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {DocumentFragment}
|
||||||
|
*/
|
||||||
|
renderDetails() {
|
||||||
|
// throw new Error(`${this.constructor.name}.renderDetails() is not implemented`);
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
const specs = document.createElement('div'); {
|
||||||
|
specs.classList.add('specs');
|
||||||
|
const name = document.createElement('div'); {
|
||||||
|
name.classList.add('name');
|
||||||
|
name.classList.add('monospace');
|
||||||
|
name.textContent = this.name;
|
||||||
|
specs.append(name);
|
||||||
|
}
|
||||||
|
frag.append(specs);
|
||||||
|
}
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
|
||||||
|
|
||||||
|
export class AutoCompleteSecondaryNameResult extends AutoCompleteNameResult {
|
||||||
|
/**@type {boolean}*/ isRequired = false;
|
||||||
|
}
|
29
public/scripts/autocomplete/BlankAutoCompleteOption.js
Normal file
29
public/scripts/autocomplete/BlankAutoCompleteOption.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||||
|
|
||||||
|
export class BlankAutoCompleteOption extends AutoCompleteOption {
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
constructor(name) {
|
||||||
|
super(name);
|
||||||
|
this.dom = this.renderItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() { return null; }
|
||||||
|
|
||||||
|
|
||||||
|
renderItem() {
|
||||||
|
const li = document.createElement('li'); {
|
||||||
|
li.classList.add('item');
|
||||||
|
li.classList.add('blank');
|
||||||
|
li.textContent = this.name;
|
||||||
|
}
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
renderDetails() {
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
}
|
44
public/scripts/autocomplete/MacroAutoCompleteOption.js
Normal file
44
public/scripts/autocomplete/MacroAutoCompleteOption.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { AutoCompleteOption } from './AutoCompleteOption.js';
|
||||||
|
|
||||||
|
export class MacroAutoCompleteOption extends AutoCompleteOption {
|
||||||
|
/**@type {string}*/ fullName;
|
||||||
|
/**@type {string}*/ description;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(name, fullName, description) {
|
||||||
|
super(name, '{}');
|
||||||
|
this.fullName = fullName;
|
||||||
|
this.description = description;
|
||||||
|
this.nameOffset = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
renderItem() {
|
||||||
|
let li;
|
||||||
|
li = this.makeItem(`${this.fullName}`, '{}', true, [], [], null, this.description);
|
||||||
|
li.setAttribute('data-name', this.name);
|
||||||
|
li.setAttribute('data-option-type', 'macro');
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
renderDetails() {
|
||||||
|
const frag = document.createDocumentFragment();
|
||||||
|
const specs = document.createElement('div'); {
|
||||||
|
specs.classList.add('specs');
|
||||||
|
const name = document.createElement('div'); {
|
||||||
|
name.classList.add('name');
|
||||||
|
name.classList.add('monospace');
|
||||||
|
name.textContent = this.fullName;
|
||||||
|
specs.append(name);
|
||||||
|
}
|
||||||
|
frag.append(specs);
|
||||||
|
}
|
||||||
|
const help = document.createElement('span'); {
|
||||||
|
help.classList.add('help');
|
||||||
|
help.innerHTML = this.description;
|
||||||
|
frag.append(help);
|
||||||
|
}
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,8 @@
|
|||||||
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
|
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
|
||||||
import { saveMetadataDebounced } from './extensions.js';
|
import { saveMetadataDebounced } from './extensions.js';
|
||||||
import { registerSlashCommand } from './slash-commands.js';
|
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||||
import { stringFormat } from './utils.js';
|
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||||
|
import { flashHighlight, stringFormat } from './utils.js';
|
||||||
|
|
||||||
const BG_METADATA_KEY = 'custom_background';
|
const BG_METADATA_KEY = 'custom_background';
|
||||||
const LIST_METADATA_KEY = 'chat_backgrounds';
|
const LIST_METADATA_KEY = 'chat_backgrounds';
|
||||||
@@ -453,8 +454,7 @@ function highlightNewBackground(bg) {
|
|||||||
const newBg = $(`.bg_example[bgfile="${bg}"]`);
|
const newBg = $(`.bg_example[bgfile="${bg}"]`);
|
||||||
const scrollOffset = newBg.offset().top - newBg.parent().offset().top;
|
const scrollOffset = newBg.offset().top - newBg.parent().offset().top;
|
||||||
$('#Backgrounds').scrollTop(scrollOffset);
|
$('#Backgrounds').scrollTop(scrollOffset);
|
||||||
newBg.addClass('flash animated');
|
flashHighlight(newBg);
|
||||||
setTimeout(() => newBg.removeClass('flash animated'), 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBackgroundFilterInput() {
|
function onBackgroundFilterInput() {
|
||||||
@@ -481,7 +481,20 @@ export function initBackgrounds() {
|
|||||||
$('#auto_background').on('click', autoBackgroundCommand);
|
$('#auto_background').on('click', autoBackgroundCommand);
|
||||||
$('#add_bg_button').on('change', onBackgroundUploadSelected);
|
$('#add_bg_button').on('change', onBackgroundUploadSelected);
|
||||||
$('#bg-filter').on('input', onBackgroundFilterInput);
|
$('#bg-filter').on('input', onBackgroundFilterInput);
|
||||||
registerSlashCommand('lockbg', onLockBackgroundClick, ['bglock'], '– locks a background for the currently selected chat', true, true);
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg',
|
||||||
registerSlashCommand('unlockbg', onUnlockBackgroundClick, ['bgunlock'], '– unlocks a background for the currently selected chat', true, true);
|
callback: onLockBackgroundClick,
|
||||||
registerSlashCommand('autobg', autoBackgroundCommand, ['bgauto'], '– automatically changes the background based on the chat context using the AI request prompt', true, true);
|
aliases: ['bglock'],
|
||||||
|
helpString: 'Locks a background for the currently selected chat',
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg',
|
||||||
|
callback: onUnlockBackgroundClick,
|
||||||
|
aliases: ['bgunlock'],
|
||||||
|
helpString: 'Unlocks a background for the currently selected chat',
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'autobg',
|
||||||
|
callback: autoBackgroundCommand,
|
||||||
|
aliases: ['bgauto'],
|
||||||
|
helpString: 'Automatically changes the background based on the chat context using the AI request prompt',
|
||||||
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
98
public/scripts/char-data.js
Normal file
98
public/scripts/char-data.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {object} v2DataWorldInfoEntry
|
||||||
|
* @property {string[]} keys - An array of primary keys associated with the entry.
|
||||||
|
* @property {string[]} secondary_keys - An array of secondary keys associated with the entry (optional).
|
||||||
|
* @property {string} comment - A human-readable description or explanation for the entry.
|
||||||
|
* @property {string} content - The main content or data associated with the entry.
|
||||||
|
* @property {boolean} constant - Indicates if the entry's content is fixed and unchangeable.
|
||||||
|
* @property {boolean} selective - Indicates if the entry's inclusion is controlled by specific conditions.
|
||||||
|
* @property {number} insertion_order - Defines the order in which the entry is inserted during processing.
|
||||||
|
* @property {boolean} enabled - Controls whether the entry is currently active and used.
|
||||||
|
* @property {string} position - Specifies the location or context where the entry applies.
|
||||||
|
* @property {v2DataWorldInfoEntryExtensionInfos} extensions - An object containing additional details for extensions associated with the entry.
|
||||||
|
* @property {number} id - A unique identifier assigned to the entry.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {object} v2DataWorldInfoEntryExtensionInfos
|
||||||
|
* @property {number} position - The order in which the extension is applied relative to other extensions.
|
||||||
|
* @property {boolean} exclude_recursion - Prevents the extension from being applied recursively.
|
||||||
|
* @property {number} probability - The chance (between 0 and 1) of the extension being applied.
|
||||||
|
* @property {boolean} useProbability - Determines if the `probability` property is used.
|
||||||
|
* @property {number} depth - The maximum level of nesting allowed for recursive application of the extension.
|
||||||
|
* @property {number} selectiveLogic - Defines the logic used to determine if the extension is applied selectively.
|
||||||
|
* @property {string} group - A category or grouping for the extension.
|
||||||
|
* @property {boolean} group_override - Overrides any existing group assignment for the extension.
|
||||||
|
* @property {number} group_weight - A value used for prioritizing extensions within the same group.
|
||||||
|
* @property {boolean} prevent_recursion - Completely disallows recursive application of the extension.
|
||||||
|
* @property {boolean} delay_until_recursion - Will only be checked during recursion.
|
||||||
|
* @property {number} scan_depth - The maximum depth to search for matches when applying the extension.
|
||||||
|
* @property {boolean} match_whole_words - Specifies if only entire words should be matched during extension application.
|
||||||
|
* @property {boolean} use_group_scoring - Indicates if group weight is considered when selecting extensions.
|
||||||
|
* @property {boolean} case_sensitive - Controls whether case sensitivity is applied during matching for the extension.
|
||||||
|
* @property {string} automation_id - An identifier used for automation purposes related to the extension.
|
||||||
|
* @property {number} role - The specific function or purpose of the extension.
|
||||||
|
* @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing.
|
||||||
|
* @property {number} display_index - The order in which the extension should be displayed for user interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} v2WorldInfoBook
|
||||||
|
* @property {string} name - the name of the book
|
||||||
|
* @property {v2DataWorldInfoEntry[]} entries - the entries of the book
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} v2CharData
|
||||||
|
* @property {string} name - The character's name.
|
||||||
|
* @property {string} description - A brief description of the character.
|
||||||
|
* @property {string} character_version - The character's data version.
|
||||||
|
* @property {string} personality - A short summary of the character's personality traits.
|
||||||
|
* @property {string} scenario - A description of the character's background or setting.
|
||||||
|
* @property {string} first_mes - The character's opening message in a conversation.
|
||||||
|
* @property {string} mes_example - An example message demonstrating the character's conversation style.
|
||||||
|
* @property {string} creator_notes - Internal notes or comments left by the character's creator.
|
||||||
|
* @property {string[]} tags - A list of keywords or labels associated with the character.
|
||||||
|
* @property {string} system_prompt - The system prompt used to interact with the character.
|
||||||
|
* @property {string} post_history_instructions - Instructions for handling the character's conversation history.
|
||||||
|
* @property {string} creator - The name of the person who created the character.
|
||||||
|
* @property {string[]} alternate_greetings - Additional greeting messages the character can use.
|
||||||
|
* @property {v2WorldInfoBook} character_book - Data about the character's world or story (if applicable).
|
||||||
|
* @property {v2CharDataExtensionInfos} extensions - Additional details specific to the character.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {object} v2CharDataExtensionInfos
|
||||||
|
* @property {number} talkativeness - A numerical value indicating the character's propensity to talk.
|
||||||
|
* @property {boolean} fav - A flag indicating whether the character is a favorite.
|
||||||
|
* @property {string} world - The fictional world or setting where the character exists (if applicable).
|
||||||
|
* @property {object} depth_prompt - Prompts used to explore the character's depth and complexity.
|
||||||
|
* @property {number} depth_prompt.depth - The level of detail or nuance targeted by the prompt.
|
||||||
|
* @property {string} depth_prompt.prompt - The actual prompt text used for deeper character interaction.
|
||||||
|
* @property {"system" | "user" | "assistant"} depth_prompt.role - The role the character takes on during the prompted interaction (system, user, or assistant).
|
||||||
|
* // Non-standard extensions added by external tools
|
||||||
|
* @property {string} [pygmalion_id] - The unique identifier assigned to the character by the Pygmalion.chat.
|
||||||
|
* @property {string} [github_repo] - The gitHub repository associated with the character.
|
||||||
|
* @property {string} [source_url] - The source URL associated with the character.
|
||||||
|
* @property {{full_path: string}} [chub] - The Chub-specific data associated with the character.
|
||||||
|
* @property {{source: string[]}} [risuai] - The RisuAI-specific data associated with the character.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} v1CharData
|
||||||
|
* @property {string} name - the name of the character
|
||||||
|
* @property {string} description - the description of the character
|
||||||
|
* @property {string} personality - a short personality description of the character
|
||||||
|
* @property {string} scenario - a scenario description of the character
|
||||||
|
* @property {string} first_mes - the first message in the conversation
|
||||||
|
* @property {string} mes_example - the example message in the conversation
|
||||||
|
* @property {string} creatorcomment - creator's notes of the character
|
||||||
|
* @property {string[]} tags - the tags of the character
|
||||||
|
* @property {number} talkativeness - talkativeness
|
||||||
|
* @property {boolean|string} fav - fav
|
||||||
|
* @property {string} create_date - create_date
|
||||||
|
* @property {v2CharData} data - v2 data extension
|
||||||
|
* // Non-standard extensions added by the ST server (not part of the original data)
|
||||||
|
* @property {string} chat - name of the current chat file chat
|
||||||
|
* @property {string} avatar - file name of the avatar image (acts as a unique identifier)
|
||||||
|
* @property {string} json_data - the full raw JSON data of the character
|
||||||
|
*/
|
||||||
|
export default 0;// now this file is a module
|
@@ -53,11 +53,11 @@ import { ScraperManager } from './scrapers.js';
|
|||||||
* @returns {Promise<string>} Converted file text
|
* @returns {Promise<string>} Converted file text
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fileSizeLimit = 1024 * 1024 * 10; // 10 MB
|
const fileSizeLimit = 1024 * 1024 * 100; // 100 MB
|
||||||
const ATTACHMENT_SOURCE = {
|
const ATTACHMENT_SOURCE = {
|
||||||
GLOBAL: 'global',
|
GLOBAL: 'global',
|
||||||
CHAT: 'chat',
|
|
||||||
CHARACTER: 'character',
|
CHARACTER: 'character',
|
||||||
|
CHAT: 'chat',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -592,9 +592,10 @@ async function deleteMessageImage() {
|
|||||||
/**
|
/**
|
||||||
* Deletes file from the server.
|
* Deletes file from the server.
|
||||||
* @param {string} url Path to the file on the server
|
* @param {string} url Path to the file on the server
|
||||||
|
* @param {boolean} [silent=false] If true, do not show error messages
|
||||||
* @returns {Promise<boolean>} True if file was deleted, false otherwise.
|
* @returns {Promise<boolean>} True if file was deleted, false otherwise.
|
||||||
*/
|
*/
|
||||||
async function deleteFileFromServer(url) {
|
async function deleteFileFromServer(url, silent = false) {
|
||||||
try {
|
try {
|
||||||
const result = await fetch('/api/files/delete', {
|
const result = await fetch('/api/files/delete', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -602,7 +603,7 @@ async function deleteFileFromServer(url) {
|
|||||||
body: JSON.stringify({ path: url }),
|
body: JSON.stringify({ path: url }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok && !silent) {
|
||||||
const error = await result.text();
|
const error = await result.text();
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
@@ -669,6 +670,79 @@ async function editAttachment(attachment, source, callback) {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads an attachment to the user's device.
|
||||||
|
* @param {FileAttachment} attachment Attachment to download
|
||||||
|
*/
|
||||||
|
async function downloadAttachment(attachment) {
|
||||||
|
const fileText = attachment.text || (await getFileAttachment(attachment.url));
|
||||||
|
const blob = new Blob([fileText], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = attachment.name;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an attachment from the disabled list.
|
||||||
|
* @param {FileAttachment} attachment Attachment to enable
|
||||||
|
* @param {function} callback Success callback
|
||||||
|
*/
|
||||||
|
function enableAttachment(attachment, callback) {
|
||||||
|
ensureAttachmentsExist();
|
||||||
|
extension_settings.disabled_attachments = extension_settings.disabled_attachments.filter(url => url !== attachment.url);
|
||||||
|
saveSettingsDebounced();
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attachment to the disabled list.
|
||||||
|
* @param {FileAttachment} attachment Attachment to disable
|
||||||
|
* @param {function} callback Success callback
|
||||||
|
*/
|
||||||
|
function disableAttachment(attachment, callback) {
|
||||||
|
ensureAttachmentsExist();
|
||||||
|
extension_settings.disabled_attachments.push(attachment.url);
|
||||||
|
saveSettingsDebounced();
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a file attachment to a different source.
|
||||||
|
* @param {FileAttachment} attachment Attachment to moves
|
||||||
|
* @param {string} source Source of the attachment
|
||||||
|
* @param {function} callback Success callback
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the attachment is moved.
|
||||||
|
*/
|
||||||
|
async function moveAttachment(attachment, source, callback) {
|
||||||
|
let selectedTarget = source;
|
||||||
|
const targets = getAvailableTargets();
|
||||||
|
const template = $(await renderExtensionTemplateAsync('attachments', 'move-attachment', { name: attachment.name, targets }));
|
||||||
|
template.find('.moveAttachmentTarget').val(source).on('input', function () {
|
||||||
|
selectedTarget = String($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { wide: false, large: false, okButton: 'Move', cancelButton: 'Cancel' });
|
||||||
|
|
||||||
|
if (result !== POPUP_RESULT.AFFIRMATIVE) {
|
||||||
|
console.debug('Move attachment cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedTarget === source) {
|
||||||
|
console.debug('Move attachment cancelled: same source and target');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await getFileAttachment(attachment.url);
|
||||||
|
const file = new File([content], attachment.name, { type: 'text/plain' });
|
||||||
|
await deleteAttachment(attachment, source, () => { }, false);
|
||||||
|
await uploadFileAttachmentToServer(file, selectedTarget);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an attachment from the server and the chat.
|
* Deletes an attachment from the server and the chat.
|
||||||
* @param {FileAttachment} attachment Attachment to delete
|
* @param {FileAttachment} attachment Attachment to delete
|
||||||
@@ -702,10 +776,25 @@ async function deleteAttachment(attachment, source, callback, confirm = true) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteFileFromServer(attachment.url);
|
if (Array.isArray(extension_settings.disabled_attachments) && extension_settings.disabled_attachments.includes(attachment.url)) {
|
||||||
|
extension_settings.disabled_attachments = extension_settings.disabled_attachments.filter(url => url !== attachment.url);
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
const silent = confirm === false;
|
||||||
|
await deleteFileFromServer(attachment.url, silent);
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the attachment is disabled.
|
||||||
|
* @param {FileAttachment} attachment Attachment to check
|
||||||
|
* @returns {boolean} True if attachment is disabled, false otherwise.
|
||||||
|
*/
|
||||||
|
function isAttachmentDisabled(attachment) {
|
||||||
|
return extension_settings.disabled_attachments.some(url => url === attachment?.url);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the attachment manager.
|
* Opens the attachment manager.
|
||||||
*/
|
*/
|
||||||
@@ -755,13 +844,20 @@ async function openAttachmentManager() {
|
|||||||
const sortedAttachmentList = attachments.slice().filter(filterFn).sort(sortFn);
|
const sortedAttachmentList = attachments.slice().filter(filterFn).sort(sortFn);
|
||||||
|
|
||||||
for (const attachment of sortedAttachmentList) {
|
for (const attachment of sortedAttachmentList) {
|
||||||
|
const isDisabled = isAttachmentDisabled(attachment);
|
||||||
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
|
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
|
||||||
|
attachmentTemplate.toggleClass('disabled', isDisabled);
|
||||||
|
attachmentTemplate.find('.attachmentFileIcon').attr('title', attachment.url);
|
||||||
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
|
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
|
||||||
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
|
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
|
||||||
attachmentTemplate.find('.attachmentListItemCreated').text(new Date(attachment.created).toLocaleString());
|
attachmentTemplate.find('.attachmentListItemCreated').text(new Date(attachment.created).toLocaleString());
|
||||||
attachmentTemplate.find('.viewAttachmentButton').on('click', () => openFilePopup(attachment));
|
attachmentTemplate.find('.viewAttachmentButton').on('click', () => openFilePopup(attachment));
|
||||||
attachmentTemplate.find('.editAttachmentButton').on('click', () => editAttachment(attachment, source, renderAttachments));
|
attachmentTemplate.find('.editAttachmentButton').on('click', () => editAttachment(attachment, source, renderAttachments));
|
||||||
attachmentTemplate.find('.deleteAttachmentButton').on('click', () => deleteAttachment(attachment, source, renderAttachments));
|
attachmentTemplate.find('.deleteAttachmentButton').on('click', () => deleteAttachment(attachment, source, renderAttachments));
|
||||||
|
attachmentTemplate.find('.downloadAttachmentButton').on('click', () => downloadAttachment(attachment));
|
||||||
|
attachmentTemplate.find('.moveAttachmentButton').on('click', () => moveAttachment(attachment, source, renderAttachments));
|
||||||
|
attachmentTemplate.find('.enableAttachmentButton').toggle(isDisabled).on('click', () => enableAttachment(attachment, renderAttachments));
|
||||||
|
attachmentTemplate.find('.disableAttachmentButton').toggle(!isDisabled).on('click', () => disableAttachment(attachment, renderAttachments));
|
||||||
template.find(sources[source]).append(attachmentTemplate);
|
template.find(sources[source]).append(attachmentTemplate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,7 +882,13 @@ async function openAttachmentManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buttonTemplate = template.find('.actionButtonTemplate .actionButton').clone();
|
const buttonTemplate = template.find('.actionButtonTemplate .actionButton').clone();
|
||||||
buttonTemplate.find('.actionButtonIcon').addClass(scraper.iconClass);
|
if (scraper.iconAvailable) {
|
||||||
|
buttonTemplate.find('.actionButtonIcon').addClass(scraper.iconClass);
|
||||||
|
buttonTemplate.find('.actionButtonImg').remove();
|
||||||
|
} else {
|
||||||
|
buttonTemplate.find('.actionButtonImg').attr('src', scraper.iconClass);
|
||||||
|
buttonTemplate.find('.actionButtonIcon').remove();
|
||||||
|
}
|
||||||
buttonTemplate.find('.actionButtonText').text(scraper.name);
|
buttonTemplate.find('.actionButtonText').text(scraper.name);
|
||||||
buttonTemplate.attr('title', scraper.description);
|
buttonTemplate.attr('title', scraper.description);
|
||||||
buttonTemplate.on('click', () => {
|
buttonTemplate.on('click', () => {
|
||||||
@@ -860,6 +962,50 @@ async function openAttachmentManager() {
|
|||||||
template.find('.chatAttachmentsName').text(chatName);
|
template.find('.chatAttachmentsName').text(chatName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addDragAndDrop() {
|
||||||
|
$(document.body).on('dragover', '.dialogue_popup', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
$(event.target).closest('.dialogue_popup').addClass('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document.body).on('dragleave', '.dialogue_popup', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
$(event.target).closest('.dialogue_popup').removeClass('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document.body).on('drop', '.dialogue_popup', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
$(event.target).closest('.dialogue_popup').removeClass('dragover');
|
||||||
|
|
||||||
|
const files = Array.from(event.originalEvent.dataTransfer.files);
|
||||||
|
let selectedTarget = ATTACHMENT_SOURCE.GLOBAL;
|
||||||
|
const targets = getAvailableTargets();
|
||||||
|
|
||||||
|
const targetSelectTemplate = $(await renderExtensionTemplateAsync('attachments', 'files-dropped', { count: files.length, targets: targets }));
|
||||||
|
targetSelectTemplate.find('.droppedFilesTarget').on('input', function () {
|
||||||
|
selectedTarget = String($(this).val());
|
||||||
|
});
|
||||||
|
const result = await callGenericPopup(targetSelectTemplate, POPUP_TYPE.CONFIRM, '', { wide: false, large: false, okButton: 'Upload', cancelButton: 'Cancel' });
|
||||||
|
if (result !== POPUP_RESULT.AFFIRMATIVE) {
|
||||||
|
console.log('File upload cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const file of files) {
|
||||||
|
await uploadFileAttachmentToServer(file, selectedTarget);
|
||||||
|
}
|
||||||
|
renderAttachments();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDragAndDrop() {
|
||||||
|
$(document.body).off('dragover', '.shadow_popup');
|
||||||
|
$(document.body).off('dragleave', '.shadow_popup');
|
||||||
|
$(document.body).off('drop', '.shadow_popup');
|
||||||
|
}
|
||||||
|
|
||||||
let sortField = localStorage.getItem('DataBank_sortField') || 'created';
|
let sortField = localStorage.getItem('DataBank_sortField') || 'created';
|
||||||
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
|
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
|
||||||
let filterString = '';
|
let filterString = '';
|
||||||
@@ -883,10 +1029,34 @@ async function openAttachmentManager() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const cleanupFn = await renderButtons();
|
const cleanupFn = await renderButtons();
|
||||||
|
await verifyAttachments();
|
||||||
await renderAttachments();
|
await renderAttachments();
|
||||||
|
addDragAndDrop();
|
||||||
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
|
await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
|
||||||
|
|
||||||
cleanupFn();
|
cleanupFn();
|
||||||
|
removeDragAndDrop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of available targets for attachments.
|
||||||
|
* @returns {string[]} List of available targets
|
||||||
|
*/
|
||||||
|
function getAvailableTargets() {
|
||||||
|
const targets = Object.values(ATTACHMENT_SOURCE);
|
||||||
|
|
||||||
|
const isNotCharacter = this_chid === undefined || selected_group;
|
||||||
|
const isNotInChat = getCurrentChatId() === undefined;
|
||||||
|
|
||||||
|
if (isNotCharacter) {
|
||||||
|
targets.splice(targets.indexOf(ATTACHMENT_SOURCE.CHARACTER), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotInChat) {
|
||||||
|
targets.splice(targets.indexOf(ATTACHMENT_SOURCE.CHAT), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -989,6 +1159,10 @@ export async function uploadFileAttachmentToServer(file, target) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ensureAttachmentsExist() {
|
function ensureAttachmentsExist() {
|
||||||
|
if (!Array.isArray(extension_settings.disabled_attachments)) {
|
||||||
|
extension_settings.disabled_attachments = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (!Array.isArray(extension_settings.attachments)) {
|
if (!Array.isArray(extension_settings.attachments)) {
|
||||||
extension_settings.attachments = [];
|
extension_settings.attachments = [];
|
||||||
}
|
}
|
||||||
@@ -1009,7 +1183,7 @@ function ensureAttachmentsExist() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all currently available attachments.
|
* Gets all currently available attachments. Ignores disabled attachments.
|
||||||
* @returns {FileAttachment[]} List of attachments
|
* @returns {FileAttachment[]} List of attachments
|
||||||
*/
|
*/
|
||||||
export function getDataBankAttachments() {
|
export function getDataBankAttachments() {
|
||||||
@@ -1018,11 +1192,11 @@ export function getDataBankAttachments() {
|
|||||||
const chatAttachments = chat_metadata.attachments ?? [];
|
const chatAttachments = chat_metadata.attachments ?? [];
|
||||||
const characterAttachments = extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
const characterAttachments = extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
||||||
|
|
||||||
return [...globalAttachments, ...chatAttachments, ...characterAttachments];
|
return [...globalAttachments, ...chatAttachments, ...characterAttachments].filter(x => !isAttachmentDisabled(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all attachments for a specific source.
|
* Gets all attachments for a specific source. Includes disabled attachments.
|
||||||
* @param {string} source Attachment source
|
* @param {string} source Attachment source
|
||||||
* @returns {FileAttachment[]} List of attachments
|
* @returns {FileAttachment[]} List of attachments
|
||||||
*/
|
*/
|
||||||
@@ -1037,6 +1211,50 @@ export function getDataBankAttachmentsForSource(source) {
|
|||||||
case ATTACHMENT_SOURCE.CHARACTER:
|
case ATTACHMENT_SOURCE.CHARACTER:
|
||||||
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
return extension_settings.character_attachments?.[characters[this_chid]?.avatar] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies all attachments in the Data Bank.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when attachments are verified.
|
||||||
|
*/
|
||||||
|
async function verifyAttachments() {
|
||||||
|
for (const source of Object.values(ATTACHMENT_SOURCE)) {
|
||||||
|
await verifyAttachmentsForSource(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies all attachments for a specific source.
|
||||||
|
* @param {string} source Attachment source
|
||||||
|
* @returns {Promise<void>} A promise that resolves when attachments are verified.
|
||||||
|
*/
|
||||||
|
async function verifyAttachmentsForSource(source) {
|
||||||
|
try {
|
||||||
|
const attachments = getDataBankAttachmentsForSource(source);
|
||||||
|
const urls = attachments.map(a => a.url);
|
||||||
|
const response = await fetch('/api/files/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({ urls }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const verifiedUrls = await response.json();
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (verifiedUrls[attachment.url] === false) {
|
||||||
|
console.log('Deleting orphaned attachment', attachment);
|
||||||
|
await deleteAttachment(attachment, source, () => { }, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Attachment verification failed', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1116,6 +1334,7 @@ jQuery(function () {
|
|||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = String(bro.val());
|
textarea.value = String(bro.val());
|
||||||
textarea.classList.add('height100p', 'wide100p');
|
textarea.classList.add('height100p', 'wide100p');
|
||||||
|
bro.hasClass('monospace') && textarea.classList.add('monospace');
|
||||||
textarea.addEventListener('input', function () {
|
textarea.addEventListener('input', function () {
|
||||||
bro.val(textarea.value).trigger('input');
|
bro.val(textarea.value).trigger('input');
|
||||||
});
|
});
|
||||||
|
14
public/scripts/constants.js
Normal file
14
public/scripts/constants.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Common debounce timeout values to use with `debounce` calls.
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
export const debounce_timeout = {
|
||||||
|
/** [100 ms] For ultra-fast responses, typically for keypresses or executions that might happen multiple times in a loop or recursion. */
|
||||||
|
quick: 100,
|
||||||
|
/** [300 ms] Default time for general use, good balance between responsiveness and performance. */
|
||||||
|
standard: 300,
|
||||||
|
/** [1.000 ms] For situations where the function triggers more intensive tasks. */
|
||||||
|
relaxed: 1000,
|
||||||
|
/** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */
|
||||||
|
extended: 5000,
|
||||||
|
};
|
@@ -145,8 +145,18 @@ const extension_settings = {
|
|||||||
variables: {
|
variables: {
|
||||||
global: {},
|
global: {},
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @type {import('./chats.js').FileAttachment[]}
|
||||||
|
*/
|
||||||
attachments: [],
|
attachments: [],
|
||||||
|
/**
|
||||||
|
* @type {Record<string, import('./chats.js').FileAttachment[]>}
|
||||||
|
*/
|
||||||
character_attachments: {},
|
character_attachments: {},
|
||||||
|
/**
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
disabled_attachments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
let modules = [];
|
let modules = [];
|
||||||
|
9
public/scripts/extensions/assets/character.html
Normal file
9
public/scripts/extensions/assets/character.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<div class="characterAsset">
|
||||||
|
<div class="characterAssetName">{{name}}</div>
|
||||||
|
<img class="characterAssetImage" alt="{{name}}" src="{{url}}" />
|
||||||
|
<div class="characterAssetDescription" title="{{description}}">{{description}}</div>
|
||||||
|
<div class="characterAssetButtons flex-container">
|
||||||
|
<div class="characterAssetDownloadButton right_menu_button fa-fw fa-solid fa-download" title="Download"></div>
|
||||||
|
<div class="characterAssetCheckMark right_menu_button fa-fw fa-solid fa-check" title="Installed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -3,8 +3,9 @@ TODO:
|
|||||||
*/
|
*/
|
||||||
//const DEBUG_TONY_SAMA_FORK_MODE = true
|
//const DEBUG_TONY_SAMA_FORK_MODE = true
|
||||||
|
|
||||||
import { getRequestHeaders, callPopup, processDroppedFiles } from '../../../script.js';
|
import { getRequestHeaders, callPopup, processDroppedFiles, eventSource, event_types } from '../../../script.js';
|
||||||
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js';
|
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
|
import { POPUP_TYPE, callGenericPopup } from '../../popup.js';
|
||||||
import { executeSlashCommands } from '../../slash-commands.js';
|
import { executeSlashCommands } from '../../slash-commands.js';
|
||||||
import { getStringHash, isValidUrl } from '../../utils.js';
|
import { getStringHash, isValidUrl } from '../../utils.js';
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
@@ -108,7 +109,7 @@ function downloadAssetsList(url) {
|
|||||||
</div>`);
|
</div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const i in availableAssets[assetType]) {
|
for (const i in availableAssets[assetType].sort((a, b) => a?.name && b?.name && a['name'].localeCompare(b['name']))) {
|
||||||
const asset = availableAssets[assetType][i];
|
const asset = availableAssets[assetType][i];
|
||||||
const elemId = `assets_install_${assetType}_${i}`;
|
const elemId = `assets_install_${assetType}_${i}`;
|
||||||
let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
|
let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
|
||||||
@@ -199,6 +200,9 @@ function downloadAssetsList(url) {
|
|||||||
</div>`);
|
</div>`);
|
||||||
|
|
||||||
if (assetType === 'character') {
|
if (assetType === 'character') {
|
||||||
|
if (asset.highlight) {
|
||||||
|
assetBlock.find('.asset-name').append('<i class="fa-solid fa-sm fa-trophy"></i>');
|
||||||
|
}
|
||||||
assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['url']}" alt="${displayName}"></div>`);
|
assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['url']}" alt="${displayName}"></div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,6 +332,41 @@ async function deleteAsset(assetType, filename) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openCharacterBrowser(forceDefault) {
|
||||||
|
const url = forceDefault ? ASSETS_JSON_URL : String($('#assets-json-url-field').val());
|
||||||
|
const fetchResult = await fetch(url, { cache: 'no-cache' });
|
||||||
|
const json = await fetchResult.json();
|
||||||
|
const characters = json.filter(x => x.type === 'character');
|
||||||
|
|
||||||
|
if (!characters.length) {
|
||||||
|
toastr.error('No characters found in the assets list', 'Character browser');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'market', {}));
|
||||||
|
|
||||||
|
for (const character of characters.sort((a, b) => a.name.localeCompare(b.name))) {
|
||||||
|
const listElement = template.find(character.highlight ? '.contestWinnersList' : '.featuredCharactersList');
|
||||||
|
const characterElement = $(await renderExtensionTemplateAsync(MODULE_NAME, 'character', character));
|
||||||
|
const downloadButton = characterElement.find('.characterAssetDownloadButton');
|
||||||
|
const checkMark = characterElement.find('.characterAssetCheckMark');
|
||||||
|
const isInstalled = isAssetInstalled('character', character.id);
|
||||||
|
|
||||||
|
downloadButton.toggle(!isInstalled).on('click', async () => {
|
||||||
|
downloadButton.toggleClass('fa-download fa-spinner fa-spin');
|
||||||
|
await installAsset(character.url, 'character', character.id);
|
||||||
|
downloadButton.hide();
|
||||||
|
checkMark.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
checkMark.toggle(isInstalled);
|
||||||
|
|
||||||
|
listElement.append(characterElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
callGenericPopup(template, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: true, large: true, allowVerticalScrolling: true, allowHorizontalScrolling: false });
|
||||||
|
}
|
||||||
|
|
||||||
//#############################//
|
//#############################//
|
||||||
// API Calls //
|
// API Calls //
|
||||||
//#############################//
|
//#############################//
|
||||||
@@ -361,6 +400,11 @@ jQuery(async () => {
|
|||||||
const assetsJsonUrl = windowHtml.find('#assets-json-url-field');
|
const assetsJsonUrl = windowHtml.find('#assets-json-url-field');
|
||||||
assetsJsonUrl.val(ASSETS_JSON_URL);
|
assetsJsonUrl.val(ASSETS_JSON_URL);
|
||||||
|
|
||||||
|
const charactersButton = windowHtml.find('#assets-characters-button');
|
||||||
|
charactersButton.on('click', async function () {
|
||||||
|
openCharacterBrowser(false);
|
||||||
|
});
|
||||||
|
|
||||||
const connectButton = windowHtml.find('#assets-connect-button');
|
const connectButton = windowHtml.find('#assets-connect-button');
|
||||||
connectButton.on('click', async function () {
|
connectButton.on('click', async function () {
|
||||||
const url = String(assetsJsonUrl.val());
|
const url = String(assetsJsonUrl.val());
|
||||||
@@ -397,4 +441,8 @@ jQuery(async () => {
|
|||||||
|
|
||||||
windowHtml.find('#assets_filters').hide();
|
windowHtml.find('#assets_filters').hide();
|
||||||
$('#extensions_settings').append(windowHtml);
|
$('#extensions_settings').append(windowHtml);
|
||||||
|
|
||||||
|
eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {
|
||||||
|
openCharacterBrowser(forceDefault);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
19
public/scripts/extensions/assets/market.html
Normal file
19
public/scripts/extensions/assets/market.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<div class="flex-container flexFlowColumn padding5">
|
||||||
|
<div class="contestWinners flex-container flexFlowColumn">
|
||||||
|
<h3 class="flex-container alignItemsBaseline justifyCenter" title="These characters are the winners of character design contests and have outstandable quality.">
|
||||||
|
<span data-i18n="Contest Winners">Contest Winners</span>
|
||||||
|
<i class="fa-solid fa-star"></i>
|
||||||
|
</h3>
|
||||||
|
<div class="contestWinnersList characterAssetList">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="featuredCharacters flex-container flexFlowColumn">
|
||||||
|
<h3 class="flex-container alignItemsBaseline justifyCenter" title="These characters are the finalists of character design contests and have remarkable quality.">
|
||||||
|
<span data-i18n="Featured Characters">Featured Characters</span>
|
||||||
|
<i class="fa-solid fa-thumbs-up"></i>
|
||||||
|
</h3>
|
||||||
|
<div class="featuredCharactersList characterAssetList">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -105,3 +105,54 @@
|
|||||||
transform: rotate(1turn);
|
transform: rotate(1turn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.characterAssetList {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characterAsset {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
gap: 10px;
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
background-color: var(--black30a);
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 17%;
|
||||||
|
min-width: 150px;
|
||||||
|
margin: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characterAssetName {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characterAssetImage {
|
||||||
|
max-height: 140px;
|
||||||
|
object-fit: scale-down;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characterAssetDescription {
|
||||||
|
font-size: 0.75em;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.characterAssetButtons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
@@ -14,6 +14,10 @@
|
|||||||
<select id="assets_type_select" class="text_pole flex1">
|
<select id="assets_type_select" class="text_pole flex1">
|
||||||
</select>
|
</select>
|
||||||
<input id="assets_search" class="text_pole flex1" placeholder="Search" type="search">
|
<input id="assets_search" class="text_pole flex1" placeholder="Search" type="search">
|
||||||
|
<div id="assets-characters-button" class="menu_button menu_button_icon">
|
||||||
|
<i class="fa-solid fa-image-portrait"></i>
|
||||||
|
Characters
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-drawer-content" id="assets_menu">
|
<div class="inline-drawer-content" id="assets_menu">
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
Enter a URL or the ID of a Fandom wiki page to scrape:
|
Enter a URL or the ID of a Fandom wiki page to scrape:
|
||||||
</label>
|
</label>
|
||||||
<small>
|
<small>
|
||||||
<span data-i18n=Examples:">Examples:</span>
|
<span data-i18n="Examples:">Examples:</span>
|
||||||
<code>https://harrypotter.fandom.com/</code>
|
<code>https://harrypotter.fandom.com/</code>
|
||||||
<span data-i18n="or">or</span>
|
<span data-i18n="or">or</span>
|
||||||
<code>harrypotter</code>
|
<code>harrypotter</code>
|
||||||
|
8
public/scripts/extensions/attachments/files-dropped.html
Normal file
8
public/scripts/extensions/attachments/files-dropped.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="flex-container justifyCenter alignItemsBaseline">
|
||||||
|
<span>Save <span class="droppedFilesCount">{{count}}</span> file(s) to...</span>
|
||||||
|
<select class="droppedFilesTarget">
|
||||||
|
{{#each targets}}
|
||||||
|
<option value="{{this}}">{{this}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
@@ -1,6 +1,15 @@
|
|||||||
import { renderExtensionTemplateAsync } from '../../extensions.js';
|
import { renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
|
|
||||||
jQuery(async () => {
|
jQuery(async () => {
|
||||||
const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {});
|
const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {});
|
||||||
$('#extensionsMenu').prepend(buttons);
|
$('#extensionsMenu').prepend(buttons);
|
||||||
|
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'db',
|
||||||
|
callback: () => document.getElementById('manageAttachments')?.click(),
|
||||||
|
aliases: ['databank', 'data-bank'],
|
||||||
|
helpString: 'Open the data bank',
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -7,8 +7,13 @@
|
|||||||
<div data-i18n="These files will be available for extensions that support attachments (e.g. Vector Storage).">
|
<div data-i18n="These files will be available for extensions that support attachments (e.g. Vector Storage).">
|
||||||
These files will be available for extensions that support attachments (e.g. Vector Storage).
|
These files will be available for extensions that support attachments (e.g. Vector Storage).
|
||||||
</div>
|
</div>
|
||||||
<div data-i18n="Supported file types: Plain Text, PDF, Markdown, HTML, EPUB." class="marginTopBot5">
|
<div class="marginTopBot5">
|
||||||
Supported file types: Plain Text, PDF, Markdown, HTML, EPUB.
|
<span data-i18n="Supported file types: Plain Text, PDF, Markdown, HTML, EPUB." >
|
||||||
|
Supported file types: Plain Text, PDF, Markdown, HTML, EPUB.
|
||||||
|
</span>
|
||||||
|
<span data-i18n="Drag and drop files here to upload.">
|
||||||
|
Drag and drop files here to upload.
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-container marginTopBot5">
|
<div class="flex-container marginTopBot5">
|
||||||
<input type="search" id="attachmentSearch" class="attachmentSearch text_pole margin0 flex1" placeholder="Search...">
|
<input type="search" id="attachmentSearch" class="attachmentSearch text_pole margin0 flex1" placeholder="Search...">
|
||||||
@@ -101,15 +106,20 @@
|
|||||||
<div class="attachmentListItemName flex1"></div>
|
<div class="attachmentListItemName flex1"></div>
|
||||||
<small class="attachmentListItemCreated"></small>
|
<small class="attachmentListItemCreated"></small>
|
||||||
<small class="attachmentListItemSize"></small>
|
<small class="attachmentListItemSize"></small>
|
||||||
<div class="viewAttachmentButton right_menu_button fa-solid fa-magnifying-glass" title="View attachment content"></div>
|
<div class="viewAttachmentButton right_menu_button fa-fw fa-solid fa-magnifying-glass" title="View attachment content"></div>
|
||||||
<div class="editAttachmentButton right_menu_button fa-solid fa-pencil" title="Edit attachment"></div>
|
<div class="disableAttachmentButton right_menu_button fa-fw fa-solid fa-comment" title="Disable attachment"></div>
|
||||||
<div class="deleteAttachmentButton right_menu_button fa-solid fa-trash" title="Delete attachment"></div>
|
<div class="enableAttachmentButton right_menu_button fa-fw fa-solid fa-comment-slash" title="Enable attachment"></div>
|
||||||
|
<div class="moveAttachmentButton right_menu_button fa-fw fa-solid fa-arrows-alt" title="Move attachment"></div>
|
||||||
|
<div class="editAttachmentButton right_menu_button fa-fw fa-solid fa-pencil" title="Edit attachment"></div>
|
||||||
|
<div class="downloadAttachmentButton right_menu_button fa-fw fa-solid fa-download" title="Download attachment"></div>
|
||||||
|
<div class="deleteAttachmentButton right_menu_button fa-fw fa-solid fa-trash" title="Delete attachment"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actionButtonTemplate">
|
<div class="actionButtonTemplate">
|
||||||
<div class="actionButton list-group-item flex-container flexGap5" title="">
|
<div class="actionButton list-group-item flex-container flexGap5" style="align-items: center;" title="">
|
||||||
<i class="actionButtonIcon"></i>
|
<i class="actionButtonIcon"></i>
|
||||||
|
<img class="actionButtonImg"/>
|
||||||
<span class="actionButtonText"></span>
|
<span class="actionButtonText"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"display_name": "Chat Attachments",
|
"display_name": "Data Bank (Chat Attachments)",
|
||||||
"loading_order": 3,
|
"loading_order": 3,
|
||||||
"requires": [],
|
"requires": [],
|
||||||
"optional": [],
|
"optional": [],
|
||||||
|
54
public/scripts/extensions/attachments/mediawiki-scrape.html
Normal file
54
public/scripts/extensions/attachments/mediawiki-scrape.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<div>
|
||||||
|
<div class="flex-container flexFlowColumn">
|
||||||
|
<label for="scrapeInput" data-i18n="Enter a base URL of the MediaWiki to scrape.">
|
||||||
|
Enter a <strong>base URL</strong> of the MediaWiki to scrape.
|
||||||
|
</label>
|
||||||
|
<i data-i18n="Don't include the page name!">
|
||||||
|
Don't include the page name!
|
||||||
|
</i>
|
||||||
|
<small>
|
||||||
|
<span data-i18n="Examples:">Examples:</span>
|
||||||
|
<code>https://streetcat.wiki/index.php</code>
|
||||||
|
<span data-i18n="or">or</span>
|
||||||
|
<code>https://tcrf.net</code>
|
||||||
|
</small>
|
||||||
|
<input type="text" id="scrapeInput" name="scrapeInput" class="text_pole" placeholder="">
|
||||||
|
</div>
|
||||||
|
<div class="flex-container flexFlowColumn">
|
||||||
|
<label for="scrapeFilter">
|
||||||
|
Optional regex to pick the content by its title:
|
||||||
|
</label>
|
||||||
|
<small>
|
||||||
|
<span data-i18n="Example:">Example:</span>
|
||||||
|
<code>/Mr. (Fresh|Snack)/gi</code>
|
||||||
|
</small>
|
||||||
|
<input type="text" id="scrapeFilter" name="scrapeFilter" class="text_pole" placeholder="">
|
||||||
|
</div>
|
||||||
|
<div class="flex-container flexFlowColumn">
|
||||||
|
<label>
|
||||||
|
Output format:
|
||||||
|
</label>
|
||||||
|
<label class="checkbox_label justifyLeft" for="scrapeOutputSingle">
|
||||||
|
<input id="scrapeOutputSingle" type="radio" name="scrapeOutput" value="single" checked>
|
||||||
|
<div class="flex-container flexFlowColumn flexNoGap">
|
||||||
|
<span data-i18n="Single file">
|
||||||
|
Single file
|
||||||
|
</span>
|
||||||
|
<small data-i18n="All articles will be concatenated into a single file.">
|
||||||
|
All articles will be concatenated into a single file.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox_label justifyLeft" for="scrapeOutputMulti">
|
||||||
|
<input id="scrapeOutputMulti" type="radio" name="scrapeOutput" value="multi">
|
||||||
|
<div class="flex-container flexFlowColumn flexNoGap">
|
||||||
|
<span data-i18n="File per article">
|
||||||
|
File per article
|
||||||
|
</span>
|
||||||
|
<small data-i18n="Each article will be saved as a separate file.">
|
||||||
|
Not recommended. Each article will be saved as a separate file.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="flex-container justifyCenter alignItemsBaseline">
|
||||||
|
<span>Move <strong class="moveAttachmentName">{{name}}</strong> to...</span>
|
||||||
|
<select class="moveAttachmentTarget">
|
||||||
|
{{#each targets}}
|
||||||
|
<option value="{{this}}">{{this}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
@@ -19,6 +19,16 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachmentListItem.disabled .attachmentListItemName {
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachmentListItem.disabled .attachmentFileIcon {
|
||||||
|
opacity: 0.75;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.attachmentListItemSize {
|
.attachmentListItemSize {
|
||||||
min-width: 4em;
|
min-width: 4em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
@@ -5,7 +5,9 @@ import { getMessageTimeStamp } from '../../RossAscends-mods.js';
|
|||||||
import { SECRET_KEYS, secret_state } from '../../secrets.js';
|
import { SECRET_KEYS, secret_state } from '../../secrets.js';
|
||||||
import { getMultimodalCaption } from '../shared.js';
|
import { getMultimodalCaption } from '../shared.js';
|
||||||
import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js';
|
import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js';
|
||||||
import { registerSlashCommand } from '../../slash-commands.js';
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
const MODULE_NAME = 'caption';
|
const MODULE_NAME = 'caption';
|
||||||
@@ -254,6 +256,19 @@ async function onSelectImage(e, prompt, quiet) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const caption = await getCaptionForFile(file, prompt, quiet);
|
||||||
|
form && form.reset();
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a caption for an image file.
|
||||||
|
* @param {File} file Input file
|
||||||
|
* @param {string} prompt Caption prompt
|
||||||
|
* @param {boolean} quiet Suppresses sending a message
|
||||||
|
* @returns {Promise<string>} Generated caption
|
||||||
|
*/
|
||||||
|
async function getCaptionForFile(file, prompt, quiet) {
|
||||||
try {
|
try {
|
||||||
setSpinnerIcon();
|
setSpinnerIcon();
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
@@ -273,7 +288,6 @@ async function onSelectImage(e, prompt, quiet) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
form && form.reset();
|
|
||||||
setImageIcon();
|
setImageIcon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,9 +302,26 @@ function onRefineModeInput() {
|
|||||||
* @param {object} args Named parameters
|
* @param {object} args Named parameters
|
||||||
* @param {string} prompt Caption prompt
|
* @param {string} prompt Caption prompt
|
||||||
*/
|
*/
|
||||||
function captionCommandCallback(args, prompt) {
|
async function captionCommandCallback(args, prompt) {
|
||||||
|
const quiet = isTrueBoolean(args?.quiet);
|
||||||
|
const id = args?.id;
|
||||||
|
|
||||||
|
if (!isNaN(Number(id))) {
|
||||||
|
const message = getContext().chat[id];
|
||||||
|
if (message?.extra?.image) {
|
||||||
|
try {
|
||||||
|
const fetchResult = await fetch(message.extra.image);
|
||||||
|
const blob = await fetchResult.blob();
|
||||||
|
const file = new File([blob], 'image.jpg', { type: blob.type });
|
||||||
|
return await getCaptionForFile(file, prompt, quiet);
|
||||||
|
} catch (error) {
|
||||||
|
toastr.error('Failed to get image from the message. Make sure the image is accessible.');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const quiet = isTrueBoolean(args?.quiet);
|
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.accept = 'image/*';
|
input.accept = 'image/*';
|
||||||
@@ -404,12 +435,17 @@ jQuery(function () {
|
|||||||
<select id="caption_multimodal_model" class="flex1 text_pole">
|
<select id="caption_multimodal_model" class="flex1 text_pole">
|
||||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||||
|
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
||||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
||||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||||
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
<option data-type="google" value="gemini-pro-vision">gemini-pro-vision</option>
|
||||||
|
<option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option>
|
||||||
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
<option data-type="openrouter" value="openai/gpt-4-vision-preview">openai/gpt-4-vision-preview</option>
|
||||||
|
<option data-type="openrouter" value="openai/gpt-4o">openai/gpt-4o</option>
|
||||||
|
<option data-type="openrouter" value="openai/gpt-4-turbo">openai/gpt-4-turbo</option>
|
||||||
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
|
<option data-type="openrouter" value="haotian-liu/llava-13b">haotian-liu/llava-13b</option>
|
||||||
|
<option data-type="openrouter" value="fireworks/firellava-13b">fireworks/firellava-13b</option>
|
||||||
<option data-type="openrouter" value="anthropic/claude-3-haiku">anthropic/claude-3-haiku</option>
|
<option data-type="openrouter" value="anthropic/claude-3-haiku">anthropic/claude-3-haiku</option>
|
||||||
<option data-type="openrouter" value="anthropic/claude-3-sonnet">anthropic/claude-3-sonnet</option>
|
<option data-type="openrouter" value="anthropic/claude-3-sonnet">anthropic/claude-3-sonnet</option>
|
||||||
<option data-type="openrouter" value="anthropic/claude-3-opus">anthropic/claude-3-opus</option>
|
<option data-type="openrouter" value="anthropic/claude-3-opus">anthropic/claude-3-opus</option>
|
||||||
@@ -418,6 +454,8 @@ jQuery(function () {
|
|||||||
<option data-type="openrouter" value="anthropic/claude-3-opus:beta">anthropic/claude-3-opus:beta</option>
|
<option data-type="openrouter" value="anthropic/claude-3-opus:beta">anthropic/claude-3-opus:beta</option>
|
||||||
<option data-type="openrouter" value="nousresearch/nous-hermes-2-vision-7b">nousresearch/nous-hermes-2-vision-7b</option>
|
<option data-type="openrouter" value="nousresearch/nous-hermes-2-vision-7b">nousresearch/nous-hermes-2-vision-7b</option>
|
||||||
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
|
<option data-type="openrouter" value="google/gemini-pro-vision">google/gemini-pro-vision</option>
|
||||||
|
<option data-type="openrouter" value="google/gemini-flash-1.5">google/gemini-flash-1.5</option>
|
||||||
|
<option data-type="openrouter" value="liuhaotian/llava-yi-34b">liuhaotian/llava-yi-34b</option>
|
||||||
<option data-type="ollama" value="ollama_current">[Currently selected]</option>
|
<option data-type="ollama" value="ollama_current">[Currently selected]</option>
|
||||||
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
|
<option data-type="ollama" value="bakllava:latest">bakllava:latest</option>
|
||||||
<option data-type="ollama" value="llava:latest">llava:latest</option>
|
<option data-type="ollama" value="llava:latest">llava:latest</option>
|
||||||
@@ -492,5 +530,35 @@ jQuery(function () {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
registerSlashCommand('caption', captionCommandCallback, [], '<span class="monospace">quiet=true/false [prompt]</span> - caption an image with an optional prompt and passes the caption down the pipe. Only multimodal sources support custom prompts. Set the "quiet" argument to true to suppress sending a captioned message, default: false.', true, true);
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'caption',
|
||||||
|
callback: captionCommandCallback,
|
||||||
|
returns: 'caption',
|
||||||
|
namedArgumentList: [
|
||||||
|
new SlashCommandNamedArgument(
|
||||||
|
'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
|
||||||
|
),
|
||||||
|
new SlashCommandNamedArgument(
|
||||||
|
'id', 'get image from a message with this ID', [ARGUMENT_TYPE.NUMBER], false, false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'prompt', [ARGUMENT_TYPE.STRING], false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Caption an image with an optional prompt and passes the caption down the pipe.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Only multimodal sources support custom prompts.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Provide a message ID to get an image from a message instead of uploading one.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Set the "quiet" argument to true to suppress sending a captioned message, default: false.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@@ -2,10 +2,13 @@ import { callPopup, eventSource, event_types, generateQuietPrompt, getRequestHea
|
|||||||
import { dragElement, isMobile } from '../../RossAscends-mods.js';
|
import { dragElement, isMobile } from '../../RossAscends-mods.js';
|
||||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
|
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||||
import { loadMovingUIState, power_user } from '../../power-user.js';
|
import { loadMovingUIState, power_user } from '../../power-user.js';
|
||||||
import { registerSlashCommand } from '../../slash-commands.js';
|
|
||||||
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence } from '../../utils.js';
|
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence } from '../../utils.js';
|
||||||
import { hideMutedSprites } from '../../group-chats.js';
|
import { hideMutedSprites } from '../../group-chats.js';
|
||||||
import { isJsonSchemaSupported } from '../../textgen-settings.js';
|
import { isJsonSchemaSupported } from '../../textgen-settings.js';
|
||||||
|
import { debounce_timeout } from '../../constants.js';
|
||||||
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||||
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||||
|
import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
const MODULE_NAME = 'expressions';
|
const MODULE_NAME = 'expressions';
|
||||||
@@ -94,7 +97,7 @@ async function forceUpdateVisualNovelMode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, 100);
|
const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick);
|
||||||
|
|
||||||
async function updateVisualNovelMode(name, expression) {
|
async function updateVisualNovelMode(name, expression) {
|
||||||
const container = $('#visual-novel-wrapper');
|
const container = $('#visual-novel-wrapper');
|
||||||
@@ -905,8 +908,10 @@ async function setSpriteSetCommand(_, folder) {
|
|||||||
|
|
||||||
$('#expression_override').val(folder.trim());
|
$('#expression_override').val(folder.trim());
|
||||||
onClickExpressionOverrideButton();
|
onClickExpressionOverrideButton();
|
||||||
removeExpression();
|
// removeExpression();
|
||||||
moduleWorker();
|
// moduleWorker();
|
||||||
|
const vnMode = isVisualNovelMode();
|
||||||
|
await sendExpressionCall(folder, lastExpression, true, vnMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function classifyCommand(_, text) {
|
async function classifyCommand(_, text) {
|
||||||
@@ -971,8 +976,8 @@ function sampleClassifyText(text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove asterisks and quotes
|
// Replace macros, remove asterisks and quotes
|
||||||
let result = text.replace(/[*"]/g, '');
|
let result = substituteParams(text).replace(/[*"]/g, '');
|
||||||
|
|
||||||
const SAMPLE_THRESHOLD = 500;
|
const SAMPLE_THRESHOLD = 500;
|
||||||
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;
|
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;
|
||||||
@@ -1015,12 +1020,12 @@ function parseLlmResponse(emotionResponse, labels) {
|
|||||||
const parsedEmotion = JSON.parse(emotionResponse);
|
const parsedEmotion = JSON.parse(emotionResponse);
|
||||||
return parsedEmotion?.emotion ?? fallbackExpression;
|
return parsedEmotion?.emotion ?? fallbackExpression;
|
||||||
} catch {
|
} catch {
|
||||||
const fuse = new Fuse([emotionResponse]);
|
const fuse = new Fuse(labels, { includeScore: true });
|
||||||
for (const label of labels) {
|
console.debug('Using fuzzy search in labels:', labels);
|
||||||
const result = fuse.search(label);
|
const result = fuse.search(emotionResponse);
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return label;
|
console.debug(`fuzzy search found: ${result[0].item} as closest for the LLM response:`, emotionResponse);
|
||||||
}
|
return result[0].item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1270,13 +1275,10 @@ async function getExpressionsList() {
|
|||||||
* @returns {Promise<string[]>}
|
* @returns {Promise<string[]>}
|
||||||
*/
|
*/
|
||||||
async function resolveExpressionsList() {
|
async function resolveExpressionsList() {
|
||||||
// get something for offline mode (default images)
|
// See if we can retrieve a specific expression list from the API
|
||||||
if (!modules.includes('classify') && extension_settings.expressions.api == EXPRESSION_API.extras) {
|
|
||||||
return DEFAULT_EXPRESSIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (extension_settings.expressions.api == EXPRESSION_API.extras) {
|
// Check Extras api first, if enabled and that module active
|
||||||
|
if (extension_settings.expressions.api == EXPRESSION_API.extras && modules.includes('classify')) {
|
||||||
const url = new URL(getApiUrl());
|
const url = new URL(getApiUrl());
|
||||||
url.pathname = '/api/classify/labels';
|
url.pathname = '/api/classify/labels';
|
||||||
|
|
||||||
@@ -1291,7 +1293,10 @@ async function getExpressionsList() {
|
|||||||
expressionsList = data.labels;
|
expressionsList = data.labels;
|
||||||
return expressionsList;
|
return expressionsList;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// If running the local classify model (not using the LLM), we ask that one
|
||||||
|
if (extension_settings.expressions.api == EXPRESSION_API.local) {
|
||||||
const apiResult = await fetch('/api/extra/classify/labels', {
|
const apiResult = await fetch('/api/extra/classify/labels', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders(),
|
||||||
@@ -1303,11 +1308,12 @@ async function getExpressionsList() {
|
|||||||
return expressionsList;
|
return expressionsList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there was no specific list, or an error, just return the default expressions
|
||||||
|
return DEFAULT_EXPRESSIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await resolveExpressionsList();
|
const result = await resolveExpressionsList();
|
||||||
@@ -1965,9 +1971,61 @@ function migrateSettings() {
|
|||||||
});
|
});
|
||||||
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
|
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
|
||||||
eventSource.on(event_types.GROUP_UPDATED, 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);
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite',
|
||||||
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">(optional folder)</span> – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
|
aliases: ['emote'],
|
||||||
registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '<span class="monospace">(charName)</span> – Returns the last set sprite / expression for the named character.', true, true);
|
callback: setSpriteSlashCommand,
|
||||||
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.', true, true);
|
unnamedArgumentList: [
|
||||||
registerSlashCommand('classify', classifyCommand, [], '<span class="monospace">(text)</span> – performs an emotion classification of the given text and returns a label.', true, true);
|
new SlashCommandArgument(
|
||||||
|
'spriteId', [ARGUMENT_TYPE.STRING], true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: 'Force sets the sprite for the current character.',
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'spriteoverride',
|
||||||
|
aliases: ['costume'],
|
||||||
|
callback: setSpriteSetCommand,
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'optional folder', [ARGUMENT_TYPE.STRING], false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: 'Sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.',
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lastsprite',
|
||||||
|
callback: (_, value) => lastExpression[value.trim()] ?? '',
|
||||||
|
returns: 'sprite',
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'charName', [ARGUMENT_TYPE.STRING], true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
helpString: 'Returns the last set sprite / expression for the named character.',
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'th',
|
||||||
|
callback: toggleTalkingHeadCommand,
|
||||||
|
aliases: ['talkinghead'],
|
||||||
|
helpString: 'Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.',
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'classify',
|
||||||
|
callback: classifyCommand,
|
||||||
|
unnamedArgumentList: [
|
||||||
|
new SlashCommandArgument(
|
||||||
|
'text', [ARGUMENT_TYPE.STRING], true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
returns: 'emotion classification label for the given text',
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Performs an emotion classification of the given text and returns a label.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Example:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<pre><code>/classify I am so happy today!</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
})();
|
})();
|
||||||
|
@@ -64,10 +64,6 @@
|
|||||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
<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" />
|
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||||
</div>
|
</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 class="expression_buttons flex-container spaceEvenly">
|
||||||
<div id="expression_upload_pack_button" class="menu_button">
|
<div id="expression_upload_pack_button" class="menu_button">
|
||||||
<i class="fa-solid fa-file-zipper"></i>
|
<i class="fa-solid fa-file-zipper"></i>
|
||||||
@@ -78,8 +74,13 @@
|
|||||||
<span>Remove all image overrides</span>
|
<span>Remove all image overrides</span>
|
||||||
</div>
|
</div>
|
||||||
</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.
|
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>/characters/</b> folder of your user data directory and name it as the name of the character.
|
||||||
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||||
|
<h3 id="image_list_header">
|
||||||
|
<strong>Sprite set:</strong> <span id="image_list_header_name"></span>
|
||||||
|
</h3>
|
||||||
|
<div id="image_list"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -14,7 +14,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - var(--topBarBlockSize));
|
height: calc(100vh - var(--topBarBlockSize));
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user