Merge branch 'staging' into disk-cache

694df8ca5524978eac504a4169641085f46e89d3

Cohee <18619528+Cohee1207@users.noreply.github.com>

90 files changed, +1988 -456Showing whitespace changes
.eslintrc.cjs+4 -0
@@ -3,6 +3,9 @@ module.exports = {
3 extends: [3 extends: [
4 'eslint:recommended',4 'eslint:recommended',
5 ],5 ],
6 plugins: [
7 'jsdoc',
8 ],
6 env: {9 env: {
7 es6: true,10 es6: true,
8 },11 },
@@ -78,6 +81,7 @@ module.exports = {
78 'public/scripts/extensions/tts/lib/**',81 'public/scripts/extensions/tts/lib/**',
79 ],82 ],
80 rules: {83 rules: {
84 'jsdoc/no-undefined-types': ['warn', { disableReporting: true, markVariablesAsUsed: true }],
81 'no-unused-vars': ['error', { args: 'none' }],85 'no-unused-vars': ['error', { args: 'none' }],
82 'no-control-regex': 'off',86 'no-control-regex': 'off',
83 'no-constant-condition': ['error', { checkLoops: false }],87 'no-constant-condition': ['error', { checkLoops: false }],
.github/issues-auto-labels.yml+2 -2
@@ -14,7 +14,7 @@
14 - '(🐧 Linux)'14 - '(🐧 Linux)'
1515
16🦊 Firefox:16🦊 Firefox:
17 - '(firefox|mozilla)'17 - '\b(firefox|mozilla)\b'
1818
19πŸ“± Mobile:19πŸ“± Mobile:
20 - '(iphone|ios|android|πŸ“± Termux)'20 - '\b(iphone|ios|android|πŸ“± Termux)\b'
.github/pr-auto-labels-by-branch.yml+6 -0
@@ -34,6 +34,9 @@
34🦊 Firefox:34🦊 Firefox:
35- head-branch: ['\bfirefox\b']35- head-branch: ['\bfirefox\b']
3636
37πŸ§‘β€πŸ€β€πŸ§‘ Group Chat:
38- head-branch: ['\bgroups?\b']
39
37πŸ–ΌοΈ Image Gen:40πŸ–ΌοΈ Image Gen:
38- head-branch: ['\bimage-gen\b']41- head-branch: ['\bimage-gen\b']
3942
@@ -58,6 +61,9 @@
58πŸ“œ Prompt:61πŸ“œ Prompt:
59- head-branch: ['\bprompt\b']62- head-branch: ['\bprompt\b']
6063
64🧠 Reasoning:
65- head-branch: ['\breasoning\b', '\breason\b', '\bthinking\b']
66
61🚚 Refactor:67🚚 Refactor:
62- head-branch: ['\brefactor(s|ed)?\b']68- head-branch: ['\brefactor(s|ed)?\b']
6369
.github/readme.md+28 -13
@@ -23,7 +23,7 @@ We have a [Documentation website](https://docs.sillytavern.app/) to answer most
2323
24SillyTavern (or ST for short) is a locally installed user interface that allows you to interact with text generation LLMs, image generation engines, and TTS voice models.24SillyTavern (or ST for short) is a locally installed user interface that allows you to interact with text generation LLMs, image generation engines, and TTS voice models.
2525
26Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over 200 contributors and 3 years of independent development under its belt, and continues to serve as a leading software for savvy AI hobbyists.26Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over 200 contributors and 2 years of independent development under its belt, and continues to serve as a leading software for savvy AI hobbyists.
2727
28## Our Vision28## Our Vision
2929
@@ -192,28 +192,43 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
192192
193##### Volume Mappings193##### Volume Mappings
194194
195* [config] - The directory where SillyTavern configuration files will be stored on your host machine195* `CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
196* [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine196* `DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
197* [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine197* `PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
198* [extensions] - (optional) The directory where global UI extensions will be stored on your host machine198* `EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
199199
200##### Port Mappings200##### Port Mappings
201201
202* [PublicPort] - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.202* `PUBLIC_PORT` - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
203203
204##### Additional Settings204##### Additional Settings
205205
206* [DockerNet] - The docker network that the container should be created with a connection to. If you don't know what it is, see the [official Docker documentation](https://docs.docker.com/reference/cli/docker/network/).206* `SILLYTAVERN_VERSION` - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" that points to the nightly image the respective branch.
207* [version] - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" and "release" tags that point to the nightly images of the respective branches, but this may not be appropriate, if you are utilizing extensions that could be broken, and may need time to update.
208207
209#### Install command208#### Running the container
210209
2111. Open your Command Line2101. Open your Command Line
2122. Run the following command2112. Run the following command in a folder where you want to store the configuration and data files:
213212
214`docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`213```bash
214SILLYTAVERN_VERSION="latest"
215PUBLIC_PORT="8000"
216CONFIG_PATH="./config"
217DATA_PATH="./data"
218PLUGINS_PATH="./plugins"
219EXTENSIONS_PATH="./extensions"
220
221docker run \
222 --name="sillytavern" \
223 -p "$PUBLIC_PORT:8000/tcp" \
224 -v "$CONFIG_PATH:/home/node/app/config:rw" \
225 -v "$DATA_PATH:/home/node/app/data:rw" \
226 -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \
227 -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \
228 ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION"
229```
215230
216> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.231> By default the container will run in the foreground. If you want to run it in the background, add the `-d` flag to the `docker run` command.
217232
218### Building the image yourself233### Building the image yourself
219234
.github/workflows/issues-auto-manager.yml+20 -16
@@ -7,6 +7,10 @@ on:
7 issue_comment:7 issue_comment:
8 types: [created]8 types: [created]
99
10permissions:
11 contents: read
12 issues: write
13
10jobs:14jobs:
11 label-on-content:15 label-on-content:
12 name: 🏷️ Label Issues by Content16 name: 🏷️ Label Issues by Content
@@ -16,7 +20,7 @@ jobs:
16 - name: Checkout Repository20 - name: Checkout Repository
17 # Checkout21 # Checkout
18 # https://github.com/marketplace/actions/checkout22 # https://github.com/marketplace/actions/checkout
19 uses: actions/checkout@v423 uses: actions/checkout@v4.2.2
2024
21 - name: Auto-Label Issues (Based on Issue Content)25 - name: Auto-Label Issues (Based on Issue Content)
22 # only auto label based on issue content once, on open (to prevent re-labeling removed labels)26 # only auto label based on issue content once, on open (to prevent re-labeling removed labels)
@@ -24,11 +28,11 @@ jobs:
2428
25 # Issue Labeler29 # Issue Labeler
26 # https://github.com/marketplace/actions/regex-issue-labeler30 # https://github.com/marketplace/actions/regex-issue-labeler
27 uses: github/issue-labeler@v331 uses: github/issue-labeler@v3.4
28 with:32 with:
29 configuration-path: .github/issues-auto-labels.yml33 configuration-path: .github/issues-auto-labels.yml
30 enable-versioned-regex: 034 enable-versioned-regex: 0
31 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}35 repo-token: ${{ secrets.GITHUB_TOKEN }}
3236
33 label-on-labels:37 label-on-labels:
34 name: 🏷️ Label Issues by Labels38 name: 🏷️ Label Issues by Labels
@@ -39,40 +43,40 @@ jobs:
39 if: contains(fromJSON('["πŸ‘©β€πŸ’» Good First Issue", "πŸ™ Help Wanted", "πŸͺ² Confirmed", "⚠️ High Priority", "❕ Medium Priority", "πŸ’€ Low Priority"]'), github.event.label.name)43 if: contains(fromJSON('["πŸ‘©β€πŸ’» Good First Issue", "πŸ™ Help Wanted", "πŸͺ² Confirmed", "⚠️ High Priority", "❕ Medium Priority", "πŸ’€ Low Priority"]'), github.event.label.name)
40 # πŸ€– Issues Helper44 # πŸ€– Issues Helper
41 # https://github.com/marketplace/actions/issues-helper45 # https://github.com/marketplace/actions/issues-helper
42 uses: actions-cool/issues-helper@v346 uses: actions-cool/issues-helper@v3.6.0
43 with:47 with:
44 actions: 'add-labels'48 actions: 'add-labels'
45 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}49 token: ${{ secrets.GITHUB_TOKEN }}
46 labels: 'πŸ‘ Approved'50 labels: 'πŸ‘ Approved'
4751
48 - name: ❌ Remove progress labels when issue is marked done or stale52 - name: ❌ Remove progress labels when issue is marked done or stale
49 if: contains(fromJSON('["βœ… Done", "βœ… Done (staging)", "⚰️ Stale", "❌ wontfix"]'), github.event.label.name)53 if: contains(fromJSON('["βœ… Done", "βœ… Done (staging)", "⚰️ Stale", "❌ wontfix"]'), github.event.label.name)
50 # πŸ€– Issues Helper54 # πŸ€– Issues Helper
51 # https://github.com/marketplace/actions/issues-helper55 # https://github.com/marketplace/actions/issues-helper
52 uses: actions-cool/issues-helper@v356 uses: actions-cool/issues-helper@v3.6.0
53 with:57 with:
54 actions: 'remove-labels'58 actions: 'remove-labels'
55 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}59 token: ${{ secrets.GITHUB_TOKEN }}
56 labels: 'πŸ§‘β€πŸ’» In Progress,πŸ€” Unsure,πŸ€” Under Consideration'60 labels: 'πŸ§‘β€πŸ’» In Progress,πŸ€” Unsure,πŸ€” Under Consideration'
5761
58 - name: ❌ Remove temporary labels when confirmed labels are added62 - name: ❌ Remove temporary labels when confirmed labels are added
59 if: contains(fromJSON('["❌ wontfix","πŸ‘ Approved","πŸ‘©β€πŸ’» Good First Issue"]'), github.event.label.name)63 if: contains(fromJSON('["❌ wontfix","πŸ‘ Approved","πŸ‘©β€πŸ’» Good First Issue"]'), github.event.label.name)
60 # πŸ€– Issues Helper64 # πŸ€– Issues Helper
61 # https://github.com/marketplace/actions/issues-helper65 # https://github.com/marketplace/actions/issues-helper
62 uses: actions-cool/issues-helper@v366 uses: actions-cool/issues-helper@v3.6.0
63 with:67 with:
64 actions: 'remove-labels'68 actions: 'remove-labels'
65 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}69 token: ${{ secrets.GITHUB_TOKEN }}
66 labels: 'πŸ€” Unsure,πŸ€” Under Consideration'70 labels: 'πŸ€” Unsure,πŸ€” Under Consideration'
6771
68 - name: ❌ Remove no bug labels when "πŸͺ² Confirmed" is added72 - name: ❌ Remove no bug labels when "πŸͺ² Confirmed" is added
69 if: github.event.label.name == 'πŸͺ² Confirmed'73 if: github.event.label.name == 'πŸͺ² Confirmed'
70 # πŸ€– Issues Helper74 # πŸ€– Issues Helper
71 # https://github.com/marketplace/actions/issues-helper75 # https://github.com/marketplace/actions/issues-helper
72 uses: actions-cool/issues-helper@v376 uses: actions-cool/issues-helper@v3.6.0
73 with:77 with:
74 actions: 'remove-labels'78 actions: 'remove-labels'
75 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}79 token: ${{ secrets.GITHUB_TOKEN }}
76 labels: 'βœ–οΈ Not Reproducible,βœ–οΈ Not A Bug'80 labels: 'βœ–οΈ Not Reproducible,βœ–οΈ Not A Bug'
7781
78 remove-stale-label:82 remove-stale-label:
@@ -85,10 +89,10 @@ jobs:
85 - name: Remove Stale Label89 - name: Remove Stale Label
86 # πŸ€– Issues Helper90 # πŸ€– Issues Helper
87 # https://github.com/marketplace/actions/issues-helper91 # https://github.com/marketplace/actions/issues-helper
88 uses: actions-cool/issues-helper@v392 uses: actions-cool/issues-helper@v3.6.0
89 with:93 with:
90 actions: 'remove-labels'94 actions: 'remove-labels'
91 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}95 token: ${{ secrets.GITHUB_TOKEN }}
92 issue-number: ${{ github.event.issue.number }}96 issue-number: ${{ github.event.issue.number }}
93 labels: '⚰️ Stale,πŸ•ΈοΈ Inactive,🚏 Awaiting User Response,πŸ›‘ No Response'97 labels: '⚰️ Stale,πŸ•ΈοΈ Inactive,🚏 Awaiting User Response,πŸ›‘ No Response'
9498
@@ -101,12 +105,12 @@ jobs:
101 - name: Checkout Repository105 - name: Checkout Repository
102 # Checkout106 # Checkout
103 # https://github.com/marketplace/actions/checkout107 # https://github.com/marketplace/actions/checkout
104 uses: actions/checkout@v4108 uses: actions/checkout@v4.2.2
105109
106 - name: Post Issue Comments Based on Labels110 - name: Post Issue Comments Based on Labels
107 # Label Commenter111 # Label Commenter
108 # https://github.com/marketplace/actions/label-commenter112 # https://github.com/marketplace/actions/label-commenter
109 uses: peaceiris/actions-label-commenter@v1113 uses: peaceiris/actions-label-commenter@v1.10.0
110 with:114 with:
111 config_file: .github/issues-auto-comments.yml115 config_file: .github/issues-auto-comments.yml
112 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}116 github_token: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/issues-updates-on-merge.yml+7 -3
@@ -6,6 +6,10 @@ on:
6 - staging6 - staging
7 - release7 - release
88
9permissions:
10 contents: read
11 issues: write
12
9jobs:13jobs:
10 # This runs commits to staging/release, reading the commit messages. Check `pr-auto-manager.yml`:`update-linked-issues` for PR-linked updates.14 # This runs commits to staging/release, reading the commit messages. Check `pr-auto-manager.yml`:`update-linked-issues` for PR-linked updates.
11 update-linked-issues:15 update-linked-issues:
@@ -16,18 +20,18 @@ jobs:
16 - name: Checkout Repository20 - name: Checkout Repository
17 # Checkout21 # Checkout
18 # https://github.com/marketplace/actions/checkout22 # https://github.com/marketplace/actions/checkout
19 uses: actions/checkout@v423 uses: actions/checkout@v4.2.2
2024
21 - name: Extract Linked Issues from Commit Message25 - name: Extract Linked Issues from Commit Message
22 id: extract_issues26 id: extract_issues
23 run: |27 run: |
24 ISSUES=$(git log -1 --pretty=%B | grep -oiE '(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) #([0-9]+)' | awk '{print $2}' | tr -d '#' | jq -R -s -c 'split("\n")[:-1]')28 ISSUES=$(git log ${{ github.event.before }}..${{ github.event.after }} --pretty=%B | grep -oiE '(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) #([0-9]+)' | awk '{print $2}' | tr -d '#' | jq -R -s -c 'split("\n")[:-1]')
25 echo "issues=$ISSUES" >> $GITHUB_ENV29 echo "issues=$ISSUES" >> $GITHUB_ENV
2630
27 - name: Label Linked Issues31 - name: Label Linked Issues
28 id: label_linked_issues32 id: label_linked_issues
29 env:33 env:
30 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}34 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 run: |35 run: |
32 for ISSUE in $(echo $issues | jq -r '.[]'); do36 for ISSUE in $(echo $issues | jq -r '.[]'); do
33 if [ "${{ github.ref }}" == "refs/heads/staging" ]; then37 if [ "${{ github.ref }}" == "refs/heads/staging" ]; then
.github/workflows/job-close-stale.yml+11 -6
@@ -6,6 +6,11 @@ on:
6 schedule:6 schedule:
7 - cron: '0 0 * * *' # Runs every day at midnight UTC7 - cron: '0 0 * * *' # Runs every day at midnight UTC
88
9permissions:
10 contents: read
11 issues: write
12 pull-requests: write
13
9jobs:14jobs:
10 mark-inactivity:15 mark-inactivity:
11 name: ⏳ Mark Issues/PRs without Activity16 name: ⏳ Mark Issues/PRs without Activity
@@ -15,9 +20,9 @@ jobs:
15 - name: Mark Issues/PRs without Activity20 - name: Mark Issues/PRs without Activity
16 # Close Stale Issues and PRs21 # Close Stale Issues and PRs
17 # https://github.com/marketplace/actions/close-stale-issues22 # https://github.com/marketplace/actions/close-stale-issues
18 uses: actions/stale@v923 uses: actions/stale@v9.1.0
19 with:24 with:
20 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}25 repo-token: ${{ secrets.GITHUB_TOKEN }}
21 days-before-stale: 18326 days-before-stale: 183
22 days-before-close: 727 days-before-close: 7
23 operations-per-run: 3028 operations-per-run: 30
@@ -49,9 +54,9 @@ jobs:
49 - name: Mark Issues/PRs Awaiting User Response54 - name: Mark Issues/PRs Awaiting User Response
50 # Close Stale Issues and PRs55 # Close Stale Issues and PRs
51 # https://github.com/marketplace/actions/close-stale-issues56 # https://github.com/marketplace/actions/close-stale-issues
52 uses: actions/stale@v957 uses: actions/stale@v9.1.0
53 with:58 with:
54 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}59 repo-token: ${{ secrets.GITHUB_TOKEN }}
55 days-before-stale: 760 days-before-stale: 7
56 days-before-close: 761 days-before-close: 7
57 operations-per-run: 3062 operations-per-run: 30
@@ -76,9 +81,9 @@ jobs:
76 - name: Mark Issues with Alternative Exists81 - name: Mark Issues with Alternative Exists
77 # Close Stale Issues and PRs82 # Close Stale Issues and PRs
78 # https://github.com/marketplace/actions/close-stale-issues83 # https://github.com/marketplace/actions/close-stale-issues
79 uses: actions/stale@v984 uses: actions/stale@v9.1.0
80 with:85 with:
81 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}86 repo-token: ${{ secrets.GITHUB_TOKEN }}
82 days-before-stale: 787 days-before-stale: 7
83 days-before-close: 788 days-before-close: 7
84 operations-per-run: 3089 operations-per-run: 30
.github/workflows/on-close-handler.yml+7 -2
@@ -6,6 +6,11 @@ on:
6 pull_request_target:6 pull_request_target:
7 types: [closed]7 types: [closed]
88
9permissions:
10 contents: read
11 issues: write
12 pull-requests: write
13
9jobs:14jobs:
10 remove-labels:15 remove-labels:
11 name: πŸ—‘οΈ Remove Pending Labels on Close16 name: πŸ—‘οΈ Remove Pending Labels on Close
@@ -15,9 +20,9 @@ jobs:
15 - name: Remove Pending Labels on Close20 - name: Remove Pending Labels on Close
16 # πŸ€– Issues Helper21 # πŸ€– Issues Helper
17 # https://github.com/marketplace/actions/issues-helper22 # https://github.com/marketplace/actions/issues-helper
18 uses: actions-cool/issues-helper@v323 uses: actions-cool/issues-helper@v3.6.0
19 with:24 with:
20 actions: remove-labels25 actions: remove-labels
21 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}26 token: ${{ secrets.GITHUB_TOKEN }}
22 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}27 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
23 labels: '🚏 Awaiting User Response,πŸ§‘β€πŸ’» In Progress,πŸ“Œ Keep Open,🚫 Merge Conflicts,πŸ”¬ Needs Testing,πŸ”¨ Needs Work,⚰️ Stale,β›” Waiting For External/Upstream'28 labels: '🚏 Awaiting User Response,πŸ§‘β€πŸ’» In Progress,πŸ“Œ Keep Open,🚫 Merge Conflicts,πŸ”¬ Needs Testing,πŸ”¨ Needs Work,⚰️ Stale,β›” Waiting For External/Upstream'
.github/workflows/on-open-handler.yml+7 -2
@@ -6,6 +6,11 @@ on:
6 pull_request_target:6 pull_request_target:
7 types: [opened]7 types: [opened]
88
9permissions:
10 contents: read
11 issues: write
12 pull-requests: write
13
9jobs:14jobs:
10 label-maintainer:15 label-maintainer:
11 name: 🏷️ Label if Author is a Repo Maintainer16 name: 🏷️ Label if Author is a Repo Maintainer
@@ -16,9 +21,9 @@ jobs:
16 - name: Label if Author is a Repo Maintainer21 - name: Label if Author is a Repo Maintainer
17 # πŸ€– Issues Helper22 # πŸ€– Issues Helper
18 # https://github.com/marketplace/actions/issues-helper23 # https://github.com/marketplace/actions/issues-helper
19 uses: actions-cool/issues-helper@v324 uses: actions-cool/issues-helper@v3.6.0
20 with:25 with:
21 actions: 'add-labels'26 actions: 'add-labels'
22 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}27 token: ${{ secrets.GITHUB_TOKEN }}
23 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}28 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
24 labels: 'πŸ‘· Maintainer'29 labels: 'πŸ‘· Maintainer'
.github/workflows/pr-auto-manager.yml+91 -23
@@ -6,18 +6,67 @@ on:
6 pull_request_review_comment:6 pull_request_review_comment:
7 types: [created]7 types: [created]
88
9permissions:
10 contents: read
11 pull-requests: write
12
9jobs:13jobs:
14 run-eslint:
15 name: βœ… Check ESLint on PR
16 runs-on: ubuntu-latest
17 # Only needs to run when code is changed
18 if: github.event.action == 'opened' || github.event.action == 'synchronize'
19
20 steps:
21 - name: Checkout Repository
22 # Checkout
23 # https://github.com/marketplace/actions/checkout
24 uses: actions/checkout@v4.2.2
25
26 - name: Setup Node.js
27 # Setup Node.js environment
28 # https://github.com/marketplace/actions/setup-node-js-environment
29 uses: actions/setup-node@v4.3.0
30 with:
31 node-version: 20
32
33 - name: Run npm install
34 run: npm ci
35
36 - name: Run ESLint
37 # Action ESLint
38 # https://github.com/marketplace/actions/action-eslint
39 uses: sibiraj-s/action-eslint@v3.0.1
40 with:
41 token: ${{ secrets.GITHUB_TOKEN }}
42 eslint-args: '--ignore-path=.gitignore --quiet'
43 extensions: 'js,ts'
44 annotations: true
45 ignore-patterns: |
46 dist/
47 lib/
48
10 label-by-size:49 label-by-size:
11 name: 🏷️ Label PR by Size50 name: 🏷️ Label PR by Size
51 # This job should run after all others, to prevent possible concurrency issues
52 needs: [label-by-branches, label-by-files, remove-stale-label, check-merge-blocking-labels, write-auto-comments]
12 runs-on: ubuntu-latest53 runs-on: ubuntu-latest
54 # Only needs to run when code is changed
55 if: always() && (github.event.action == 'opened' || github.event.action == 'synchronize')
56
57 # Override permissions, the labeler needs issues write access
58 permissions:
59 contents: read
60 issues: write
61 pull-requests: write
1362
14 steps:63 steps:
15 - name: Label PR Size64 - name: Label PR Size
16 # Pull Request Size Labeler65 # Pull Request Size Labeler
17 # https://github.com/marketplace/actions/pull-request-size-labeler66 # https://github.com/marketplace/actions/pull-request-size-labeler
18 uses: codelytv/pr-size-labeler@v167 uses: codelytv/pr-size-labeler@v1.10.2
19 with:68 with:
20 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}69 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 xs_label: '🟩 ⬀○○○○'70 xs_label: '🟩 ⬀○○○○'
22 xs_max_size: '20'71 xs_max_size: '20'
23 s_label: '🟩 ⬀⬀○○○'72 s_label: '🟩 ⬀⬀○○○'
@@ -28,7 +77,6 @@ jobs:
28 l_max_size: '1000'77 l_max_size: '1000'
29 xl_label: 'πŸŸ₯ ⬀⬀⬀⬀⬀'78 xl_label: 'πŸŸ₯ ⬀⬀⬀⬀⬀'
30 fail_if_xl: 'false'79 fail_if_xl: 'false'
31 github_api_url: 'https://api.github.com'
32 files_to_ignore: |80 files_to_ignore: |
33 "package-lock.json"81 "package-lock.json"
34 "public/lib/*"82 "public/lib/*"
@@ -36,55 +84,63 @@ jobs:
36 label-by-branches:84 label-by-branches:
37 name: 🏷️ Label PR by Branches85 name: 🏷️ Label PR by Branches
38 runs-on: ubuntu-latest86 runs-on: ubuntu-latest
39 # Only label once when PR is created or branches are changed, to allow manual label removal87 # Only label once when PR is created or when base branch is changed, to allow manual label removal
40 if: github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))88 if: github.event.action == 'opened' || (github.event.action == 'synchronize' && github.event.changes.base)
4189
42 steps:90 steps:
43 - name: Checkout Repository91 - name: Checkout Repository
44 # Checkout92 # Checkout
45 # https://github.com/marketplace/actions/checkout93 # https://github.com/marketplace/actions/checkout
46 uses: actions/checkout@v494 uses: actions/checkout@v4.2.2
4795
48 - name: Apply Labels Based on Branch Name and Target Branch96 - name: Apply Labels Based on Branch Name and Target Branch
49 # Pull Request Labeler97 # Pull Request Labeler
50 # https://github.com/marketplace/actions/labeler98 # https://github.com/marketplace/actions/labeler
51 uses: actions/labeler@v599 uses: actions/labeler@v5.0.0
52 with:100 with:
53 configuration-path: .github/pr-auto-labels-by-branch.yml101 configuration-path: .github/pr-auto-labels-by-branch.yml
54 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}102 repo-token: ${{ secrets.GITHUB_TOKEN }}
55103
56 label-by-files:104 label-by-files:
57 name: 🏷️ Label PR by Files105 name: 🏷️ Label PR by Files
58 runs-on: ubuntu-latest106 runs-on: ubuntu-latest
107 # Only needs to run when code is changed
108 if: github.event.action == 'opened' || github.event.action == 'synchronize'
59109
60 steps:110 steps:
61 - name: Checkout Repository111 - name: Checkout Repository
62 # Checkout112 # Checkout
63 # https://github.com/marketplace/actions/checkout113 # https://github.com/marketplace/actions/checkout
64 uses: actions/checkout@v4114 uses: actions/checkout@v4.2.2
65115
66 - name: Apply Labels Based on Changed Files116 - name: Apply Labels Based on Changed Files
67 # Pull Request Labeler117 # Pull Request Labeler
68 # https://github.com/marketplace/actions/labeler118 # https://github.com/marketplace/actions/labeler
69 uses: actions/labeler@v5119 uses: actions/labeler@v5.0.0
70 with:120 with:
71 configuration-path: .github/pr-auto-labels-by-files.yml121 configuration-path: .github/pr-auto-labels-by-files.yml
72 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}122 repo-token: ${{ secrets.GITHUB_TOKEN }}
73123
74 remove-stale-label:124 remove-stale-label:
75 name: πŸ—‘οΈ Remove Stale Label on Comment125 name: πŸ—‘οΈ Remove Stale Label on Comment
76 runs-on: ubuntu-latest126 runs-on: ubuntu-latest
77 # Only runs when this is not done by the github actions bot127 # Only runs on comments not done by the github actions bot
78 if: github.actor != 'github-actions[bot]'128 if: github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
129
130 # Override permissions, issue labeler needs issues write access
131 permissions:
132 contents: read
133 issues: write
134 pull-requests: write
79135
80 steps:136 steps:
81 - name: Remove Stale Label137 - name: Remove Stale Label
82 # πŸ€– Issues Helper138 # πŸ€– Issues Helper
83 # https://github.com/marketplace/actions/issues-helper139 # https://github.com/marketplace/actions/issues-helper
84 uses: actions-cool/issues-helper@v3140 uses: actions-cool/issues-helper@v3.6.0
85 with:141 with:
86 actions: 'remove-labels'142 actions: 'remove-labels'
87 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}143 token: ${{ secrets.GITHUB_TOKEN }}
88 issue-number: ${{ github.event.pull_request.number }}144 issue-number: ${{ github.event.pull_request.number }}
89 labels: '⚰️ Stale'145 labels: '⚰️ Stale'
90146
@@ -95,12 +151,18 @@ jobs:
95 # Run, even if the previous jobs were skipped/failed151 # Run, even if the previous jobs were skipped/failed
96 if: always()152 if: always()
97153
154 # Override permissions, as this needs to write a check
155 permissions:
156 checks: write
157 contents: read
158 pull-requests: read
159
98 steps:160 steps:
99 - name: Check Merge Blocking161 - name: Check Merge Blocking
100 # GitHub Script162 # GitHub Script
101 # https://github.com/marketplace/actions/github-scriptLabels163 # https://github.com/marketplace/actions/github-script
102 id: label-check164 id: label-check
103 uses: actions/github-script@v7165 uses: actions/github-script@v7.0.1
104 with:166 with:
105 script: |167 script: |
106 const prLabels = context.payload.pull_request.labels.map(label => label.name);168 const prLabels = context.payload.pull_request.labels.map(label => label.name);
@@ -134,7 +196,7 @@ jobs:
134196
135 write-auto-comments:197 write-auto-comments:
136 name: πŸ’¬ Post PR Comments Based on Labels198 name: πŸ’¬ Post PR Comments Based on Labels
137 needs: [label-by-size, label-by-branches, label-by-files]199 needs: [label-by-branches, label-by-files]
138 runs-on: ubuntu-latest200 runs-on: ubuntu-latest
139 # Run, even if the previous jobs were skipped/failed201 # Run, even if the previous jobs were skipped/failed
140 if: always()202 if: always()
@@ -143,15 +205,15 @@ jobs:
143 - name: Checkout Repository205 - name: Checkout Repository
144 # Checkout206 # Checkout
145 # https://github.com/marketplace/actions/checkout207 # https://github.com/marketplace/actions/checkout
146 uses: actions/checkout@v4208 uses: actions/checkout@v4.2.2
147209
148 - name: Post PR Comments Based on Labels210 - name: Post PR Comments Based on Labels
149 # Label Commenter for PRs211 # Label Commenter for PRs
150 # https://github.com/marketplace/actions/label-commenter212 # https://github.com/marketplace/actions/label-commenter
151 uses: peaceiris/actions-label-commenter@v1213 uses: peaceiris/actions-label-commenter@v1.10.0
152 with:214 with:
153 config_file: .github/pr-auto-comments.yml215 config_file: .github/pr-auto-comments.yml
154 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}216 github_token: ${{ secrets.GITHUB_TOKEN }}
155217
156 # This runs on merged PRs to staging, reading the PR body and directly linked issues. Check `issues-updates-on-merge.yml`:`update-linked-issues` for commit-based updates.218 # This runs on merged PRs to staging, reading the PR body and directly linked issues. Check `issues-updates-on-merge.yml`:`update-linked-issues` for commit-based updates.
157 update-linked-issues:219 update-linked-issues:
@@ -159,6 +221,12 @@ jobs:
159 runs-on: ubuntu-latest221 runs-on: ubuntu-latest
160 if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'222 if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'
161223
224 # Override permissions, We need to be able to write to issues
225 permissions:
226 contents: read
227 issues: write
228 pull-requests: write
229
162 steps:230 steps:
163 - name: Extract Linked Issues From PR Description231 - name: Extract Linked Issues From PR Description
164 id: extract_issues232 id: extract_issues
@@ -172,7 +240,7 @@ jobs:
172 PR_NUMBER=${{ github.event.pull_request.number }}240 PR_NUMBER=${{ github.event.pull_request.number }}
173 REPO=${{ github.repository }}241 REPO=${{ github.repository }}
174 API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"242 API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"
175 ISSUES=$(curl -s -H "Authorization: token ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" "$API_URL" | jq -r '.[].number' | jq -R -s -c 'split("\n")[:-1]')243 ISSUES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$API_URL" | jq -r '.[].number' | jq -R -s -c 'split("\n")[:-1]')
176 echo "linked_issues=$ISSUES" >> $GITHUB_ENV244 echo "linked_issues=$ISSUES" >> $GITHUB_ENV
177245
178 - name: Merge Issue Lists246 - name: Merge Issue Lists
@@ -184,7 +252,7 @@ jobs:
184 - name: Label Linked Issues252 - name: Label Linked Issues
185 id: label_linked_issues253 id: label_linked_issues
186 env:254 env:
187 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}255 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
188 run: |256 run: |
189 for ISSUE in $(echo $final_issues | jq -r '.[]'); do257 for ISSUE in $(echo $final_issues | jq -r '.[]'); do
190 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "βœ… Done (staging)"258 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "βœ… Done (staging)"
.github/workflows/pr-check-merge-conflicts.yaml+6 -2
@@ -7,6 +7,10 @@ on:
7 pull_request_target:7 pull_request_target:
8 types: [synchronize]8 types: [synchronize]
99
10permissions:
11 contents: read
12 pull-requests: write
13
10jobs:14jobs:
11 check-merge-conflicts:15 check-merge-conflicts:
12 name: βš”οΈ Check Merge Conflicts16 name: βš”οΈ Check Merge Conflicts
@@ -16,9 +20,9 @@ jobs:
16 - name: Check Merge Conflicts20 - name: Check Merge Conflicts
17 # Label Conflicting Pull Requests21 # Label Conflicting Pull Requests
18 # https://github.com/marketplace/actions/label-conflicting-pull-requests22 # https://github.com/marketplace/actions/label-conflicting-pull-requests
19 uses: eps1lon/actions-label-merge-conflict@v323 uses: eps1lon/actions-label-merge-conflict@v3.0.3
20 with:24 with:
21 dirtyLabel: '🚫 Merge Conflicts'25 dirtyLabel: '🚫 Merge Conflicts'
22 repoToken: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}26 repoToken: ${{ secrets.GITHUB_TOKEN }}
23 commentOnDirty: >27 commentOnDirty: >
24 ⚠️ This PR has conflicts that need to be resolved before it can be merged.28 ⚠️ This PR has conflicts that need to be resolved before it can be merged.
default/config.yaml+2 -0
@@ -114,6 +114,8 @@ backups:
114 chat:114 chat:
115 # Enable automatic chat backups115 # Enable automatic chat backups
116 enabled: true116 enabled: true
117 # Verify integrity of chat files before saving
118 checkIntegrity: true
117 # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.119 # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.
118 maxTotalBackups: -1120 maxTotalBackups: -1
119 # Interval in milliseconds to throttle chat backups per user121 # Interval in milliseconds to throttle chat backups per user
default/content/index.json+8 -0
@@ -786,5 +786,13 @@
786 {786 {
787 "filename": "presets/context/DeepSeek-V2.5.json",787 "filename": "presets/context/DeepSeek-V2.5.json",
788 "type": "context"788 "type": "context"
789 },
790 {
791 "filename": "presets/reasoning/DeepSeek.json",
792 "type": "reasoning"
793 },
794 {
795 "filename": "presets/reasoning/Blank.json",
796 "type": "reasoning"
789 }797 }
790]798]
default/content/presets/reasoning/Blank.json+6 -0
@@ -0,0 +1,6 @@
1{
2 "name": "Blank",
3 "prefix": "",
4 "suffix": "",
5 "separator": ""
6}
default/content/presets/reasoning/DeepSeek.json+6 -0
@@ -0,0 +1,6 @@
1{
2 "name": "DeepSeek",
3 "prefix": "<think>\n",
4 "suffix": "\n</think>",
5 "separator": "\n\n"
6}
package-lock.json+202 -4
@@ -110,7 +110,8 @@
110 "@types/write-file-atomic": "^4.0.3",110 "@types/write-file-atomic": "^4.0.3",
111 "@types/yargs": "^17.0.33",111 "@types/yargs": "^17.0.33",
112 "@types/yauzl": "^2.10.3",112 "@types/yauzl": "^2.10.3",
113 "eslint": "^8.57.1"113 "eslint": "^8.57.1",
114 "eslint-plugin-jsdoc": "^48.10.0"
114 },115 },
115 "engines": {116 "engines": {
116 "node": ">= 18"117 "node": ">= 18"
@@ -147,6 +148,21 @@
147 "integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA==",148 "integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA==",
148 "license": "Apache-2.0"149 "license": "Apache-2.0"
149 },150 },
151 "node_modules/@es-joy/jsdoccomment": {
152 "version": "0.46.0",
153 "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz",
154 "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==",
155 "dev": true,
156 "license": "MIT",
157 "dependencies": {
158 "comment-parser": "1.4.1",
159 "esquery": "^1.6.0",
160 "jsdoc-type-pratt-parser": "~4.0.0"
161 },
162 "engines": {
163 "node": ">=16"
164 }
165 },
150 "node_modules/@eslint-community/eslint-utils": {166 "node_modules/@eslint-community/eslint-utils": {
151 "version": "4.4.0",167 "version": "4.4.0",
152 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",168 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -970,6 +986,19 @@
970 "node": ">=14"986 "node": ">=14"
971 }987 }
972 },988 },
989 "node_modules/@pkgr/core": {
990 "version": "0.1.1",
991 "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
992 "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
993 "dev": true,
994 "license": "MIT",
995 "engines": {
996 "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
997 },
998 "funding": {
999 "url": "https://opencollective.com/unts"
1000 }
1001 },
973 "node_modules/@popperjs/core": {1002 "node_modules/@popperjs/core": {
974 "version": "2.11.8",1003 "version": "2.11.8",
975 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",1004 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -2099,6 +2128,16 @@
2099 "safe-buffer": "~5.2.0"2128 "safe-buffer": "~5.2.0"
2100 }2129 }
2101 },2130 },
2131 "node_modules/are-docs-informative": {
2132 "version": "0.0.2",
2133 "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
2134 "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
2135 "dev": true,
2136 "license": "MIT",
2137 "engines": {
2138 "node": ">=14"
2139 }
2140 },
2102 "node_modules/argparse": {2141 "node_modules/argparse": {
2103 "version": "2.0.1",2142 "version": "2.0.1",
2104 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",2143 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2614,6 +2653,16 @@
2614 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",2653 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
2615 "license": "MIT"2654 "license": "MIT"
2616 },2655 },
2656 "node_modules/comment-parser": {
2657 "version": "1.4.1",
2658 "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
2659 "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
2660 "dev": true,
2661 "license": "MIT",
2662 "engines": {
2663 "node": ">= 12.0.0"
2664 }
2665 },
2617 "node_modules/compress-commons": {2666 "node_modules/compress-commons": {
2618 "version": "6.0.2",2667 "version": "6.0.2",
2619 "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",2668 "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
@@ -3560,6 +3609,69 @@
3560 "url": "https://opencollective.com/eslint"3609 "url": "https://opencollective.com/eslint"
3561 }3610 }
3562 },3611 },
3612 "node_modules/eslint-plugin-jsdoc": {
3613 "version": "48.10.0",
3614 "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.10.0.tgz",
3615 "integrity": "sha512-BEli0k8E0dzhJairAllwlkGnyYDZVKNn4WDmyKy+v6J5qGNuofjzxwNUi+55BOGmyO9mKBhqaidwGy+dxndn/Q==",
3616 "dev": true,
3617 "license": "BSD-3-Clause",
3618 "dependencies": {
3619 "@es-joy/jsdoccomment": "~0.46.0",
3620 "are-docs-informative": "^0.0.2",
3621 "comment-parser": "1.4.1",
3622 "debug": "^4.3.5",
3623 "escape-string-regexp": "^4.0.0",
3624 "esquery": "^1.6.0",
3625 "parse-imports": "^2.1.1",
3626 "semver": "^7.6.3",
3627 "spdx-expression-parse": "^4.0.0",
3628 "synckit": "^0.9.1"
3629 },
3630 "engines": {
3631 "node": ">=18"
3632 },
3633 "peerDependencies": {
3634 "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
3635 }
3636 },
3637 "node_modules/eslint-plugin-jsdoc/node_modules/debug": {
3638 "version": "4.4.0",
3639 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
3640 "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
3641 "dev": true,
3642 "license": "MIT",
3643 "dependencies": {
3644 "ms": "^2.1.3"
3645 },
3646 "engines": {
3647 "node": ">=6.0"
3648 },
3649 "peerDependenciesMeta": {
3650 "supports-color": {
3651 "optional": true
3652 }
3653 }
3654 },
3655 "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": {
3656 "version": "4.0.0",
3657 "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
3658 "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
3659 "dev": true,
3660 "license": "MIT",
3661 "engines": {
3662 "node": ">=10"
3663 },
3664 "funding": {
3665 "url": "https://github.com/sponsors/sindresorhus"
3666 }
3667 },
3668 "node_modules/eslint-plugin-jsdoc/node_modules/ms": {
3669 "version": "2.1.3",
3670 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
3671 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
3672 "dev": true,
3673 "license": "MIT"
3674 },
3563 "node_modules/eslint-scope": {3675 "node_modules/eslint-scope": {
3564 "version": "7.2.2",3676 "version": "7.2.2",
3565 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",3677 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -3690,9 +3802,9 @@
3690 }3802 }
3691 },3803 },
3692 "node_modules/esquery": {3804 "node_modules/esquery": {
3693 "version": "1.5.0",3805 "version": "1.6.0",
3694 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",3806 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
3695 "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",3807 "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
3696 "dev": true,3808 "dev": true,
3697 "license": "BSD-3-Clause",3809 "license": "BSD-3-Clause",
3698 "dependencies": {3810 "dependencies": {
@@ -5015,6 +5127,16 @@
5015 "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",5127 "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
5016 "license": "MIT"5128 "license": "MIT"
5017 },5129 },
5130 "node_modules/jsdoc-type-pratt-parser": {
5131 "version": "4.0.0",
5132 "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz",
5133 "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==",
5134 "dev": true,
5135 "license": "MIT",
5136 "engines": {
5137 "node": ">=12.0.0"
5138 }
5139 },
5018 "node_modules/json-buffer": {5140 "node_modules/json-buffer": {
5019 "version": "3.0.1",5141 "version": "3.0.1",
5020 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",5142 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5891,6 +6013,20 @@
5891 "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",6013 "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",
5892 "license": "MIT"6014 "license": "MIT"
5893 },6015 },
6016 "node_modules/parse-imports": {
6017 "version": "2.2.1",
6018 "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz",
6019 "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==",
6020 "dev": true,
6021 "license": "Apache-2.0 AND MIT",
6022 "dependencies": {
6023 "es-module-lexer": "^1.5.3",
6024 "slashes": "^3.0.12"
6025 },
6026 "engines": {
6027 "node": ">= 18"
6028 }
6029 },
5894 "node_modules/parse5": {6030 "node_modules/parse5": {
5895 "version": "7.1.2",6031 "version": "7.1.2",
5896 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",6032 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@@ -6600,6 +6736,19 @@
6600 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",6736 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
6601 "license": "MIT"6737 "license": "MIT"
6602 },6738 },
6739 "node_modules/semver": {
6740 "version": "7.7.1",
6741 "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
6742 "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
6743 "dev": true,
6744 "license": "ISC",
6745 "bin": {
6746 "semver": "bin/semver.js"
6747 },
6748 "engines": {
6749 "node": ">=10"
6750 }
6751 },
6603 "node_modules/send": {6752 "node_modules/send": {
6604 "version": "0.19.0",6753 "version": "0.19.0",
6605 "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",6754 "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
@@ -6811,6 +6960,13 @@
6811 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",6960 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
6812 "license": "MIT"6961 "license": "MIT"
6813 },6962 },
6963 "node_modules/slashes": {
6964 "version": "3.0.12",
6965 "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz",
6966 "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==",
6967 "dev": true,
6968 "license": "ISC"
6969 },
6814 "node_modules/sliced": {6970 "node_modules/sliced": {
6815 "version": "1.0.1",6971 "version": "1.0.1",
6816 "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",6972 "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
@@ -6903,6 +7059,31 @@
6903 "source-map": "^0.6.0"7059 "source-map": "^0.6.0"
6904 }7060 }
6905 },7061 },
7062 "node_modules/spdx-exceptions": {
7063 "version": "2.5.0",
7064 "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
7065 "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
7066 "dev": true,
7067 "license": "CC-BY-3.0"
7068 },
7069 "node_modules/spdx-expression-parse": {
7070 "version": "4.0.0",
7071 "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
7072 "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
7073 "dev": true,
7074 "license": "MIT",
7075 "dependencies": {
7076 "spdx-exceptions": "^2.1.0",
7077 "spdx-license-ids": "^3.0.0"
7078 }
7079 },
7080 "node_modules/spdx-license-ids": {
7081 "version": "3.0.21",
7082 "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
7083 "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
7084 "dev": true,
7085 "license": "CC0-1.0"
7086 },
6906 "node_modules/sprintf-js": {7087 "node_modules/sprintf-js": {
6907 "version": "1.1.3",7088 "version": "1.1.3",
6908 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",7089 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@@ -7042,6 +7223,23 @@
7042 "node": ">=8"7223 "node": ">=8"
7043 }7224 }
7044 },7225 },
7226 "node_modules/synckit": {
7227 "version": "0.9.2",
7228 "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
7229 "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
7230 "dev": true,
7231 "license": "MIT",
7232 "dependencies": {
7233 "@pkgr/core": "^0.1.0",
7234 "tslib": "^2.6.2"
7235 },
7236 "engines": {
7237 "node": "^14.18.0 || >=16.0.0"
7238 },
7239 "funding": {
7240 "url": "https://opencollective.com/unts"
7241 }
7242 },
7045 "node_modules/tapable": {7243 "node_modules/tapable": {
7046 "version": "2.2.1",7244 "version": "2.2.1",
7047 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",7245 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
package.json+2 -1
@@ -140,6 +140,7 @@
140 "@types/write-file-atomic": "^4.0.3",140 "@types/write-file-atomic": "^4.0.3",
141 "@types/yargs": "^17.0.33",141 "@types/yargs": "^17.0.33",
142 "@types/yauzl": "^2.10.3",142 "@types/yauzl": "^2.10.3",
143 "eslint": "^8.57.1"143 "eslint": "^8.57.1",
144 "eslint-plugin-jsdoc": "^48.10.0"
144 }145 }
145}146}
post-install.js+0 -3
@@ -101,15 +101,12 @@ const keyMigrationMap = [
101 newKey: 'performance.memoryCacheCapacity',101 newKey: 'performance.memoryCacheCapacity',
102 migrate: (value) => `${value}mb`,102 migrate: (value) => `${value}mb`,
103 },103 },
104 // uncomment one release after 1.12.13
105 /*
106 {104 {
107 oldKey: 'cookieSecret',105 oldKey: 'cookieSecret',
108 newKey: 'cookieSecret',106 newKey: 'cookieSecret',
109 migrate: () => void 0,107 migrate: () => void 0,
110 remove: true,108 remove: true,
111 },109 },
112 */
113];110];
114111
115/**112/**
public/global.d.ts+4 -0
@@ -1,7 +1,11 @@
1import libs from './lib';1import libs from './lib';
2import getContext from './scripts/st-context';2import getContext from './scripts/st-context';
3import { power_user } from './scripts/power-user';
34
4declare global {5declare global {
6 // Custom types
7 declare type InstructSettings = typeof power_user.instruct;
8
5 // Global namespace modules9 // Global namespace modules
6 interface Window {10 interface Window {
7 ai: any;11 ai: any;
public/index.html+34 -11
@@ -1957,7 +1957,7 @@
1957 <span data-i18n="Enable web search">Enable web search</span>1957 <span data-i18n="Enable web search">Enable web search</span>
1958 </label>1958 </label>
1959 <div class="flexBasis100p toggle-description justifyLeft">1959 <div class="flexBasis100p toggle-description justifyLeft">
1960 <span>1960 <span data-i18n="Use search capabilities provided by the backend.">
1961 Use search capabilities provided by the backend.1961 Use search capabilities provided by the backend.
1962 </span>1962 </span>
1963 </div>1963 </div>
@@ -2188,7 +2188,7 @@
2188 <input id="horde_trusted_workers_only" type="checkbox" />2188 <input id="horde_trusted_workers_only" type="checkbox" />
2189 <span data-i18n="Trusted workers only">Trusted workers only</span>2189 <span data-i18n="Trusted workers only">Trusted workers only</span>
2190 </label>2190 </label>
2191 <small id="adjustedHordeParams">Context: --, Response: --</small>2191 <small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
2192 <h4 data-i18n="API key">API key</h4>2192 <h4 data-i18n="API key">API key</h4>
2193 <small>2193 <small>
2194 <span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://aihorde.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>2194 <span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://aihorde.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>
@@ -3193,6 +3193,7 @@
3193 <option value="mistral-small-latest">mistral-small-latest</option>3193 <option value="mistral-small-latest">mistral-small-latest</option>
3194 <option value="mistral-medium-latest">mistral-medium-latest</option>3194 <option value="mistral-medium-latest">mistral-medium-latest</option>
3195 <option value="mistral-large-latest">mistral-large-latest</option>3195 <option value="mistral-large-latest">mistral-large-latest</option>
3196 <option value="mistral-saba-latest">mistral-saba-latest</option>
3196 <option value="codestral-latest">codestral-latest</option>3197 <option value="codestral-latest">codestral-latest</option>
3197 <option value="codestral-mamba-latest">codestral-mamba-latest</option>3198 <option value="codestral-mamba-latest">codestral-mamba-latest</option>
3198 <option value="pixtral-12b-latest">pixtral-12b-latest</option>3199 <option value="pixtral-12b-latest">pixtral-12b-latest</option>
@@ -3208,13 +3209,20 @@
3208 <option value="mistral-small-2312">mistral-small-2312</option>3209 <option value="mistral-small-2312">mistral-small-2312</option>
3209 <option value="mistral-small-2402">mistral-small-2402</option>3210 <option value="mistral-small-2402">mistral-small-2402</option>
3210 <option value="mistral-small-2409">mistral-small-2409</option>3211 <option value="mistral-small-2409">mistral-small-2409</option>
3212 <option value="mistral-small-2501">mistral-small-2501</option>
3213 <option value="mistral-small-2503">mistral-small-2503</option>
3211 <option value="mistral-medium-2312">mistral-medium-2312</option>3214 <option value="mistral-medium-2312">mistral-medium-2312</option>
3212 <option value="mistral-large-2402">mistral-large-2402</option>3215 <option value="mistral-large-2402">mistral-large-2402</option>
3213 <option value="mistral-large-2407">mistral-large-2407</option>3216 <option value="mistral-large-2407">mistral-large-2407</option>
3214 <option value="mistral-large-2411">mistral-large-2411</option>3217 <option value="mistral-large-2411">mistral-large-2411</option>
3218 <option value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option>
3219 <option value="mistral-saba-2502">mistral-saba-2502</option>
3215 <option value="codestral-2405">codestral-2405</option>3220 <option value="codestral-2405">codestral-2405</option>
3216 <option value="codestral-2405-blue">codestral-2405-blue</option>3221 <option value="codestral-2405-blue">codestral-2405-blue</option>
3217 <option value="codestral-mamba-2407">codestral-mamba-2407</option>3222 <option value="codestral-mamba-2407">codestral-mamba-2407</option>
3223 <option value="codestral-2411-rc5">codestral-2411-rc5</option>
3224 <option value="codestral-2412">codestral-2412</option>
3225 <option value="codestral-2501">codestral-2501</option>
3218 <option value="pixtral-12b-2409">pixtral-12b-2409</option>3226 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
3219 <option value="pixtral-large-2411">pixtral-large-2411</option>3227 <option value="pixtral-large-2411">pixtral-large-2411</option>
3220 </optgroup>3228 </optgroup>
@@ -3407,17 +3415,10 @@
3407 <div class="flex-container">3415 <div class="flex-container">
3408 <select id="model_custom_select" class="text_pole model_custom_select"></select>3416 <select id="model_custom_select" class="text_pole model_custom_select"></select>
3409 </div>3417 </div>
3410 <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
3411 <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
3412 <option data-i18n="prompt_post_processing_none" value="">None</option>
3413 <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
3414 <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
3415 <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>
3416 </select>
3417 </form>3418 </form>
3418 <div id="01ai_form" data-source="01ai">3419 <div id="01ai_form" data-source="01ai">
3419 <h4>3420 <h4>
3420 <a data-i18n="01.AI API Key" href="https://platform.01.ai/" target="_blank" rel="noopener noreferrer">3421 <a data-i18n="01.AI API Key" href="https://platform.lingyiwanwu.com/" target="_blank" rel="noopener noreferrer">
3421 01.AI API Key3422 01.AI API Key
3422 </a>3423 </a>
3423 </h4>3424 </h4>
@@ -3432,6 +3433,15 @@
3432 <select id="model_01ai_select">3433 <select id="model_01ai_select">
3433 </select>3434 </select>
3434 </div>3435 </div>
3436 <div id="prompt_post_porcessing_form" data-source="custom,openrouter">
3437 <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
3438 <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
3439 <option data-i18n="prompt_post_processing_none" value="">None</option>
3440 <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
3441 <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
3442 <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>
3443 </select>
3444 </div>
3435 <div class="flex-container flex">3445 <div class="flex-container flex">
3436 <div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>3446 <div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
3437 <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>3447 <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>
@@ -3917,6 +3927,19 @@
3917 <summary data-i18n="Reasoning Formatting">3927 <summary data-i18n="Reasoning Formatting">
3918 Reasoning Formatting3928 Reasoning Formatting
3919 </summary>3929 </summary>
3930 <div class="flex-container" title="Select your current Reasoning Template" data-i18n="[title]Select your current Reasoning Template">
3931 <select id="reasoning_select" data-preset-manager-for="reasoning" class="flex1 text_pole"></select>
3932 <div class="flex-container margin0 justifyCenter gap3px">
3933 <input type="file" hidden data-preset-manager-file="reasoning" accept=".json, .settings">
3934 <i data-preset-manager-update="reasoning" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3935 <i data-preset-manager-rename="reasoning" class="menu_button fa-pencil fa-solid" title="Rename current template" data-i18n="[title]Rename current template"></i>
3936 <i data-preset-manager-new="reasoning" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3937 <i data-preset-manager-import="reasoning" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3938 <i data-preset-manager-export="reasoning" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3939 <i data-preset-manager-restore="reasoning" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3940 <i data-preset-manager-delete="reasoning" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3941 </div>
3942 </div>
3920 <div class="flex-container">3943 <div class="flex-container">
3921 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">3944 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
3922 <small data-i18n="Prefix">Prefix</small>3945 <small data-i18n="Prefix">Prefix</small>
@@ -6563,7 +6586,7 @@
6563 <div class="ch_name"></div>6586 <div class="ch_name"></div>
6564 <small class="ch_additional_info group_select_counter"></small>6587 <small class="ch_additional_info group_select_counter"></small>
6565 </div>6588 </div>
6566 <small class="character_name_block_sub_line">in this group</small>6589 <small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
6567 <i class='group_fav_icon fa-solid fa-star'></i>6590 <i class='group_fav_icon fa-solid fa-star'></i>
6568 <input class="ch_fav" value="" hidden />6591 <input class="ch_fav" value="" hidden />
6569 <div class="group_select_block_list ch_description"></div>6592 <div class="group_select_block_list ch_description"></div>
public/locales/ru-ru.json+157 -21
@@ -23,9 +23,8 @@
23 "Mirostat Mode": "Π Π΅ΠΆΠΈΠΌ",23 "Mirostat Mode": "Π Π΅ΠΆΠΈΠΌ",
24 "Mirostat Tau": "Tau",24 "Mirostat Tau": "Tau",
25 "Mirostat Eta": "Eta",25 "Mirostat Eta": "Eta",
26 "Variability parameter for Mirostat outputs": "ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ измСнчивости для Π²Ρ‹Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… Mirostat.",26 "Variability parameter for Mirostat outputs": "Π’Π°Ρ€ΠΈΠ°Ρ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ для Π²Ρ‹Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… Mirostat.",
27 "Learning rate of Mirostat": "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ обучСния Mirostat.",27 "Learning rate of Mirostat": "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ обучСния Mirostat.",
28 "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Π‘ΠΈΠ»Π° условия рСгуляризации контрастивного поиска. УстановитС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ 0, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ CS.",
29 "Temperature Last": "Π’Π΅ΠΌΠΏΠ΅Ρ€Π°Ρ‚ΡƒΡ€Π° послСднСй",28 "Temperature Last": "Π’Π΅ΠΌΠΏΠ΅Ρ€Π°Ρ‚ΡƒΡ€Π° послСднСй",
30 "LLaMA / Mistral / Yi models only": "Волько для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ LLaMA / Mistral / Yi. ΠŸΠ΅Ρ€Π΅Π΄ этим ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ подходящий Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€.\nΠŸΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Π½Π° Π²Ρ‹Ρ…ΠΎΠ΄Π΅.\nОдна Π½Π° строку. ВСкст ΠΈΠ»ΠΈ [ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²].\nМногиС Ρ‚ΠΎΠΊΠ΅Π½Ρ‹ ΠΈΠΌΠ΅ΡŽΡ‚ ΠΏΡ€ΠΎΠ±Π΅Π» Π²ΠΏΠ΅Ρ€Π΅Π΄ΠΈ. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ счСтчик Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Ссли Π½Π΅ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹.",29 "LLaMA / Mistral / Yi models only": "Волько для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ LLaMA / Mistral / Yi. ΠŸΠ΅Ρ€Π΅Π΄ этим ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ подходящий Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€.\nΠŸΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Π½Π° Π²Ρ‹Ρ…ΠΎΠ΄Π΅.\nОдна Π½Π° строку. ВСкст ΠΈΠ»ΠΈ [ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²].\nМногиС Ρ‚ΠΎΠΊΠ΅Π½Ρ‹ ΠΈΠΌΠ΅ΡŽΡ‚ ΠΏΡ€ΠΎΠ±Π΅Π» Π²ΠΏΠ΅Ρ€Π΅Π΄ΠΈ. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ счСтчик Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Ссли Π½Π΅ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹.",
31 "Example: some text [42, 69, 1337]": "ΠŸΡ€ΠΈΠΌΠ΅Ρ€:\nΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ тСкст\n[42, 69, 1337]",30 "Example: some text [42, 69, 1337]": "ΠŸΡ€ΠΈΠΌΠ΅Ρ€:\nΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ тСкст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
60 "Add BOS Token": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ BOS-Ρ‚ΠΎΠΊΠ΅Π½",59 "Add BOS Token": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ BOS-Ρ‚ΠΎΠΊΠ΅Π½",
61 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ BOS-Ρ‚ΠΎΠΊΠ΅Π½ Π² Π½Π°Ρ‡Π°Π»Π΅ ΠΏΡ€ΠΎΠΌΠΏΡ‚Π°. Если Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ, ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ ΡΡ‚Π°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ ΠΊΡ€Π΅Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ.",60 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ BOS-Ρ‚ΠΎΠΊΠ΅Π½ Π² Π½Π°Ρ‡Π°Π»Π΅ ΠΏΡ€ΠΎΠΌΠΏΡ‚Π°. Если Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ, ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ ΡΡ‚Π°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ ΠΊΡ€Π΅Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ.",
62 "Ban EOS Token": "Π—Π°ΠΏΡ€Π΅Ρ‚ΠΈΡ‚ΡŒ EOS-Ρ‚ΠΎΠΊΠ΅Π½",61 "Ban EOS Token": "Π—Π°ΠΏΡ€Π΅Ρ‚ΠΈΡ‚ΡŒ EOS-Ρ‚ΠΎΠΊΠ΅Π½",
63 "Ban the eos_token. This forces the model to never end the generation prematurely": "Π—Π°ΠΏΡ€Π΅Ρ‚ EOS-Ρ‚ΠΎΠΊΠ΅Π½Π° Π½Π΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π·Π°Π²Π΅Ρ€ΡˆΠΈΡ‚ΡŒ Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΡŽ ΠΏΡ€Π΅ΠΆΠ΄Π΅Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ",62 "Ban the eos_token. This forces the model to never end the generation prematurely": "Π—Π°ΠΏΡ€Π΅Ρ‚ EOS-Ρ‚ΠΎΠΊΠ΅Π½Π° Π½Π΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π·Π°Π²Π΅Ρ€ΡˆΠΈΡ‚ΡŒ Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΡŽ ΡΠ°ΠΌΠΎΡΡ‚ΠΎΡΡ‚Π΅Π»ΡŒΠ½ΠΎ (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ достиТСнии Π»ΠΈΠΌΠΈΡ‚Π° Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²)",
64 "Skip Special Tokens": "ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ спСц. Ρ‚ΠΎΠΊΠ΅Π½Ρ‹",63 "Skip Special Tokens": "ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ спСц. Ρ‚ΠΎΠΊΠ΅Π½Ρ‹",
65 "Beam search": "Поиск Beam",64 "Beam search": "Beam Search",
66 "Number of Beams": "ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Beam",
67 "Length Penalty": "Π¨Ρ‚Ρ€Π°Ρ„ Π·Π° Π΄Π»ΠΈΠ½Ρƒ",65 "Length Penalty": "Π¨Ρ‚Ρ€Π°Ρ„ Π·Π° Π΄Π»ΠΈΠ½Ρƒ",
68 "Early Stopping": "ΠŸΡ€Π΅ΠΆΠ΄Π΅Π²Ρ€Π΅ΠΌΠ΅Π½Π½Π°Ρ остановка",66 "Early Stopping": "ΠŸΡ€Π΅ΠΊΡ€Π°Ρ‰Π°Ρ‚ΡŒ сразу",
69 "Contrastive search": "ΠšΠΎΠ½Ρ‚Ρ€Π°ΡΡ‚Π½Ρ‹ΠΉ поиск",
70 "Penalty Alpha": "Penalty Alpha",67 "Penalty Alpha": "Penalty Alpha",
71 "Seed": "Π—Π΅Ρ€Π½ΠΎ",68 "Seed": "Π—Π΅Ρ€Π½ΠΎ",
72 "Epsilon Cutoff": "Epsilon Cutoff",69 "Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
89 "Text Completion presets": "ΠŸΡ€Π΅ΡΠ΅Ρ‚Ρ‹ для Text Completion",86 "Text Completion presets": "ΠŸΡ€Π΅ΡΠ΅Ρ‚Ρ‹ для Text Completion",
90 "Documentation on sampling parameters": "ДокумСнтация ΠΏΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌ сэмплСров",87 "Documentation on sampling parameters": "ДокумСнтация ΠΏΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌ сэмплСров",
91 "Set all samplers to their neutral/disabled state.": "Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС сэмплСры Π² Π½Π΅ΠΉΡ‚Ρ€Π°Π»ΡŒΠ½ΠΎΠ΅/ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½Π½ΠΎΠ΅ состояниС.",88 "Set all samplers to their neutral/disabled state.": "Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС сэмплСры Π² Π½Π΅ΠΉΡ‚Ρ€Π°Π»ΡŒΠ½ΠΎΠ΅/ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½Π½ΠΎΠ΅ состояниС.",
92 "Only enable this if your model supports context sizes greater than 8192 tokens": "Π’ΠΊΠ»ΡŽΡ‡Π°ΠΉΡ‚Π΅ эту ΠΎΠΏΡ†ΠΈΡŽ, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ваша модСль ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€ контСкста Π±ΠΎΠ»Π΅Π΅ 8192 Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ².\nΠ£Π²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°ΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Π²Ρ‹ Π·Π½Π°Π΅Ρ‚Π΅, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚Π΅.",89 "Only enable this if your model supports context sizes greater than 8192 tokens": "Π’ΠΊΠ»ΡŽΡ‡Π°ΠΉΡ‚Π΅ эту ΠΎΠΏΡ†ΠΈΡŽ, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ваша модСль ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€ контСкста Π±ΠΎΠ»Π΅Π΅ 8192 Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ².\nΠ£Π²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°ΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Π²Ρ‹ ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ‚Π΅, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚Π΅.",
93 "Wrap in Quotes": "Π—Π°ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π² ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ",90 "Wrap in Quotes": "Π—Π°ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π² ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ",
94 "Wrap entire user message in quotes before sending.": "ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ Π·Π°ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ всё сообщСниС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π² ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ.",91 "Wrap entire user message in quotes before sending.": "ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ Π·Π°ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ всё сообщСниС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π² ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ.",
95 "Leave off if you use quotes manually for speech.": "ΠžΡΡ‚Π°Π²ΡŒΡ‚Π΅ Π²Ρ‹ΠΊΠ»ΡŽΡ‡Π΅Π½Π½Ρ‹ΠΌ, Ссли Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ выставляСтС ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ для прямой Ρ€Π΅Ρ‡ΠΈ.",92 "Leave off if you use quotes manually for speech.": "ΠžΡΡ‚Π°Π²ΡŒΡ‚Π΅ Π²Ρ‹ΠΊΠ»ΡŽΡ‡Π΅Π½Π½Ρ‹ΠΌ, Ссли Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ выставляСтС ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ для прямой Ρ€Π΅Ρ‡ΠΈ.",
@@ -109,7 +106,7 @@
109 "Adjust response length to worker capabilities": "ΠŸΠΎΠ΄ΡΡ‚Ρ€Π°ΠΈΠ²Π°Ρ‚ΡŒ Π΄Π»ΠΈΠ½Ρƒ ΠΎΡ‚Π²Π΅Ρ‚Π° ΠΏΠΎΠ΄ возмоТности Ρ€Π°Π±ΠΎΡ‡ΠΈΡ… машин",106 "Adjust response length to worker capabilities": "ΠŸΠΎΠ΄ΡΡ‚Ρ€Π°ΠΈΠ²Π°Ρ‚ΡŒ Π΄Π»ΠΈΠ½Ρƒ ΠΎΡ‚Π²Π΅Ρ‚Π° ΠΏΠΎΠ΄ возмоТности Ρ€Π°Π±ΠΎΡ‡ΠΈΡ… машин",
110 "API key": "API-ΠΊΠ»ΡŽΡ‡",107 "API key": "API-ΠΊΠ»ΡŽΡ‡",
111 "Tabby API key": "Tabby API-ΠΊΠ»ΡŽΡ‡",108 "Tabby API key": "Tabby API-ΠΊΠ»ΡŽΡ‡",
112 "Get it here:": "ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ здСсь:",109 "Get it here:": "ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚Π΅ здСсь:",
113 "Register": "Π—Π°Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ",110 "Register": "Π—Π°Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ",
114 "TogetherAI Model": "МодСль TogetherAI",111 "TogetherAI Model": "МодСль TogetherAI",
115 "Example: 127.0.0.1:5001": "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: http://127.0.0.1:5001",112 "Example: 127.0.0.1:5001": "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: http://127.0.0.1:5001",
@@ -289,10 +286,10 @@
289 "Author's Note": "Π—Π°ΠΌΠ΅Ρ‚ΠΊΠΈ Π°Π²Ρ‚ΠΎΡ€Π°",286 "Author's Note": "Π—Π°ΠΌΠ΅Ρ‚ΠΊΠΈ Π°Π²Ρ‚ΠΎΡ€Π°",
290 "Replace empty message": "Π—Π°ΠΌΠ΅Π½ΡΡ‚ΡŒ пустыС сообщСния",287 "Replace empty message": "Π—Π°ΠΌΠ΅Π½ΡΡ‚ΡŒ пустыС сообщСния",
291 "Send this text instead of nothing when the text box is empty.": "Π­Ρ‚ΠΎΡ‚ тСкст Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ Π² случаС отсутствия тСкста Π½Π° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΡƒ.",288 "Send this text instead of nothing when the text box is empty.": "Π­Ρ‚ΠΎΡ‚ тСкст Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ Π² случаС отсутствия тСкста Π½Π° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΡƒ.",
292 "Unrestricted maximum value for the context slider": "Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠ»ΠΎΠΊ для ΠΏΠΎΠ»Π·ΡƒΠ½ΠΊΠ° контСкста. Π’ΠΊΠ»ΡŽΡ‡Π°ΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Ρ‚ΠΎΡ‡Π½ΠΎ Π·Π½Π°Π΅Ρ‚Π΅, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚Π΅",289 "Unrestricted maximum value for the context slider": "Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠ»ΠΎΠΊ для ΠΏΠΎΠ»Π·ΡƒΠ½ΠΊΠ° контСкста. Π’ΠΊΠ»ΡŽΡ‡Π°ΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Ρ‚ΠΎΡ‡Π½ΠΎ ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ‚Π΅, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚Π΅",
293 "Chat Completion Source": "Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ для Chat Completion",290 "Chat Completion Source": "Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ для Chat Completion",
294 "Avoid sending sensitive information to the Horde.": "Π˜Π·Π±Π΅Π³Π°ΠΉΡ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ Π»ΠΈΡ‡Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ Horde",291 "Avoid sending sensitive information to the Horde.": "Π˜Π·Π±Π΅Π³Π°ΠΉΡ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ Π»ΠΈΡ‡Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ Horde.",
295 "Review the Privacy statement": "ΠžΠ·Π½Π°ΠΊΠΎΠΌΠΈΡ‚ΡŒΡΡ с заявлСниСм ΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ΄Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ",292 "Review the Privacy statement": "ΠžΠ·Π½Π°ΠΊΠΎΠΌΡŒΡ‚Π΅ΡΡŒ с заявлСниСм ΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ΄Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ",
296 "Trusted workers only": "Волько Π΄ΠΎΠ²Π΅Ρ€Π΅Π½Π½Ρ‹Π΅ Ρ€Π°Π±ΠΎΡ‡ΠΈΠ΅ ΠΌΠ°ΡˆΠΈΠ½Ρ‹",293 "Trusted workers only": "Волько Π΄ΠΎΠ²Π΅Ρ€Π΅Π½Π½Ρ‹Π΅ Ρ€Π°Π±ΠΎΡ‡ΠΈΠ΅ ΠΌΠ°ΡˆΠΈΠ½Ρ‹",
297 "For privacy reasons, your API key will be hidden after you reload the page.": "Из сообраТСний бСзопасности ваш API-ΠΊΠ»ΡŽΡ‡ Π±ΡƒΠ΄Π΅Ρ‚ скрыт послС ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ страницы.",294 "For privacy reasons, your API key will be hidden after you reload the page.": "Из сообраТСний бСзопасности ваш API-ΠΊΠ»ΡŽΡ‡ Π±ΡƒΠ΄Π΅Ρ‚ скрыт послС ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ страницы.",
298 "-- Horde models not loaded --": "--МодСль Horde Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π°--",295 "-- Horde models not loaded --": "--МодСль Horde Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π°--",
@@ -699,7 +696,7 @@
699 "Aggressive": "АгрСссивный",696 "Aggressive": "АгрСссивный",
700 "Very aggressive": "ΠžΡ‡Π΅Π½ΡŒ агрСссивный",697 "Very aggressive": "ΠžΡ‡Π΅Π½ΡŒ агрСссивный",
701 "Eta_Cutoff_desc": "Eta cutoff - основной ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΉ Ρ‚Π΅Ρ…Π½ΠΈΠΊΠΈ сэмплинга ΠΏΠΎΠ΄ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ Eta Sampling.&#13;Π’ Π΅Π΄ΠΈΠ½ΠΈΡ†Π°Ρ… 1e-4; Ρ€Π°Π·ΡƒΠΌΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ - 3.&#13;УстановитС Π² 0, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ.&#13;Π‘ΠΌ. ΡΡ‚Π°Ρ‚ΡŒΡŽ Truncation Sampling as Language Model Desmoothing ΠΎΡ‚ Π₯ΡŒΡŽΠΈΡ‚Ρ‚ ΠΈ Π΄Ρ€. (2022) для получСния ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ.",698 "Eta_Cutoff_desc": "Eta cutoff - основной ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΉ Ρ‚Π΅Ρ…Π½ΠΈΠΊΠΈ сэмплинга ΠΏΠΎΠ΄ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ Eta Sampling.&#13;Π’ Π΅Π΄ΠΈΠ½ΠΈΡ†Π°Ρ… 1e-4; Ρ€Π°Π·ΡƒΠΌΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ - 3.&#13;УстановитС Π² 0, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ.&#13;Π‘ΠΌ. ΡΡ‚Π°Ρ‚ΡŒΡŽ Truncation Sampling as Language Model Desmoothing ΠΎΡ‚ Π₯ΡŒΡŽΠΈΡ‚Ρ‚ ΠΈ Π΄Ρ€. (2022) для получСния ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ.",
702 "Learn how to contribute your idle GPU cycles to the Horde": "Π£Π·Π½Π°ΠΉΡ‚Π΅, ΠΊΠ°ΠΊ внСсти свой Π²ΠΊΠ»Π°Π΄ Π² свои свободныС GPU-Ρ†ΠΈΠΊΠ»Ρ‹ Π² ΠΎΡ€Π΄Ρƒ",699 "Learn how to contribute your idle GPU cycles to the Horde": "Π£Π·Π½Π°ΠΉΡ‚Π΅, ΠΊΠ°ΠΊ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ врСмя простоя вашСго GPU для ΠΏΠΎΠΌΠΎΡ‰ΠΈ Horde",
703 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€ для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Google Ρ‡Π΅Ρ€Π΅Π· ΠΈΡ… API. МСдлСнная ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° подсказок, Π½ΠΎ ΠΏΡ€Π΅Π΄Π»Π°Π³Π°Π΅Ρ‚ Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ Π±ΠΎΠ»Π΅Π΅ Ρ‚ΠΎΡ‡Π½Ρ‹ΠΉ подсчСт Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ².",700 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€ для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Google Ρ‡Π΅Ρ€Π΅Π· ΠΈΡ… API. МСдлСнная ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° подсказок, Π½ΠΎ ΠΏΡ€Π΅Π΄Π»Π°Π³Π°Π΅Ρ‚ Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ Π±ΠΎΠ»Π΅Π΅ Ρ‚ΠΎΡ‡Π½Ρ‹ΠΉ подсчСт Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ².",
704 "Load koboldcpp order": "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ порядок ΠΈΠ· koboldcpp",701 "Load koboldcpp order": "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ порядок ΠΈΠ· koboldcpp",
705 "Use Google Tokenizer": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€ Google",702 "Use Google Tokenizer": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€ Google",
@@ -744,7 +741,7 @@
744 "Last Assistant Prefix": "ПослСдний прСфикс ассистСнта",741 "Last Assistant Prefix": "ПослСдний прСфикс ассистСнта",
745 "System Instruction Prefix": "ΠŸΡ€Π΅Ρ„ΠΈΠΊΡ систСмной инструкции",742 "System Instruction Prefix": "ΠŸΡ€Π΅Ρ„ΠΈΠΊΡ систСмной инструкции",
746 "User Filler Message": "ΠŸΡ€ΠΈΠ½ΡƒΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ сообщСниС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ",743 "User Filler Message": "ΠŸΡ€ΠΈΠ½ΡƒΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ сообщСниС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ",
747 "Permanent": "ΠΏΠ΅Ρ€ΠΌΠ°Π½Π΅Π½Ρ‚Π½Ρ‹Ρ…",744 "Permanent": "постоянных",
748 "Alt. Greetings": "Π”Ρ€. Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹",745 "Alt. Greetings": "Π”Ρ€. Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹",
749 "Smooth Streaming": "ΠŸΠ»Π°Π²Π½Ρ‹ΠΉ стриминг",746 "Smooth Streaming": "ΠŸΠ»Π°Π²Π½Ρ‹ΠΉ стриминг",
750 "Save checkpoint": "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Ρ‡Π΅ΠΊΠΏΠΎΠΈΠ½Ρ‚",747 "Save checkpoint": "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Ρ‡Π΅ΠΊΠΏΠΎΠΈΠ½Ρ‚",
@@ -1227,7 +1224,6 @@
1227 "JSON-serialized array of strings.": "Бписок строк Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ JSON.",1224 "JSON-serialized array of strings.": "Бписок строк Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ JSON.",
1228 "Mirostat_desc": "Mirostat - своСго Ρ€ΠΎΠ΄Π° Ρ‚Π΅Ρ€ΠΌΠΎΠΌΠ΅Ρ‚Ρ€, ΠΈΠ·ΠΌΠ΅Ρ€ΡΡŽΡ‰ΠΈΠΉ ΠΏΠ΅Ρ€ΠΏΠ»Π΅ΠΊΡΠΈΡŽ для Π²Ρ‹Π²ΠΎΠ΄ΠΈΠΌΠΎΠ³ΠΎ тСкста.\nMirostat подгоняСт ΠΏΠ΅Ρ€ΠΏΠ»Π΅ΠΊΡΠΈΡŽ Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅ΠΌΠΎΠ³ΠΎ тСкста ΠΊ пСрплСксии Π²Ρ…ΠΎΠ΄Π½ΠΎΠ³ΠΎ тСкста, Ρ‡Ρ‚ΠΎ позволяСт ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΎΠ².\n(ΠΊΠΎΠ³Π΄Π° ΠΏΠΎ ΠΌΠ΅Ρ€Π΅ Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ тСкста авторСгрСссионным инфСрСнсом, пСрплСксия всё большС приблиТаСтся ΠΊ Π½ΡƒΠ»ΡŽ)\n Π° Ρ‚Π°ΠΊΠΆΠ΅ Π»ΠΎΠ²ΡƒΡˆΠΊΠΈ пСрплСксии (ΠΊΠΎΠ³Π΄Π° пСрплСксия Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΡƒΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ Π² сторону)\nΠ‘ΠΎΠ»Π΅Π΅ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Π² ΡΡ‚Π°Ρ‚ΡŒΠ΅ Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nΠ Π΅ΠΆΠΈΠΌ Π²Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ Π²Π΅Ρ€ΡΠΈΡŽ Mirostat. 0=ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ, 1=Mirostat 1.0 (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ llama.cpp), 2=Mirostat 2.0.",1225 "Mirostat_desc": "Mirostat - своСго Ρ€ΠΎΠ΄Π° Ρ‚Π΅Ρ€ΠΌΠΎΠΌΠ΅Ρ‚Ρ€, ΠΈΠ·ΠΌΠ΅Ρ€ΡΡŽΡ‰ΠΈΠΉ ΠΏΠ΅Ρ€ΠΏΠ»Π΅ΠΊΡΠΈΡŽ для Π²Ρ‹Π²ΠΎΠ΄ΠΈΠΌΠΎΠ³ΠΎ тСкста.\nMirostat подгоняСт ΠΏΠ΅Ρ€ΠΏΠ»Π΅ΠΊΡΠΈΡŽ Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅ΠΌΠΎΠ³ΠΎ тСкста ΠΊ пСрплСксии Π²Ρ…ΠΎΠ΄Π½ΠΎΠ³ΠΎ тСкста, Ρ‡Ρ‚ΠΎ позволяСт ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΎΠ².\n(ΠΊΠΎΠ³Π΄Π° ΠΏΠΎ ΠΌΠ΅Ρ€Π΅ Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ тСкста авторСгрСссионным инфСрСнсом, пСрплСксия всё большС приблиТаСтся ΠΊ Π½ΡƒΠ»ΡŽ)\n Π° Ρ‚Π°ΠΊΠΆΠ΅ Π»ΠΎΠ²ΡƒΡˆΠΊΠΈ пСрплСксии (ΠΊΠΎΠ³Π΄Π° пСрплСксия Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΡƒΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ Π² сторону)\nΠ‘ΠΎΠ»Π΅Π΅ ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ΅ описаниС Π² ΡΡ‚Π°Ρ‚ΡŒΠ΅ Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nΠ Π΅ΠΆΠΈΠΌ Π²Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ Π²Π΅Ρ€ΡΠΈΡŽ Mirostat. 0=ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ, 1=Mirostat 1.0 (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ llama.cpp), 2=Mirostat 2.0.",
1229 "Helpful tip coming soon.": "ΠŸΠΎΠ΄ΡΠΊΠ°Π·ΠΊΡƒ скоро Π΄ΠΎΠ±Π°Π²ΠΈΠΌ.",1226 "Helpful tip coming soon.": "ΠŸΠΎΠ΄ΡΠΊΠ°Π·ΠΊΡƒ скоро Π΄ΠΎΠ±Π°Π²ΠΈΠΌ.",
1230 "Temperature_Last_desc": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Temperature сэмплСр Π² послСднюю ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ. Π­Ρ‚ΠΎ ΠΏΠΎΡ‡Ρ‚ΠΈ всСгда Ρ€Π°Π·ΡƒΠΌΠ½ΠΎ.\nΠŸΡ€ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ: сначала Π²Ρ‹Π±ΠΎΡ€ΠΊΠ° Π½Π°Π±ΠΎΡ€Π° ΠΏΡ€Π°Π²Π΄ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Π·Π°Ρ‚Π΅ΠΌ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Temperature для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²ΠΊΠΈ ΠΈΡ… ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… вСроятностСй (тСхничСски, Π»ΠΎΠ³ΠΈΡ‚ΠΎΠ²).\nΠŸΡ€ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ: сначала ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Temperature для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²ΠΊΠΈ ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… вСроятностСй Π’Π‘Π•Π₯ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Π·Π°Ρ‚Π΅ΠΌ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ° ΠΏΡ€Π°Π²Π΄ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² ΠΈΠ· этого.\nΠžΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ Temperature Last ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ вСроятности Π² хвостС распрСдСлСния, Ρ‡Ρ‚ΠΎ ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ ΡˆΠ°Π½ΡΡ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ нСсогласованный ΠΎΡ‚Π²Π΅Ρ‚.",
1231 "Speculative Ngram": "Speculative Ngram",1227 "Speculative Ngram": "Speculative Ngram",
1232 "Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",1228 "Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",
1233 "Spaces Between Special Tokens": "Spaces Between Special Tokens",1229 "Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1734,7 +1730,7 @@
1734 "markdown_hotkeys_desc": "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ горячиС клавиши для вставки символов Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠΈ Π² Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… полях Π²Π²ΠΎΠ΄Π°. Π‘ΠΌ. '/help hotkeys'.",1730 "markdown_hotkeys_desc": "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ горячиС клавиши для вставки символов Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠΈ Π² Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… полях Π²Π²ΠΎΠ΄Π°. Π‘ΠΌ. '/help hotkeys'.",
1735 "Save and Update": "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ",1731 "Save and Update": "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ",
1736 "Profile name:": "НазваниС профиля:",1732 "Profile name:": "НазваниС профиля:",
1737 "API returned an error": "API Π²Π΅Ρ€Π½ΡƒΠ»ΠΎ ΠΎΡˆΠΈΠ±ΠΊΡƒ",1733 "API returned an error": "API ΠΎΡ‚Π²Π΅Ρ‚ΠΈΠ»ΠΎ ошибкой",
1738 "Failed to save preset": "НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ прСсСт",1734 "Failed to save preset": "НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ прСсСт",
1739 "Preset name should be unique.": "НазваниС прСсСта Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΌ.",1735 "Preset name should be unique.": "НазваниС прСсСта Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΌ.",
1740 "Invalid file": "НСвалидный Ρ„Π°ΠΉΠ»",1736 "Invalid file": "НСвалидный Ρ„Π°ΠΉΠ»",
@@ -1756,8 +1752,7 @@
1756 "dot quota_error": "имССтся достаточно ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΎΠ².",1752 "dot quota_error": "имССтся достаточно ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΎΠ².",
1757 "If you have sufficient credits, please try again later.": "Если ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΎΠ² достаточно, Ρ‚ΠΎ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΡƒ ΠΏΠΎΠ·Π΄Π½Π΅Π΅.",1753 "If you have sufficient credits, please try again later.": "Если ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΎΠ² достаточно, Ρ‚ΠΎ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΡƒ ΠΏΠΎΠ·Π΄Π½Π΅Π΅.",
1758 "Proxy preset '${0}' not found": "ΠŸΡ€Π΅ΡΠ΅Ρ‚ '${0}' Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½",1754 "Proxy preset '${0}' not found": "ΠŸΡ€Π΅ΡΠ΅Ρ‚ '${0}' Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½",
1759 "Window.ai returned an error": "Window.ai Π²Π΅Ρ€Π½ΡƒΠ» ΠΎΡˆΠΈΠ±ΠΊΡƒ",1755 "Window.ai returned an error": "Window.ai ΠΎΡ‚Π²Π΅Ρ‚ΠΈΠ» ошибкой",
1760 "Get it here:": "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚Π΅ здСсь:",
1761 "Extension is not installed": "Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π½Π΅ установлСно",1756 "Extension is not installed": "Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π½Π΅ установлСно",
1762 "Update or remove your reverse proxy settings.": "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚Π΅ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»ΠΈΡ‚Π΅ ваши настройки прокси.",1757 "Update or remove your reverse proxy settings.": "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚Π΅ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»ΠΈΡ‚Π΅ ваши настройки прокси.",
1763 "An error occurred while importing prompts. More info available in console.": "Π’ процСссС ΠΈΠΌΠΏΠΎΡ€Ρ‚Π° ΠΏΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ см. Π² консоли.",1758 "An error occurred while importing prompts. More info available in console.": "Π’ процСссС ΠΈΠΌΠΏΠΎΡ€Ρ‚Π° ΠΏΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ см. Π² консоли.",
@@ -1866,7 +1861,7 @@
1866 "Group Chat could not be saved": "НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΠΎΠ²ΠΎΠΉ Ρ‡Π°Ρ‚",1861 "Group Chat could not be saved": "НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΠΎΠ²ΠΎΠΉ Ρ‡Π°Ρ‚",
1867 "Deleted group member swiped. To get a reply, add them back to the group.": "Π’Ρ‹ ΠΏΡ‹Ρ‚Π°Π΅Ρ‚Π΅ΡΡŒ ΡΠ²Π°ΠΉΠΏΠ½ΡƒΡ‚ΡŒ ΡƒΠ΄Π°Π»Ρ‘Π½Π½ΠΎΠ³ΠΎ Ρ‡Π»Π΅Π½Π° Π³Ρ€ΡƒΠΏΠΏΡ‹. Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚, Π΄ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ этого пСрсонаТа ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ Π² Π³Ρ€ΡƒΠΏΠΏΡƒ.",1862 "Deleted group member swiped. To get a reply, add them back to the group.": "Π’Ρ‹ ΠΏΡ‹Ρ‚Π°Π΅Ρ‚Π΅ΡΡŒ ΡΠ²Π°ΠΉΠΏΠ½ΡƒΡ‚ΡŒ ΡƒΠ΄Π°Π»Ρ‘Π½Π½ΠΎΠ³ΠΎ Ρ‡Π»Π΅Π½Π° Π³Ρ€ΡƒΠΏΠΏΡ‹. Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚, Π΄ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ этого пСрсонаТа ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ Π² Π³Ρ€ΡƒΠΏΠΏΡƒ.",
1868 "Currently no group selected.": "Π’ Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π½Π΅ Π²Ρ‹Π±Ρ€Π°Π½ΠΎ Π½ΠΈ ΠΎΠ΄Π½ΠΎΠΉ Π³Ρ€ΡƒΠΏΠΏΡ‹.",1863 "Currently no group selected.": "Π’ Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π½Π΅ Π²Ρ‹Π±Ρ€Π°Π½ΠΎ Π½ΠΈ ΠΎΠ΄Π½ΠΎΠΉ Π³Ρ€ΡƒΠΏΠΏΡ‹.",
1869 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Π§ΡƒΡ‚ΡŒ ΠΏΠΎΠΌΠ΅Π΄Π»Π΅Π½Π½Π΅Π΅! ΠŸΠ΅Ρ€Π΅Π΄ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ΠΌ Π³Ρ€ΡƒΠΏΠΏΡ‹ Π΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ΡΡŒ, ΠΏΠΎΠΊΠ° пСрсонаТ Π·Π°ΠΊΠΎΠ½Ρ‡ΠΈΡ‚ ΠΏΠ΅Ρ‡Π°Ρ‚Π°Ρ‚ΡŒ.",1864 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Π§ΡƒΡ‚ΡŒ ΠΏΠΎΠΌΠ΅Π΄Π»Π΅Π½Π½Π΅Π΅! ΠŸΠ΅Ρ€Π΅Π΄ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ΠΌ Π³Ρ€ΡƒΠΏΠΏΡ‹ Π΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ΡΡŒ, ΠΏΠΎΠΊΠ° пСрсонаТи Π·Π°ΠΊΠΎΠ½Ρ‡Π°Ρ‚ ΠΏΠ΅Ρ‡Π°Ρ‚Π°Ρ‚ΡŒ.",
1870 "Delete the group?": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΡƒ?",1865 "Delete the group?": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΡƒ?",
1871 "This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "ВмСстС с Π½Π΅ΠΉ Π±ΡƒΠ΄ΡƒΡ‚ ΡƒΠ΄Π°Π»Π΅Π½Ρ‹ ΠΈ всС Π΅Ρ‘ Ρ‡Π°Ρ‚Ρ‹. Если трСбуСтся ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½ Ρ‡Π°Ρ‚, Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ΡΡŒ ΠΊΠ½ΠΎΠΏΠΊΠΎΠΉ \"ВсС Ρ‡Π°Ρ‚Ρ‹\" Π² мСню Π² Π»Π΅Π²ΠΎΠΌ Π½ΠΈΠΆΠ½Π΅ΠΌ ΡƒΠ³Π»Ρƒ.",1866 "This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "ВмСстС с Π½Π΅ΠΉ Π±ΡƒΠ΄ΡƒΡ‚ ΡƒΠ΄Π°Π»Π΅Π½Ρ‹ ΠΈ всС Π΅Ρ‘ Ρ‡Π°Ρ‚Ρ‹. Если трСбуСтся ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½ Ρ‡Π°Ρ‚, Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ΡΡŒ ΠΊΠ½ΠΎΠΏΠΊΠΎΠΉ \"ВсС Ρ‡Π°Ρ‚Ρ‹\" Π² мСню Π² Π»Π΅Π²ΠΎΠΌ Π½ΠΈΠΆΠ½Π΅ΠΌ ΡƒΠ³Π»Ρƒ.",
1872 "Can't peek a character while group reply is being generated": "НСвозмоТно ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΊΠ°Ρ€Ρ‚ΠΎΡ‡ΠΊΡƒ пСрсонаТа Π²ΠΎ врСмя Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ ΠΎΡ‚Π²Π΅Ρ‚Π°",1867 "Can't peek a character while group reply is being generated": "НСвозмоТно ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΊΠ°Ρ€Ρ‚ΠΎΡ‡ΠΊΡƒ пСрсонаТа Π²ΠΎ врСмя Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ ΠΎΡ‚Π²Π΅Ρ‚Π°",
@@ -1997,7 +1992,7 @@
1997 "Default persona deleted": "Π£Π΄Π°Π»Π΅Π½Π° пСрсона ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ",1992 "Default persona deleted": "Π£Π΄Π°Π»Π΅Π½Π° пСрсона ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ",
1998 "The locked persona was deleted. You will need to set a new persona for this chat.": "Π£Π΄Π°Π»Π΅Π½Π° привязанная ΠΊ Ρ‡Π°Ρ‚Ρƒ пСрсона. Π’Π°ΠΌ Π±ΡƒΠ΄Π΅Ρ‚ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ„ΠΈΠΊΡΠΈΡ€ΠΎΠ²Π°Π½Π½ΡƒΡŽ пСрсону для этого Ρ‡Π°Ρ‚Π°.",1993 "The locked persona was deleted. You will need to set a new persona for this chat.": "Π£Π΄Π°Π»Π΅Π½Π° привязанная ΠΊ Ρ‡Π°Ρ‚Ρƒ пСрсона. Π’Π°ΠΌ Π±ΡƒΠ΄Π΅Ρ‚ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ„ΠΈΠΊΡΠΈΡ€ΠΎΠ²Π°Π½Π½ΡƒΡŽ пСрсону для этого Ρ‡Π°Ρ‚Π°.",
1999 "Persona deleted": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ΡƒΠ΄Π°Π»Π΅Π½Π°",1994 "Persona deleted": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ΡƒΠ΄Π°Π»Π΅Π½Π°",
2000 "You must bind a name to this persona before you can set it as the default.": "ΠŸΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ эту пСрсону Π² качСствС пСрсоны ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ, Π΅ΠΉ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π·Π°Π΄Π°Ρ‚ΡŒ имя.",1995 "You must bind a name to this persona before you can set it as the default.": "ΠŸΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ эту пСрсону Π² качСствС пСрсоны ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ, Π΅ΠΉ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΡ€ΠΈΡΠ²ΠΎΠΈΡ‚ΡŒ имя.",
2001 "Persona name not set": "Π£ пСрсоны отсутствуСт имя",1996 "Persona name not set": "Π£ пСрсоны отсутствуСт имя",
2002 "Are you sure you want to remove the default persona?": "Π’Ρ‹ Ρ‚ΠΎΡ‡Π½ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡΠ½ΡΡ‚ΡŒ статус пСрсоны ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ?",1997 "Are you sure you want to remove the default persona?": "Π’Ρ‹ Ρ‚ΠΎΡ‡Π½ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡΠ½ΡΡ‚ΡŒ статус пСрсоны ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ?",
2003 "This persona will no longer be used by default when you open a new chat.": "Π­Ρ‚Π° пСрсона большС Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ автоматичСски Π²Ρ‹Π±ΠΈΡ€Π°Ρ‚ΡŒΡΡ ΠΏΡ€ΠΈ стартС Π½ΠΎΠ²ΠΎΠ³ΠΎ Ρ‡Π°Ρ‚Π°",1998 "This persona will no longer be used by default when you open a new chat.": "Π­Ρ‚Π° пСрсона большС Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ автоматичСски Π²Ρ‹Π±ΠΈΡ€Π°Ρ‚ΡŒΡΡ ΠΏΡ€ΠΈ стартС Π½ΠΎΠ²ΠΎΠ³ΠΎ Ρ‡Π°Ρ‚Π°",
@@ -2203,5 +2198,146 @@
2203 "Input:": "Π’Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅:",2198 "Input:": "Π’Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅:",
2204 "Tokenized text:": "Π’ΠΎΠΊΠ΅Π½ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ тСкст:",2199 "Tokenized text:": "Π’ΠΎΠΊΠ΅Π½ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ тСкст:",
2205 "Token IDs:": "Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²:",2200 "Token IDs:": "Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²:",
2206 "Tokens:": "Π’ΠΎΠΊΠ΅Π½ΠΎΠ²:"2201 "Tokens:": "Π’ΠΎΠΊΠ΅Π½ΠΎΠ²:",
2202 "Max prompt cost:": "Макс. ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΡ€ΠΎΠΌΠΏΡ‚Π°:",
2203 "Reset custom sampler selection": "Π‘Π±Ρ€ΠΎΡΠΈΡ‚ΡŒ ΠΏΠΎΠ΄Π±ΠΎΡ€ΠΊΡƒ сСмплСров",
2204 "Here you can toggle the display of individual samplers. (WIP)": "Π—Π΄Π΅ΡΡŒ ΠΌΠΎΠΆΠ½ΠΎ Π²ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΈΠ»ΠΈ Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ ΠΎΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΈΠ· сэмплСров ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ. (WIP)",
2205 "Request Model Reasoning": "Π—Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Ρ‚ΡŒ Ρ†Π΅ΠΏΠΎΡ‡ΠΊΡƒ рассуТдСний",
2206 "Reasoning": "РассуТдСния / Reasoning",
2207 "Auto-Parse": "Авто-парсинг",
2208 "reasoning_auto_parse": "АвтоматичСски ΡΡ‡ΠΈΡ‚Ρ‹Π²Π°Ρ‚ΡŒ Π±Π»ΠΎΠΊΠΈ рассуТдСний, располоТСнныС ΠΌΠ΅ΠΆΠ΄Ρƒ прСфиксом ΠΈ суффиксом рассуТдСний. Для Ρ€Π°Π±ΠΎΡ‚Ρ‹ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΡƒΠΊΠ°Π·Π°Π½ΠΎ ΠΈ Ρ‚ΠΎ, ΠΈ Π΄Ρ€ΡƒΠ³ΠΎΠ΅.",
2209 "Auto-Expand": "Π Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ",
2210 "reasoning_auto_expand": "АвтоматичСски Ρ€Π°Π·Π²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ Π±Π»ΠΎΠΊΠΈ рассуТдСний.",
2211 "Show Hidden": "ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ врСмя",
2212 "reasoning_show_hidden": "ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Ρ‚ΡŒ Π·Π°Ρ‚Ρ€Π°Ρ‡Π΅Π½Π½ΠΎΠ΅ Π½Π° рассуТдСния врСмя для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ со скрытой Ρ†Π΅ΠΏΠΎΡ‡ΠΊΠΎΠΉ рассуТдСний",
2213 "Add to Prompts": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ Π² ΠΏΡ€ΠΎΠΌΠΏΡ‚",
2214 "reasoning_add_to_prompts": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π±Π»ΠΎΠΊΠΈ рассуТдСний Π² ΠΏΡ€ΠΎΠΌΠΏΡ‚. Для добавлСния Π½ΠΎΠ²Ρ‹Ρ… ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ мСню рСдактирования сообщСний.",
2215 "reasoning_max_additions": "Макс. ΠΊΠΎΠ»-Π²ΠΎ Π±Π»ΠΎΠΊΠΎΠ² рассуТдСний Π² ΠΏΡ€ΠΎΠΌΠΏΡ‚Π΅, считаСтся ΠΎΡ‚ послСднСго сообщСния",
2216 "Max": "Макс.",
2217 "Reasoning Formatting": "Π€ΠΎΡ€ΠΌΠ°Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ рассуТдСний",
2218 "Prefix": "ΠŸΡ€Π΅Ρ„ΠΈΠΊΡ",
2219 "Suffix": "ΠŸΠΎΡΡ‚Ρ„ΠΈΠΊΡ",
2220 "Separator": "Π Π°Π·Π΄Π΅Π»ΠΈΡ‚Π΅Π»ΡŒ",
2221 "reasoning_separator": "ВставляСтся ΠΌΠ΅ΠΆΠ΄Ρƒ рассуТдСниями ΠΈ содСрТаниСм самого сообщСния.",
2222 "reasoning_prefix": "ВставляСтся ΠΏΠ΅Ρ€Π΅Π΄ рассуТдСниями.",
2223 "reasoning_suffix": "ВставляСтся послС рассуТдСний.",
2224 "Seed_desc": "ЀиксированноС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π·Π΅Ρ€Π½Π° позволяСт ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ прСдсказуСмыС, ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Π΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ Π½Π° ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Ρ… настройках. ΠŸΠΎΡΡ‚Π°Π²ΡŒΡ‚Π΅ -1 для Ρ€Π°Π½Π΄ΠΎΠΌΠ½ΠΎΠ³ΠΎ Π·Π΅Ρ€Π½Π°.",
2225 "# of Beams": "Кол-Π²ΠΎ Π»ΡƒΡ‡Π΅ΠΉ",
2226 "The number of sequences generated at each step with Beam Search.": "Кол-Π²ΠΎ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ², Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅ΠΌΡ‹Ρ… Beam Search Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΌ шагС Ρ€Π°Π±ΠΎΡ‚Ρ‹.",
2227 "Penalize sequences based on their length.": "Π¨Ρ‚Ρ€Π°Ρ„ΡƒΠ΅Ρ‚ строки Π² зависимости ΠΎΡ‚ Π΄Π»ΠΈΠ½Ρ‹",
2228 "Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates.": "ΠžΠΏΡ€Π΅Π΄Π΅Π»ΡΠ΅Ρ‚, ΠΊΠΎΠ³Π΄Π° ΠΎΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°Ρ‚ΡŒ Ρ€Π°Π±ΠΎΡ‚Ρƒ Beam Search. ΠŸΠΎΡΡ‚Π°Π²ΠΈΠ² Π³Π°Π»ΠΎΡ‡ΠΊΡƒ, Π²Ρ‹ ΡƒΠΊΠ°ΠΆΠ΅Ρ‚Π΅ поиску ΠΎΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒΡΡ Ρ‚ΠΎΠ³Π΄Π°, ΠΊΠΎΠ³Π΄Π° Π±ΡƒΠ΄Π΅Ρ‚ достигнуто ΠΊΠΎΠ»-Π²ΠΎ Π»ΡƒΡ‡Π΅ΠΉ ΠΈΠ· ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ поля. Если Π³Π°Π»ΠΎΡ‡ΠΊΡƒ Π½Π΅ ΠΎΡ‚ΠΌΠ΅Ρ‡Π°Ρ‚ΡŒ, Ρ‚ΠΎ гСнСрация остановится Ρ‚ΠΎΠ³Π΄Π°, ΠΊΠΎΠ³Π΄Π° сочтёт, Ρ‡Ρ‚ΠΎ дальшС Π½Π°ΠΉΡ‚ΠΈ Π»ΡƒΡ‡ΡˆΠΈΡ… ΠΊΠ°Π½Π΄ΠΈΠ΄Π°Ρ‚ΠΎΠ² слишком маловСроятно.",
2229 "A greedy, brute-force algorithm used in LLM sampling to find the most likely sequence of words or tokens. It expands multiple candidate sequences at once, maintaining a fixed number (beam width) of top sequences at each step.": "Π–Π°Π΄Π½Ρ‹ΠΉ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌ LLM-сэмплинга, ΠΏΠΎΠ΄Π±ΠΈΡ€Π°ΡŽΡ‰ΠΈΠΉ Π½Π°ΠΈΠ±ΠΎΠ»Π΅Π΅ Π²Π΅Ρ€ΠΎΡΡ‚Π½ΡƒΡŽ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ слов ΠΈΠ»ΠΈ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² ΠΏΡƒΡ‚Ρ‘ΠΌ исслСдования ΠΈ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ сразу Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ². На ΠΊΠ°ΠΆΠ΄ΠΎΠΌ шагС ΠΎΠ½ ΡƒΠ΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ фиксированноС ΠΊΠΎΠ»-Π²ΠΎ самых подходящих Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ² (ΡˆΠΈΡ€ΠΈΠ½Π° Π»ΡƒΡ‡Π°).",
2230 "Smooth_Sampling_desc": "Π˜Π·ΠΌΠ΅Π½ΡΠ΅Ρ‚ распрСдСлСниС с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚ΠΈΡ‡Π½Ρ‹Ρ… ΠΈ кубичСских ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½ΠΈΠΉ. Π‘Π½ΠΈΠΆΠ΅Π½ΠΈΠ΅ ΠšΠΎΡΡ„Ρ„ΠΈΡ†ΠΈΠ΅Π½Ρ‚Π° сглаТивания Π΄Π°Ρ‘Ρ‚ Π±ΠΎΠ»Π΅Π΅ ΠΊΡ€Π΅Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ ΠΎΡ‚Π²Π΅Ρ‚Ρ‹, ΠΎΠ±Ρ‹Ρ‡Π½ΠΎ идСальноС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ находится Π² Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π΅ 0.2-0.3 (ΠΏΡ€ΠΈ ΠΊΡ€ΠΈΠ²ΠΎΠΉ сглаТивания=1.0). ΠŸΠΎΠ²Ρ‹ΡˆΠ΅Π½ΠΈΠ΅ значСния ΠšΡ€ΠΈΠ²ΠΎΠΉ сглаТивания сдСлаСт ΠΊΡ€ΠΈΠ²ΡƒΡŽ ΠΊΡ€ΡƒΡ‡Π΅, Ρ‡Ρ‚ΠΎ ΠΏΡ€ΠΈΠ²Π΅Π΄Ρ‘Ρ‚ ΠΊ Π±ΠΎΠ»Π΅Π΅ агрСссивной Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Π°Ρ†ΠΈΠΈ маловСроятных Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ². Установив ΠšΡ€ΠΈΠ²ΡƒΡŽ сглаТивания = 1.0, Π²Ρ‹ фактичСски Π½Π΅ΠΉΡ‚Ρ€Π°Π»ΠΈΠ·ΡƒΠ΅Ρ‚Π΅ этот ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΠΈ Π±ΡƒΠ΄Π΅Ρ‚Π΅ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ с ΠšΠΎΡΡ„Ρ„ΠΈΡ†ΠΈΠ΅Π½Ρ‚ΠΎΠΌ",
2231 "Temperature_Last_desc": "ΠŸΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ сэмплСр Π’Π΅ΠΌΠΏΠ΅Ρ€Π°Ρ‚ΡƒΡ€Ρ‹ Π² послСднюю ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ. ΠŸΠΎΡ‡Ρ‚ΠΈ всСгда ΠΎΠΏΡ€Π°Π²Π΄Π°Π½ΠΎ.\nΠŸΡ€ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ: сначала всС Ρ‚ΠΎΠΊΠ΅Π½Ρ‹ ΡΠ΅ΠΌΠΏΠ»ΠΈΡ€ΡƒΡŽΡ‚ΡΡ, ΠΈ Π·Π°Ρ‚Π΅ΠΌ Ρ‚Π΅ΠΌΠΏΠ΅Ρ€Π°Ρ‚ΡƒΡ€Π° Ρ€Π΅Π³ΡƒΠ»ΠΈΡ€ΡƒΠ΅Ρ‚ распрСдСлСниС Ρƒ ΠΎΡΡ‚Π°Π²ΡˆΠΈΡ…ΡΡ (тСхничСски, Ρƒ ΠΎΡΡ‚Π°Π²ΡˆΠΈΡ…ΡΡ Π»ΠΎΠ³ΠΈΡ‚ΠΎΠ²).\nΠŸΡ€ΠΈ Π²Ρ‹ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ: сначала Ρ‚Π΅ΠΌΠΏΠ΅Ρ€Π°Ρ‚ΡƒΡ€Π° настраиваСт распрСдСлСниС Π’Π‘Π•Π₯ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², ΠΈ ΠΏΠΎΡ‚ΠΎΠΌ ΠΎΠ½ΠΈ ΡΠ΅ΠΌΠΏΠ»ΠΈΡ€ΡƒΡŽΡ‚ΡΡ ΡƒΠΆΠ΅ с этим ΠΎΠ±Π½ΠΎΠ²Π»Ρ‘Π½Π½Ρ‹ΠΌ распрСдСлСниСм.\nΠŸΡ€ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ этой ΠΎΠΏΡ†ΠΈΠΈ Ρ‚ΠΎΠΊΠ΅Π½Ρ‹ Π² хвостС ΠΏΠΎΠ»ΡƒΡ‡Π°ΡŽΡ‚ большС шансов ΠΏΠΎΠΏΠ°ΡΡ‚ΡŒ Π² ΠΈΡ‚ΠΎΠ³ΠΎΠ²ΡƒΡŽ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΌΠΎΠΆΠ΅Ρ‚ привСсти ΠΊ ΠΌΠ΅Π½Π΅Π΅ связным ΠΈ Π»ΠΎΠ³ΠΈΡ‡Π½Ρ‹ΠΌ ΠΎΡ‚Π²Π΅Ρ‚Π°ΠΌ.",
2232 "Swipe # for All Messages": "НомСр свайпа Π½Π° всСх сообщСниях",
2233 "Display swipe numbers for all messages, not just the last.": "ΠžΡ‚ΠΎΠ±Ρ€Π°ΠΆΠ°Ρ‚ΡŒ Π½ΠΎΠΌΠ΅Ρ€ свайпа для всСх сообщСний, Π° Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для послСднСго.",
2234 "Penalty Range": "Окно для ΡˆΡ‚Ρ€Π°Ρ„Π°",
2235 "Never": "Никогда",
2236 "Groups and Past Personas": "Для Π³Ρ€ΡƒΠΏΠΏ ΠΈ ΠΏΡ€ΠΎΡˆΠ»Ρ‹Ρ… пСрсон",
2237 "Always": "ВсСгда",
2238 "Request model reasoning": "Π—Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Ρ‚ΡŒ рассуТдСния",
2239 "Allows the model to return its thinking process.": "ΠŸΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ‚ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π²Ρ‹ΡΡ‹Π»Π°Ρ‚ΡŒ Π² ΠΎΡ‚Π²Π΅Ρ‚Π΅ свою Ρ†Π΅ΠΏΠΎΡ‡ΠΊΡƒ рассуТдСний.",
2240 "Rename Persona": "ΠŸΠ΅Ρ€Π΅ΠΈΠΌΠ΅Π½ΠΎΠ²Π°Ρ‚ΡŒ пСрсону",
2241 "Change Persona Image": "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ пСрсоны",
2242 "Duplicate Persona": "ΠšΠ»ΠΎΠ½ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ пСрсону",
2243 "Delete Persona": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ пСрсону",
2244 "Enter a new name for this persona:": "Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²ΠΎΠ΅ имя пСрсоны:",
2245 "Connections": "Бвязи",
2246 "Click to select this as default persona for the new chats. Click again to remove it.": "НаТмитС, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ эту пСрсону стандартной для всСх Π½ΠΎΠ²Ρ‹Ρ… Ρ‡Π°Ρ‚ΠΎΠ². НаТмитС Π΅Ρ‰Ρ‘ Ρ€Π°Π·, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ.",
2247 "Character": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π°ΠΆ",
2248 "Click to lock your selected persona to the current character. Click again to remove the lock.": "НаТмитС, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π·Π°ΠΊΡ€Π΅ΠΏΠΈΡ‚ΡŒ эту пСрсону для Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ пСрсонаТа. НаТмитС Π΅Ρ‰Ρ‘ Ρ€Π°Π·, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΡ€Π΅ΠΏΠΈΡ‚ΡŒ.",
2249 "Chat": "Π§Π°Ρ‚",
2250 "[No character connections. Click one of the buttons above to connect this persona.]": "[Бвязи ΠΎΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‚. НаТмитС Π½Π° ΠΎΠ΄Π½Ρƒ ΠΈΠ· ΠΊΠ½ΠΎΠΏΠΎΠΊ Π²Ρ‹ΡˆΠ΅, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ.]",
2251 "Global Settings": "ΠžΠ±Ρ‰ΠΈΠ΅ настройки",
2252 "Allow multiple persona connections per character": "Π Π°Π·Ρ€Π΅ΡˆΠΈΡ‚ΡŒ ΠΏΡ€ΠΈΠ²ΡΠ·Ρ‹Π²Π°Ρ‚ΡŒ нСсколько пСрсон ΠΊ ΠΎΠ΄Π½ΠΎΠΌΡƒ пСрсонаТу",
2253 "When multiple personas are connected to a character, a popup will appear to select which one to use": "ΠŸΡ€ΠΈ связывании Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… пСрсон с пСрсонаТСм, Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠΎΡΠ²Π»ΡΡ‚ΡŒΡΡ окошко с ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΠΌ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Π½ΡƒΠΆΠ½ΡƒΡŽ.",
2254 "Auto-lock a chosen persona to the chat": "АвтоматичСски ΠΏΡ€ΠΈΠ²ΡΠ·Ρ‹Π²Π°Ρ‚ΡŒ Π²Ρ‹Π±Ρ€Π°Π½Π½ΡƒΡŽ пСрсону ΠΊ Ρ‡Π°Ρ‚Ρƒ",
2255 "Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "ΠŸΡ€ΠΈ Π²Ρ‹Π±ΠΎΡ€Π΅ Π½ΠΎΠ²ΠΎΠΉ пСрсоны ΠΎΠ½Π° автоматичСски Π±ΡƒΠ΄Π΅Ρ‚ привязана ΠΊ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΌΡƒ Ρ‡Π°Ρ‚Ρƒ, ΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ‹Π±ΠΈΡ€Π°Ρ‚ΡŒΡΡ ΠΏΡ€ΠΈ Π΅Π³ΠΎ ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΠΈΠΈ.",
2256 "Current Persona": "ВСкущая пСрсона",
2257 "The chat has been successfully converted!": "Π§Π°Ρ‚ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΠΎΠ²Π°Π½!",
2258 "Manual": "Когда Π²Ρ‹ скаТСтС",
2259 "Auto Mode delay": "Π—Π°Π΄Π΅Ρ€ΠΆΠΊΠ° Π°Π²Ρ‚ΠΎ-Ρ€Π΅ΠΆΠΈΠΌΠ°",
2260 "Use tag as folder": "Π’Π΅Π³-ΠΏΠ°ΠΏΠΊΠ°",
2261 "All connections to ${0} have been removed.": "ВсС связи с пСрсонаТСм ${0} Π±Ρ‹Π»ΠΈ ΡƒΠ΄Π°Π»Π΅Π½Ρ‹.",
2262 "Personas Unlocked": "ΠŸΠ΅Ρ€ΡΠΎΠ½Ρ‹ отвязаны",
2263 "Remove All Connections": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС связи",
2264 "Persona ${0} selected and auto-locked to current chat": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ${0} Π²Ρ‹Π±Ρ€Π°Π½Π° ΠΈ автоматичСски Π·Π°ΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π° Π·Π° этим Ρ‡Π°Ρ‚ΠΎΠΌ",
2265 "This persona is only temporarily chosen. Click for more info.": "Данная пСрсона Π²Ρ‹Π±Ρ€Π°Π½Π° лишь Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ. НаТмитС, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ·Π½Π°Ρ‚ΡŒ большС.",
2266 "Temporary Persona": "ВрСмСнная пСрсона",
2267 "A different persona is locked to this chat, or you have a different default persona set. The currently selected persona will only be temporary, and resets on reload. Consider locking this persona to the chat if you want to permanently use it.": "К этому Ρ‡Π°Ρ‚Ρƒ ΡƒΠΆΠ΅ привязана иная пСрсона, Π»ΠΈΠ±ΠΎ Ρƒ вас Π²Ρ‹Π±Ρ€Π°Π½Π° иная пСрсона ΠΏΠΎ-ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ. Выбранная Π² Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ пСрсона Π±ΡƒΠ΄Π΅Ρ‚ Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ, ΠΈ сбросится послС ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ. Если Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ всСгда ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΅Ρ‘ Π² этом Ρ‡Π°Ρ‚Π΅, совСтуСм Π΅Ρ‘ ΠΏΡ€ΠΈΠΊΡ€Π΅ΠΏΠΈΡ‚ΡŒ.",
2268 "Current Persona: ${0}": "Выбранная пСрсона: ${0}",
2269 "Chat persona: ${0}": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° для этого Ρ‡Π°Ρ‚Π°: ${0}",
2270 "Default persona: ${0}": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ (стандартная): ${0}",
2271 "Persona ${0} is now unlocked from this chat.": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ${0} отвязана ΠΎΡ‚ этого Ρ‡Π°Ρ‚Π°.",
2272 "Persona Unlocked": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° отвязана",
2273 "Persona ${0} is now unlocked from character ${1}.": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ${0} отвязана ΠΎΡ‚ пСрсонаТа ${1}.",
2274 "Persona Not Found": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Π°",
2275 "Persona Locked": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° Π·Π°ΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π°",
2276 "User persona ${0} is locked to character ${1}${2}": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ${0} ΠΏΡ€ΠΈΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π° ΠΊ пСрсонаТу ${1}${2}",
2277 "Persona Name Not Set": "Π£ пСрсоны отсутствуСт имя",
2278 "You must bind a name to this persona before you can set a lorebook.": "ΠŸΠ΅Ρ€Π΅Π΄ привязкой Π»ΠΎΡ€Π±ΡƒΠΊΠ° пСрсонС Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΡ€ΠΈΡΠ²ΠΎΠΈΡ‚ΡŒ имя.",
2279 "Default Persona Removed": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ снята",
2280 "Persona is locked to the current character": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° Π·Π°ΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π° Π·Π° этим пСрсонаТСм",
2281 "Persona is locked to the current chat": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° Π·Π°ΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π° Π·Π° этим Ρ‡Π°Ρ‚ΠΎΠΌ",
2282 "characters": "пСрс.",
2283 "character": "пСрсонаТ",
2284 "in this group": "Π² Π³Ρ€ΡƒΠΏΠΏΠ΅",
2285 "Chatting Since": "ΠŸΠ΅Ρ€Π²Π°Ρ бСсСда",
2286 "Context": "ΠšΠΎΠ½Ρ‚Π΅ΠΊΡΡ‚",
2287 "Response": "ΠžΡ‚Π²Π΅Ρ‚",
2288 "Connected": "ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΎ",
2289 "Enter new background name:": "Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π½ΠΎΠ²ΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ для Ρ„ΠΎΠ½Π°:",
2290 "AI Horde Website": "Π‘Π°ΠΉΡ‚ AI Horde",
2291 "Enable web search": "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ поиск Π² Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅",
2292 "Use search capabilities provided by the backend.": "Π Π°Π·Ρ€Π΅ΡˆΠΈΡ‚ΡŒ использованиС прСдоставляСмых бэкСндом Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ поиска.",
2293 "Request inline images": "Π—Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Ρ‚ΡŒ inline-изобраТСния",
2294 "Allows the model to return image attachments.": "Π Π°Π·Ρ€Π΅ΡˆΠΈΡ‚ΡŒ ΠΌΠΎΠ΄Π΅Π»ΠΈ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ влоТСния Π² Π²ΠΈΠ΄Π΅ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΎΠΊ.",
2295 "Request inline images_desc_2": "НС совмСстимо со ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΎΠ½Π°Π»ΠΎΠΌ: Π²Ρ‹Π·ΠΎΠ² Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΉ, поиск Π² Π˜Π½Ρ‚Π΅Ρ€Π½Π΅Ρ‚Π΅, систСмный ΠΏΡ€ΠΎΠΌΠΏΡ‚.",
2296 "Connected Personas": "БвязанныС пСрсоны",
2297 "[Currently no personas connected]": "[Бвязанных пСрсон Π½Π΅Ρ‚]",
2298 "The following personas are connected to the current character.\n\nClick on a persona to select it for the current character.\nShift + Click to unlink the persona from the character.": "Π‘ этим пСрсонаТСм связаны ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ пСрсоны.\n\nНаТмитС Π½Π° пСрсону, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Π΅Ρ‘ для Π΄Π°Π½Π½ΠΎΠ³ΠΎ пСрсонаТа.\nShift + Π›ΠšΠœ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΅Ρ‘ ΠΎΡ‚Π²ΡΠ·Π°Ρ‚ΡŒ.",
2299 "Persona Connections": "Бвязи с пСрсонами",
2300 "Pooled order": "Если ΡƒΠΆΠ΅ Π΄Π°Π²Π½ΠΎ Π½Π΅ ΠΎΡ‚Π²Π΅Ρ‡Π°Π»ΠΈ",
2301 "Attach a File": "ΠŸΡ€ΠΈΠ»ΠΎΠΆΠΈΡ‚ΡŒ Ρ„Π°ΠΉΠ»",
2302 "Attach a file or image to a current chat.": "ΠŸΡ€ΠΈΠ»ΠΎΠΆΠΈΡ‚ΡŒ Ρ„Π°ΠΉΠ» ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΊ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΌΡƒ Ρ‡Π°Ρ‚Ρƒ",
2303 "Remove the file": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Ρ„Π°ΠΉΠ»",
2304 "Delete the Chat File?": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Ρ‡Π°Ρ‚?",
2305 "Forbidden": "Доступ Π·Π°ΠΏΡ€Π΅Ρ‰Ρ‘Π½",
2306 "To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.": "Π§Ρ‚ΠΎΠ±Ρ‹ Π²ΠΈΠ΄Π΅Ρ‚ΡŒ здСсь ваши API-ΠΊΠ»ΡŽΡ‡ΠΈ, установитС ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ allowKeysExposure Π² config.yaml Π² ΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ true, послС Ρ‡Π΅Π³ΠΎ пСрСзапуститС сСрвСр SillyTavern.",
2307 "Invalid endpoint URL. Requests may fail.": "НСкоррСктный адрСс эндпоинта. Запросы ΠΌΠΎΠ³ΡƒΡ‚ Π½Π΅ ΠΏΡ€ΠΎΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ.",
2308 "How to install extensions?": "Как ΡƒΡΡ‚Π°Π½Π°Π²Π»ΠΈΠ²Π°Ρ‚ΡŒ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ?",
2309 "Click the flashing button to install extensions.": "Π§Ρ‚ΠΎΠ±Ρ‹ ΠΈΡ… ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ, Π½Π°ΠΆΠΌΠΈΡ‚Π΅ Π½Π° ΠΌΠΈΠ³Π°ΡŽΡ‰ΡƒΡŽ ΠΊΠ½ΠΎΠΏΠΊΡƒ.",
2310 "ext_regex_reasoning_desc": "Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ Π±Π»ΠΎΠΊΠΎΠ² рассуТдСний. ΠŸΡ€ΠΈ ΠΎΡ‚ΠΌΠ΅Ρ‡Π΅Π½Π½ΠΎΠΉ Π³Π°Π»ΠΎΡ‡ΠΊΠ΅ \"Волько ΠΏΡ€ΠΎΠΌΠΏΡ‚\" Π±ΡƒΠ΄ΡƒΡ‚ Ρ‚Π°ΠΊΠΆΠ΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½Ρ‹ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Π΅ Π² ΠΏΡ€ΠΎΠΌΠΏΡ‚ рассуТдСния.",
2311 "Macro in Find Regex": "ΠœΠ°ΠΊΡ€ΠΎΡΡ‹ Π² Ρ€Π΅Π³. Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠΈ",
2312 "Don't substitute": "НС Π·Π°ΠΌΠ΅Π½ΡΡ‚ΡŒ",
2313 "Substitute (raw)": "Π—Π°ΠΌΠ΅Π½ΡΡ‚ΡŒ Π² \"чистом\" Π²ΠΈΠ΄Π΅",
2314 "Substitute (escaped)": "Π—Π°ΠΌΠ΅Π½ΡΡ‚ΡŒ послС экранирования",
2315 "ext_regex_other_options_desc": "По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ, Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ вносит измСнСния Π² сам Ρ„Π°ΠΉΠ» Ρ‡Π°Ρ‚Π°.\nΠŸΡ€ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ ΠΎΠ΄Π½ΠΎΠΉ ΠΈΠ· ΠΎΠΏΡ†ΠΈΠΉ (ΠΈΠ»ΠΈ ΠΎΠ±Π΅ΠΈΡ…), Ρ„Π°ΠΉΠ» Ρ‡Π°Ρ‚Π° останСтся Π½Π΅Ρ‚Ρ€ΠΎΠ½ΡƒΡ‚Ρ‹ΠΌ, ΠΏΡ€ΠΈ этом сами измСнСния ΠΏΠΎ-ΠΏΡ€Π΅ΠΆΠ½Π΅ΠΌΡƒ Π±ΡƒΠ΄ΡƒΡ‚ Π΄Π΅ΠΉΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ.",
2316 "ext_regex_flags_help": "НаТмитС, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ·Π½Π°Ρ‚ΡŒ большС ΠΎ Ρ„Π»Π°Π³Π°Ρ… Π² Ρ€Π΅Π³. выраТСниях.",
2317 "Applies to all matches": "ЗамСняСт всС вхоТдСния",
2318 "Applies to the first match": "ЗамСняСт ΠΏΠ΅Ρ€Π²ΠΎΠ΅ Π²Ρ…ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅",
2319 "Case insensitive": "НС Ρ‡ΡƒΠ²ΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΊ рСгистру",
2320 "Case sensitive": "Π§ΡƒΠ²ΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ ΠΊ рСгистру",
2321 "Find Regex is empty": "Π Π΅Π³. Π²Ρ‹Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½ΠΎ",
2322 "Click the button to save it as a file.": "НаТмитС Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡƒ справа, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π΅Π³ΠΎ Π² Ρ„Π°ΠΉΠ».",
2323 "Export as JSONL": "Экспорт Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ JSONL",
2324 "Thought for some time": "КакоС-Ρ‚ΠΎ врСмя заняли Ρ€Π°Π·ΠΌΡ‹ΡˆΠ»Π΅Π½ΠΈΡ",
2325 "Thinking...": "Π’ Ρ€Π°Π·Π΄ΡƒΠΌΡŒΡΡ…...",
2326 "Thought for ${0}": "Π Π°Π·ΠΌΡ‹ΡˆΠ»Π΅Π½ΠΈΡ заняли ${0}",
2327 "Hidden reasoning - Add reasoning block": "РассуТдСния скрыты - Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π±Π»ΠΎΠΊ рассуТдСний",
2328 "Add reasoning block": "Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π±Π»ΠΎΠΊ рассуТдСний",
2329 "Edit reasoning": "Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ рассуТдСния",
2330 "Copy reasoning": "Π‘ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ рассуТдСния",
2331 "Confirm Edit": "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€Π΄ΠΈΡ‚ΡŒ",
2332 "Remove reasoning": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ рассуТдСния",
2333 "Cancel edit": "ΠžΡ‚ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ€Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅",
2334 "Remove Reasoning": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ рассуТдСния",
2335 "Are you sure you want to clear the reasoning?<br />Visible message contents will stay intact.": "Π’Ρ‹ Ρ‚ΠΎΡ‡Π½ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Π±Π»ΠΎΠΊ рассуТдСний?<br />ОсновноС сообщСниС останСтся Π½Π° мСстС.",
2336 "Reasoning Parse": "ΠŸΠ°Ρ€ΡΠΈΠ½Π³ рассуТдСний",
2337 "Both prefix and suffix must be set in the Reasoning Formatting settings.": "Π’ настройках форматирования рассуТдСний Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π·Π°Π΄Π°Π½Ρ‹ прСфикс ΠΈ суффикс.",
2338 "Invalid return type '${0}', defaulting to 'reasoning'.": "НСкоррСктный Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΡ‹ΠΉ Ρ‚ΠΈΠΏ, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ стандартный 'reasoning'.",
2339 "Reasoning already exists.": "РассуТдСния ΡƒΠΆΠ΅ ΠΏΡ€ΠΈΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‚.",
2340 "Edit Message": "Π Π΅Π΄Π°ΠΊΡ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅",
2341 "Status check bypassed": "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° статуса ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½Π°",
2342 "Valid": "Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚"
2207}2343}
public/script.js+227 -75
@@ -172,6 +172,7 @@ import {
172 copyText,172 copyText,
173 escapeHtml,173 escapeHtml,
174 saveBase64AsFile,174 saveBase64AsFile,
175 uuidv4,
175} from './scripts/utils.js';176} from './scripts/utils.js';
176import { debounce_timeout } from './scripts/constants.js';177import { debounce_timeout } from './scripts/constants.js';
177178
@@ -494,6 +495,8 @@ export const event_types = {
494 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',495 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
495 GENERATE_AFTER_DATA: 'generate_after_data',496 GENERATE_AFTER_DATA: 'generate_after_data',
496 GROUP_MEMBER_DRAFTED: 'group_member_drafted',497 GROUP_MEMBER_DRAFTED: 'group_member_drafted',
498 GROUP_WRAPPER_STARTED: 'group_wrapper_started',
499 GROUP_WRAPPER_FINISHED: 'group_wrapper_finished',
497 WORLD_INFO_ACTIVATED: 'world_info_activated',500 WORLD_INFO_ACTIVATED: 'world_info_activated',
498 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',501 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
499 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',502 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',
@@ -514,6 +517,9 @@ export const event_types = {
514 ONLINE_STATUS_CHANGED: 'online_status_changed',517 ONLINE_STATUS_CHANGED: 'online_status_changed',
515 IMAGE_SWIPED: 'image_swiped',518 IMAGE_SWIPED: 'image_swiped',
516 CONNECTION_PROFILE_LOADED: 'connection_profile_loaded',519 CONNECTION_PROFILE_LOADED: 'connection_profile_loaded',
520 CONNECTION_PROFILE_CREATED: 'connection_profile_created',
521 CONNECTION_PROFILE_DELETED: 'connection_profile_deleted',
522 CONNECTION_PROFILE_UPDATED: 'connection_profile_updated',
517 TOOL_CALLS_PERFORMED: 'tool_calls_performed',523 TOOL_CALLS_PERFORMED: 'tool_calls_performed',
518 TOOL_CALLS_RENDERED: 'tool_calls_rendered',524 TOOL_CALLS_RENDERED: 'tool_calls_rendered',
519};525};
@@ -589,7 +595,7 @@ let is_delete_mode = false;
589let fav_ch_checked = false;595let fav_ch_checked = false;
590let scrollLock = false;596let scrollLock = false;
591export let abortStatusCheck = new AbortController();597export let abortStatusCheck = new AbortController();
592let charDragDropHandler = null;598export let charDragDropHandler = null;
593599
594/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */600/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */
595export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;601export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;
@@ -1140,7 +1146,7 @@ export async function clearItemizedPrompts() {
1140async function getStatusHorde() {1146async function getStatusHorde() {
1141 try {1147 try {
1142 const hordeStatus = await checkHordeStatus();1148 const hordeStatus = await checkHordeStatus();
1143 setOnlineStatus(hordeStatus ? 'Connected' : 'no_connection');1149 setOnlineStatus(hordeStatus ? t`Connected` : 'no_connection');
1144 }1150 }
1145 catch {1151 catch {
1146 setOnlineStatus('no_connection');1152 setOnlineStatus('no_connection');
@@ -1207,7 +1213,7 @@ async function getStatusTextgen() {
1207 }1213 }
12081214
1209 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {1215 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
1210 setOnlineStatus('Status check bypassed');1216 setOnlineStatus(t`Status check bypassed`);
1211 return resultCheckStatus();1217 return resultCheckStatus();
1212 }1218 }
12131219
@@ -1232,7 +1238,7 @@ async function getStatusTextgen() {
1232 setOnlineStatus(textgen_settings.togetherai_model);1238 setOnlineStatus(textgen_settings.togetherai_model);
1233 } else if (textgen_settings.type === textgen_types.OLLAMA) {1239 } else if (textgen_settings.type === textgen_types.OLLAMA) {
1234 loadOllamaModels(data?.data);1240 loadOllamaModels(data?.data);
1235 setOnlineStatus(textgen_settings.ollama_model || 'Connected');1241 setOnlineStatus(textgen_settings.ollama_model || t`Connected`);
1236 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {1242 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
1237 loadInfermaticAIModels(data?.data);1243 loadInfermaticAIModels(data?.data);
1238 setOnlineStatus(textgen_settings.infermaticai_model);1244 setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1256,7 +1262,7 @@ async function getStatusTextgen() {
1256 setOnlineStatus(textgen_settings.tabby_model || data?.result);1262 setOnlineStatus(textgen_settings.tabby_model || data?.result);
1257 } else if (textgen_settings.type === textgen_types.GENERIC) {1263 } else if (textgen_settings.type === textgen_types.GENERIC) {
1258 loadGenericModels(data?.data);1264 loadGenericModels(data?.data);
1259 setOnlineStatus(textgen_settings.generic_model || data?.result || 'Connected');1265 setOnlineStatus(textgen_settings.generic_model || data?.result || t`Connected`);
1260 } else {1266 } else {
1261 setOnlineStatus(data?.result);1267 setOnlineStatus(data?.result);
1262 }1268 }
@@ -1370,8 +1376,11 @@ export function resultCheckStatus() {
1370 * If the character ID doesn't exist, if the chat is being saved, or if a group is being generated, this function does nothing.1376 * If the character ID doesn't exist, if the chat is being saved, or if a group is being generated, this function does nothing.
1371 * If the character is different from the currently selected one, it will clear the chat and reset any selected character or group.1377 * If the character is different from the currently selected one, it will clear the chat and reset any selected character or group.
1372 * @param {number} id The ID of the character to switch to.1378 * @param {number} id The ID of the character to switch to.
1379 * @param {object} [options] Options for the switch.
1380 * @param {boolean} [options.switchMenu=true] Whether to switch the right menu to the character edit menu if the character is already selected.
1381 * @returns {Promise<void>} A promise that resolves when the character is switched.
1373 */1382 */
1374export async function selectCharacterById(id) {1383export async function selectCharacterById(id, { switchMenu = true } = {}) {
1375 if (characters[id] === undefined) {1384 if (characters[id] === undefined) {
1376 return;1385 return;
1377 }1386 }
@@ -1400,9 +1409,9 @@ export async function selectCharacterById(id) {
1400 }1409 }
1401 } else {1410 } else {
1402 //if clicked on character that was already selected1411 //if clicked on character that was already selected
1403 selected_button = 'character_edit';1412 switchMenu && (selected_button = 'character_edit');
1404 await unshallowCharacter(this_chid);1413 await unshallowCharacter(this_chid);
1405 select_selected_character(this_chid);1414 select_selected_character(this_chid, { switchMenu });
1406 }1415 }
1407}1416}
14081417
@@ -1787,6 +1796,7 @@ export async function getCharacters() {
1787 body: JSON.stringify({}),1796 body: JSON.stringify({}),
1788 });1797 });
1789 if (response.ok === true) {1798 if (response.ok === true) {
1799 const previousAvatar = this_chid !== undefined ? characters[this_chid]?.avatar : null;
1790 characters.splice(0, characters.length);1800 characters.splice(0, characters.length);
1791 const getData = await response.json();1801 const getData = await response.json();
1792 for (let i = 0; i < getData.length; i++) {1802 for (let i = 0; i < getData.length; i++) {
@@ -1800,8 +1810,16 @@ export async function getCharacters() {
18001810
1801 characters[i]['chat'] = String(characters[i]['chat']);1811 characters[i]['chat'] = String(characters[i]['chat']);
1802 }1812 }
1803 if (this_chid !== undefined) {1813
1804 $('#avatar_url_pole').val(characters[this_chid].avatar);1814 if (previousAvatar) {
1815 const newCharacterId = characters.findIndex(x => x.avatar === previousAvatar);
1816 if (newCharacterId >= 0) {
1817 setCharacterId(newCharacterId);
1818 await selectCharacterById(newCharacterId, { switchMenu: false });
1819 } else {
1820 await Popup.show.text(t`ERROR: The active character is no longer available.`, t`The page will be refreshed to prevent data loss. Press "OK" to continue.`);
1821 return location.reload();
1822 }
1805 }1823 }
18061824
1807 await getGroups();1825 await getGroups();
@@ -2730,6 +2748,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2730 environment.mesExamplesRaw = fields.mesExamples || '';2748 environment.mesExamplesRaw = fields.mesExamples || '';
2731 environment.charVersion = fields.version || '';2749 environment.charVersion = fields.version || '';
2732 environment.char_version = fields.version || '';2750 environment.char_version = fields.version || '';
2751 environment.charDepthPrompt = fields.charDepthPrompt || '';
2733 }2752 }
27342753
2735 // Must be substituted last so that they're replaced inside {{description}}2754 // Must be substituted last so that they're replaced inside {{description}}
@@ -3081,13 +3100,38 @@ export function baseChatReplace(value, name1, name2) {
30813100
3082/**3101/**
3083 * Returns the character card fields for the current character.3102 * Returns the character card fields for the current character.
3084 * @returns {{system: string, mesExamples: string, description: string, personality: string, persona: string, scenario: string, jailbreak: string, version: string}}3103 * @param {object} [options]
3104 * @param {number} [options.chid] Optional character index
3105 *
3106 * @typedef {object} CharacterCardFields
3107 * @property {string} system System prompt
3108 * @property {string} mesExamples Message examples
3109 * @property {string} description Description
3110 * @property {string} personality Personality
3111 * @property {string} persona Persona
3112 * @property {string} scenario Scenario
3113 * @property {string} jailbreak Jailbreak instructions
3114 * @property {string} version Character version
3115 * @property {string} charDepthPrompt Character depth note
3116 * @returns {CharacterCardFields} Character card fields
3085 */3117 */
3086export function getCharacterCardFields() {3118export function getCharacterCardFields({ chid = null } = {}) {
3087 const result = { system: '', mesExamples: '', description: '', personality: '', persona: '', scenario: '', jailbreak: '', version: '' };3119 const currentChid = chid ?? this_chid;
3120
3121 const result = {
3122 system: '',
3123 mesExamples: '',
3124 description: '',
3125 personality: '',
3126 persona: '',
3127 scenario: '',
3128 jailbreak: '',
3129 version: '',
3130 charDepthPrompt: '',
3131 };
3088 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);3132 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
30893133
3090 const character = characters[this_chid];3134 const character = characters[currentChid];
30913135
3092 if (!character) {3136 if (!character) {
3093 return result;3137 return result;
@@ -3101,9 +3145,10 @@ export function getCharacterCardFields() {
3101 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?.trim(), name1, name2) : '';3145 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?.trim(), name1, name2) : '';
3102 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';3146 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
3103 result.version = character.data?.character_version ?? '';3147 result.version = character.data?.character_version ?? '';
3148 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
31043149
3105 if (selected_group) {3150 if (selected_group) {
3106 const groupCards = getGroupCharacterCards(selected_group, Number(this_chid));3151 const groupCards = getGroupCharacterCards(selected_group, Number(currentChid));
31073152
3108 if (groupCards) {3153 if (groupCards) {
3109 result.description = groupCards.description;3154 result.description = groupCards.description;
@@ -3596,7 +3641,8 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
3596 throw new Error(data.response);3641 throw new Error(data.response);
3597 }3642 }
35983643
3599 const message = cleanUpMessage(extractMessageFromData(data), false, false, true);3644 // format result, exclude user prompt bias
3645 const message = cleanUpMessage(extractMessageFromData(data), false, false, true, null, false);
36003646
3601 if (!message) {3647 if (!message) {
3602 throw new Error('No message generated');3648 throw new Error('No message generated');
@@ -3905,6 +3951,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3905 mesExamples,3951 mesExamples,
3906 system,3952 system,
3907 jailbreak,3953 jailbreak,
3954 charDepthPrompt,
3908 } = getCharacterCardFields();3955 } = getCharacterCardFields();
39093956
3910 if (main_api !== 'openai') {3957 if (main_api !== 'openai') {
@@ -3927,7 +3974,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3927 setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);3974 setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);
3928 });3975 });
3929 } else {3976 } else {
3930 const depthPromptText = baseChatReplace(characters[this_chid]?.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2) || '';3977 const depthPromptText = charDepthPrompt || '';
3931 const depthPromptDepth = characters[this_chid]?.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;3978 const depthPromptDepth = characters[this_chid]?.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;
3932 const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid]?.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);3979 const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid]?.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);
3933 setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);3980 setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);
@@ -5868,13 +5915,14 @@ function extractMultiSwipes(data, type) {
5868 return swipes;5915 return swipes;
5869}5916}
58705917
5871export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncompleteSentences = false, stoppingStrings = null) {5918export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncompleteSentences = false, stoppingStrings = null, includeUserPromptBias = true) {
5872 if (!getMessage) {5919 if (!getMessage) {
5873 return '';5920 return '';
5874 }5921 }
58755922
5876 // Add the prompt bias before anything else5923 // Add the prompt bias before anything else
5877 if (5924 if (
5925 includeUserPromptBias &&
5878 power_user.user_prompt_bias &&5926 power_user.user_prompt_bias &&
5879 !isImpersonate &&5927 !isImpersonate &&
5880 !isContinue &&5928 !isContinue &&
@@ -6261,7 +6309,6 @@ export function syncMesToSwipe(messageId = null) {
6261 }6309 }
62626310
6263 const targetMessage = chat[targetMessageId];6311 const targetMessage = chat[targetMessageId];
6264
6265 if (!targetMessage) {6312 if (!targetMessage) {
6266 return false;6313 return false;
6267 }6314 }
@@ -6295,6 +6342,68 @@ export function syncMesToSwipe(messageId = null) {
6295}6342}
62966343
6297/**6344/**
6345 * Syncs swipe data back to the message data at the given message ID (or the last message if no ID is given).
6346 * If the swipe ID is not provided, the current swipe ID in the message object is used.
6347 *
6348 * If the swipe data is invalid in some way, this function will exit out without doing anything.
6349 * @param {number?} [messageId=null] - The ID of the message to sync with the swipe data. If no ID is given, the last message is used.
6350 * @param {number?} [swipeId=null] - The ID of the swipe to sync. If no ID is given, the current swipe ID in the message object is used.
6351 * @returns {boolean} Whether the swipe data was successfully synced to the message
6352 */
6353export function syncSwipeToMes(messageId = null, swipeId = null) {
6354 if (!chat.length) {
6355 return false;
6356 }
6357
6358 const targetMessageId = messageId ?? chat.length - 1;
6359 if (targetMessageId >= chat.length || targetMessageId < 0) {
6360 console.warn(`[syncSwipeToMes] Invalid message ID: ${messageId}`);
6361 return false;
6362 }
6363
6364 const targetMessage = chat[targetMessageId];
6365 if (!targetMessage) {
6366 return false;
6367 }
6368
6369 if (swipeId !== null) {
6370 if (isNaN(swipeId) || swipeId < 0) {
6371 console.warn(`[syncSwipeToMes] Invalid swipe ID: ${swipeId}`);
6372 return false;
6373 }
6374 targetMessage.swipe_id = swipeId;
6375 }
6376
6377 // No swipe data there yet, exit out
6378 if (typeof targetMessage.swipe_id !== 'number') {
6379 return false;
6380 }
6381 // If swipes structure is invalid, exit out
6382 if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) {
6383 return false;
6384 }
6385
6386 const targetSwipeId = targetMessage.swipe_id;
6387 if (!targetMessage.swipes[targetSwipeId] || !targetMessage.swipe_info[targetSwipeId]) {
6388 console.warn(`[syncSwipeToMes] Invalid swipe ID: ${targetSwipeId}`);
6389 return false;
6390 }
6391
6392 const targetSwipeInfo = targetMessage.swipe_info[targetSwipeId];
6393 if (typeof targetSwipeInfo !== 'object') {
6394 return false;
6395 }
6396
6397 targetMessage.mes = targetMessage.swipes[targetSwipeId];
6398 targetMessage.send_date = targetSwipeInfo.send_date;
6399 targetMessage.gen_started = targetSwipeInfo.gen_started;
6400 targetMessage.gen_finished = targetSwipeInfo.gen_finished;
6401 targetMessage.extra = structuredClone(targetSwipeInfo.extra);
6402
6403 return true;
6404}
6405
6406/**
6298 * Saves the image to the message object.6407 * Saves the image to the message object.
6299 * @param {ParsedImage} img Image object6408 * @param {ParsedImage} img Image object
6300 * @param {object} mes Chat message object6409 * @param {object} mes Chat message object
@@ -6527,6 +6636,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65276636
6528 await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar);6637 await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar);
65296638
6639 // Unload current character
6640 setCharacterId(undefined);
6530 // Reload characters list6641 // Reload characters list
6531 await getCharacters();6642 await getCharacters();
65326643
@@ -6535,7 +6646,6 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65356646
6536 if (newChId !== -1) {6647 if (newChId !== -1) {
6537 // Select the character after the renaming6648 // Select the character after the renaming
6538 setCharacterId(undefined);
6539 await selectCharacterById(newChId);6649 await selectCharacterById(newChId);
65406650
6541 // Async delay to update UI6651 // Async delay to update UI
@@ -6668,7 +6778,22 @@ export function saveChatDebounced() {
6668 }, DEFAULT_SAVE_EDIT_TIMEOUT);6778 }, DEFAULT_SAVE_EDIT_TIMEOUT);
6669}6779}
66706780
6671export async function saveChat(chatName, withMetadata, mesId) {6781/**
6782 * Saves the chat to the server.
6783 * @param {object} [options] - Additional options.
6784 * @param {string} [options.chatName] The name of the chat file to save to
6785 * @param {object} [options.withMetadata] Additional metadata to save with the chat
6786 * @param {number} [options.mesId] The message ID to save the chat up to
6787 * @param {boolean} [options.force] Force the saving despire the integrity check result
6788 *
6789 * @returns {Promise<void>}
6790 */
6791export async function saveChat({ chatName, withMetadata, mesId, force = false } = {}) {
6792 if (arguments.length > 0 && typeof arguments[0] !== 'object') {
6793 console.trace('saveChat called with positional arguments. Please use an object instead.');
6794 [chatName, withMetadata, mesId, force] = arguments;
6795 }
6796
6672 const metadata = { ...chat_metadata, ...(withMetadata || {}) };6797 const metadata = { ...chat_metadata, ...(withMetadata || {}) };
6673 const fileName = chatName ?? characters[this_chid]?.chat;6798 const fileName = chatName ?? characters[this_chid]?.chat;
66746799
@@ -6688,53 +6813,59 @@ export async function saveChat(chatName, withMetadata, mesId) {
6688 toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);6813 toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);
6689 throw new Error('Group chat saved from saveChat');6814 throw new Error('Group chat saved from saveChat');
6690 }6815 }
6691 /*
6692 if (item.is_user) {
6693 //var str = item.mes.replace(`${name1}:`, `${name1}:`);
6694 //chat[i].mes = str;
6695 //chat[i].name = name1;
6696 } else if (i !== chat.length - 1 && chat[i].swipe_id !== undefined) {
6697 // delete chat[i].swipes;
6698 // delete chat[i].swipe_id;
6699 }
6700 */
6701 });6816 });
67026817
6703 const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)6818 const trimmedChat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
6704 ? chat.slice(0, parseInt(mesId) + 1)6819 ? chat.slice(0, Number(mesId) + 1)
6705 : chat;6820 : chat.slice();
67066821
6707 var save_chat = [6822 const chatToSave = [
6708 {6823 {
6709 user_name: name1,6824 user_name: name1,
6710 character_name: name2,6825 character_name: name2,
6711 create_date: chat_create_date,6826 create_date: chat_create_date,
6712 chat_metadata: metadata,6827 chat_metadata: metadata,
6713 },6828 },
6714 ...trimmed_chat,6829 ...trimmedChat,
6715 ];6830 ];
6716 return jQuery.ajax({6831
6717 type: 'POST',6832 try {
6718 url: '/api/chats/save',6833 const result = await fetch('/api/chats/save', {
6719 data: JSON.stringify({6834 method: 'POST',
6835 cache: 'no-cache',
6836 headers: getRequestHeaders(),
6837 body: JSON.stringify({
6720 ch_name: characters[this_chid].name,6838 ch_name: characters[this_chid].name,
6721 file_name: fileName,6839 file_name: fileName,
6722 chat: save_chat,6840 chat: chatToSave,
6723 avatar_url: characters[this_chid].avatar,6841 avatar_url: characters[this_chid].avatar,
6842 force: force,
6724 }),6843 }),
6725 beforeSend: function () {6844 });
67266845
6727 },6846 if (result.ok) {
6728 cache: false,6847 return;
6729 dataType: 'json',6848 }
6730 contentType: 'application/json',6849
6731 success: function (data) { },6850 const errorData = await result.json();
6732 error: function (jqXHR, exception) {6851 const isIntegrityError = errorData?.error === 'integrity' && !force;
6852 if (!isIntegrityError) {
6853 throw new Error(result.statusText);
6854 }
6855
6856 const forceSaveConfirmed = await Popup.show.confirm(
6857 t`ERROR: Chat integrity check failed.`,
6858 t`Continuing the operation may result in data loss. Would you like to overwrite the chat file anyway? Pressing "NO" will cancel the save operation.`,
6859 { okButton: t`Yes, overwrite`, cancelButton: t`No, cancel` },
6860 ) === POPUP_RESULT.AFFIRMATIVE;
6861
6862 if (forceSaveConfirmed) {
6863 await saveChat({ chatName, withMetadata, mesId, force: true });
6864 }
6865 } catch (error) {
6866 console.error(error);
6733 toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`);6867 toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`);
6734 console.log(exception);6868 }
6735 console.log(jqXHR);
6736 },
6737 });
6738}6869}
67396870
6740async function read_avatar_load(input) {6871async function read_avatar_load(input) {
@@ -6861,14 +6992,14 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t
6861 */6992 */
6862export async function unshallowCharacter(characterId) {6993export async function unshallowCharacter(characterId) {
6863 if (characterId === undefined) {6994 if (characterId === undefined) {
6864 console.warn('Undefined character cannot be unshallowed');6995 console.debug('Undefined character cannot be unshallowed');
6865 return;6996 return;
6866 }6997 }
68676998
6868 /** @type {import('./scripts/char-data.js').v1CharData} */6999 /** @type {import('./scripts/char-data.js').v1CharData} */
6869 const character = characters[characterId];7000 const character = characters[characterId];
6870 if (!character) {7001 if (!character) {
6871 console.warn('Character not found:', characterId);7002 console.debug('Character not found:', characterId);
6872 return;7003 return;
6873 }7004 }
68747005
@@ -6879,7 +7010,7 @@ export async function unshallowCharacter(characterId) {
68797010
6880 const avatar = character.avatar;7011 const avatar = character.avatar;
6881 if (!avatar) {7012 if (!avatar) {
6882 console.warn('Character has no avatar field:', characterId);7013 console.debug('Character has no avatar field:', characterId);
6883 return;7014 return;
6884 }7015 }
68857016
@@ -6911,6 +7042,9 @@ export async function getChat() {
6911 } else {7042 } else {
6912 chat_create_date = humanizedDateTime();7043 chat_create_date = humanizedDateTime();
6913 }7044 }
7045 if (!chat_metadata['integrity']) {
7046 chat_metadata['integrity'] = uuidv4();
7047 }
6914 await getChatResult();7048 await getChatResult();
6915 eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });7049 eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
69167050
@@ -7882,14 +8016,19 @@ export function select_rm_info(type, charId, previousCharId = null) {
7882 }8016 }
7883}8017}
78848018
7885export function select_selected_character(chid) {8019/**
8020 * Selects the right menu for displaying the character editor.
8021 * @param {number|string} chid Character array index
8022 * @param {object} [param1] Options for the switch
8023 * @param {boolean} [param1.switchMenu=true] Whether to switch the menu
8024 */
8025export function select_selected_character(chid, { switchMenu = true } = {}) {
7886 //character select8026 //character select
7887 //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');8027 //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');
7888 select_rm_create();8028 select_rm_create({ switchMenu });
7889 setMenuType('character_edit');8029 switchMenu && setMenuType('character_edit');
7890 $('#delete_button').css('display', 'flex');8030 $('#delete_button').css('display', 'flex');
7891 $('#export_button').css('display', 'flex');8031 $('#export_button').css('display', 'flex');
7892 var display_name = characters[chid].name;
78938032
7894 //create text poles8033 //create text poles
7895 $('#rm_button_back').css('display', 'none');8034 $('#rm_button_back').css('display', 'none');
@@ -7904,7 +8043,7 @@ export function select_selected_character(chid) {
79048043
7905 // Don't update the navbar name if we're peeking the group member defs8044 // Don't update the navbar name if we're peeking the group member defs
7906 if (!selected_group) {8045 if (!selected_group) {
7907 $('#rm_button_selected_ch').children('h2').text(display_name);8046 $('#rm_button_selected_ch').children('h2').text(characters[chid].name);
7908 }8047 }
79098048
7910 $('#add_avatar_button').val('');8049 $('#add_avatar_button').val('');
@@ -7935,22 +8074,20 @@ export function select_selected_character(chid) {
7935 $('#chat_import_avatar_url').val(characters[chid].avatar);8074 $('#chat_import_avatar_url').val(characters[chid].avatar);
7936 $('#chat_import_character_name').val(characters[chid].name);8075 $('#chat_import_character_name').val(characters[chid].name);
7937 $('#character_json_data').val(characters[chid].json_data);8076 $('#character_json_data').val(characters[chid].json_data);
7938 let this_avatar = default_avatar;
7939 if (characters[chid].avatar != 'none') {
7940 this_avatar = getThumbnailUrl('avatar', characters[chid].avatar);
7941 }
79428077
7943 updateFavButtonState(characters[chid].fav || characters[chid].fav == 'true');8078 updateFavButtonState(characters[chid].fav || characters[chid].fav == 'true');
79448079
7945 $('#avatar_load_preview').attr('src', this_avatar);8080 const avatarUrl = characters[chid].avatar != 'none' ? getThumbnailUrl('avatar', characters[chid].avatar) : default_avatar;
7946 $('#name_div').removeClass('displayBlock');8081 $('#avatar_load_preview').attr('src', avatarUrl);
7947 $('#name_div').addClass('displayNone');
7948 $('#renameCharButton').css('display', '');
7949 $('.open_alternate_greetings').data('chid', chid);8082 $('.open_alternate_greetings').data('chid', chid);
7950 $('#set_character_world').data('chid', chid);8083 $('#set_character_world').data('chid', chid);
7951 setWorldInfoButtonClass(chid);8084 setWorldInfoButtonClass(chid);
7952 checkEmbeddedWorld(chid);8085 checkEmbeddedWorld(chid);
79538086
8087 $('#name_div').removeClass('displayBlock');
8088 $('#name_div').addClass('displayNone');
8089 $('#renameCharButton').css('display', '');
8090
7954 $('#form_create').attr('actiontype', 'editcharacter');8091 $('#form_create').attr('actiontype', 'editcharacter');
7955 $('.form_create_bottom_buttons_block .chat_lorebook_button').show();8092 $('.form_create_bottom_buttons_block .chat_lorebook_button').show();
79568093
@@ -7962,8 +8099,13 @@ export function select_selected_character(chid) {
7962 saveSettingsDebounced();8099 saveSettingsDebounced();
7963}8100}
79648101
7965function select_rm_create() {8102/**
7966 setMenuType('create');8103 * Selects the right menu for creating a new character.
8104 * @param {object} [options] Options for the switch
8105 * @param {boolean} [options.switchMenu=true] Whether to switch the menu
8106 */
8107function select_rm_create({ switchMenu = true } = {}) {
8108 switchMenu && setMenuType('create');
79678109
7968 //console.log('select_rm_Create() -- selected button: '+selected_button);8110 //console.log('select_rm_Create() -- selected button: '+selected_button);
7969 if (selected_button == 'create') {8111 if (selected_button == 'create') {
@@ -7973,7 +8115,7 @@ function select_rm_create() {
7973 }8115 }
7974 }8116 }
79758117
7976 selectRightMenuWithAnimation('rm_ch_create_block');8118 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
79778119
7978 $('#set_chat_scenario').hide();8120 $('#set_chat_scenario').hide();
7979 $('#delete_button_div').css('display', 'none');8121 $('#delete_button_div').css('display', 'none');
@@ -8293,10 +8435,9 @@ export async function deleteSwipe(swipeId = null) {
8293 lastMessage.swipe_info.splice(swipeId, 1);8435 lastMessage.swipe_info.splice(swipeId, 1);
8294 }8436 }
82958437
8296 // Select the next swip, or the one before if it was the last one8438 // Select the next swipe, or the one before if it was the last one
8297 const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);8439 const newSwipeId = Math.min(swipeId, lastMessage.swipes.length - 1);
8298 lastMessage.swipe_id = newSwipeId;8440 syncSwipeToMes(null, newSwipeId);
8299 lastMessage.mes = lastMessage.swipes[newSwipeId];
83008441
8301 await saveChatConditional();8442 await saveChatConditional();
8302 await reloadCurrentChat();8443 await reloadCurrentChat();
@@ -9196,6 +9337,17 @@ function swipe_right(_event, { source, repeated } = {}) {
9196 }9337 }
9197}9338}
91989339
9340/**
9341 * @typedef {object} ConnectAPIMap
9342 * @property {string} selected - API name (e.g. "textgenerationwebui", "openai")
9343 * @property {string?} [button] - CSS selector for the API button
9344 * @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter")
9345 * @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai")
9346 */
9347
9348/**
9349 * @type {Record<string, ConnectAPIMap>}
9350 */
9199export const CONNECT_API_MAP = {9351export const CONNECT_API_MAP = {
9200 // Default APIs not contined inside text gen / chat gen9352 // Default APIs not contined inside text gen / chat gen
9201 'kobold': {9353 'kobold': {
@@ -10417,7 +10569,7 @@ jQuery(async function () {
10417 e.stopPropagation();10569 e.stopPropagation();
10418 chat_file_for_del = $(this).attr('file_name');10570 chat_file_for_del = $(this).attr('file_name');
10419 console.debug('detected cross click for' + chat_file_for_del);10571 console.debug('detected cross click for' + chat_file_for_del);
10420 callPopup('<h3>Delete the Chat File?</h3>', 'del_chat');10572 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
10421 });10573 });
1042210574
10423 $('#advanced_div').click(function () {10575 $('#advanced_div').click(function () {
public/scripts/PromptManager.js+1 -1
@@ -4,7 +4,7 @@ import { DOMPurify, Popper } from '../lib.js';
44
5import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';5import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';
6import { is_group_generating } from './group-chats.js';6import { is_group_generating } from './group-chats.js';
7import { Message, TokenHandler } from './openai.js';7import { Message, MessageCollection, TokenHandler } from './openai.js';
8import { power_user } from './power-user.js';8import { power_user } from './power-user.js';
9import { debounce, waitUntilCondition, escapeHtml } from './utils.js';9import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
10import { debounce_timeout } from './constants.js';10import { debounce_timeout } from './constants.js';
public/scripts/authors-note.js+5 -0
@@ -17,6 +17,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
17import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';17import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
18export { MODULE_NAME as NOTE_MODULE_NAME };18export { MODULE_NAME as NOTE_MODULE_NAME };
19import { t } from './i18n.js';19import { t } from './i18n.js';
20import { MacrosParser } from './macros.js';
2021
21const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory22const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
2223
@@ -576,4 +577,8 @@ export function initAuthorsNote() {
576 `,577 `,
577 }));578 }));
578 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);579 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
580
581 MacrosParser.registerMacro('authorsNote', () => chat_metadata[metadata_keys.prompt] ?? '', t`The contents of the Author's Note`);
582 MacrosParser.registerMacro('charAuthorsNote', () => this_chid !== undefined ? (extension_settings.note.chara.find((e) => e.name === getCharaFilename())?.prompt ?? '') : '', t`The contents of the Character Author's Note`);
583 MacrosParser.registerMacro('defaultAuthorsNote', () => extension_settings.note.default ?? '', t`The contents of the Default Author's Note`);
579}584}
public/scripts/autocomplete/AutoComplete.js+0 -2
@@ -3,10 +3,8 @@ import { debounce, escapeRegex } from '../utils.js';
3import { AutoCompleteOption } from './AutoCompleteOption.js';3import { AutoCompleteOption } from './AutoCompleteOption.js';
4import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';4import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
5import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';5import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';
6// eslint-disable-next-line no-unused-vars
7import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';6import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
8import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';7import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
9import { Popup, getTopmostModalLayer } from '../popup.js';
108
11/**@readonly*/9/**@readonly*/
12/**@enum {Number}*/10/**@enum {Number}*/
public/scripts/autocomplete/AutoCompleteNameResultBase.js+0 -1
@@ -1,4 +1,3 @@
1import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
2import { AutoCompleteOption } from './AutoCompleteOption.js';1import { AutoCompleteOption } from './AutoCompleteOption.js';
32
43
public/scripts/autocomplete/AutoCompleteOption.js+0 -1
@@ -1,4 +1,3 @@
1import { SlashCommand } from '../slash-commands/SlashCommand.js';
2import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';1import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
32
43
public/scripts/backgrounds.js+2 -1
@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
5import { SlashCommand } from './slash-commands/SlashCommand.js';5import { SlashCommand } from './slash-commands/SlashCommand.js';
6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
7import { flashHighlight, stringFormat } from './utils.js';7import { flashHighlight, stringFormat } from './utils.js';
8import { t } from './i18n.js';
89
9const BG_METADATA_KEY = 'custom_background';10const BG_METADATA_KEY = 'custom_background';
10const LIST_METADATA_KEY = 'chat_backgrounds';11const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
243 const fileExtension = oldBg.split('.').pop();244 const fileExtension = oldBg.split('.').pop();
244 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;245 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
245 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');246 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
246 const newBgExtensionless = await callPopup('<h3>Enter new background name:</h3>', 'input', oldBgExtensionless);247 const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
247248
248 if (!newBgExtensionless) {249 if (!newBgExtensionless) {
249 console.debug('no new_bg_extensionless');250 console.debug('no new_bg_extensionless');
public/scripts/bookmarks.js+4 -5
@@ -1,7 +1,6 @@
1import {1import {
2 characters,2 characters,
3 saveChat,3 saveChat,
4 system_messages,
5 system_message_types,4 system_message_types,
6 this_chid,5 this_chid,
7 openCharacterChat,6 openCharacterChat,
@@ -13,7 +12,7 @@ import {
13 saveChatConditional,12 saveChatConditional,
14 saveItemizedPrompts,13 saveItemizedPrompts,
15} from '../script.js';14} from '../script.js';
16import { humanizedDateTime, getMessageTimeStamp } from './RossAscends-mods.js';15import { humanizedDateTime } from './RossAscends-mods.js';
17import {16import {
18 getGroupPastChats,17 getGroupPastChats,
19 group_activation_strategy,18 group_activation_strategy,
@@ -156,7 +155,7 @@ export async function createBranch(mesId) {
156 if (selected_group) {155 if (selected_group) {
157 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);156 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
158 } else {157 } else {
159 await saveChat(name, newMetadata, mesId);158 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
160 }159 }
161 // append to branches list if it exists160 // append to branches list if it exists
162 // otherwise create it161 // otherwise create it
@@ -212,7 +211,7 @@ export async function createNewBookmark(mesId, { forceName = null } = {}) {
212 if (selected_group) {211 if (selected_group) {
213 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);212 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
214 } else {213 } else {
215 await saveChat(name, newMetadata, mesId);214 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
216 }215 }
217216
218 lastMes.extra['bookmark_link'] = name;217 lastMes.extra['bookmark_link'] = name;
@@ -358,7 +357,7 @@ export async function convertSoloToGroupChat() {
358 // Click on the freshly selected group to open it357 // Click on the freshly selected group to open it
359 await openGroupById(group.id);358 await openGroupById(group.id);
360359
361 toastr.success('The chat has been successfully converted!');360 toastr.success(t`The chat has been successfully converted!`);
362}361}
363362
364/**363/**
public/scripts/custom-request.js+234 -34
@@ -1,20 +1,20 @@
1import { getPresetManager } from './preset-manager.js';1import { getPresetManager } from './preset-manager.js';
2import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';2import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';
3import { getTextGenServer } from './textgen-settings.js';3import { getTextGenServer } from './textgen-settings.js';
4import { extractReasoningFromData } from './reasoning.js';
5import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types } from './instruct-mode.js';
46
5// #region Type Definitions7// #region Type Definitions
6/**8/**
7 * @typedef {Object} TextCompletionRequestBase9 * @typedef {Object} TextCompletionRequestBase
8 * @property {string} prompt - The text prompt for completion
9 * @property {number} max_tokens - Maximum number of tokens to generate10 * @property {number} max_tokens - Maximum number of tokens to generate
10 * @property {string} [model] - Optional model name11 * @property {string} [model] - Optional model name
11 * @property {string} api_type - Type of API to use12 * @property {string} api_type - Type of API to use
12 * @property {string} [api_server] - Optional API server URL13 * @property {string} [api_server] - Optional API server URL
13 * @property {number} [temperature] - Optional temperature parameter14 * @property {number} [temperature] - Optional temperature parameter
15 * @property {number} [min_p] - Optional min_p parameter
14 */16 */
1517
16/** @typedef {Record<string, any> & TextCompletionRequestBase} TextCompletionRequest */
17
18/**18/**
19 * @typedef {Object} TextCompletionPayloadBase19 * @typedef {Object} TextCompletionPayloadBase
20 * @property {string} prompt - The text prompt for completion20 * @property {string} prompt - The text prompt for completion
@@ -41,9 +41,17 @@ import { getTextGenServer } from './textgen-settings.js';
41 * @property {string} chat_completion_source - Source provider for chat completion41 * @property {string} chat_completion_source - Source provider for chat completion
42 * @property {number} max_tokens - Maximum number of tokens to generate42 * @property {number} max_tokens - Maximum number of tokens to generate
43 * @property {number} [temperature] - Optional temperature parameter for response randomness43 * @property {number} [temperature] - Optional temperature parameter for response randomness
44 * @property {string} [custom_url] - Optional custom URL for chat completion
44 */45 */
4546
46/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */47/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
48
49/**
50 * @typedef {Object} ExtractedData
51 * @property {string} content - Extracted content.
52 * @property {string} reasoning - Extracted reasoning.
53 */
54
47// #endregion55// #endregion
4856
49/**57/**
@@ -53,11 +61,11 @@ export class TextCompletionService {
53 static TYPE = 'textgenerationwebui';61 static TYPE = 'textgenerationwebui';
5462
55 /**63 /**
56 * @param {TextCompletionRequest} custom64 * @param {Record<string, any> & TextCompletionRequestBase & {prompt: string}} custom
57 * @returns {TextCompletionPayload}65 * @returns {TextCompletionPayload}
58 */66 */
59 static createRequestData({ prompt, max_tokens, model, api_type, api_server, temperature, ...props }) {67 static createRequestData({ prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) {
60 return {68 const payload = {
61 ...props,69 ...props,
62 prompt,70 prompt,
63 max_tokens,71 max_tokens,
@@ -66,15 +74,25 @@ export class TextCompletionService {
66 api_type,74 api_type,
67 api_server: api_server ?? getTextGenServer(api_type),75 api_server: api_server ?? getTextGenServer(api_type),
68 temperature,76 temperature,
77 min_p,
69 stream: false,78 stream: false,
70 };79 };
80
81 // Remove undefined values to avoid API errors
82 Object.keys(payload).forEach(key => {
83 if (payload[key] === undefined) {
84 delete payload[key];
85 }
86 });
87
88 return payload;
71 }89 }
7290
73 /**91 /**
74 * Sends a text completion request to the specified server92 * Sends a text completion request to the specified server
75 * @param {TextCompletionPayload} data Request data93 * @param {TextCompletionPayload} data Request data
76 * @param {boolean?} extractData Extract message from the response. Default true94 * @param {boolean?} extractData Extract message from the response. Default true
77 * @returns {Promise<string | any>} Extracted data or the raw response95 * @returns {Promise<ExtractedData | any>} Extracted data or the raw response
78 * @throws {Error}96 * @throws {Error}
79 */97 */
80 static async sendRequest(data, extractData = true) {98 static async sendRequest(data, extractData = true) {
@@ -91,31 +109,150 @@ export class TextCompletionService {
91 throw json;109 throw json;
92 }110 }
93111
94 return extractData ? extractMessageFromData(json, this.TYPE) : json;112 if (!extractData) {
113 return json;
114 }
115
116 return {
117 content: extractMessageFromData(json, this.TYPE),
118 reasoning: extractReasoningFromData(json, {
119 mainApi: this.TYPE,
120 textGenType: data.api_type,
121 ignoreShowThoughts: true,
122 }),
123 };
95 }124 }
96125
97 /**126 /**
98 * @param {string} presetName127 * Process and send a text completion request with optional preset & instruct
99 * @param {TextCompletionRequest} custom128 * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom
100 * @param {boolean?} extractData Extract message from the response. Default true129 * @param {Object} options - Configuration options
101 * @returns {Promise<string | any>} Extracted data or the raw response130 * @param {string?} [options.presetName] - Name of the preset to use for generation settings
131 * @param {string?} [options.instructName] - Name of instruct preset for message formatting
132 * @param {boolean} extractData - Whether to extract structured data from response
133 * @returns {Promise<ExtractedData | any>} Extracted data or the raw response
102 * @throws {Error}134 * @throws {Error}
103 */135 */
104 static async sendRequestWithPreset(presetName, custom, extractData = true) {136 static async processRequest(
137 custom,
138 options = {},
139 extractData = true,
140 ) {
141 const { presetName, instructName } = options;
142 let requestData = { ...custom };
143 const prompt = custom.prompt;
144
145 // Apply generation preset if specified
146 if (presetName) {
105 const presetManager = getPresetManager(this.TYPE);147 const presetManager = getPresetManager(this.TYPE);
106 if (!presetManager) {148 if (presetManager) {
107 throw new Error('Preset manager not found');149 const preset = presetManager.getCompletionPresetByName(presetName);
150 if (preset) {
151 // Convert preset to payload and merge with custom parameters
152 const presetPayload = this.presetToGeneratePayload(preset, {});
153 requestData = { ...presetPayload, ...requestData };
154 } else {
155 console.warn(`Preset "${presetName}" not found, continuing with default settings`);
156 }
157 } else {
158 console.warn('Preset manager not found, continuing with default settings');
159 }
108 }160 }
109161
110 const preset = presetManager.getCompletionPresetByName(presetName);162 // Handle instruct formatting if requested
111 if (!preset) {163 if (Array.isArray(prompt) && instructName) {
112 throw new Error('Preset not found');164 const instructPresetManager = getPresetManager('instruct');
165 let instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
166 if (instructPreset) {
167 // Clone the preset to avoid modifying the original
168 instructPreset = structuredClone(instructPreset);
169 instructPreset.macro = false;
170 instructPreset.names_behavior = names_behavior_types.NONE;
171
172 // Format messages using instruct formatting
173 const formattedMessages = [];
174 for (const message of prompt) {
175 let messageContent = message.content;
176 if (!message.ignoreInstruct) {
177 messageContent = formatInstructModeChat(
178 message.role,
179 message.content,
180 message.role === 'user',
181 false,
182 undefined,
183 undefined,
184 undefined,
185 undefined,
186 instructPreset,
187 );
188
189 // Add prompt formatting for the last message
190 if (message === prompt[prompt.length - 1]) {
191 messageContent += formatInstructModePrompt(
192 undefined,
193 false,
194 undefined,
195 undefined,
196 undefined,
197 false,
198 false,
199 instructPreset,
200 );
201 }
202 }
203 formattedMessages.push(messageContent);
204 }
205 requestData.prompt = formattedMessages.join('');
206 if (instructPreset.output_suffix) {
207 requestData.stop = [instructPreset.output_suffix];
208 requestData.stopping_strings = [instructPreset.output_suffix];
209 }
210 } else {
211 console.warn(`Instruct preset "${instructName}" not found, using basic formatting`);
212 requestData.prompt = prompt.map(x => x.content).join('\n\n');
213 }
214 } else if (typeof prompt === 'string') {
215 requestData.prompt = prompt;
216 } else {
217 requestData.prompt = prompt.map(x => x.content).join('\n\n');
113 }218 }
114219
115 const data = this.createRequestData({ ...preset, ...custom });220 // @ts-ignore
221 const data = this.createRequestData(requestData);
116222
117 return await this.sendRequest(data, extractData);223 return await this.sendRequest(data, extractData);
118 }224 }
225
226 /**
227 * Converts a preset to a valid text completion payload.
228 * Only supports temperature.
229 * @param {Object} preset - The preset configuration
230 * @param {Object} customPreset - Additional parameters to override preset values
231 * @returns {Object} - Formatted payload for text completion API
232 */
233 static presetToGeneratePayload(preset, customPreset = {}) {
234 if (!preset || typeof preset !== 'object') {
235 throw new Error('Invalid preset: must be an object');
236 }
237
238 // Merge preset with custom parameters
239 const settings = { ...preset, ...customPreset };
240
241 // Initialize base payload with common parameters
242 let payload = {
243 'temperature': settings.temp ? Number(settings.temp) : undefined,
244 'min_p': settings.min_p ? Number(settings.min_p) : undefined,
245 };
246
247 // Remove undefined values to avoid API errors
248 Object.keys(payload).forEach(key => {
249 if (payload[key] === undefined) {
250 delete payload[key];
251 }
252 });
253
254 return payload;
255 }
119}256}
120257
121/**258/**
@@ -128,23 +265,33 @@ export class ChatCompletionService {
128 * @param {ChatCompletionPayload} custom265 * @param {ChatCompletionPayload} custom
129 * @returns {ChatCompletionPayload}266 * @returns {ChatCompletionPayload}
130 */267 */
131 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, ...props }) {268 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
132 return {269 const payload = {
133 ...props,270 ...props,
134 messages,271 messages,
135 model,272 model,
136 chat_completion_source,273 chat_completion_source,
137 max_tokens,274 max_tokens,
138 temperature,275 temperature,
276 custom_url,
139 stream: false,277 stream: false,
140 };278 };
279
280 // Remove undefined values to avoid API errors
281 Object.keys(payload).forEach(key => {
282 if (payload[key] === undefined) {
283 delete payload[key];
284 }
285 });
286
287 return payload;
141 }288 }
142289
143 /**290 /**
144 * Sends a chat completion request291 * Sends a chat completion request
145 * @param {ChatCompletionPayload} data Request data292 * @param {ChatCompletionPayload} data Request data
146 * @param {boolean?} extractData Extract message from the response. Default true293 * @param {boolean?} extractData Extract message from the response. Default true
147 * @returns {Promise<string | any>} Extracted data or the raw response294 * @returns {Promise<ExtractedData | any>} Extracted data or the raw response
148 * @throws {Error}295 * @throws {Error}
149 */296 */
150 static async sendRequest(data, extractData = true) {297 static async sendRequest(data, extractData = true) {
@@ -161,29 +308,82 @@ export class ChatCompletionService {
161 throw json;308 throw json;
162 }309 }
163310
164 return extractData ? extractMessageFromData(json, this.TYPE) : json;311 if (!extractData) {
312 return json;
313 }
314
315 return {
316 content: extractMessageFromData(json, this.TYPE),
317 reasoning: extractReasoningFromData(json, {
318 mainApi: this.TYPE,
319 textGenType: data.chat_completion_source,
320 ignoreShowThoughts: true,
321 }),
322 };
165 }323 }
166324
167 /**325 /**
168 * @param {string} presetName326 * Process and send a chat completion request with optional preset
169 * @param {ChatCompletionPayload} custom327 * @param {ChatCompletionPayload} custom
170 * @param {boolean} extractData Extract message from the response. Default true328 * @param {Object} options - Configuration options
171 * @returns {Promise<string | any>} Extracted data or the raw response329 * @param {string?} [options.presetName] - Name of the preset to use for generation settings
330 * @param {boolean} extractData - Whether to extract structured data from response
331 * @returns {Promise<ExtractedData | any>} Extracted data or the raw response
172 * @throws {Error}332 * @throws {Error}
173 */333 */
174 static async sendRequestWithPreset(presetName, custom, extractData = true) {334 static async processRequest(custom, options, extractData = true) {
175 const presetManager = getPresetManager(this.TYPE);335 const { presetName } = options;
176 if (!presetManager) {336 let requestData = { ...custom };
177 throw new Error('Preset manager not found');
178 }
179337
338 // Apply generation preset if specified
339 if (presetName) {
340 const presetManager = getPresetManager(this.TYPE);
341 if (presetManager) {
180 const preset = presetManager.getCompletionPresetByName(presetName);342 const preset = presetManager.getCompletionPresetByName(presetName);
181 if (!preset) {343 if (preset) {
182 throw new Error('Preset not found');344 // Convert preset to payload and merge with custom parameters
345 const presetPayload = this.presetToGeneratePayload(preset, {});
346 requestData = { ...presetPayload, ...requestData };
347 } else {
348 console.warn(`Preset "${presetName}" not found, continuing with default settings`);
349 }
350 } else {
351 console.warn('Preset manager not found, continuing with default settings');
352 }
183 }353 }
184354
185 const data = this.createRequestData({ ...preset, ...custom });355 const data = this.createRequestData(requestData);
186356
187 return await this.sendRequest(data, extractData);357 return await this.sendRequest(data, extractData);
188 }358 }
359
360 /**
361 * Converts a preset to a valid chat completion payload
362 * Only supports temperature.
363 * @param {Object} preset - The preset configuration
364 * @param {Object} customParams - Additional parameters to override preset values
365 * @returns {Object} - Formatted payload for chat completion API
366 */
367 static presetToGeneratePayload(preset, customParams = {}) {
368 if (!preset || typeof preset !== 'object') {
369 throw new Error('Invalid preset: must be an object');
370 }
371
372 // Merge preset with custom parameters
373 const settings = { ...preset, ...customParams };
374
375 // Initialize base payload with common parameters
376 const payload = {
377 temperature: settings.temperature ? Number(settings.temperature) : undefined,
378 };
379
380 // Remove undefined values to avoid API errors
381 Object.keys(payload).forEach(key => {
382 if (payload[key] === undefined) {
383 delete payload[key];
384 }
385 });
386
387 return payload;
388 }
189}389}
public/scripts/extensions/assets/index.js+1 -1
@@ -424,7 +424,7 @@ jQuery(async () => {
424 installHintButton.on('click', async function () {424 installHintButton.on('click', async function () {
425 const installButton = $('#third_party_extension_button');425 const installButton = $('#third_party_extension_button');
426 flashHighlight(installButton, 5000);426 flashHighlight(installButton, 5000);
427 toastr.info('Click the flashing button to install extensions.', 'How to install extensions?');427 toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
428 });428 });
429429
430 const connectButton = windowHtml.find('#assets-connect-button');430 const connectButton = windowHtml.find('#assets-connect-button');
public/scripts/extensions/attachments/attach-button.html+1 -1
@@ -1,4 +1,4 @@
1<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">1<div id="attachFile" class="list-group-item flex-container flexGap5" data-i18n="[title]Attach a file or image to a current chat." title="Attach a file or image to a current chat.">
2 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>2 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
3 <span data-i18n="Attach a File">Attach a File</span>3 <span data-i18n="Attach a File">Attach a File</span>
4</div>4</div>
public/scripts/extensions/caption/settings.html+3 -0
@@ -42,6 +42,9 @@
42 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>42 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
43 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>43 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>
44 <option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option>44 <option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option>
45 <option data-type="mistral" value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option>
46 <option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option>
47 <option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option>
45 <option data-type="zerooneai" value="yi-vision">yi-vision</option>48 <option data-type="zerooneai" value="yi-vision">yi-vision</option>
46 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>49 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
47 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>50 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
public/scripts/extensions/connection-manager/index.js+32 -6
@@ -1,4 +1,4 @@
1import { Fuse } from '../../../lib.js';1import { DOMPurify, Fuse } from '../../../lib.js';
22
3import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';3import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
39 'proxy',39 'proxy',
40 'stop-strings',40 'stop-strings',
41 'start-reply-with',41 'start-reply-with',
42 'reasoning-template',
42];43];
4344
44const TC_COMMANDS = [45const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
54 'tokenizer',55 'tokenizer',
55 'stop-strings',56 'stop-strings',
56 'start-reply-with',57 'start-reply-with',
58 'reasoning-template',
57];59];
5860
59const FANCY_NAMES = {61const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
70 'tokenizer': 'Tokenizer',72 'tokenizer': 'Tokenizer',
71 'stop-strings': 'Custom Stopping Strings',73 'stop-strings': 'Custom Stopping Strings',
72 'start-reply-with': 'Start Reply With',74 'start-reply-with': 'Start Reply With',
75 'reasoning-template': 'Reasoning Template',
73};76};
7477
75/**78/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
154 * @property {string} [tokenizer] Tokenizer157 * @property {string} [tokenizer] Tokenizer
155 * @property {string} [stop-strings] Custom Stopping Strings158 * @property {string} [stop-strings] Custom Stopping Strings
156 * @property {string} [start-reply-with] Start Reply With159 * @property {string} [start-reply-with] Start Reply With
160 * @property {string} [reasoning-template] Reasoning Template
157 * @property {string[]} [exclude] Commands to exclude161 * @property {string[]} [exclude] Commands to exclude
158 */162 */
159163
@@ -267,9 +271,14 @@ async function createConnectionProfile(forceName = null) {
267 });271 });
268 const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);272 const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
269 const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);273 const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
270 const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });274 let name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
271275 // If it's cancelled, it will be false
276 if (!name) {
277 return null;
278 }
279 name = DOMPurify.sanitize(String(name));
272 if (!name) {280 if (!name) {
281 toastr.error('Name cannot be empty.');
273 return null;282 return null;
274 }283 }
275284
@@ -303,7 +312,8 @@ async function deleteConnectionProfile() {
303 return;312 return;
304 }313 }
305314
306 const name = extension_settings.connectionManager.profiles[index].name;315 const profile = extension_settings.connectionManager.profiles[index];
316 const name = profile.name;
307 const confirm = await Popup.show.confirm(t`Are you sure you want to delete the selected profile?`, name);317 const confirm = await Popup.show.confirm(t`Are you sure you want to delete the selected profile?`, name);
308318
309 if (!confirm) {319 if (!confirm) {
@@ -313,6 +323,8 @@ async function deleteConnectionProfile() {
313 extension_settings.connectionManager.profiles.splice(index, 1);323 extension_settings.connectionManager.profiles.splice(index, 1);
314 extension_settings.connectionManager.selectedProfile = null;324 extension_settings.connectionManager.selectedProfile = null;
315 saveSettingsDebounced();325 saveSettingsDebounced();
326
327 await eventSource.emit(event_types.CONNECTION_PROFILE_DELETED, profile);
316}328}
317329
318/**330/**
@@ -512,6 +524,7 @@ async function renderDetailsContent(detailsContent) {
512 saveSettingsDebounced();524 saveSettingsDebounced();
513 renderConnectionProfiles(profiles);525 renderConnectionProfiles(profiles);
514 await renderDetailsContent(detailsContent);526 await renderDetailsContent(detailsContent);
527 await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
515 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);528 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
516 });529 });
517530
@@ -523,9 +536,11 @@ async function renderDetailsContent(detailsContent) {
523 console.log('No profile selected');536 console.log('No profile selected');
524 return;537 return;
525 }538 }
539 const oldProfile = structuredClone(profile);
526 await updateConnectionProfile(profile);540 await updateConnectionProfile(profile);
527 await renderDetailsContent(detailsContent);541 await renderDetailsContent(detailsContent);
528 saveSettingsDebounced();542 saveSettingsDebounced();
543 await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
529 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);544 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
530 toastr.success('Connection profile updated', '', { timeOut: 1500 });545 toastr.success('Connection profile updated', '', { timeOut: 1500 });
531 });546 });
@@ -559,7 +574,7 @@ async function renderDetailsContent(detailsContent) {
559 return acc;574 return acc;
560 }, {});575 }, {});
561 const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));576 const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));
562 const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {577 let newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
563 rows: 2,578 rows: 2,
564 customButtons: [{579 customButtons: [{
565 text: t`Save and Update`,580 text: t`Save and Update`,
@@ -571,7 +586,13 @@ async function renderDetailsContent(detailsContent) {
571 }],586 }],
572 });587 });
573588
589 // If it's cancelled, it will be false
590 if (!newName) {
591 return;
592 }
593 newName = DOMPurify.sanitize(String(newName));
574 if (!newName) {594 if (!newName) {
595 toastr.error('Name cannot be empty.');
575 return;596 return;
576 }597 }
577598
@@ -584,6 +605,7 @@ async function renderDetailsContent(detailsContent) {
584 return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];605 return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];
585 }).get();606 }).get();
586607
608 const oldProfile = structuredClone(profile);
587 if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {609 if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {
588 profile.exclude = newExcludeList;610 profile.exclude = newExcludeList;
589 for (const command of newExcludeList) {611 for (const command of newExcludeList) {
@@ -598,10 +620,11 @@ async function renderDetailsContent(detailsContent) {
598620
599 if (profile.name !== newName) {621 if (profile.name !== newName) {
600 toastr.success('Connection profile renamed.');622 toastr.success('Connection profile renamed.');
601 profile.name = String(newName);623 profile.name = newName;
602 }624 }
603625
604 saveSettingsDebounced();626 saveSettingsDebounced();
627 await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
605 renderConnectionProfiles(profiles);628 renderConnectionProfiles(profiles);
606 await renderDetailsContent(detailsContent);629 await renderDetailsContent(detailsContent);
607 });630 });
@@ -704,6 +727,7 @@ async function renderDetailsContent(detailsContent) {
704 saveSettingsDebounced();727 saveSettingsDebounced();
705 renderConnectionProfiles(profiles);728 renderConnectionProfiles(profiles);
706 await renderDetailsContent(detailsContent);729 await renderDetailsContent(detailsContent);
730 await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
707 return profile.name;731 return profile.name;
708 },732 },
709 }));733 }));
@@ -718,9 +742,11 @@ async function renderDetailsContent(detailsContent) {
718 toastr.warning('No profile selected.');742 toastr.warning('No profile selected.');
719 return '';743 return '';
720 }744 }
745 const oldProfile = structuredClone(profile);
721 await updateConnectionProfile(profile);746 await updateConnectionProfile(profile);
722 await renderDetailsContent(detailsContent);747 await renderDetailsContent(detailsContent);
723 saveSettingsDebounced();748 saveSettingsDebounced();
749 await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
724 return profile.name;750 return profile.name;
725 },751 },
726 }));752 }));
public/scripts/extensions/expressions/index.js+59 -17
@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateRaw, getRequestHeaders, m
4import { dragElement, isMobile } from '../../RossAscends-mods.js';4import { dragElement, isMobile } from '../../RossAscends-mods.js';
5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js';7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js';
8import { hideMutedSprites, selected_group } from '../../group-chats.js';8import { hideMutedSprites, selected_group } from '../../group-chats.js';
9import { isJsonSchemaSupported } from '../../textgen-settings.js';9import { isJsonSchemaSupported } from '../../textgen-settings.js';
10import { debounce_timeout } from '../../constants.js';10import { debounce_timeout } from '../../constants.js';
@@ -17,6 +17,7 @@ import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandRetur
17import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';17import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
18import { Popup, POPUP_RESULT } from '../../popup.js';18import { Popup, POPUP_RESULT } from '../../popup.js';
19import { t } from '../../i18n.js';19import { t } from '../../i18n.js';
20import { removeReasoningFromString } from '../../reasoning.js';
20export { MODULE_NAME };21export { MODULE_NAME };
2122
22/**23/**
@@ -678,7 +679,7 @@ async function setSpriteFolderCommand(_, folder) {
678 return '';679 return '';
679}680}
680681
681async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ { api = null, prompt = null }, text) {682async function classifyCallback(/** @type {{api: string?, filter: string?, prompt: string?}} */ { api = null, filter = null, prompt = null }, text) {
682 if (!text) {683 if (!text) {
683 toastr.error('No text provided');684 toastr.error('No text provided');
684 return '';685 return '';
@@ -689,13 +690,14 @@ async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ {
689 }690 }
690691
691 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;692 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;
693 const filterAvailable = !isFalseBoolean(filter);
692694
693 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {695 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {
694 toastr.warning('Text classification is disabled or not available');696 toastr.warning('Text classification is disabled or not available');
695 return '';697 return '';
696 }698 }
697699
698 const label = await getExpressionLabel(text, expressionApi, { customPrompt: prompt });700 const label = await getExpressionLabel(text, expressionApi, { filterAvailable: filterAvailable, customPrompt: prompt });
699 console.debug(`Classification result for "${text}": ${label}`);701 console.debug(`Classification result for "${text}": ${label}`);
700 return label;702 return label;
701}703}
@@ -928,6 +930,9 @@ function parseLlmResponse(emotionResponse, labels) {
928930
929 return response;931 return response;
930 } catch {932 } catch {
933 // Clean possible reasoning from response
934 emotionResponse = removeReasoningFromString(emotionResponse);
935
931 const fuse = new Fuse(labels, { includeScore: true });936 const fuse = new Fuse(labels, { includeScore: true });
932 console.debug('Using fuzzy search in labels:', labels);937 console.debug('Using fuzzy search in labels:', labels);
933 const result = fuse.search(emotionResponse);938 const result = fuse.search(emotionResponse);
@@ -988,10 +993,11 @@ function onTextGenSettingsReady(args) {
988 * @param {string} text - The text to classify and retrieve the expression label for.993 * @param {string} text - The text to classify and retrieve the expression label for.
989 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.994 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
990 * @param {object} [options={}] - Optional arguments.995 * @param {object} [options={}] - Optional arguments.
996 * @param {boolean?} [options.filterAvailable=null] - Whether to filter available expressions. If not specified, uses the extension setting.
991 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.997 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
992 * @returns {Promise<string?>} - The label of the expression.998 * @returns {Promise<string?>} - The label of the expression.
993 */999 */
994export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) {1000export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { filterAvailable = null, customPrompt = null } = {}) {
995 // Return if text is undefined, saving a costly fetch request1001 // Return if text is undefined, saving a costly fetch request
996 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {1002 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
997 return extension_settings.expressions.fallback_expression;1003 return extension_settings.expressions.fallback_expression;
@@ -1003,6 +1009,11 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10031009
1004 text = sampleClassifyText(text);1010 text = sampleClassifyText(text);
10051011
1012 filterAvailable ??= extension_settings.expressions.filterAvailable;
1013 if (filterAvailable && ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(expressionsApi)) {
1014 console.debug('Filter available is only supported for LLM and WebLLM expressions');
1015 }
1016
1006 try {1017 try {
1007 switch (expressionsApi) {1018 switch (expressionsApi) {
1008 // Local BERT pipeline1019 // Local BERT pipeline
@@ -1027,7 +1038,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1027 return extension_settings.expressions.fallback_expression;1038 return extension_settings.expressions.fallback_expression;
1028 }1039 }
10291040
1030 const expressionsList = await getExpressionsList();1041 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1031 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1042 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1032 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);1043 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
1033 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);1044 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
@@ -1040,7 +1051,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1040 return extension_settings.expressions.fallback_expression;1051 return extension_settings.expressions.fallback_expression;
1041 }1052 }
10421053
1043 const expressionsList = await getExpressionsList();1054 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1044 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1055 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1045 const messages = [1056 const messages = [
1046 { role: 'user', content: text + '\n\n' + prompt },1057 { role: 'user', content: text + '\n\n' + prompt },
@@ -1320,12 +1331,28 @@ function getCachedExpressions() {
1320 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);1331 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
1321}1332}
13221333
1323export async function getExpressionsList() {1334export async function getExpressionsList({ filterAvailable = false } = {}) {
1324 // Return cached list if available1335 // If there is no cached list, load and cache it
1325 if (Array.isArray(expressionsList)) {1336 if (!Array.isArray(expressionsList)) {
1326 return getCachedExpressions();1337 expressionsList = await resolveExpressionsList();
1327 }1338 }
13281339
1340 const expressions = getCachedExpressions();
1341
1342 // Filtering is only available for llm and webllm APIs
1343 if (!filterAvailable || ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api)) {
1344 return expressions;
1345 }
1346
1347 // Get expressions with available sprites
1348 const currentLastMessage = selected_group ? getLastCharacterMessage() : null;
1349 const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage?.name);
1350
1351 return expressions.filter(label => {
1352 const expression = spriteCache[spriteFolderName]?.find(x => x.label === label);
1353 return (expression?.files.length ?? 0) > 0;
1354 });
1355
1329 /**1356 /**
1330 * Returns the list of expressions from the API or fallback in offline mode.1357 * Returns the list of expressions from the API or fallback in offline mode.
1331 * @returns {Promise<string[]>}1358 * @returns {Promise<string[]>}
@@ -1372,9 +1399,6 @@ export async function getExpressionsList() {
1372 expressionsList = DEFAULT_EXPRESSIONS.slice();1399 expressionsList = DEFAULT_EXPRESSIONS.slice();
1373 return expressionsList;1400 return expressionsList;
1374 }1401 }
1375
1376 const result = await resolveExpressionsList();
1377 return [...result, ...extension_settings.expressions.custom].filter(onlyUnique);
1378}1402}
13791403
1380/**1404/**
@@ -1810,7 +1834,7 @@ async function onClickExpressionUpload(event) {
1810 }1834 }
1811 }1835 }
1812 } else {1836 } else {
1813 spriteName = withoutExtension(clickedFileName);1837 spriteName = withoutExtension(expression);
1814 }1838 }
18151839
1816 if (!spriteName) {1840 if (!spriteName) {
@@ -2102,6 +2126,10 @@ function migrateSettings() {
2102 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');2126 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');
2103 saveSettingsDebounced();2127 saveSettingsDebounced();
2104 });2128 });
2129 $('#expressions_filter_available').prop('checked', extension_settings.expressions.filterAvailable).on('input', function () {
2130 extension_settings.expressions.filterAvailable = !!$(this).prop('checked');
2131 saveSettingsDebounced();
2132 });
2105 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);2133 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
2106 $(document).on('dragstart', '.expression', (e) => {2134 $(document).on('dragstart', '.expression', (e) => {
2107 e.preventDefault();2135 e.preventDefault();
@@ -2154,7 +2182,7 @@ function migrateSettings() {
2154 imgElement.src = '';2182 imgElement.src = '';
2155 }2183 }
21562184
2157 setExpressionOverrideHtml();2185 setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
21582186
2159 if (isVisualNovelMode()) {2187 if (isVisualNovelMode()) {
2160 $('#visual-novel-wrapper').empty();2188 $('#visual-novel-wrapper').empty();
@@ -2279,13 +2307,13 @@ function migrateSettings() {
2279 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2307 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2280 name: 'expression-list',2308 name: 'expression-list',
2281 aliases: ['expressions'],2309 aliases: ['expressions'],
2282 /** @type {(args: {return: string}) => Promise<string>} */2310 /** @type {(args: {return: string, filter: string}) => Promise<string>} */
2283 callback: async (args) => {2311 callback: async (args) => {
2284 let returnType =2312 let returnType =
2285 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */2313 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
2286 (args.return);2314 (args.return);
22872315
2288 const list = await getExpressionsList();2316 const list = await getExpressionsList({ filterAvailable: !isFalseBoolean(args.filter) });
22892317
2290 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });2318 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
2291 },2319 },
@@ -2298,6 +2326,13 @@ function migrateSettings() {
2298 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),2326 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
2299 forceEnum: true,2327 forceEnum: true,
2300 }),2328 }),
2329 SlashCommandNamedArgument.fromProps({
2330 name: 'filter',
2331 description: 'Filter the list to only include expressions that have available sprites for the current character.',
2332 typeList: [ARGUMENT_TYPE.BOOLEAN],
2333 enumList: commonEnumProviders.boolean('trueFalse')(),
2334 defaultValue: 'true',
2335 }),
2301 ],2336 ],
2302 returns: 'The comma-separated list of available expressions, including custom expressions.',2337 returns: 'The comma-separated list of available expressions, including custom expressions.',
2303 helpString: 'Returns a list of available expressions, including custom expressions.',2338 helpString: 'Returns a list of available expressions, including custom expressions.',
@@ -2314,6 +2349,13 @@ function migrateSettings() {
2314 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),2349 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),
2315 }),2350 }),
2316 SlashCommandNamedArgument.fromProps({2351 SlashCommandNamedArgument.fromProps({
2352 name: 'filter',
2353 description: 'Filter the list to only include expressions that have available sprites for the current character.',
2354 typeList: [ARGUMENT_TYPE.BOOLEAN],
2355 enumList: commonEnumProviders.boolean('trueFalse')(),
2356 defaultValue: 'true',
2357 }),
2358 SlashCommandNamedArgument.fromProps({
2317 name: 'prompt',2359 name: 'prompt',
2318 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',2360 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',
2319 typeList: [ARGUMENT_TYPE.STRING],2361 typeList: [ARGUMENT_TYPE.STRING],
public/scripts/extensions/expressions/settings.html+5 -1
@@ -29,7 +29,11 @@
29 </select>29 </select>
30 </div>30 </div>
31 <div class="expression_llm_prompt_block m-b-1 m-t-1">31 <div class="expression_llm_prompt_block m-b-1 m-t-1">
32 <label for="expression_llm_prompt" class="title_restorable">32 <label class="checkbox_label" for="expressions_filter_available" title="When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them." data-i18n="[title]When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.">
33 <input id="expressions_filter_available" type="checkbox">
34 <span data-i18n="Filter expressions for available sprites">Filter expressions for available sprites</span>
35 </label>
36 <label for="expression_llm_prompt" class="title_restorable m-t-1">
33 <span data-i18n="LLM Prompt">LLM Prompt</span>37 <span data-i18n="LLM Prompt">LLM Prompt</span>
34 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">38 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
35 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>39 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>
public/scripts/extensions/quick-reply/api/QuickReplyApi.js+0 -3
@@ -1,10 +1,7 @@
1// eslint-disable-next-line no-unused-vars
2import { QuickReply } from '../src/QuickReply.js';1import { QuickReply } from '../src/QuickReply.js';
3import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';2import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';
4import { QuickReplySet } from '../src/QuickReplySet.js';3import { QuickReplySet } from '../src/QuickReplySet.js';
5// eslint-disable-next-line no-unused-vars
6import { QuickReplySettings } from '../src/QuickReplySettings.js';4import { QuickReplySettings } from '../src/QuickReplySettings.js';
7// eslint-disable-next-line no-unused-vars
8import { SettingsUi } from '../src/ui/SettingsUi.js';5import { SettingsUi } from '../src/ui/SettingsUi.js';
9import { onlyUnique } from '../../../utils.js';6import { onlyUnique } from '../../../utils.js';
107
public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js+0 -2
@@ -1,7 +1,5 @@
1import { warn } from '../index.js';1import { warn } from '../index.js';
2// eslint-disable-next-line no-unused-vars
3import { QuickReply } from './QuickReply.js';2import { QuickReply } from './QuickReply.js';
4// eslint-disable-next-line no-unused-vars
5import { QuickReplySettings } from './QuickReplySettings.js';3import { QuickReplySettings } from './QuickReplySettings.js';
64
7export class AutoExecuteHandler {5export class AutoExecuteHandler {
public/scripts/extensions/quick-reply/src/QuickReply.js+0 -2
@@ -635,7 +635,6 @@ export class QuickReply {
635 }, { passive:true });635 }, { passive:true });
636 const getLineStart = ()=>{636 const getLineStart = ()=>{
637 const start = message.selectionStart;637 const start = message.selectionStart;
638 const end = message.selectionEnd;
639 let lineStart;638 let lineStart;
640 if (start == 0 || message.value[start - 1] == '\n') {639 if (start == 0 || message.value[start - 1] == '\n') {
641 // cursor is already at beginning of line640 // cursor is already at beginning of line
@@ -701,7 +700,6 @@ export class QuickReply {
701 } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {700 } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {
702 // new line, keep indent701 // new line, keep indent
703 const start = message.selectionStart;702 const start = message.selectionStart;
704 const end = message.selectionEnd;
705 let lineStart = getLineStart();703 let lineStart = getLineStart();
706 const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? '';704 const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? '';
707 if (indent.length) {705 if (indent.length) {
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+0 -1
@@ -8,7 +8,6 @@ import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashC
8import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';8import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
9import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';9import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
10import { isTrueBoolean } from '../../../utils.js';10import { isTrueBoolean } from '../../../utils.js';
11// eslint-disable-next-line no-unused-vars
12import { QuickReplyApi } from '../api/QuickReplyApi.js';11import { QuickReplyApi } from '../api/QuickReplyApi.js';
13import { QuickReply } from './QuickReply.js';12import { QuickReply } from './QuickReply.js';
14import { QuickReplySet } from './QuickReplySet.js';13import { QuickReplySet } from './QuickReplySet.js';
public/scripts/extensions/quick-reply/src/ui/ButtonUi.js+0 -1
@@ -1,7 +1,6 @@
1import { animation_duration } from '../../../../../script.js';1import { animation_duration } from '../../../../../script.js';
2import { dragElement } from '../../../../RossAscends-mods.js';2import { dragElement } from '../../../../RossAscends-mods.js';
3import { loadMovingUIState } from '../../../../power-user.js';3import { loadMovingUIState } from '../../../../power-user.js';
4// eslint-disable-next-line no-unused-vars
5import { QuickReplySettings } from '../QuickReplySettings.js';4import { QuickReplySettings } from '../QuickReplySettings.js';
65
7export class ButtonUi {6export class ButtonUi {
public/scripts/extensions/quick-reply/src/ui/SettingsUi.js+0 -1
@@ -3,7 +3,6 @@ import { getSortableDelay } from '../../../../utils.js';
3import { log, warn } from '../../index.js';3import { log, warn } from '../../index.js';
4import { QuickReply } from '../QuickReply.js';4import { QuickReply } from '../QuickReply.js';
5import { QuickReplySet } from '../QuickReplySet.js';5import { QuickReplySet } from '../QuickReplySet.js';
6// eslint-disable-next-line no-unused-vars
7import { QuickReplySettings } from '../QuickReplySettings.js';6import { QuickReplySettings } from '../QuickReplySettings.js';
87
9export class SettingsUi {8export class SettingsUi {
public/scripts/extensions/quick-reply/src/ui/ctx/ContextMenu.js+0 -1
@@ -1,5 +1,4 @@
1import { QuickReply } from '../../QuickReply.js';1import { QuickReply } from '../../QuickReply.js';
2// eslint-disable-next-line no-unused-vars
3import { QuickReplySet } from '../../QuickReplySet.js';2import { QuickReplySet } from '../../QuickReplySet.js';
4import { MenuHeader } from './MenuHeader.js';3import { MenuHeader } from './MenuHeader.js';
5import { MenuItem } from './MenuItem.js';4import { MenuItem } from './MenuItem.js';
public/scripts/extensions/regex/editor.html+3 -4
@@ -18,9 +18,8 @@
1818
19 <div id="regex_info_block_wrapper">19 <div id="regex_info_block_wrapper">
20 <div id="regex_info_block" class="info-block"></div>20 <div id="regex_info_block" class="info-block"></div>
21 <!-- TODO replace 3rd-party link with our own docs when it's done -->21 <a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">
22 <a id="regex_info_block_flags_hint" href="http://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#advanced_searching_with_flags" target="_blank" rel="noopener noreferrer">22 <i class="fa-solid fa-circle-info" data-i18n="[title]ext_regex_flags_help" title="Click here to learn more about regex flags."></i>
23 <i class="fa-solid fa-circle-info" title="Click here to learn more about regex flags."></i>
24 </a>23 </a>
25 </div>24 </div>
2625
@@ -148,7 +147,7 @@
148 </label>147 </label>
149 <span>148 <span>
150 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
151 <span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>150 <span class="fa-solid fa-circle-question note-link-span" data-i18n="[title]ext_regex_other_options_desc" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
152 </span>151 </span>
153 <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">152 <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
154 <input type="checkbox" name="only_format_display" />153 <input type="checkbox" name="only_format_display" />
public/scripts/extensions/regex/index.js+1 -1
@@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
398 for (const script of scripts) {398 for (const script of scripts) {
399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
400 if (script.disabled) {400 if (script.disabled) {
401 toastr.warning(`Regex script "${scriptName}" is disabled.`);401 toastr.warning(t`Regex script "${scriptName}" is disabled.`);
402 return value;402 return value;
403 }403 }
404404
public/scripts/extensions/shared.js+309 -1
@@ -1,5 +1,6 @@
1import { getRequestHeaders } from '../../script.js';1import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';
2import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';2import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
3import { t } from '../i18n.js';
3import { oai_settings } from '../openai.js';4import { oai_settings } from '../openai.js';
4import { SECRET_KEYS, secret_state } from '../secrets.js';5import { SECRET_KEYS, secret_state } from '../secrets.js';
5import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';6import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
@@ -273,3 +274,310 @@ export async function getWebLlmContextSize() {
273 const model = await engine.getCurrentModelInfo();274 const model = await engine.getCurrentModelInfo();
274 return model?.context_size;275 return model?.context_size;
275}276}
277
278/**
279 * It uses the profiles to send a generate request to the API. Doesn't support streaming.
280 */
281export class ConnectionManagerRequestService {
282 static defaultSendRequestParams = {
283 extractData: true,
284 includePreset: true,
285 includeInstruct: true,
286 };
287
288 static getAllowedTypes() {
289 return {
290 openai: t`Chat Completion`,
291 textgenerationwebui: t`Text Completion`,
292 };
293 }
294
295 /**
296 * @param {string} profileId
297 * @param {string | (import('../custom-request.js').ChatCompletionMessage & {ignoreInstruct?: boolean})[]} prompt
298 * @param {number} maxTokens
299 * @param {{extractData?: boolean, includePreset?: boolean, includeInstruct?: boolean}} custom - default values are true
300 * @returns {Promise<import('../custom-request.js').ExtractedData | any>} Extracted data or the raw response
301 */
302 static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams) {
303 const { extractData, includePreset, includeInstruct } = { ...this.defaultSendRequestParams, ...custom };
304
305 const context = SillyTavern.getContext();
306 if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
307 throw new Error('Connection Manager is not available');
308 }
309
310 const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
311 const selectedApiMap = this.validateProfile(profile);
312
313 try {
314 switch (selectedApiMap.selected) {
315 case 'openai': {
316 if (!selectedApiMap.source) {
317 throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`);
318 }
319
320 const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }];
321 return await context.ChatCompletionService.processRequest({
322 messages,
323 max_tokens: maxTokens,
324 model: profile.model,
325 chat_completion_source: selectedApiMap.source,
326 custom_url: profile['api-url'],
327 }, {
328 presetName: includePreset ? profile.preset : undefined,
329 }, extractData);
330 }
331 case 'textgenerationwebui': {
332 if (!selectedApiMap.type) {
333 throw new Error(`API type ${selectedApiMap.selected} does not support text completions`);
334 }
335
336 return await context.TextCompletionService.processRequest({
337 prompt,
338 max_tokens: maxTokens,
339 model: profile.model,
340 api_type: selectedApiMap.type,
341 api_server: profile['api-url'],
342 }, {
343 instructName: includeInstruct ? profile.instruct : undefined,
344 presetName: includePreset ? profile.preset : undefined,
345 }, extractData);
346 }
347 default: {
348 throw new Error(`Unknown API type ${selectedApiMap.selected}`);
349 }
350 }
351 } catch (error) {
352 throw new Error('API request failed', { cause: error });
353 }
354 }
355
356 /**
357 * Respects allowed types.
358 * @returns {import('./connection-manager/index.js').ConnectionProfile[]}
359 */
360 static getSupportedProfiles() {
361 const context = SillyTavern.getContext();
362 if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
363 throw new Error('Connection Manager is not available');
364 }
365
366 const profiles = context.extensionSettings.connectionManager.profiles;
367 return profiles.filter((p) => this.isProfileSupported(p));
368 }
369
370 /**
371 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
372 * @returns {boolean}
373 */
374 static isProfileSupported(profile) {
375 if (!profile) {
376 return false;
377 }
378
379 const apiMap = CONNECT_API_MAP[profile.api];
380 if (!Object.hasOwn(this.getAllowedTypes(), apiMap.selected)) {
381 return false;
382 }
383
384 // Some providers not need model, like koboldcpp. But I don't want to check by provider.
385 switch (apiMap.selected) {
386 case 'openai':
387 return !!apiMap.source;
388 case 'textgenerationwebui':
389 return !!apiMap.type;
390 }
391
392 return false;
393 }
394
395 /**
396 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
397 * @return {import('../../script.js').ConnectAPIMap}
398 * @throws {Error}
399 */
400 static validateProfile(profile) {
401 if (!profile) {
402 throw new Error('Could not find profile.');
403 }
404 if (!profile.api) {
405 throw new Error('Select a connection profile that has an API');
406 }
407
408 const context = SillyTavern.getContext();
409 const selectedApiMap = context.CONNECT_API_MAP[profile.api];
410 if (!selectedApiMap) {
411 throw new Error(`Unknown API type ${profile.api}`);
412 }
413 if (!Object.hasOwn(this.getAllowedTypes(), selectedApiMap.selected)) {
414 throw new Error(`API type ${selectedApiMap.selected} is not supported. Supported types: ${Object.values(this.getAllowedTypes()).join(', ')}`);
415 }
416
417 return selectedApiMap;
418 }
419
420 /**
421 * Create profiles dropdown and updates select element accordingly. Use onChange, onCreate, unUpdate, onDelete callbacks for custom behaviour. e.g updating extension settings.
422 * @param {string} selector
423 * @param {string} initialSelectedProfileId
424 * @param {(profile?: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onChange - 3 cases. 1- When user selects new profile. 2- When user deletes selected profile. 3- When user updates selected profile.
425 * @param {(profile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onCreate
426 * @param {(oldProfile: import('./connection-manager/index.js').ConnectionProfile, newProfile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} unUpdate
427 * @param {(profile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onDelete
428 */
429 static handleDropdown(
430 selector,
431 initialSelectedProfileId,
432 onChange = () => { },
433 onCreate = () => { },
434 unUpdate = () => { },
435 onDelete = () => { },
436 ) {
437 const context = SillyTavern.getContext();
438 if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
439 throw new Error('Connection Manager is not available');
440 }
441
442 /**
443 * @type {JQuery<HTMLSelectElement>}
444 */
445 const dropdown = $(selector);
446
447 if (!dropdown || !dropdown.length) {
448 throw new Error(`Could not find dropdown with selector ${selector}`);
449 }
450
451 dropdown.empty();
452
453 // Create default option using document.createElement
454 const defaultOption = document.createElement('option');
455 defaultOption.value = '';
456 defaultOption.textContent = 'Select a Connection Profile';
457 defaultOption.dataset.i18n = 'Select a Connection Profile';
458 dropdown.append(defaultOption);
459
460 const profiles = context.extensionSettings.connectionManager.profiles;
461
462 // Create optgroups using document.createElement
463 const groups = {};
464 for (const [apiType, groupLabel] of Object.entries(this.getAllowedTypes())) {
465 const optgroup = document.createElement('optgroup');
466 optgroup.label = groupLabel;
467 groups[apiType] = optgroup;
468 }
469
470 const sortedProfilesByGroup = {};
471 for (const apiType of Object.keys(this.getAllowedTypes())) {
472 sortedProfilesByGroup[apiType] = [];
473 }
474
475 for (const profile of profiles) {
476 if (this.isProfileSupported(profile)) {
477 const apiMap = CONNECT_API_MAP[profile.api];
478 if (sortedProfilesByGroup[apiMap.selected]) {
479 sortedProfilesByGroup[apiMap.selected].push(profile);
480 }
481 }
482 }
483
484 // Sort each group alphabetically and add to dropdown
485 for (const [apiType, groupProfiles] of Object.entries(sortedProfilesByGroup)) {
486 if (groupProfiles.length === 0) continue;
487
488 groupProfiles.sort((a, b) => a.name.localeCompare(b.name));
489
490 const group = groups[apiType];
491 for (const profile of groupProfiles) {
492 const option = document.createElement('option');
493 option.value = profile.id;
494 option.textContent = profile.name;
495 group.appendChild(option);
496 }
497 }
498
499 for (const group of Object.values(groups)) {
500 if (group.children.length > 0) {
501 dropdown.append(group);
502 }
503 }
504
505 const selectedProfile = profiles.find((p) => p.id === initialSelectedProfileId);
506 if (selectedProfile) {
507 dropdown.val(selectedProfile.id);
508 }
509
510 context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_CREATED, async (profile) => {
511 const isSupported = this.isProfileSupported(profile);
512 if (!isSupported) {
513 return;
514 }
515
516 const group = groups[CONNECT_API_MAP[profile.api].selected];
517 const option = document.createElement('option');
518 option.value = profile.id;
519 option.textContent = profile.name;
520 group.appendChild(option);
521
522 await onCreate(profile);
523 });
524
525 context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_UPDATED, async (oldProfile, newProfile) => {
526 const currentSelected = dropdown.val();
527 const isSelectedProfile = currentSelected === oldProfile.id;
528 await unUpdate(oldProfile, newProfile);
529
530 if (!this.isProfileSupported(newProfile)) {
531 if (isSelectedProfile) {
532 dropdown.val('');
533 dropdown.trigger('change');
534 }
535 return;
536 }
537
538 const group = groups[CONNECT_API_MAP[newProfile.api].selected];
539 const oldOption = group.querySelector(`option[value="${oldProfile.id}"]`);
540 if (oldOption) {
541 oldOption.remove();
542 }
543
544 const option = document.createElement('option');
545 option.value = newProfile.id;
546 option.textContent = newProfile.name;
547 group.appendChild(option);
548
549 if (isSelectedProfile) {
550 // Ackchyually, we don't need to reselect but what if id changes? It is not possible for now I couldn't stop myself.
551 dropdown.val(newProfile.id);
552 dropdown.trigger('change');
553 }
554 });
555
556 context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_DELETED, async (profile) => {
557 const currentSelected = dropdown.val();
558 const isSelectedProfile = currentSelected === profile.id;
559 if (!this.isProfileSupported(profile)) {
560 return;
561 }
562
563 const group = groups[CONNECT_API_MAP[profile.api].selected];
564 const optionToRemove = group.querySelector(`option[value="${profile.id}"]`);
565 if (optionToRemove) {
566 optionToRemove.remove();
567 }
568
569 if (isSelectedProfile) {
570 dropdown.val('');
571 dropdown.trigger('change');
572 }
573
574 await onDelete(profile);
575 });
576
577 dropdown.on('change', async () => {
578 const profileId = dropdown.val();
579 const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
580 await onChange(profile);
581 });
582 }
583}
public/scripts/extensions/stable-diffusion/index.js+0 -1
@@ -3772,7 +3772,6 @@ async function addSDGenButtons() {
3772 $('#sd_wand_container').append(buttonHtml);3772 $('#sd_wand_container').append(buttonHtml);
3773 $(document.body).append(dropdownHtml);3773 $(document.body).append(dropdownHtml);
37743774
3775 const messageButton = $('.sd_message_gen');
3776 const button = $('#sd_gen');3775 const button = $('#sd_gen');
3777 const dropdown = $('#sd_dropdown');3776 const dropdown = $('#sd_dropdown');
3778 dropdown.hide();3777 dropdown.hide();
public/scripts/extensions/token-counter/index.js+0 -0
public/scripts/extensions/tts/gpt-sovits-v2.js+0 -2
@@ -183,8 +183,6 @@ class GptSovitsV2Provider {
183183
184 let prompt_text = replaceSpeaker(voiceId);184 let prompt_text = replaceSpeaker(voiceId);
185185
186 const streaming = this.settings.streaming;
187
188 const params = {186 const params = {
189 text: inputText,187 text: inputText,
190 prompt_text: prompt_text,188 prompt_text: prompt_text,
public/scripts/extensions/tts/index.js+3 -3
@@ -1,5 +1,5 @@
1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
2import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules, renderExtensionTemplateAsync } from '../../extensions.js';2import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';
4import { EdgeTtsProvider } from './edge.js';4import { EdgeTtsProvider } from './edge.js';
5import { ElevenLabsTtsProvider } from './elevenlabs.js';5import { ElevenLabsTtsProvider } from './elevenlabs.js';
@@ -1207,8 +1207,8 @@ jQuery(async function () {
1207 eventSource.on(event_types.GROUP_UPDATED, onChatChanged);1207 eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
1208 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);1208 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
1209 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);1209 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
1210 eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent);1210 eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
1211 eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);1211 eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
1212 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1212 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1213 name: 'speak',1213 name: 'speak',
1214 callback: async (args, value) => {1214 callback: async (args, value) => {
public/scripts/group-chats.js+11 -9
@@ -46,7 +46,6 @@ import {
46 hideSwipeButtons,46 hideSwipeButtons,
47 chat_metadata,47 chat_metadata,
48 updateChatMetadata,48 updateChatMetadata,
49 isStreamingEnabled,
50 getThumbnailUrl,49 getThumbnailUrl,
51 getRequestHeaders,50 getRequestHeaders,
52 setMenuType,51 setMenuType,
@@ -435,16 +434,18 @@ export function getGroupCharacterCards(groupId, characterId) {
435 * @param {string} value Value to replace434 * @param {string} value Value to replace
436 * @param {string} fieldName Name of the field435 * @param {string} fieldName Name of the field
437 * @param {string} characterName Name of the character436 * @param {string} characterName Name of the character
437 * @param {boolean} trim Whether to trim the value
438 * @returns {string} Replaced text438 * @returns {string} Replaced text
439 * */439 * */
440 function customBaseChatReplace(value, fieldName, characterName) {440 function customBaseChatReplace(value, fieldName, characterName, trim) {
441 if (!value) {441 if (!value) {
442 return '';442 return '';
443 }443 }
444444
445 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names445 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names
446 value = value.replace(/<FIELDNAME>/gi, fieldName);446 value = value.replace(/<FIELDNAME>/gi, fieldName);
447 return baseChatReplace(value.trim(), name1, characterName);447 value = trim ? value.trim() : value;
448 return baseChatReplace(value, name1, characterName);
448 }449 }
449450
450 /**451 /**
@@ -467,13 +468,12 @@ export function getGroupCharacterCards(groupId, characterId) {
467 }468 }
468469
469 // Prepare and replace prefixes470 // Prepare and replace prefixes
470 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName);471 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName, false);
471 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName);472 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName, false);
472 const separator = power_user.instruct.wrap ? '\n' : '';
473 // Also run the macro replacement on the actual content473 // Also run the macro replacement on the actual content
474 value = customBaseChatReplace(value, fieldName, characterName);474 value = customBaseChatReplace(value, fieldName, characterName, true);
475475
476 return `${prefix ? prefix + separator : ''}${value}${suffix ? separator + suffix : ''}`;476 return `${prefix}${value}${suffix}`;
477 }477 }
478478
479 const scenarioOverride = chat_metadata['scenario'];479 const scenarioOverride = chat_metadata['scenario'];
@@ -696,7 +696,7 @@ export function getGroupBlock(group) {
696 template.find('.group_fav_icon').css('display', 'none');696 template.find('.group_fav_icon').css('display', 'none');
697 template.addClass(group.fav ? 'is_fav' : '');697 template.addClass(group.fav ? 'is_fav' : '');
698 template.find('.ch_fav').val(group.fav);698 template.find('.ch_fav').val(group.fav);
699 template.find('.group_select_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);699 template.find('.group_select_counter').text(count + ' ' + (count != 1 ? t`characters` : t`character`));
700 template.find('.group_select_block_list').text(namesList.join(', '));700 template.find('.group_select_block_list').text(namesList.join(', '));
701701
702 // Display inline tags702 // Display inline tags
@@ -904,6 +904,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
904 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);904 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);
905 }905 }
906 }906 }
907 await eventSource.emit(event_types.GROUP_WRAPPER_STARTED, { selected_group, type });
907 // now the real generation begins: cycle through every activated character908 // now the real generation begins: cycle through every activated character
908 for (const chId of activatedMembers) {909 for (const chId of activatedMembers) {
909 throwIfAborted();910 throwIfAborted();
@@ -942,6 +943,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
942 setCharacterName('');943 setCharacterName('');
943 activateSendButtons();944 activateSendButtons();
944 showSwipeButtons();945 showSwipeButtons();
946 await eventSource.emit(event_types.GROUP_WRAPPER_FINISHED, { selected_group, type });
945 }947 }
946948
947 return Promise.resolve(textResult);949 return Promise.resolve(textResult);
public/scripts/horde.js+4 -3
@@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
10import { delay } from './utils.js';10import { delay } from './utils.js';
11import { isMobile } from './RossAscends-mods.js';11import { isMobile } from './RossAscends-mods.js';
12import { autoSelectInstructPreset } from './instruct-mode.js';12import { autoSelectInstructPreset } from './instruct-mode.js';
13import { t } from './i18n.js';
1314
14export {15export {
15 horde_settings,16 horde_settings,
@@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
169 }170 }
170 }171 }
171 console.log(maxContextLength, maxLength);172 console.log(maxContextLength, maxLength);
172 $('#adjustedHordeParams').text(`Context: ${maxContextLength}, Response: ${maxLength}`);173 $('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
173 return { maxContextLength, maxLength };174 return { maxContextLength, maxLength };
174}175}
175176
@@ -177,7 +178,7 @@ function setContextSizePreview() {
177 if (horde_settings.models.length) {178 if (horde_settings.models.length) {
178 adjustHordeGenerationParams(max_context, amount_gen);179 adjustHordeGenerationParams(max_context, amount_gen);
179 } else {180 } else {
180 $('#adjustedHordeParams').text('Context: --, Response: --');181 $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
181 }182 }
182}183}
183184
@@ -404,7 +405,7 @@ jQuery(function () {
404 if (horde_settings.models.length) {405 if (horde_settings.models.length) {
405 adjustHordeGenerationParams(max_context, amount_gen);406 adjustHordeGenerationParams(max_context, amount_gen);
406 } else {407 } else {
407 $('#adjustedHordeParams').text('Context: --, Response: --');408 $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
408 }409 }
409410
410 saveSettingsDebounced();411 saveSettingsDebounced();
public/scripts/instruct-mode.js+44 -37
@@ -320,59 +320,61 @@ export const force_output_sequence = {
320 * @param {string} name1 User name.320 * @param {string} name1 User name.
321 * @param {string} name2 Character name.321 * @param {string} name2 Character name.
322 * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).322 * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).
323 * @param {InstructSettings} customInstruct Custom instruct mode settings.
323 * @returns {string} Formatted instruct mode chat message.324 * @returns {string} Formatted instruct mode chat message.
324 */325 */
325export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) {326export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence, customInstruct = null) {
326 let includeNames = isNarrator ? false : power_user.instruct.names_behavior === names_behavior_types.ALWAYS;327 const instruct = structuredClone(customInstruct ?? power_user.instruct);
328 let includeNames = isNarrator ? false : instruct.names_behavior === names_behavior_types.ALWAYS;
327329
328 if (!isNarrator && power_user.instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {330 if (!isNarrator && instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {
329 includeNames = true;331 includeNames = true;
330 }332 }
331333
332 function getPrefix() {334 function getPrefix() {
333 if (isNarrator) {335 if (isNarrator) {
334 return power_user.instruct.system_same_as_user ? power_user.instruct.input_sequence : power_user.instruct.system_sequence;336 return instruct.system_same_as_user ? instruct.input_sequence : instruct.system_sequence;
335 }337 }
336338
337 if (isUser) {339 if (isUser) {
338 if (forceOutputSequence === force_output_sequence.FIRST) {340 if (forceOutputSequence === force_output_sequence.FIRST) {
339 return power_user.instruct.first_input_sequence || power_user.instruct.input_sequence;341 return instruct.first_input_sequence || instruct.input_sequence;
340 }342 }
341343
342 if (forceOutputSequence === force_output_sequence.LAST) {344 if (forceOutputSequence === force_output_sequence.LAST) {
343 return power_user.instruct.last_input_sequence || power_user.instruct.input_sequence;345 return instruct.last_input_sequence || instruct.input_sequence;
344 }346 }
345347
346 return power_user.instruct.input_sequence;348 return instruct.input_sequence;
347 }349 }
348350
349 if (forceOutputSequence === force_output_sequence.FIRST) {351 if (forceOutputSequence === force_output_sequence.FIRST) {
350 return power_user.instruct.first_output_sequence || power_user.instruct.output_sequence;352 return instruct.first_output_sequence || instruct.output_sequence;
351 }353 }
352354
353 if (forceOutputSequence === force_output_sequence.LAST) {355 if (forceOutputSequence === force_output_sequence.LAST) {
354 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;356 return instruct.last_output_sequence || instruct.output_sequence;
355 }357 }
356358
357 return power_user.instruct.output_sequence;359 return instruct.output_sequence;
358 }360 }
359361
360 function getSuffix() {362 function getSuffix() {
361 if (isNarrator) {363 if (isNarrator) {
362 return power_user.instruct.system_same_as_user ? power_user.instruct.input_suffix : power_user.instruct.system_suffix;364 return instruct.system_same_as_user ? instruct.input_suffix : instruct.system_suffix;
363 }365 }
364366
365 if (isUser) {367 if (isUser) {
366 return power_user.instruct.input_suffix;368 return instruct.input_suffix;
367 }369 }
368370
369 return power_user.instruct.output_suffix;371 return instruct.output_suffix;
370 }372 }
371373
372 let prefix = getPrefix() || '';374 let prefix = getPrefix() || '';
373 let suffix = getSuffix() || '';375 let suffix = getSuffix() || '';
374376
375 if (power_user.instruct.macro) {377 if (instruct.macro) {
376 prefix = substituteParams(prefix, name1, name2);378 prefix = substituteParams(prefix, name1, name2);
377 prefix = prefix.replace(/{{name}}/gi, name || 'System');379 prefix = prefix.replace(/{{name}}/gi, name || 'System');
378380
@@ -380,11 +382,11 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
380 suffix = suffix.replace(/{{name}}/gi, name || 'System');382 suffix = suffix.replace(/{{name}}/gi, name || 'System');
381 }383 }
382384
383 if (!suffix && power_user.instruct.wrap) {385 if (!suffix && instruct.wrap) {
384 suffix = '\n';386 suffix = '\n';
385 }387 }
386388
387 const separator = power_user.instruct.wrap ? '\n' : '';389 const separator = instruct.wrap ? '\n' : '';
388390
389 // Don't include the name if it's empty391 // Don't include the name if it's empty
390 const textArray = includeNames && name ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];392 const textArray = includeNames && name ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];
@@ -396,23 +398,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
396/**398/**
397 * Formats instruct mode system prompt.399 * Formats instruct mode system prompt.
398 * @param {string} systemPrompt System prompt string.400 * @param {string} systemPrompt System prompt string.
401 * @param {InstructSettings} customInstruct Custom instruct mode settings.
399 * @returns {string} Formatted instruct mode system prompt.402 * @returns {string} Formatted instruct mode system prompt.
400 */403 */
401export function formatInstructModeSystemPrompt(systemPrompt) {404export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
402 if (!systemPrompt) {405 if (!systemPrompt) {
403 return '';406 return '';
404 }407 }
405408
406 const separator = power_user.instruct.wrap ? '\n' : '';409 const instruct = structuredClone(customInstruct ?? power_user.instruct);
407410
408 if (power_user.instruct.system_sequence_prefix) {411 const separator = instruct.wrap ? '\n' : '';
412
413 if (instruct.system_sequence_prefix) {
409 // TODO: Replace with a proper 'System' prompt entity name input414 // TODO: Replace with a proper 'System' prompt entity name input
410 const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');415 const prefix = instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
411 systemPrompt = prefix + separator + systemPrompt;416 systemPrompt = prefix + separator + systemPrompt;
412 }417 }
413418
414 if (power_user.instruct.system_sequence_suffix) {419 if (instruct.system_sequence_suffix) {
415 systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;420 systemPrompt = systemPrompt + separator + instruct.system_sequence_suffix;
416 }421 }
417422
418 return systemPrompt;423 return systemPrompt;
@@ -504,30 +509,32 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
504 * @param {string} name2 Character name.509 * @param {string} name2 Character name.
505 * @param {boolean} isQuiet Is quiet mode generation.510 * @param {boolean} isQuiet Is quiet mode generation.
506 * @param {boolean} isQuietToLoud Is quiet to loud generation.511 * @param {boolean} isQuietToLoud Is quiet to loud generation.
512 * @param {InstructSettings} customInstruct Custom instruct settings.
507 * @returns {string} Formatted instruct mode last prompt line.513 * @returns {string} Formatted instruct mode last prompt line.
508 */514 */
509export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) {515export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud, customInstruct = null) {
510 const includeNames = name && (power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE)) && !(isQuiet && !isQuietToLoud);516 const instruct = structuredClone(customInstruct ?? power_user.instruct);
517 const includeNames = name && (instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && instruct.names_behavior === names_behavior_types.FORCE)) && !(isQuiet && !isQuietToLoud);
511518
512 function getSequence() {519 function getSequence() {
513 // User impersonation prompt520 // User impersonation prompt
514 if (isImpersonate) {521 if (isImpersonate) {
515 return power_user.instruct.input_sequence;522 return instruct.input_sequence;
516 }523 }
517524
518 // Neutral / system / quiet prompt525 // Neutral / system / quiet prompt
519 // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise526 // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise
520 if (isQuiet && !isQuietToLoud) {527 if (isQuiet && !isQuietToLoud) {
521 return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence;528 return instruct.last_system_sequence || instruct.output_sequence;
522 }529 }
523530
524 // Quiet in-character prompt531 // Quiet in-character prompt
525 if (isQuiet && isQuietToLoud) {532 if (isQuiet && isQuietToLoud) {
526 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;533 return instruct.last_output_sequence || instruct.output_sequence;
527 }534 }
528535
529 // Default AI response536 // Default AI response
530 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;537 return instruct.last_output_sequence || instruct.output_sequence;
531 }538 }
532539
533 let sequence = getSequence() || '';540 let sequence = getSequence() || '';
@@ -536,21 +543,21 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
536 // A hack for Mistral's formatting that has a normal output sequence ending with a space543 // A hack for Mistral's formatting that has a normal output sequence ending with a space
537 if (544 if (
538 includeNames &&545 includeNames &&
539 power_user.instruct.last_output_sequence &&546 instruct.last_output_sequence &&
540 power_user.instruct.output_sequence &&547 instruct.output_sequence &&
541 sequence === power_user.instruct.last_output_sequence &&548 sequence === instruct.last_output_sequence &&
542 /\s$/.test(power_user.instruct.output_sequence) &&549 /\s$/.test(instruct.output_sequence) &&
543 !/\s$/.test(power_user.instruct.last_output_sequence)550 !/\s$/.test(instruct.last_output_sequence)
544 ) {551 ) {
545 nameFiller = power_user.instruct.output_sequence.slice(-1);552 nameFiller = instruct.output_sequence.slice(-1);
546 }553 }
547554
548 if (power_user.instruct.macro) {555 if (instruct.macro) {
549 sequence = substituteParams(sequence, name1, name2);556 sequence = substituteParams(sequence, name1, name2);
550 sequence = sequence.replace(/{{name}}/gi, name || 'System');557 sequence = sequence.replace(/{{name}}/gi, name || 'System');
551 }558 }
552559
553 const separator = power_user.instruct.wrap ? '\n' : '';560 const separator = instruct.wrap ? '\n' : '';
554 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);561 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);
555562
556 // Quiet prompt already has a newline at the end563 // Quiet prompt already has a newline at the end
@@ -562,7 +569,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
562 text += (includeNames ? promptBias : (separator + promptBias.trimStart()));569 text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
563 }570 }
564571
565 return (power_user.instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);572 return (instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);
566}573}
567574
568/**575/**
public/scripts/nai-settings.js+1 -0
@@ -795,6 +795,7 @@ export function parseNovelAILogprobs(data) {
795795
796 // Add the chosen token to `merged` if it's not already there. This can796 // Add the chosen token to `merged` if it's not already there. This can
797 // happen if the chosen token was not among the top 10 most likely ones.797 // happen if the chosen token was not among the top 10 most likely ones.
798 // eslint-disable-next-line no-unused-vars
798 const [[chosenId], [_, chosenAfter]] = data.chosen[0];799 const [[chosenId], [_, chosenAfter]] = data.chosen[0];
799 if (!merged.some(([id]) => id === chosenId)) {800 if (!merged.some(([id]) => id === chosenId)) {
800 merged.push([chosenId, chosenAfter]);801 merged.push([chosenId, chosenAfter]);
public/scripts/openai.js+14 -12
@@ -705,16 +705,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
705 return result;705 return result;
706}706}
707707
708function formatWorldInfo(value) {708export function formatWorldInfo(value, { wiFormat = null } = {}) {
709 if (!value) {709 if (!value) {
710 return '';710 return '';
711 }711 }
712712
713 if (!oai_settings.wi_format.trim()) {713 const format = wiFormat ?? oai_settings.wi_format;
714
715 if (!format.trim()) {
714 return value;716 return value;
715 }717 }
716718
717 return stringFormat(oai_settings.wi_format, value);719 return stringFormat(format, value);
718}720}
719721
720/**722/**
@@ -952,7 +954,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
952 * @param {number} position - Prompt position in the extensions object.954 * @param {number} position - Prompt position in the extensions object.
953 * @returns {string|false} - The prompt position for prompt collection.955 * @returns {string|false} - The prompt position for prompt collection.
954 */956 */
955function getPromptPosition(position) {957export function getPromptPosition(position) {
956 if (position == extension_prompt_types.BEFORE_PROMPT) {958 if (position == extension_prompt_types.BEFORE_PROMPT) {
957 return 'start';959 return 'start';
958 }960 }
@@ -969,7 +971,7 @@ function getPromptPosition(position) {
969 * @param {number} role Role of the prompt.971 * @param {number} role Role of the prompt.
970 * @returns {string} Mapped role.972 * @returns {string} Mapped role.
971 */973 */
972function getPromptRole(role) {974export function getPromptRole(role) {
973 switch (role) {975 switch (role) {
974 case extension_prompt_roles.SYSTEM:976 case extension_prompt_roles.SYSTEM:
975 return 'system';977 return 'system';
@@ -2018,6 +2020,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2018 'reasoning_effort': String(oai_settings.reasoning_effort),2020 'reasoning_effort': String(oai_settings.reasoning_effort),
2019 'enable_web_search': Boolean(oai_settings.enable_web_search),2021 'enable_web_search': Boolean(oai_settings.enable_web_search),
2020 'request_images': Boolean(oai_settings.request_images),2022 'request_images': Boolean(oai_settings.request_images),
2023 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,
2021 };2024 };
20222025
2023 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {2026 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
@@ -2048,7 +2051,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2048 delete generate_data.stop;2051 delete generate_data.stop;
2049 delete generate_data.logprobs;2052 delete generate_data.logprobs;
2050 }2053 }
2051 if (isOAI && oai_settings.openai_model.includes('gpt-4.5-preview') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5-preview')) {2054 if (isOAI && oai_settings.openai_model.includes('gpt-4.5') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5')) {
2052 delete generate_data.logprobs;2055 delete generate_data.logprobs;
2053 }2056 }
20542057
@@ -2098,7 +2101,6 @@ async function sendOpenAIRequest(type, messages, signal) {
2098 generate_data['custom_include_body'] = oai_settings.custom_include_body;2101 generate_data['custom_include_body'] = oai_settings.custom_include_body;
2099 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;2102 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;
2100 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;2103 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;
2101 generate_data['custom_prompt_post_processing'] = oai_settings.custom_prompt_post_processing;
2102 }2104 }
21032105
2104 if (isCohere) {2106 if (isCohere) {
@@ -3476,7 +3478,7 @@ async function getStatusOpen() {
3476 let status;3478 let status;
34773479
3478 if ('ai' in window) {3480 if ('ai' in window) {
3479 status = 'Valid';3481 status = t`Valid`;
3480 }3482 }
3481 else {3483 else {
3482 showWindowExtensionError();3484 showWindowExtensionError();
@@ -3525,7 +3527,7 @@ async function getStatusOpen() {
35253527
3526 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;3528 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
3527 if (canBypass) {3529 if (canBypass) {
3528 setOnlineStatus('Status check bypassed');3530 setOnlineStatus(t`Status check bypassed`);
3529 }3531 }
35303532
3531 try {3533 try {
@@ -3547,7 +3549,7 @@ async function getStatusOpen() {
3547 saveModelList(responseData.data);3549 saveModelList(responseData.data);
3548 }3550 }
3549 if (!('error' in responseData)) {3551 if (!('error' in responseData)) {
3550 setOnlineStatus('Valid');3552 setOnlineStatus(t`Valid`);
3551 }3553 }
3552 } catch (error) {3554 } catch (error) {
3553 console.error(error);3555 console.error(error);
@@ -4435,9 +4437,9 @@ async function onModelChange() {
4435 if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {4437 if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
4436 if (oai_settings.max_context_unlocked) {4438 if (oai_settings.max_context_unlocked) {
4437 $('#openai_max_context').attr('max', unlocked_max);4439 $('#openai_max_context').attr('max', unlocked_max);
4438 } else if (oai_settings.mistralai_model.includes('codestral-mamba')) {4440 } else if (['codestral-latest', 'codestral-mamba-2407', 'codestral-2411-rc5', 'codestral-2412', 'codestral-2501'].includes(oai_settings.mistralai_model)) {
4439 $('#openai_max_context').attr('max', max_256k);4441 $('#openai_max_context').attr('max', max_256k);
4440 } else if (['mistral-large-2407', 'mistral-large-2411', 'mistral-large-latest'].includes(oai_settings.mistralai_model)) {4442 } else if (['mistral-large-2407', 'mistral-large-2411', 'mistral-large-pixtral-2411', 'mistral-large-latest'].includes(oai_settings.mistralai_model)) {
4441 $('#openai_max_context').attr('max', max_128k);4443 $('#openai_max_context').attr('max', max_128k);
4442 } else if (oai_settings.mistralai_model.includes('mistral-nemo')) {4444 } else if (oai_settings.mistralai_model.includes('mistral-nemo')) {
4443 $('#openai_max_context').attr('max', max_128k);4445 $('#openai_max_context').attr('max', max_128k);
public/scripts/personas.js+1 -1
@@ -800,7 +800,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
800 chat_metadata['persona'] = user_avatar;800 chat_metadata['persona'] = user_avatar;
801 console.log(`Auto locked persona to ${user_avatar}`);801 console.log(`Auto locked persona to ${user_avatar}`);
802 if (toastPersonaNameChange && power_user.persona_show_notifications) {802 if (toastPersonaNameChange && power_user.persona_show_notifications) {
803 toastr.success(`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);803 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
804 }804 }
805 saveMetadataDebounced();805 saveMetadataDebounced();
806 updatePersonaUIStates();806 updatePersonaUIStates();
public/scripts/power-user.js+16 -6
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
55import { loadSystemPrompts } from './sysprompt.js';55import { loadSystemPrompts } from './sysprompt.js';
56import { fuzzySearchCategories } from './filters.js';56import { fuzzySearchCategories } from './filters.js';
57import { accountStorage } from './util/AccountStorage.js';57import { accountStorage } from './util/AccountStorage.js';
58import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
5859
59export {60export {
60 loadPowerUserSettings,61 loadPowerUserSettings,
@@ -218,7 +219,9 @@ let power_user = {
218 system_sequence: '',219 system_sequence: '',
219 system_suffix: '',220 system_suffix: '',
220 last_system_sequence: '',221 last_system_sequence: '',
222 first_input_sequence: '',
221 first_output_sequence: '',223 first_output_sequence: '',
224 last_input_sequence: '',
222 last_output_sequence: '',225 last_output_sequence: '',
223 system_sequence_prefix: '',226 system_sequence_prefix: '',
224 system_sequence_suffix: '',227 system_sequence_suffix: '',
@@ -255,6 +258,7 @@ let power_user = {
255 },258 },
256259
257 reasoning: {260 reasoning: {
261 name: DEFAULT_REASONING_TEMPLATE,
258 auto_parse: false,262 auto_parse: false,
259 add_to_prompts: false,263 add_to_prompts: false,
260 auto_expand: false,264 auto_expand: false,
@@ -1622,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
1622 await loadInstructMode(data);1626 await loadInstructMode(data);
1623 await loadContextSettings();1627 await loadContextSettings();
1624 await loadSystemPrompts(data);1628 await loadSystemPrompts(data);
1629 await loadReasoningTemplates(data);
1625 loadMaxContextUnlocked();1630 loadMaxContextUnlocked();
1626 switchWaifuMode();1631 switchWaifuMode();
1627 switchSpoilerMode();1632 switchSpoilerMode();
@@ -1983,15 +1988,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
1983/**1988/**
1984 * Renders a story string template with the given parameters.1989 * Renders a story string template with the given parameters.
1985 * @param {object} params Template parameters.1990 * @param {object} params Template parameters.
1991 * @param {object} [options] Additional options.
1992 * @param {string} [options.customStoryString] Custom story string template.
1993 * @param {InstructSettings} [options.customInstructSettings] Custom instruct settings.
1986 * @returns {string} The rendered story string.1994 * @returns {string} The rendered story string.
1987 */1995 */
1988export function renderStoryString(params) {1996export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
1989 try {1997 try {
1998 const storyString = customStoryString ?? power_user.context.story_string;
1999 const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
2000
1990 // Validate and log possible warnings/errors2001 // Validate and log possible warnings/errors
1991 validateStoryString(power_user.context.story_string, params);2002 validateStoryString(storyString, params);
19922003
1993 // compile the story string template into a function, with no HTML escaping2004 // compile the story string template into a function, with no HTML escaping
1994 const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });2005 const compiledTemplate = Handlebars.compile(storyString, { noEscape: true });
19952006
1996 // render the story string template with the given params2007 // render the story string template with the given params
1997 let output = compiledTemplate(params);2008 let output = compiledTemplate(params);
@@ -2004,7 +2015,7 @@ export function renderStoryString(params) {
20042015
2005 // add a newline to the end of the story string if it doesn't have one2016 // add a newline to the end of the story string if it doesn't have one
2006 if (output.length > 0 && !output.endsWith('\n')) {2017 if (output.length > 0 && !output.endsWith('\n')) {
2007 if (!power_user.instruct.enabled || power_user.instruct.wrap) {2018 if (!instructSettings.enabled || instructSettings.wrap) {
2008 output += '\n';2019 output += '\n';
2009 }2020 }
2010 }2021 }
@@ -4227,14 +4238,13 @@ $(document).ready(() => {
4227 ],4238 ],
4228 callback: (args, value) => {4239 callback: (args, value) => {
4229 const force = isTrueBoolean(String(args?.force ?? false));4240 const force = isTrueBoolean(String(args?.force ?? false));
4230 value = String(value ?? '').trim();
42314241
4232 // Skip processing if no value and not forced4242 // Skip processing if no value and not forced
4233 if (!force && !value) {4243 if (!force && !value) {
4234 return power_user.user_prompt_bias;4244 return power_user.user_prompt_bias;
4235 }4245 }
42364246
4237 power_user.user_prompt_bias = value;4247 power_user.user_prompt_bias = String(value ?? '');
4238 $('#start_reply_with').val(power_user.user_prompt_bias);4248 $('#start_reply_with').val(power_user.user_prompt_bias);
4239 saveSettingsDebounced();4249 saveSettingsDebounced();
42404250
public/scripts/preset-manager.js+44 -2
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
21import { instruct_presets } from './instruct-mode.js';21import { instruct_presets } from './instruct-mode.js';
22import { kai_settings } from './kai-settings.js';22import { kai_settings } from './kai-settings.js';
23import { convertNovelPreset } from './nai-settings.js';23import { convertNovelPreset } from './nai-settings.js';
24import { openai_settings, openai_setting_names, oai_settings } from './openai.js';24import { openai_settings, openai_setting_names } from './openai.js';
25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
26import { context_presets, getContextSettings, power_user } from './power-user.js';26import { context_presets, getContextSettings, power_user } from './power-user.js';
27import { SlashCommand } from './slash-commands/SlashCommand.js';27import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
38} from './textgen-settings.js';38} from './textgen-settings.js';
39import { download, parseJsonFile, waitUntilCondition } from './utils.js';39import { download, parseJsonFile, waitUntilCondition } from './utils.js';
40import { t } from './i18n.js';40import { t } from './i18n.js';
41import { reasoning_templates } from './reasoning.js';
4142
42const presetManagers = {};43const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168 },169 },
169 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170 },171 },
172 'reasoning': {
173 name: 'Reasoning Formatting',
174 getData: () => {
175 const manager = getPresetManager('reasoning');
176 const name = manager.getSelectedPresetName();
177 return manager.getPresetSettings(name);
178 },
179 setData: (data) => {
180 const manager = getPresetManager('reasoning');
181 const name = data.name;
182 return manager.savePreset(name, data);
183 },
184 isValid: (data) => PresetManager.isPossiblyReasoningData(data),
185 },
171 };186 };
172187
173 static isPossiblyInstructData(data) {188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191 }206 }
192207
208 static isPossiblyReasoningData(data) {
209 const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
210 return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
211 }
212
193 /**213 /**
194 * Imports master settings from JSON data.214 * Imports master settings from JSON data.
195 * @param {object} data Data to import215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228 }248 }
229249
250 // 5. Reasoning Template
251 if (this.isPossiblyReasoningData(data)) {
252 toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
253 return await getPresetManager('reasoning').savePreset(data.name, data);
254 }
255
230 const validSections = [];256 const validSections = [];
231 for (const [key, section] of Object.entries(this.masterSections)) {257 for (const [key, section] of Object.entries(this.masterSections)) {
232 if (key in data && section.isValid(data[key])) {258 if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
478 presets = system_prompts;504 presets = system_prompts;
479 preset_names = system_prompts.map(x => x.name);505 preset_names = system_prompts.map(x => x.name);
480 break;506 break;
507 case 'reasoning':
508 presets = reasoning_templates;
509 preset_names = reasoning_templates.map(x => x.name);
510 break;
481 default:511 default:
482 console.warn(`Unknown API ID ${api}`);512 console.warn(`Unknown API ID ${api}`);
483 }513 }
@@ -490,7 +520,7 @@ class PresetManager {
490 }520 }
491521
492 isAdvancedFormatting() {522 isAdvancedFormatting() {
493 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';523 return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494 }524 }
495525
496 updateList(name, preset) {526 updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
553 sysprompt_preset['name'] = name || power_user.sysprompt.preset;583 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554 return sysprompt_preset;584 return sysprompt_preset;
555 }585 }
586 case 'reasoning': {
587 const reasoning_preset = structuredClone(power_user.reasoning);
588 reasoning_preset['name'] = name || power_user.reasoning.preset;
589 return reasoning_preset;
590 }
556 default:591 default:
557 console.warn(`Unknown API ID ${apiId}`);592 console.warn(`Unknown API ID ${apiId}`);
558 return {};593 return {};
@@ -599,6 +634,13 @@ class PresetManager {
599 'include_reasoning',634 'include_reasoning',
600 'global_banned_tokens',635 'global_banned_tokens',
601 'send_banned_tokens',636 'send_banned_tokens',
637
638 // Reasoning exclusions
639 'auto_parse',
640 'add_to_prompts',
641 'auto_expand',
642 'show_hidden',
643 'max_additions',
602 ];644 ];
603 const settings = Object.assign({}, getSettingsByApiId(this.apiId));645 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
604646
public/scripts/reasoning.js+188 -23
@@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
7import { MacrosParser } from './macros.js';7import { MacrosParser } from './macros.js';
8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
9import { Popup } from './popup.js';9import { Popup } from './popup.js';
10import { power_user } from './power-user.js';10import { performFuzzySearch, power_user } from './power-user.js';
11import { getPresetManager } from './preset-manager.js';
11import { SlashCommand } from './slash-commands/SlashCommand.js';12import { SlashCommand } from './slash-commands/SlashCommand.js';
12import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';13import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
13import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';14import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
14import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';15import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
15import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';16import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
16import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';17import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
17import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';18import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
19
20/**
21 * @typedef {object} ReasoningTemplate
22 * @property {string} name - The name of the template
23 * @property {string} prefix - Reasoning prefix
24 * @property {string} suffix - Reasoning suffix
25 * @property {string} separator - Reasoning separator
26 */
27
28/**
29 * @type {ReasoningTemplate[]} List of reasoning templates
30 */
31export const reasoning_templates = [];
32
33export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
34
35/**
36 * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
37 * @readonly
38 */
39const UI = {
40 $select: $('#reasoning_select'),
41 $suffix: $('#reasoning_suffix'),
42 $prefix: $('#reasoning_prefix'),
43 $separator: $('#reasoning_separator'),
44 $autoParse: $('#reasoning_auto_parse'),
45 $autoExpand: $('#reasoning_auto_expand'),
46 $showHidden: $('#reasoning_show_hidden'),
47 $addToPrompts: $('#reasoning_add_to_prompts'),
48 $maxAdditions: $('#reasoning_max_additions'),
49};
1850
19/**51/**
20 * Enum representing the type of the reasoning for a message (where it came from)52 * Enum representing the type of the reasoning for a message (where it came from)
@@ -57,19 +89,24 @@ function toggleReasoningAutoExpand() {
57 * @param {object} data Response data89 * @param {object} data Response data
58 * @returns {string} Extracted reasoning90 * @returns {string} Extracted reasoning
59 */91 */
60export function extractReasoningFromData(data) {92export function extractReasoningFromData(data, {
61 switch (main_api) {93 mainApi = null,
94 ignoreShowThoughts = false,
95 textGenType = null,
96 chatCompletionSource = null,
97} = {}) {
98 switch (mainApi ?? main_api) {
62 case 'textgenerationwebui':99 case 'textgenerationwebui':
63 switch (textgenerationwebui_settings.type) {100 switch (textGenType ?? textgenerationwebui_settings.type) {
64 case textgen_types.OPENROUTER:101 case textgen_types.OPENROUTER:
65 return data?.choices?.[0]?.reasoning ?? '';102 return data?.choices?.[0]?.reasoning ?? '';
66 }103 }
67 break;104 break;
68105
69 case 'openai':106 case 'openai':
70 if (!oai_settings.show_thoughts) break;107 if (!ignoreShowThoughts && !oai_settings.show_thoughts) break;
71108
72 switch (oai_settings.chat_completion_source) {109 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
73 case chat_completion_sources.DEEPSEEK:110 case chat_completion_sources.DEEPSEEK:
74 return data?.choices?.[0]?.message?.reasoning_content ?? '';111 return data?.choices?.[0]?.message?.reasoning_content ?? '';
75 case chat_completion_sources.OPENROUTER:112 case chat_completion_sources.OPENROUTER:
@@ -664,57 +701,102 @@ export class PromptReasoning {
664}701}
665702
666function loadReasoningSettings() {703function loadReasoningSettings() {
667 $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);704 UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
668 $('#reasoning_add_to_prompts').on('change', function () {705 UI.$addToPrompts.on('change', function () {
669 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');706 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
670 saveSettingsDebounced();707 saveSettingsDebounced();
671 });708 });
672709
673 $('#reasoning_prefix').val(power_user.reasoning.prefix);710 UI.$prefix.val(power_user.reasoning.prefix);
674 $('#reasoning_prefix').on('input', function () {711 UI.$prefix.on('input', function () {
675 power_user.reasoning.prefix = String($(this).val());712 power_user.reasoning.prefix = String($(this).val());
676 saveSettingsDebounced();713 saveSettingsDebounced();
677 });714 });
678715
679 $('#reasoning_suffix').val(power_user.reasoning.suffix);716 UI.$suffix.val(power_user.reasoning.suffix);
680 $('#reasoning_suffix').on('input', function () {717 UI.$suffix.on('input', function () {
681 power_user.reasoning.suffix = String($(this).val());718 power_user.reasoning.suffix = String($(this).val());
682 saveSettingsDebounced();719 saveSettingsDebounced();
683 });720 });
684721
685 $('#reasoning_separator').val(power_user.reasoning.separator);722 UI.$separator.val(power_user.reasoning.separator);
686 $('#reasoning_separator').on('input', function () {723 UI.$separator.on('input', function () {
687 power_user.reasoning.separator = String($(this).val());724 power_user.reasoning.separator = String($(this).val());
688 saveSettingsDebounced();725 saveSettingsDebounced();
689 });726 });
690727
691 $('#reasoning_max_additions').val(power_user.reasoning.max_additions);728 UI.$maxAdditions.val(power_user.reasoning.max_additions);
692 $('#reasoning_max_additions').on('input', function () {729 UI.$maxAdditions.on('input', function () {
693 power_user.reasoning.max_additions = Number($(this).val());730 power_user.reasoning.max_additions = Number($(this).val());
694 saveSettingsDebounced();731 saveSettingsDebounced();
695 });732 });
696733
697 $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);734 UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
698 $('#reasoning_auto_parse').on('change', function () {735 UI.$autoParse.on('change', function () {
699 power_user.reasoning.auto_parse = !!$(this).prop('checked');736 power_user.reasoning.auto_parse = !!$(this).prop('checked');
700 saveSettingsDebounced();737 saveSettingsDebounced();
701 });738 });
702739
703 $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);740 UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
704 $('#reasoning_auto_expand').on('change', function () {741 UI.$autoExpand.on('change', function () {
705 power_user.reasoning.auto_expand = !!$(this).prop('checked');742 power_user.reasoning.auto_expand = !!$(this).prop('checked');
706 toggleReasoningAutoExpand();743 toggleReasoningAutoExpand();
707 saveSettingsDebounced();744 saveSettingsDebounced();
708 });745 });
709 toggleReasoningAutoExpand();746 toggleReasoningAutoExpand();
710747
711 $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);748 UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
712 $('#reasoning_show_hidden').on('change', function () {749 UI.$showHidden.on('change', function () {
713 power_user.reasoning.show_hidden = !!$(this).prop('checked');750 power_user.reasoning.show_hidden = !!$(this).prop('checked');
714 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);751 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
715 saveSettingsDebounced();752 saveSettingsDebounced();
716 });753 });
717 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);754 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
755
756 UI.$select.on('change', async function () {
757 const name = String($(this).val());
758 const template = reasoning_templates.find(p => p.name === name);
759 if (!template) {
760 return;
761 }
762
763 UI.$prefix.val(template.prefix);
764 UI.$suffix.val(template.suffix);
765 UI.$separator.val(template.separator);
766
767 power_user.reasoning.name = name;
768 power_user.reasoning.prefix = template.prefix;
769 power_user.reasoning.suffix = template.suffix;
770 power_user.reasoning.separator = template.separator;
771
772 saveSettingsDebounced();
773 });
774}
775
776function selectReasoningTemplateCallback(args, name) {
777 if (!name) {
778 return power_user.reasoning.name ?? '';
779 }
780
781 const quiet = isTrueBoolean(args?.quiet);
782 const templateNames = reasoning_templates.map(preset => preset.name);
783 let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
784
785 if (!foundName) {
786 const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
787
788 if (result.length === 0) {
789 !quiet && toastr.warning(`Reasoning template "${name}" not found`);
790 return '';
791 }
792
793 foundName = result[0].item;
794 }
795
796 UI.$select.val(foundName).trigger('change');
797 !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
798 return foundName;
799
718}800}
719801
720function registerReasoningSlashCommands() {802function registerReasoningSlashCommands() {
@@ -848,6 +930,42 @@ function registerReasoningSlashCommands() {
848 : parsedReasoning.reasoning;930 : parsedReasoning.reasoning;
849 },931 },
850 }));932 }));
933 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
934 name: 'reasoning-template',
935 aliases: ['reasoning-formatting', 'reasoning-preset'],
936 callback: selectReasoningTemplateCallback,
937 returns: 'template name',
938 namedArgumentList: [
939 SlashCommandNamedArgument.fromProps({
940 name: 'quiet',
941 description: 'Suppress the toast message on template change',
942 typeList: [ARGUMENT_TYPE.BOOLEAN],
943 defaultValue: 'false',
944 enumList: commonEnumProviders.boolean('trueFalse')(),
945 }),
946 ],
947 unnamedArgumentList: [
948 SlashCommandArgument.fromProps({
949 description: 'reasoning template name',
950 typeList: [ARGUMENT_TYPE.STRING],
951 enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
952 }),
953 ],
954 helpString: `
955 <div>
956 Selects a reasoning template by name, using fuzzy search to find the closest match.
957 Gets the current template if no name is provided.
958 </div>
959 <div>
960 <strong>Example:</strong>
961 <ul>
962 <li>
963 <pre><code class="language-stscript">/reasoning-template DeepSeek</code></pre>
964 </li>
965 </ul>
966 </div>
967 `,
968 }));
851}969}
852970
853function registerReasoningMacros() {971function registerReasoningMacros() {
@@ -1207,6 +1325,53 @@ function registerReasoningAppEvents() {
1207 }1325 }
1208}1326}
12091327
1328/**
1329 * Loads reasoning templates from the settings data.
1330 * @param {object} data Settings data
1331 * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
1332 * @returns {Promise<void>}
1333 */
1334export async function loadReasoningTemplates(data) {
1335 if (data.reasoning !== undefined) {
1336 reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
1337 }
1338
1339 for (const template of reasoning_templates) {
1340 $('<option>').val(template.name).text(template.name).appendTo(UI.$select);
1341 }
1342
1343 // No template name, need to migrate
1344 if (power_user.reasoning.name === undefined) {
1345 const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
1346 if (defaultTemplate) {
1347 // If the reasoning settings were modified - migrate them to a custom template
1348 if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
1349 /** @type {ReasoningTemplate} */
1350 const data = {
1351 name: '[Migrated] Custom',
1352 prefix: power_user.reasoning.prefix,
1353 suffix: power_user.reasoning.suffix,
1354 separator: power_user.reasoning.separator,
1355 };
1356 await getPresetManager('reasoning')?.savePreset(data.name, data);
1357 power_user.reasoning.name = data.name;
1358 } else {
1359 power_user.reasoning.name = defaultTemplate.name;
1360 }
1361 } else {
1362 // Template not found (deleted or content check skipped - leave blank)
1363 power_user.reasoning.name = '';
1364 }
1365
1366 saveSettingsDebounced();
1367 }
1368
1369 UI.$select.val(power_user.reasoning.name);
1370}
1371
1372/**
1373 * Initializes reasoning settings and event handlers.
1374 */
1210export function initReasoning() {1375export function initReasoning() {
1211 loadReasoningSettings();1376 loadReasoningSettings();
1212 setReasoningEventHandlers();1377 setReasoningEventHandlers();
public/scripts/samplerSelect.js+2 -19
@@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
9//import { getSortableDelay, onlyUnique } from './utils.js';9//import { getSortableDelay, onlyUnique } from './utils.js';
10//import { getCfgPrompt } from './cfg-scale.js';10//import { getCfgPrompt } from './cfg-scale.js';
11import { setting_names } from './textgen-settings.js';11import { setting_names } from './textgen-settings.js';
12import { renderTemplateAsync } from './templates.js';
1213
1314
14const TGsamplerNames = setting_names;15const TGsamplerNames = setting_names;
@@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
25 const html = $(document.createElement('div'));26 const html = $(document.createElement('div'));
26 html.attr('id', 'sampler_view_list')27 html.attr('id', 'sampler_view_list')
27 .addClass('flex-container flexFlowColumn');28 .addClass('flex-container flexFlowColumn');
28 html.append(`29 html.append(await renderTemplateAsync('samplerSelector'));
29 <div class="title_restorable flexFlowColumn alignItemsBaseline">
30 <div class="flex-container justifyCenter">
31 <h3>Sampler Select</h3>
32 <div class="flex-container alignItemsBaseline">
33 <div id="resetSelectedSamplers" class="menu_button menu_button_icon" title="Reset custom sampler selection">
34 <i class="fa-solid fa-recycle"></i>
35 </div>
36 </div>
37 <!--<div class="flex-container alignItemsBaseline">
38 <div class="menu_button menu_button_icon" title="Create a new sampler">
39 <i class="fa-solid fa-plus"></i>
40 <span data-i18n="Create">Create</span>
41 </div>
42 </div>-->
43 </div>
44 <small>Here you can toggle the display of individual samplers. (WIP)</small>
45 </div>
46 <hr>`);
4730
48 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');31 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
49 const APISamplers = await listSamplers(main_api);32 const APISamplers = await listSamplers(main_api);
public/scripts/secrets.js+2 -1
@@ -1,5 +1,6 @@
1import { DOMPurify } from '../lib.js';1import { DOMPurify } from '../lib.js';
2import { callPopup, getRequestHeaders } from '../script.js';2import { callPopup, getRequestHeaders } from '../script.js';
3import { t } from './i18n.js';
34
4export const SECRET_KEYS = {5export const SECRET_KEYS = {
5 HORDE: 'api_key_horde',6 HORDE: 'api_key_horde',
@@ -104,7 +105,7 @@ async function viewSecrets() {
104 });105 });
105106
106 if (response.status == 403) {107 if (response.status == 403) {
107 callPopup('<h3>Forbidden</h3><p>To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.</p>', 'text');108 callPopup('<h3>' + t`Forbidden` + '</h3><p>' + t`To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.` + '</p>', 'text');
108 return;109 return;
109 }110 }
110111
public/scripts/slash-commands.js+5 -3
@@ -69,12 +69,14 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
69import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';69import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
70import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js';70import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js';
71import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';71import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
72import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';72import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
73import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';73import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
74import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';74import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
75import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';75import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
76import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';76import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
77import { accountStorage } from './util/AccountStorage.js';77import { accountStorage } from './util/AccountStorage.js';
78import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
79import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
78export {80export {
79 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,81 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
80};82};
@@ -4345,7 +4347,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
4345 * @prop {boolean} [handleParserErrors] (true) Whether to handle parser errors (show toast on error) or throw.4347 * @prop {boolean} [handleParserErrors] (true) Whether to handle parser errors (show toast on error) or throw.
4346 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.4348 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
4347 * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw4349 * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
4348 * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply4350 * @prop {import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
4349 * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution4351 * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
4350 * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution4352 * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
4351 * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events4353 * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
@@ -4355,7 +4357,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
4355/**4357/**
4356 * @typedef ExecuteSlashCommandsOnChatInputOptions4358 * @typedef ExecuteSlashCommandsOnChatInputOptions
4357 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.4359 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
4358 * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply4360 * @prop {import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
4359 * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea4361 * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
4360 * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)4362 * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
4361 */4363 */
public/scripts/slash-commands/SlashCommand.js+1 -5
@@ -3,16 +3,12 @@ import { SlashCommandAbortController } from './SlashCommandAbortController.js';
3import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';3import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
4import { SlashCommandClosure } from './SlashCommandClosure.js';4import { SlashCommandClosure } from './SlashCommandClosure.js';
5import { SlashCommandDebugController } from './SlashCommandDebugController.js';5import { SlashCommandDebugController } from './SlashCommandDebugController.js';
6import { PARSER_FLAG } from './SlashCommandParser.js';
7import { SlashCommandScope } from './SlashCommandScope.js';6import { SlashCommandScope } from './SlashCommandScope.js';
87
9
10
11
12/**8/**
13 * @typedef {{9 * @typedef {{
14 * _scope:SlashCommandScope,10 * _scope:SlashCommandScope,
15 * _parserFlags:{[id:PARSER_FLAG]:boolean},11 * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
16 * _abortController:SlashCommandAbortController,12 * _abortController:SlashCommandAbortController,
17 * _debugController:SlashCommandDebugController,13 * _debugController:SlashCommandDebugController,
18 * _hasUnnamedArgument:boolean,14 * _hasUnnamedArgument:boolean,
public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js+0 -3
@@ -1,9 +1,6 @@
1import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';1import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
2import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';
3import { AutoCompleteSecondaryNameResult } from '../autocomplete/AutoCompleteSecondaryNameResult.js';2import { AutoCompleteSecondaryNameResult } from '../autocomplete/AutoCompleteSecondaryNameResult.js';
4import { SlashCommand } from './SlashCommand.js';3import { SlashCommand } from './SlashCommand.js';
5import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
6import { SlashCommandClosure } from './SlashCommandClosure.js';
7import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAutoCompleteOption.js';4import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAutoCompleteOption.js';
8import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';5import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
9import { SlashCommandExecutor } from './SlashCommandExecutor.js';6import { SlashCommandExecutor } from './SlashCommandExecutor.js';
public/scripts/slash-commands/SlashCommandClosure.js+0 -3
@@ -2,7 +2,6 @@ import { substituteParams } from '../../script.js';
2import { delay, escapeRegex, uuidv4 } from '../utils.js';2import { delay, escapeRegex, uuidv4 } from '../utils.js';
3import { SlashCommand } from './SlashCommand.js';3import { SlashCommand } from './SlashCommand.js';
4import { SlashCommandAbortController } from './SlashCommandAbortController.js';4import { SlashCommandAbortController } from './SlashCommandAbortController.js';
5import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
6import { SlashCommandBreak } from './SlashCommandBreak.js';5import { SlashCommandBreak } from './SlashCommandBreak.js';
7import { SlashCommandBreakController } from './SlashCommandBreakController.js';6import { SlashCommandBreakController } from './SlashCommandBreakController.js';
8import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';7import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
@@ -16,9 +15,7 @@ import { SlashCommandScope } from './SlashCommandScope.js';
16export class SlashCommandClosure {15export class SlashCommandClosure {
17 /** @type {SlashCommandScope} */ scope;16 /** @type {SlashCommandScope} */ scope;
18 /** @type {boolean} */ executeNow = false;17 /** @type {boolean} */ executeNow = false;
19 // @ts-ignore
20 /** @type {SlashCommandNamedArgumentAssignment[]} */ argumentList = [];18 /** @type {SlashCommandNamedArgumentAssignment[]} */ argumentList = [];
21 // @ts-ignore
22 /** @type {SlashCommandNamedArgumentAssignment[]} */ providedArgumentList = [];19 /** @type {SlashCommandNamedArgumentAssignment[]} */ providedArgumentList = [];
23 /** @type {SlashCommandExecutor[]} */ executorList = [];20 /** @type {SlashCommandExecutor[]} */ executorList = [];
24 /** @type {SlashCommandAbortController} */ abortController;21 /** @type {SlashCommandAbortController} */ abortController;
public/scripts/slash-commands/SlashCommandDebugController.js+0 -0
public/scripts/slash-commands/SlashCommandEnumValue.js+0 -4
@@ -1,7 +1,3 @@
1import { SlashCommandExecutor } from './SlashCommandExecutor.js';
2import { SlashCommandScope } from './SlashCommandScope.js';
3
4
5/**1/**
6 * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType2 * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
7 */3 */
public/scripts/slash-commands/SlashCommandExecutor.js+1 -6
@@ -1,11 +1,7 @@
1// eslint-disable-next-line no-unused-vars
2import { uuidv4 } from '../utils.js';1import { uuidv4 } from '../utils.js';
3import { SlashCommand } from './SlashCommand.js';2import { SlashCommand } from './SlashCommand.js';
4// eslint-disable-next-line no-unused-vars
5import { SlashCommandClosure } from './SlashCommandClosure.js';3import { SlashCommandClosure } from './SlashCommandClosure.js';
6import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';4import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
7// eslint-disable-next-line no-unused-vars
8import { PARSER_FLAG } from './SlashCommandParser.js';
9import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';5import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
106
11export class SlashCommandExecutor {7export class SlashCommandExecutor {
@@ -29,10 +25,9 @@ export class SlashCommandExecutor {
29 }25 }
30 }26 }
31 /** @type {SlashCommand} */ command;27 /** @type {SlashCommand} */ command;
32 // @ts-ignore
33 /** @type {SlashCommandNamedArgumentAssignment[]} */ namedArgumentList = [];28 /** @type {SlashCommandNamedArgumentAssignment[]} */ namedArgumentList = [];
34 /** @type {SlashCommandUnnamedArgumentAssignment[]} */ unnamedArgumentList = [];29 /** @type {SlashCommandUnnamedArgumentAssignment[]} */ unnamedArgumentList = [];
35 /**@type {{[id:PARSER_FLAG]:boolean}} */ parserFlags;30 /** @type {import('./SlashCommandParser.js').ParserFlags} */ parserFlags;
3631
37 get commandCount() {32 get commandCount() {
38 return 133 return 1
public/scripts/slash-commands/SlashCommandNamedArgumentAssignment.js+0 -0
public/scripts/slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js+0 -1
@@ -1,7 +1,6 @@
1import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';1import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';
2import { SlashCommand } from './SlashCommand.js';2import { SlashCommand } from './SlashCommand.js';
3import { SlashCommandNamedArgument } from './SlashCommandArgument.js';3import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
4import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
54
6export class SlashCommandNamedArgumentAutoCompleteOption extends AutoCompleteOption {5export class SlashCommandNamedArgumentAutoCompleteOption extends AutoCompleteOption {
7 /** @type {SlashCommandNamedArgument} */ arg;6 /** @type {SlashCommandNamedArgument} */ arg;
public/scripts/slash-commands/SlashCommandParser.js+5 -6
@@ -8,11 +8,9 @@ import { SlashCommandExecutor } from './SlashCommandExecutor.js';
8import { SlashCommandParserError } from './SlashCommandParserError.js';8import { SlashCommandParserError } from './SlashCommandParserError.js';
9import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';9import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
10import { SlashCommandQuickReplyAutoCompleteOption } from './SlashCommandQuickReplyAutoCompleteOption.js';10import { SlashCommandQuickReplyAutoCompleteOption } from './SlashCommandQuickReplyAutoCompleteOption.js';
11// eslint-disable-next-line no-unused-vars
12import { SlashCommandScope } from './SlashCommandScope.js';11import { SlashCommandScope } from './SlashCommandScope.js';
13import { SlashCommandVariableAutoCompleteOption } from './SlashCommandVariableAutoCompleteOption.js';12import { SlashCommandVariableAutoCompleteOption } from './SlashCommandVariableAutoCompleteOption.js';
14import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';13import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
15// eslint-disable-next-line no-unused-vars
16import { SlashCommandAbortController } from './SlashCommandAbortController.js';14import { SlashCommandAbortController } from './SlashCommandAbortController.js';
17import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNameResult.js';15import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNameResult.js';
18import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';16import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
@@ -28,15 +26,17 @@ import { t } from '../i18n.js';
28/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */26/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
29/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */27/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
3028
31/**@readonly*/29/**
32/**@enum {Number}*/30 * @enum {Number}
31 * @readonly
32 * @typedef {{[id:PARSER_FLAG]:boolean}} ParserFlags
33 */
33export const PARSER_FLAG = {34export const PARSER_FLAG = {
34 'STRICT_ESCAPING': 1,35 'STRICT_ESCAPING': 1,
35 'REPLACE_GETVAR': 2,36 'REPLACE_GETVAR': 2,
36};37};
3738
38export class SlashCommandParser {39export class SlashCommandParser {
39 // @ts-ignore
40 /** @type {Object.<string, SlashCommand>} */ static commands = {};40 /** @type {Object.<string, SlashCommand>} */ static commands = {};
4141
42 /**42 /**
@@ -101,7 +101,6 @@ export class SlashCommandParser {
101 get commands() {101 get commands() {
102 return SlashCommandParser.commands;102 return SlashCommandParser.commands;
103 }103 }
104 // @ts-ignore
105 /** @type {Object.<string, string>} */ helpStrings = {};104 /** @type {Object.<string, string>} */ helpStrings = {};
106 /** @type {boolean} */ verifyCommandNames = true;105 /** @type {boolean} */ verifyCommandNames = true;
107 /** @type {string} */ text;106 /** @type {string} */ text;
public/scripts/slash-commands/SlashCommandScope.js+0 -0
public/scripts/slash-commands/SlashCommandUnnamedArgumentAssignment.js+0 -0
public/scripts/st-context.js+6 -1
@@ -49,6 +49,7 @@ import {
49 clearChat,49 clearChat,
50 unshallowCharacter,50 unshallowCharacter,
51 deleteLastMessage,51 deleteLastMessage,
52 getCharacterCardFields,
52} from '../script.js';53} from '../script.js';
53import {54import {
54 extension_settings,55 extension_settings,
@@ -78,8 +79,9 @@ import { ToolManager } from './tool-calling.js';
78import { accountStorage } from './util/AccountStorage.js';79import { accountStorage } from './util/AccountStorage.js';
79import { timestampToMoment, uuidv4 } from './utils.js';80import { timestampToMoment, uuidv4 } from './utils.js';
80import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';81import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
81import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';82import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
82import { ChatCompletionService, TextCompletionService } from './custom-request.js';83import { ChatCompletionService, TextCompletionService } from './custom-request.js';
84import { ConnectionManagerRequestService } from './extensions/shared.js';
83import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';85import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
8486
85export function getContext() {87export function getContext() {
@@ -188,6 +190,7 @@ export function getContext() {
188 textCompletionSettings: textgenerationwebui_settings,190 textCompletionSettings: textgenerationwebui_settings,
189 powerUserSettings: power_user,191 powerUserSettings: power_user,
190 getCharacters,192 getCharacters,
193 getCharacterCardFields,
191 uuidv4,194 uuidv4,
192 humanizedDateTime,195 humanizedDateTime,
193 updateMessageBlock,196 updateMessageBlock,
@@ -206,6 +209,7 @@ export function getContext() {
206 saveWorldInfo,209 saveWorldInfo,
207 updateWorldInfoList,210 updateWorldInfoList,
208 convertCharacterBook,211 convertCharacterBook,
212 getWorldInfoPrompt,
209 CONNECT_API_MAP,213 CONNECT_API_MAP,
210 getTextGenServer,214 getTextGenServer,
211 extractMessageFromData,215 extractMessageFromData,
@@ -215,6 +219,7 @@ export function getContext() {
215 clearChat,219 clearChat,
216 ChatCompletionService,220 ChatCompletionService,
217 TextCompletionService,221 TextCompletionService,
222 ConnectionManagerRequestService,
218 updateReasoningUI,223 updateReasoningUI,
219 parseReasoningFromString,224 parseReasoningFromString,
220 unshallowCharacter,225 unshallowCharacter,
public/scripts/tags.js+0 -1
@@ -13,7 +13,6 @@ import {
13 DEFAULT_PRINT_TIMEOUT,13 DEFAULT_PRINT_TIMEOUT,
14 printCharacters,14 printCharacters,
15} from '../script.js';15} from '../script.js';
16// eslint-disable-next-line no-unused-vars
17import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';16import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
1817
19import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';18import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
public/scripts/templates/assistantNote.html+2 -2
@@ -1,9 +1,9 @@
1<div data-type="assistant_note">1<div data-type="assistant_note">
2 <div>2 <div>
3 <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>3 <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
4 <span>Click the button to save it as a file.</span>4 <span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
5 </div>5 </div>
6 <div class="assistant_note_export menu_button menu_button_icon" title="Export as JSONL">6 <div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
7 <i class="fa-solid fa-file-export"></i>7 <i class="fa-solid fa-file-export"></i>
8 </div>8 </div>
9</div>9</div>
public/scripts/templates/macros.html+2 -1
@@ -20,7 +20,8 @@
20 <li><tt>&lcub;&lcub;summary&rcub;&rcub;</tt> – <span data-i18n="help_macros_summary">the latest chat summary generated by the "Summarize" extension (if available).</span></li>20 <li><tt>&lcub;&lcub;summary&rcub;&rcub;</tt> – <span data-i18n="help_macros_summary">the latest chat summary generated by the "Summarize" extension (if available).</span></li>
21 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>21 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>
22 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>22 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>
23 <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>23 <li><tt>&lcub;&lcub;version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>
24 <li><tt>&lcub;&lcub;charDepthPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_charDepthPrompt">the Character's @ Depth Note</span></li>
24 <li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names (including muted) or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>25 <li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names (including muted) or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>
25 <li><tt>&lcub;&lcub;groupNotMuted&rcub;&rcub;</tt> – <span data-i18n="help_groupNotMuted">the same as &lcub;&lcub;group&rcub;&rcub;, but excludes muted members</span></li>26 <li><tt>&lcub;&lcub;groupNotMuted&rcub;&rcub;</tt> – <span data-i18n="help_groupNotMuted">the same as &lcub;&lcub;group&rcub;&rcub;, but excludes muted members</span></li>
26 <li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> – <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b data-i18n="Can be inaccurate!">Can be inaccurate!</b></li>27 <li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> – <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b data-i18n="Can be inaccurate!">Can be inaccurate!</b></li>
public/scripts/templates/samplerSelector.html+18 -0
@@ -0,0 +1,18 @@
1<div class="title_restorable flexFlowColumn alignItemsBaseline">
2 <div class="flex-container justifyCenter">
3 <h3 data-i18n="Sampler Select">Sampler Select</h3>
4 <div class="flex-container alignItemsBaseline">
5 <div id="resetSelectedSamplers" class="menu_button menu_button_icon" data-i18n="[title]Reset custom sampler selection" title="Reset custom sampler selection">
6 <i class="fa-solid fa-recycle"></i>
7 </div>
8 </div>
9 <!--<div class="flex-container alignItemsBaseline">
10 <div class="menu_button menu_button_icon" title="Create a new sampler">
11 <i class="fa-solid fa-plus"></i>
12 <span data-i18n="Create">Create</span>
13 </div>
14 </div>-->
15 </div>
16 <small data-i18n="Here you can toggle the display of individual samplers. (WIP)">Here you can toggle the display of individual samplers. (WIP)</small>
17</div>
18<hr>
\ No newline at end of file18 \ No newline at end of file
public/scripts/textgen-settings.js+1 -1
@@ -86,7 +86,7 @@ const OOBA_DEFAULT_ORDER = [
86 'encoder_repetition_penalty',86 'encoder_repetition_penalty',
87 'no_repeat_ngram',87 'no_repeat_ngram',
88];88];
89const APHRODITE_DEFAULT_ORDER = [89export const APHRODITE_DEFAULT_ORDER = [
90 'dry',90 'dry',
91 'penalties',91 'penalties',
92 'no_repeat_ngram',92 'no_repeat_ngram',
public/scripts/variables.js+2 -2
@@ -9,7 +9,7 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
9import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';9import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
10import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';10import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
11import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';11import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
12import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';12import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
13import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';13import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
14import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';14import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
15import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';15import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
@@ -583,7 +583,7 @@ export function evalBoolean(rule, a, b) {
583 * Executes a slash command from a string (may be enclosed in quotes) and returns the result.583 * Executes a slash command from a string (may be enclosed in quotes) and returns the result.
584 * @param {string} command Command to execute. May contain escaped macro and batch separators.584 * @param {string} command Command to execute. May contain escaped macro and batch separators.
585 * @param {SlashCommandScope} [scope] The scope to use.585 * @param {SlashCommandScope} [scope] The scope to use.
586 * @param {{[id:PARSER_FLAG]:boolean}} [parserFlags] The parser flags to use.586 * @param {import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] The parser flags to use.
587 * @param {SlashCommandAbortController} [abortController] The abort controller to use.587 * @param {SlashCommandAbortController} [abortController] The abort controller to use.
588 * @returns {Promise<SlashCommandClosureResult>} Closure execution result588 * @returns {Promise<SlashCommandClosureResult>} Closure execution result
589 */589 */
public/scripts/world-info.js+24 -8
@@ -1,6 +1,6 @@
1import { Fuse } from '../lib.js';1import { Fuse } from '../lib.js';
22
3import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';3import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
4import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique } from './utils.js';4import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique } from './utils.js';
5import { extension_settings, getContext } from './extensions.js';5import { extension_settings, getContext } from './extensions.js';
6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
@@ -753,10 +753,17 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
753753
754/**754/**
755 * Gets the world info based on chat messages.755 * Gets the world info based on chat messages.
756 * @param {string[]} chat The chat messages to scan, in reverse order.756 * @param {string[]} chat - The chat messages to scan, in reverse order.
757 * @param {number} maxContext The maximum context size of the generation.757 * @param {number} maxContext - The maximum context size of the generation.
758 * @param {boolean} isDryRun If true, the function will not emit any events.758 * @param {boolean} isDryRun - If true, the function will not emit any events.
759 * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult759 * @typedef {object} WIPromptResult
760 * @property {string} worldInfoString - Complete world info string
761 * @property {string} worldInfoBefore - World info that goes before the prompt
762 * @property {string} worldInfoAfter - World info that goes after the prompt
763 * @property {Array} worldInfoExamples - Array of example entries
764 * @property {Array} worldInfoDepth - Array of depth entries
765 * @property {Array} anBefore - Array of entries before Author's Note
766 * @property {Array} anAfter - Array of entries after Author's Note
760 * @returns {Promise<WIPromptResult>} The world info string and depth.767 * @returns {Promise<WIPromptResult>} The world info string and depth.
761 */768 */
762export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {769export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -778,6 +785,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
778 worldInfoAfter,785 worldInfoAfter,
779 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],786 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
780 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],787 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
788 anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
789 anAfter: activatedWorldInfo.ANAfterEntries ?? [],
781 };790 };
782}791}
783792
@@ -3862,7 +3871,14 @@ function parseDecorators(content) {
3862 * @param {string[]} chat The chat messages to scan, in reverse order.3871 * @param {string[]} chat The chat messages to scan, in reverse order.
3863 * @param {number} maxContext The maximum context size of the generation.3872 * @param {number} maxContext The maximum context size of the generation.
3864 * @param {boolean} isDryRun Whether to perform a dry run.3873 * @param {boolean} isDryRun Whether to perform a dry run.
3865 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated3874 * @typedef {object} WIActivated
3875 * @property {string} worldInfoBefore The world info before the chat.
3876 * @property {string} worldInfoAfter The world info after the chat.
3877 * @property {any[]} EMEntries The entries for examples.
3878 * @property {any[]} WIDepthEntries The depth entries.
3879 * @property {any[]} ANBeforeEntries The entries before Author's Note.
3880 * @property {any[]} ANAfterEntries The entries after Author's Note.
3881 * @property {Set<any>} allActivatedEntries All entries.
3866 * @returns {Promise<WIActivated>} The world info activated.3882 * @returns {Promise<WIActivated>} The world info activated.
3867 */3883 */
3868export async function checkWorldInfo(chat, maxContext, isDryRun) {3884export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +3922,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3906 timedEffects.checkTimedEffects();3922 timedEffects.checkTimedEffects();
39073923
3908 if (sortedEntries.length === 0) {3924 if (sortedEntries.length === 0) {
3909 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };3925 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
3910 }3926 }
39113927
3912 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */3928 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4371,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4355 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));4371 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
4356 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);4372 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
43574373
4358 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };4374 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
4359}4375}
43604376
4361/**4377/**
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
43 vectors: 'vectors',43 vectors: 'vectors',
44 backups: 'backups',44 backups: 'backups',
45 sysprompt: 'sysprompt',45 sysprompt: 'sysprompt',
46 reasoning: 'reasoning',
46});47});
4748
48/**49/**
src/endpoints/backends/chat-completions.js+10 -9
@@ -49,7 +49,7 @@ const API_COHERE_V2 = 'https://api.cohere.ai/v2';
49const API_PERPLEXITY = 'https://api.perplexity.ai';49const API_PERPLEXITY = 'https://api.perplexity.ai';
50const API_GROQ = 'https://api.groq.com/openai/v1';50const API_GROQ = 'https://api.groq.com/openai/v1';
51const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';51const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
52const API_01AI = 'https://api.01.ai/v1';52const API_01AI = 'https://api.lingyiwanwu.com/v1';
53const API_BLOCKENTROPY = 'https://api.blockentropy.ai/v1';53const API_BLOCKENTROPY = 'https://api.blockentropy.ai/v1';
54const API_AI21 = 'https://api.ai21.com/studio/v1';54const API_AI21 = 'https://api.ai21.com/studio/v1';
55const API_NANOGPT = 'https://nano-gpt.com/api/v1';55const API_NANOGPT = 'https://nano-gpt.com/api/v1';
@@ -1048,6 +1048,15 @@ router.post('/generate', function (request, response) {
1048 let bodyParams;1048 let bodyParams;
1049 const isTextCompletion = Boolean(request.body.model && TEXT_COMPLETION_MODELS.includes(request.body.model)) || typeof request.body.messages === 'string';1049 const isTextCompletion = Boolean(request.body.model && TEXT_COMPLETION_MODELS.includes(request.body.model)) || typeof request.body.messages === 'string';
10501050
1051 const postProcessTypes = [CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENROUTER];
1052 if (Array.isArray(request.body.messages) && postProcessTypes.includes(request.body.chat_completion_source) && request.body.custom_prompt_post_processing) {
1053 console.info('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing);
1054 request.body.messages = postProcessPrompt(
1055 request.body.messages,
1056 request.body.custom_prompt_post_processing,
1057 getPromptNames(request));
1058 }
1059
1051 if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) {1060 if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) {
1052 apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString();1061 apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString();
1053 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI);1062 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.OPENAI);
@@ -1121,14 +1130,6 @@ router.post('/generate', function (request, response) {
11211130
1122 mergeObjectWithYaml(bodyParams, request.body.custom_include_body);1131 mergeObjectWithYaml(bodyParams, request.body.custom_include_body);
1123 mergeObjectWithYaml(headers, request.body.custom_include_headers);1132 mergeObjectWithYaml(headers, request.body.custom_include_headers);
1124
1125 if (request.body.custom_prompt_post_processing) {
1126 console.info('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing);
1127 request.body.messages = postProcessPrompt(
1128 request.body.messages,
1129 request.body.custom_prompt_post_processing,
1130 getPromptNames(request));
1131 }
1132 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.PERPLEXITY) {1133 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.PERPLEXITY) {
1133 apiUrl = API_PERPLEXITY;1134 apiUrl = API_PERPLEXITY;
1134 apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY);1135 apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY);
src/endpoints/chats.js+72 -6
@@ -21,6 +21,7 @@ import {
21const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true, 'boolean');21const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true, 'boolean');
22const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1, 'number'));22const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1, 'number'));
23const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000, 'number'));23const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000, 'number'));
24const checkIntegrity = !!getConfigValue('backups.chat.checkIntegrity', true, 'boolean');
2425
25/**26/**
26 * Saves a chat to the backups directory.27 * Saves a chat to the backups directory.
@@ -292,15 +293,81 @@ function importRisuChat(userName, characterName, jsonData) {
292 return chat.map(obj => JSON.stringify(obj)).join('\n');293 return chat.map(obj => JSON.stringify(obj)).join('\n');
293}294}
294295
296/**
297 * Reads the first line of a file asynchronously.
298 * @param {string} filePath Path to the file
299 * @returns {Promise<string>} The first line of the file
300 */
301function readFirstLine(filePath) {
302 const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
303 const rl = readline.createInterface({ input: stream });
304 return new Promise((resolve, reject) => {
305 let resolved = false;
306 rl.on('line', line => {
307 resolved = true;
308 rl.close();
309 stream.close();
310 resolve(line);
311 });
312
313 rl.on('error', error => {
314 resolved = true;
315 reject(error);
316 });
317
318 // Handle empty files
319 stream.on('end', () => {
320 if (!resolved) {
321 resolved = true;
322 resolve('');
323 }
324 });
325 });
326}
327
328/**
329 * Checks if the chat being saved has the same integrity as the one being loaded.
330 * @param {string} filePath Path to the chat file
331 * @param {string} integritySlug Integrity slug
332 * @returns {Promise<boolean>} Whether the chat is intact
333 */
334async function checkChatIntegrity(filePath, integritySlug) {
335 // If the chat file doesn't exist, assume it's intact
336 if (!fs.existsSync(filePath)) {
337 return true;
338 }
339
340 // Parse the first line of the chat file as JSON
341 const firstLine = await readFirstLine(filePath);
342 const jsonData = tryParse(firstLine);
343 const chatIntegrity = jsonData?.chat_metadata?.integrity;
344
345 // If the chat has no integrity metadata, assume it's intact
346 if (!chatIntegrity) {
347 return true;
348 }
349
350 // Check if the integrity matches
351 return chatIntegrity === integritySlug;
352}
353
295export const router = express.Router();354export const router = express.Router();
296355
297router.post('/save', validateAvatarUrlMiddleware, function (request, response) {356router.post('/save', validateAvatarUrlMiddleware, async function (request, response) {
298 try {357 try {
299 const directoryName = String(request.body.avatar_url).replace('.png', '');358 const directoryName = String(request.body.avatar_url).replace('.png', '');
300 const chatData = request.body.chat;359 const chatData = request.body.chat;
301 const jsonlData = chatData.map(JSON.stringify).join('\n');360 const jsonlData = chatData.map(JSON.stringify).join('\n');
302 const fileName = `${String(request.body.file_name)}.jsonl`;361 const fileName = `${String(request.body.file_name)}.jsonl`;
303 const filePath = path.join(request.user.directories.chats, directoryName, sanitize(fileName));362 const filePath = path.join(request.user.directories.chats, directoryName, sanitize(fileName));
363 if (checkIntegrity && !request.body.force) {
364 const integritySlug = chatData?.[0]?.chat_metadata?.integrity;
365 const isIntact = await checkChatIntegrity(filePath, integritySlug);
366 if (!isIntact) {
367 console.error(`Chat integrity check failed for ${filePath}`);
368 return response.status(400).send({ error: 'integrity' });
369 }
370 }
304 writeFileAtomicSync(filePath, jsonlData, 'utf8');371 writeFileAtomicSync(filePath, jsonlData, 'utf8');
305 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);372 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);
306 return response.send({ result: 'ok' });373 return response.send({ result: 'ok' });
@@ -716,12 +783,11 @@ router.post('/search', validateAvatarUrlMiddleware, function (request, response)
716 continue;783 continue;
717 }784 }
718785
719 // Search through messages786 // Search through title and messages of the chat
720 const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);787 const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);
721 const hasMatch = messages.some(message => {788 const text = [path.parse(chatFile.path).name,
722 const text = message?.mes?.toLowerCase();789 ...messages.map(message => message?.mes)].join('\n').toLowerCase();
723 return text && fragments.every(fragment => text.includes(fragment));790 const hasMatch = fragments.every(fragment => text.includes(fragment));
724 });
725791
726 if (hasMatch) {792 if (hasMatch) {
727 results.push({793 results.push({
src/endpoints/content-manager.js+4 -1
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
48 MOVING_UI: 'moving_ui',48 MOVING_UI: 'moving_ui',
49 QUICK_REPLIES: 'quick_replies',49 QUICK_REPLIES: 'quick_replies',
50 SYSPROMPT: 'sysprompt',50 SYSPROMPT: 'sysprompt',
51 REASONING: 'reasoning',
51};52};
5253
53/**54/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
61 const presets = [];62 const presets = [];
6263
63 for (const contentItem of contentIndex) {64 for (const contentItem of contentIndex) {
64 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {65 if (contentItem.type.endsWith('_preset') || ['instruct', 'context', 'sysprompt', 'reasoning'].includes(contentItem.type)) {
65 contentItem.name = path.parse(contentItem.filename).name;66 contentItem.name = path.parse(contentItem.filename).name;
66 contentItem.folder = getTargetByType(contentItem.type, directories);67 contentItem.folder = getTargetByType(contentItem.type, directories);
67 presets.push(contentItem);68 presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
299 return directories.quickreplies;300 return directories.quickreplies;
300 case CONTENT_TYPES.SYSPROMPT:301 case CONTENT_TYPES.SYSPROMPT:
301 return directories.sysprompt;302 return directories.sysprompt;
303 case CONTENT_TYPES.REASONING:
304 return directories.reasoning;
302 default:305 default:
303 return null;306 return null;
304 }307 }
src/endpoints/openai.js+1 -1
@@ -116,7 +116,7 @@ router.post('/caption-image', async (request, response) => {
116 }116 }
117117
118 if (request.body.api === 'zerooneai') {118 if (request.body.api === 'zerooneai') {
119 apiUrl = 'https://api.01.ai/v1/chat/completions';119 apiUrl = 'https://api.lingyiwanwu.com/v1/chat/completions';
120 }120 }
121121
122 if (request.body.api === 'groq') {122 if (request.body.api === 'groq') {
src/endpoints/presets.js+2 -0
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
30 return { folder: directories.context, extension: '.json' };30 return { folder: directories.context, extension: '.json' };
31 case 'sysprompt':31 case 'sysprompt':
32 return { folder: directories.sysprompt, extension: '.json' };32 return { folder: directories.sysprompt, extension: '.json' };
33 case 'reasoning':
34 return { folder: directories.reasoning, extension: '.json' };
33 default:35 default:
34 return { folder: null, extension: null };36 return { folder: null, extension: null };
35 }37 }
src/endpoints/settings.js+2 -0
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
255 const context = readAndParseFromDirectory(request.user.directories.context);255 const context = readAndParseFromDirectory(request.user.directories.context);
256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
257 const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
257258
258 response.send({259 response.send({
259 settings,260 settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
272 instruct,273 instruct,
273 context,274 context,
274 sysprompt,275 sysprompt,
276 reasoning,
275 enable_extensions: ENABLE_EXTENSIONS,277 enable_extensions: ENABLE_EXTENSIONS,
276 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,278 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
277 enable_accounts: ENABLE_ACCOUNTS,279 enable_accounts: ENABLE_ACCOUNTS,
src/endpoints/tokenizers.js+1 -1
@@ -411,7 +411,7 @@ export function getTokenizerModel(requestModel) {
411 return 'gpt-4o';411 return 'gpt-4o';
412 }412 }
413413
414 if (requestModel.includes('gpt-4.5-preview')) {414 if (requestModel.includes('gpt-4.5')) {
415 return 'gpt-4o';415 return 'gpt-4o';
416 }416 }
417417
src/users.js+1 -0
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
95 * @property {string} vectors - The directory where the vectors are stored95 * @property {string} vectors - The directory where the vectors are stored
96 * @property {string} backups - The directory where the backups are stored96 * @property {string} backups - The directory where the backups are stored
97 * @property {string} sysprompt - The directory where the system prompt data is stored97 * @property {string} sysprompt - The directory where the system prompt data is stored
98 * @property {string} reasoning - The directory where the reasoning templates are stored
98 */99 */
99100
100/**101/**