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 = {
33 extends: [
44 'eslint:recommended',
55 ],
6+ plugins: [
7+ 'jsdoc',
8+ ],
69 env: {
710 es6: true,
811 },
@@ -78,6 +81,7 @@ module.exports = {
7881 'public/scripts/extensions/tts/lib/**',
7982 ],
8083 rules: {
84+ 'jsdoc/no-undefined-types': ['warn', { disableReporting: true, markVariablesAsUsed: true }],
8185 'no-unused-vars': ['error', { args: 'none' }],
8286 'no-control-regex': 'off',
8387 'no-constant-condition': ['error', { checkLoops: false }],
.github/issues-auto-labels.yml+2 -2
@@ -14,7 +14,7 @@
1414 - '(🐧 Linux)'
1515
1616🦊 Firefox:
1717 - '\b(firefox|mozilla)\b'
1818
1919πŸ“± Mobile:
2020 - '\b(iphone|ios|android|πŸ“± Termux)\b'
.github/pr-auto-labels-by-branch.yml+6 -0
@@ -34,6 +34,9 @@
3434🦊 Firefox:
3535- head-branch: ['\bfirefox\b']
3636
37+πŸ§‘β€πŸ€β€πŸ§‘ Group Chat:
38+- head-branch: ['\bgroups?\b']
39+
3740πŸ–ΌοΈ Image Gen:
3841- head-branch: ['\bimage-gen\b']
3942
@@ -58,6 +61,9 @@
5861πŸ“œ Prompt:
5962- head-branch: ['\bprompt\b']
6063
64+🧠 Reasoning:
65+- head-branch: ['\breasoning\b', '\breason\b', '\bthinking\b']
66+
6167🚚 Refactor:
6268- 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
2424SillyTavern (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
2626Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over 200 contributors and 32 years of independent development under its belt, and continues to serve as a leading software for savvy AI hobbyists.
2727
2828## Our Vision
2929
@@ -192,28 +192,43 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
192192
193193##### Volume Mappings
194194
195195* [config]`CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine
196196* [data]`DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine
197197* [plugins]`PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine
198198* [extensions]`EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine
199199
200200##### Port Mappings
201201
202202* [PublicPort]`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
204204##### 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
209208#### InstallRunning commandthe container
210209
2112101. Open your Command Line
212-2. Run the following command
211+2. 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
214+SILLYTAVERN_VERSION="latest"
215+PUBLIC_PORT="8000"
216+CONFIG_PATH="./config"
217+DATA_PATH="./data"
218+PLUGINS_PATH="./plugins"
219+EXTENSIONS_PATH="./extensions"
220+
221+docker 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
218233### Building the image yourself
219234
.github/workflows/issues-auto-manager.yml+20 -16
@@ -7,6 +7,10 @@ on:
77 issue_comment:
88 types: [created]
99
10+permissions:
11+ contents: read
12+ issues: write
13+
1014jobs:
1115 label-on-content:
1216 name: 🏷️ Label Issues by Content
@@ -16,7 +20,7 @@ jobs:
1620 - name: Checkout Repository
1721 # Checkout
1822 # https://github.com/marketplace/actions/checkout
1923 uses: actions/checkout@v4.2.2
2024
2125 - name: Auto-Label Issues (Based on Issue Content)
2226 # only auto label based on issue content once, on open (to prevent re-labeling removed labels)
@@ -24,11 +28,11 @@ jobs:
2428
2529 # Issue Labeler
2630 # https://github.com/marketplace/actions/regex-issue-labeler
2731 uses: github/issue-labeler@v3.4
2832 with:
2933 configuration-path: .github/issues-auto-labels.yml
3034 enable-versioned-regex: 0
3135 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
3236
3337 label-on-labels:
3438 name: 🏷️ Label Issues by Labels
@@ -39,40 +43,40 @@ jobs:
3943 if: contains(fromJSON('["πŸ‘©β€πŸ’» Good First Issue", "πŸ™ Help Wanted", "πŸͺ² Confirmed", "⚠️ High Priority", "❕ Medium Priority", "πŸ’€ Low Priority"]'), github.event.label.name)
4044 # πŸ€– Issues Helper
4145 # https://github.com/marketplace/actions/issues-helper
4246 uses: actions-cool/issues-helper@v3.6.0
4347 with:
4448 actions: 'add-labels'
4549 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
4650 labels: 'πŸ‘ Approved'
4751
4852 - name: ❌ Remove progress labels when issue is marked done or stale
4953 if: contains(fromJSON('["βœ… Done", "βœ… Done (staging)", "⚰️ Stale", "❌ wontfix"]'), github.event.label.name)
5054 # πŸ€– Issues Helper
5155 # https://github.com/marketplace/actions/issues-helper
5256 uses: actions-cool/issues-helper@v3.6.0
5357 with:
5458 actions: 'remove-labels'
5559 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
5660 labels: 'πŸ§‘β€πŸ’» In Progress,πŸ€” Unsure,πŸ€” Under Consideration'
5761
5862 - name: ❌ Remove temporary labels when confirmed labels are added
5963 if: contains(fromJSON('["❌ wontfix","πŸ‘ Approved","πŸ‘©β€πŸ’» Good First Issue"]'), github.event.label.name)
6064 # πŸ€– Issues Helper
6165 # https://github.com/marketplace/actions/issues-helper
6266 uses: actions-cool/issues-helper@v3.6.0
6367 with:
6468 actions: 'remove-labels'
6569 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
6670 labels: 'πŸ€” Unsure,πŸ€” Under Consideration'
6771
6872 - name: ❌ Remove no bug labels when "πŸͺ² Confirmed" is added
6973 if: github.event.label.name == 'πŸͺ² Confirmed'
7074 # πŸ€– Issues Helper
7175 # https://github.com/marketplace/actions/issues-helper
7276 uses: actions-cool/issues-helper@v3.6.0
7377 with:
7478 actions: 'remove-labels'
7579 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
7680 labels: 'βœ–οΈ Not Reproducible,βœ–οΈ Not A Bug'
7781
7882 remove-stale-label:
@@ -85,10 +89,10 @@ jobs:
8589 - name: Remove Stale Label
8690 # πŸ€– Issues Helper
8791 # https://github.com/marketplace/actions/issues-helper
8892 uses: actions-cool/issues-helper@v3.6.0
8993 with:
9094 actions: 'remove-labels'
9195 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
9296 issue-number: ${{ github.event.issue.number }}
9397 labels: '⚰️ Stale,πŸ•ΈοΈ Inactive,🚏 Awaiting User Response,πŸ›‘ No Response'
9498
@@ -101,12 +105,12 @@ jobs:
101105 - name: Checkout Repository
102106 # Checkout
103107 # https://github.com/marketplace/actions/checkout
104108 uses: actions/checkout@v4.2.2
105109
106110 - name: Post Issue Comments Based on Labels
107111 # Label Commenter
108112 # https://github.com/marketplace/actions/label-commenter
109113 uses: peaceiris/actions-label-commenter@v1.10.0
110114 with:
111115 config_file: .github/issues-auto-comments.yml
112116 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
.github/workflows/issues-updates-on-merge.yml+7 -3
@@ -6,6 +6,10 @@ on:
66 - staging
77 - release
88
9+permissions:
10+ contents: read
11+ issues: write
12+
913jobs:
1014 # This runs commits to staging/release, reading the commit messages. Check `pr-auto-manager.yml`:`update-linked-issues` for PR-linked updates.
1115 update-linked-issues:
@@ -16,18 +20,18 @@ jobs:
1620 - name: Checkout Repository
1721 # Checkout
1822 # https://github.com/marketplace/actions/checkout
1923 uses: actions/checkout@v4.2.2
2024
2125 - name: Extract Linked Issues from Commit Message
2226 id: extract_issues
2327 run: |
2428 ISSUES=$(git log -1${{ 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]')
2529 echo "issues=$ISSUES" >> $GITHUB_ENV
2630
2731 - name: Label Linked Issues
2832 id: label_linked_issues
2933 env:
3034 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
3135 run: |
3236 for ISSUE in $(echo $issues | jq -r '.[]'); do
3337 if [ "${{ github.ref }}" == "refs/heads/staging" ]; then
.github/workflows/job-close-stale.yml+11 -6
@@ -6,6 +6,11 @@ on:
66 schedule:
77 - cron: '0 0 * * *' # Runs every day at midnight UTC
88
9+permissions:
10+ contents: read
11+ issues: write
12+ pull-requests: write
13+
914jobs:
1015 mark-inactivity:
1116 name: ⏳ Mark Issues/PRs without Activity
@@ -15,9 +20,9 @@ jobs:
1520 - name: Mark Issues/PRs without Activity
1621 # Close Stale Issues and PRs
1722 # https://github.com/marketplace/actions/close-stale-issues
1823 uses: actions/stale@v9.1.0
1924 with:
2025 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2126 days-before-stale: 183
2227 days-before-close: 7
2328 operations-per-run: 30
@@ -49,9 +54,9 @@ jobs:
4954 - name: Mark Issues/PRs Awaiting User Response
5055 # Close Stale Issues and PRs
5156 # https://github.com/marketplace/actions/close-stale-issues
5257 uses: actions/stale@v9.1.0
5358 with:
5459 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
5560 days-before-stale: 7
5661 days-before-close: 7
5762 operations-per-run: 30
@@ -76,9 +81,9 @@ jobs:
7681 - name: Mark Issues with Alternative Exists
7782 # Close Stale Issues and PRs
7883 # https://github.com/marketplace/actions/close-stale-issues
7984 uses: actions/stale@v9.1.0
8085 with:
8186 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
8287 days-before-stale: 7
8388 days-before-close: 7
8489 operations-per-run: 30
.github/workflows/on-close-handler.yml+7 -2
@@ -6,6 +6,11 @@ on:
66 pull_request_target:
77 types: [closed]
88
9+permissions:
10+ contents: read
11+ issues: write
12+ pull-requests: write
13+
914jobs:
1015 remove-labels:
1116 name: πŸ—‘οΈ Remove Pending Labels on Close
@@ -15,9 +20,9 @@ jobs:
1520 - name: Remove Pending Labels on Close
1621 # πŸ€– Issues Helper
1722 # https://github.com/marketplace/actions/issues-helper
1823 uses: actions-cool/issues-helper@v3.6.0
1924 with:
2025 actions: remove-labels
2126 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2227 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
2328 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:
66 pull_request_target:
77 types: [opened]
88
9+permissions:
10+ contents: read
11+ issues: write
12+ pull-requests: write
13+
914jobs:
1015 label-maintainer:
1116 name: 🏷️ Label if Author is a Repo Maintainer
@@ -16,9 +21,9 @@ jobs:
1621 - name: Label if Author is a Repo Maintainer
1722 # πŸ€– Issues Helper
1823 # https://github.com/marketplace/actions/issues-helper
1924 uses: actions-cool/issues-helper@v3.6.0
2025 with:
2126 actions: 'add-labels'
2227 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2328 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
2429 labels: 'πŸ‘· Maintainer'
.github/workflows/pr-auto-manager.yml+91 -23
@@ -6,18 +6,67 @@ on:
66 pull_request_review_comment:
77 types: [created]
88
9+permissions:
10+ contents: read
11+ pull-requests: write
12+
913jobs:
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+
1049 label-by-size:
1150 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]
1253 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
1463 steps:
1564 - name: Label PR Size
1665 # Pull Request Size Labeler
1766 # https://github.com/marketplace/actions/pull-request-size-labeler
1867 uses: codelytv/pr-size-labeler@v1.10.2
1968 with:
2069 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2170 xs_label: '🟩 ⬀○○○○'
2271 xs_max_size: '20'
2372 s_label: '🟩 ⬀⬀○○○'
@@ -28,7 +77,6 @@ jobs:
2877 l_max_size: '1000'
2978 xl_label: 'πŸŸ₯ ⬀⬀⬀⬀⬀'
3079 fail_if_xl: 'false'
31- github_api_url: 'https://api.github.com'
3280 files_to_ignore: |
3381 "package-lock.json"
3482 "public/lib/*"
@@ -36,55 +84,63 @@ jobs:
3684 label-by-branches:
3785 name: 🏷️ Label PR by Branches
3886 runs-on: ubuntu-latest
3987 # Only label once when PR is created or brancheswhen arebase branch is changed, to allow manual label removal
4088 if: github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))
4189
4290 steps:
4391 - name: Checkout Repository
4492 # Checkout
4593 # https://github.com/marketplace/actions/checkout
4694 uses: actions/checkout@v4.2.2
4795
4896 - name: Apply Labels Based on Branch Name and Target Branch
4997 # Pull Request Labeler
5098 # https://github.com/marketplace/actions/labeler
5199 uses: actions/labeler@v5.0.0
52100 with:
53101 configuration-path: .github/pr-auto-labels-by-branch.yml
54102 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
55103
56104 label-by-files:
57105 name: 🏷️ Label PR by Files
58106 runs-on: ubuntu-latest
107+ # Only needs to run when code is changed
108+ if: github.event.action == 'opened' || github.event.action == 'synchronize'
59109
60110 steps:
61111 - name: Checkout Repository
62112 # Checkout
63113 # https://github.com/marketplace/actions/checkout
64114 uses: actions/checkout@v4.2.2
65115
66116 - name: Apply Labels Based on Changed Files
67117 # Pull Request Labeler
68118 # https://github.com/marketplace/actions/labeler
69119 uses: actions/labeler@v5.0.0
70120 with:
71121 configuration-path: .github/pr-auto-labels-by-files.yml
72122 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
73123
74124 remove-stale-label:
75125 name: πŸ—‘οΈ Remove Stale Label on Comment
76126 runs-on: ubuntu-latest
77127 # Only runs when thison iscomments not done by the github actions bot
78128 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
80136 steps:
81137 - name: Remove Stale Label
82138 # πŸ€– Issues Helper
83139 # https://github.com/marketplace/actions/issues-helper
84140 uses: actions-cool/issues-helper@v3.6.0
85141 with:
86142 actions: 'remove-labels'
87143 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
88144 issue-number: ${{ github.event.pull_request.number }}
89145 labels: '⚰️ Stale'
90146
@@ -95,12 +151,18 @@ jobs:
95151 # Run, even if the previous jobs were skipped/failed
96152 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+
98160 steps:
99161 - name: Check Merge Blocking
100162 # GitHub Script
101163 # https://github.com/marketplace/actions/github-scriptLabelsscript
102164 id: label-check
103165 uses: actions/github-script@v7.0.1
104166 with:
105167 script: |
106168 const prLabels = context.payload.pull_request.labels.map(label => label.name);
@@ -134,7 +196,7 @@ jobs:
134196
135197 write-auto-comments:
136198 name: πŸ’¬ Post PR Comments Based on Labels
137199 needs: [label-by-size, label-by-branches, label-by-files]
138200 runs-on: ubuntu-latest
139201 # Run, even if the previous jobs were skipped/failed
140202 if: always()
@@ -143,15 +205,15 @@ jobs:
143205 - name: Checkout Repository
144206 # Checkout
145207 # https://github.com/marketplace/actions/checkout
146208 uses: actions/checkout@v4.2.2
147209
148210 - name: Post PR Comments Based on Labels
149211 # Label Commenter for PRs
150212 # https://github.com/marketplace/actions/label-commenter
151213 uses: peaceiris/actions-label-commenter@v1.10.0
152214 with:
153215 config_file: .github/pr-auto-comments.yml
154216 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
155217
156218 # 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.
157219 update-linked-issues:
@@ -159,6 +221,12 @@ jobs:
159221 runs-on: ubuntu-latest
160222 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+
162230 steps:
163231 - name: Extract Linked Issues From PR Description
164232 id: extract_issues
@@ -172,7 +240,7 @@ jobs:
172240 PR_NUMBER=${{ github.event.pull_request.number }}
173241 REPO=${{ github.repository }}
174242 API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"
175243 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]')
176244 echo "linked_issues=$ISSUES" >> $GITHUB_ENV
177245
178246 - name: Merge Issue Lists
@@ -184,7 +252,7 @@ jobs:
184252 - name: Label Linked Issues
185253 id: label_linked_issues
186254 env:
187255 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
188256 run: |
189257 for ISSUE in $(echo $final_issues | jq -r '.[]'); do
190258 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:
77 pull_request_target:
88 types: [synchronize]
99
10+permissions:
11+ contents: read
12+ pull-requests: write
13+
1014jobs:
1115 check-merge-conflicts:
1216 name: βš”οΈ Check Merge Conflicts
@@ -16,9 +20,9 @@ jobs:
1620 - name: Check Merge Conflicts
1721 # Label Conflicting Pull Requests
1822 # https://github.com/marketplace/actions/label-conflicting-pull-requests
1923 uses: eps1lon/actions-label-merge-conflict@v3.0.3
2024 with:
2125 dirtyLabel: '🚫 Merge Conflicts'
2226 repoToken: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2327 commentOnDirty: >
2428 ⚠️ This PR has conflicts that need to be resolved before it can be merged.
default/config.yaml+2 -0
@@ -114,6 +114,8 @@ backups:
114114 chat:
115115 # Enable automatic chat backups
116116 enabled: true
117+ # Verify integrity of chat files before saving
118+ checkIntegrity: true
117119 # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.
118120 maxTotalBackups: -1
119121 # Interval in milliseconds to throttle chat backups per user
default/content/index.json+8 -0
@@ -786,5 +786,13 @@
786786 {
787787 "filename": "presets/context/DeepSeek-V2.5.json",
788788 "type": "context"
789+ },
790+ {
791+ "filename": "presets/reasoning/DeepSeek.json",
792+ "type": "reasoning"
793+ },
794+ {
795+ "filename": "presets/reasoning/Blank.json",
796+ "type": "reasoning"
789797 }
790798]
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 @@
110110 "@types/write-file-atomic": "^4.0.3",
111111 "@types/yargs": "^17.0.33",
112112 "@types/yauzl": "^2.10.3",
113113 "eslint": "^8.57.1",
114+ "eslint-plugin-jsdoc": "^48.10.0"
114115 },
115116 "engines": {
116117 "node": ">= 18"
@@ -147,6 +148,21 @@
147148 "integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA==",
148149 "license": "Apache-2.0"
149150 },
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+ },
150166 "node_modules/@eslint-community/eslint-utils": {
151167 "version": "4.4.0",
152168 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -970,6 +986,19 @@
970986 "node": ">=14"
971987 }
972988 },
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+ },
9731002 "node_modules/@popperjs/core": {
9741003 "version": "2.11.8",
9751004 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -2099,6 +2128,16 @@
20992128 "safe-buffer": "~5.2.0"
21002129 }
21012130 },
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+ },
21022141 "node_modules/argparse": {
21032142 "version": "2.0.1",
21042143 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2614,6 +2653,16 @@
26142653 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
26152654 "license": "MIT"
26162655 },
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+ },
26172666 "node_modules/compress-commons": {
26182667 "version": "6.0.2",
26192668 "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
@@ -3560,6 +3609,69 @@
35603609 "url": "https://opencollective.com/eslint"
35613610 }
35623611 },
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+ },
35633675 "node_modules/eslint-scope": {
35643676 "version": "7.2.2",
35653677 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -3690,9 +3802,9 @@
36903802 }
36913803 },
36923804 "node_modules/esquery": {
36933805 "version": "1.56.0",
36943806 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.56.0.tgz",
36953807 "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idISca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/9fIU5GjG73IgjKMVg5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
36963808 "dev": true,
36973809 "license": "BSD-3-Clause",
36983810 "dependencies": {
@@ -5015,6 +5127,16 @@
50155127 "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
50165128 "license": "MIT"
50175129 },
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+ },
50185140 "node_modules/json-buffer": {
50195141 "version": "3.0.1",
50205142 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5891,6 +6013,20 @@
58916013 "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",
58926014 "license": "MIT"
58936015 },
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+ },
58946030 "node_modules/parse5": {
58956031 "version": "7.1.2",
58966032 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@@ -6600,6 +6736,19 @@
66006736 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
66016737 "license": "MIT"
66026738 },
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+ },
66036752 "node_modules/send": {
66046753 "version": "0.19.0",
66056754 "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
@@ -6811,6 +6960,13 @@
68116960 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
68126961 "license": "MIT"
68136962 },
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+ },
68146970 "node_modules/sliced": {
68156971 "version": "1.0.1",
68166972 "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
@@ -6903,6 +7059,31 @@
69037059 "source-map": "^0.6.0"
69047060 }
69057061 },
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+ },
69067087 "node_modules/sprintf-js": {
69077088 "version": "1.1.3",
69087089 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@@ -7042,6 +7223,23 @@
70427223 "node": ">=8"
70437224 }
70447225 },
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+ },
70457243 "node_modules/tapable": {
70467244 "version": "2.2.1",
70477245 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
package.json+2 -1
@@ -140,6 +140,7 @@
140140 "@types/write-file-atomic": "^4.0.3",
141141 "@types/yargs": "^17.0.33",
142142 "@types/yauzl": "^2.10.3",
143143 "eslint": "^8.57.1",
144+ "eslint-plugin-jsdoc": "^48.10.0"
144145 }
145146}
post-install.js+0 -3
@@ -101,15 +101,12 @@ const keyMigrationMap = [
101101 newKey: 'performance.memoryCacheCapacity',
102102 migrate: (value) => `${value}mb`,
103103 },
104- // uncomment one release after 1.12.13
105- /*
106104 {
107105 oldKey: 'cookieSecret',
108106 newKey: 'cookieSecret',
109107 migrate: () => void 0,
110108 remove: true,
111109 },
112- */
113110];
114111
115112/**
public/global.d.ts+4 -0
@@ -1,7 +1,11 @@
11import libs from './lib';
22import getContext from './scripts/st-context';
3+import { power_user } from './scripts/power-user';
34
45declare global {
6+ // Custom types
7+ declare type InstructSettings = typeof power_user.instruct;
8+
59 // Global namespace modules
610 interface Window {
711 ai: any;
public/index.html+34 -11
@@ -1957,7 +1957,7 @@
19571957 <span data-i18n="Enable web search">Enable web search</span>
19581958 </label>
19591959 <div class="flexBasis100p toggle-description justifyLeft">
1960- <span>
1960+ <span data-i18n="Use search capabilities provided by the backend.">
19611961 Use search capabilities provided by the backend.
19621962 </span>
19631963 </div>
@@ -2188,7 +2188,7 @@
21882188 <input id="horde_trusted_workers_only" type="checkbox" />
21892189 <span data-i18n="Trusted workers only">Trusted workers only</span>
21902190 </label>
21912191 <small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
21922192 <h4 data-i18n="API key">API key</h4>
21932193 <small>
21942194 <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 @@
31933193 <option value="mistral-small-latest">mistral-small-latest</option>
31943194 <option value="mistral-medium-latest">mistral-medium-latest</option>
31953195 <option value="mistral-large-latest">mistral-large-latest</option>
3196+ <option value="mistral-saba-latest">mistral-saba-latest</option>
31963197 <option value="codestral-latest">codestral-latest</option>
31973198 <option value="codestral-mamba-latest">codestral-mamba-latest</option>
31983199 <option value="pixtral-12b-latest">pixtral-12b-latest</option>
@@ -3208,13 +3209,20 @@
32083209 <option value="mistral-small-2312">mistral-small-2312</option>
32093210 <option value="mistral-small-2402">mistral-small-2402</option>
32103211 <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>
32113214 <option value="mistral-medium-2312">mistral-medium-2312</option>
32123215 <option value="mistral-large-2402">mistral-large-2402</option>
32133216 <option value="mistral-large-2407">mistral-large-2407</option>
32143217 <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>
32153220 <option value="codestral-2405">codestral-2405</option>
32163221 <option value="codestral-2405-blue">codestral-2405-blue</option>
32173222 <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>
32183226 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
32193227 <option value="pixtral-large-2411">pixtral-large-2411</option>
32203228 </optgroup>
@@ -3407,17 +3415,10 @@
34073415 <div class="flex-container">
34083416 <select id="model_custom_select" class="text_pole model_custom_select"></select>
34093417 </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>
34173418 </form>
34183419 <div id="01ai_form" data-source="01ai">
34193420 <h4>
34203421 <a data-i18n="01.AI API Key" href="https://platform.01lingyiwanwu.aicom/" target="_blank" rel="noopener noreferrer">
34213422 01.AI API Key
34223423 </a>
34233424 </h4>
@@ -3432,6 +3433,15 @@
34323433 <select id="model_01ai_select">
34333434 </select>
34343435 </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>
34353445 <div class="flex-container flex">
34363446 <div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
34373447 <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>
@@ -3917,6 +3927,19 @@
39173927 <summary data-i18n="Reasoning Formatting">
39183928 Reasoning Formatting
39193929 </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>
39203943 <div class="flex-container">
39213944 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
39223945 <small data-i18n="Prefix">Prefix</small>
@@ -6563,7 +6586,7 @@
65636586 <div class="ch_name"></div>
65646587 <small class="ch_additional_info group_select_counter"></small>
65656588 </div>
65666589 <small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
65676590 <i class='group_fav_icon fa-solid fa-star'></i>
65686591 <input class="ch_fav" value="" hidden />
65696592 <div class="group_select_block_list ch_description"></div>
public/locales/ru-ru.json+157 -21
@@ -23,9 +23,8 @@
2323 "Mirostat Mode": "Π Π΅ΠΆΠΈΠΌ",
2424 "Mirostat Tau": "Tau",
2525 "Mirostat Eta": "Eta",
2626 "Variability parameter for Mirostat outputs": "ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΠΈΠ·ΠΌΠ΅Π½Ρ‡ΠΈΠ²ΠΎΡΡ‚ΠΈΠ’Π°Ρ€ΠΈΠ°Ρ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ для Π²Ρ‹Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ… Mirostat.",
2727 "Learning rate of Mirostat": "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ обучСния Mirostat.",
28- "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Π‘ΠΈΠ»Π° условия рСгуляризации контрастивного поиска. УстановитС Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ 0, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ CS.",
2928 "Temperature Last": "Π’Π΅ΠΌΠΏΠ΅Ρ€Π°Ρ‚ΡƒΡ€Π° послСднСй",
3029 "LLaMA / Mistral / Yi models only": "Волько для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ LLaMA / Mistral / Yi. ΠŸΠ΅Ρ€Π΅Π΄ этим ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ Π²Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ подходящий Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€.\nΠŸΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Π½Π° Π²Ρ‹Ρ…ΠΎΠ΄Π΅.\nОдна Π½Π° строку. ВСкст ΠΈΠ»ΠΈ [ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²].\nМногиС Ρ‚ΠΎΠΊΠ΅Π½Ρ‹ ΠΈΠΌΠ΅ΡŽΡ‚ ΠΏΡ€ΠΎΠ±Π΅Π» Π²ΠΏΠ΅Ρ€Π΅Π΄ΠΈ. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ счСтчик Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Ссли Π½Π΅ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹.",
3130 "Example: some text [42, 69, 1337]": "ΠŸΡ€ΠΈΠΌΠ΅Ρ€:\nΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ тСкст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
6059 "Add BOS Token": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ BOS-Ρ‚ΠΎΠΊΠ΅Π½",
6160 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Π”ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ BOS-Ρ‚ΠΎΠΊΠ΅Π½ Π² Π½Π°Ρ‡Π°Π»Π΅ ΠΏΡ€ΠΎΠΌΠΏΡ‚Π°. Если Π²Ρ‹ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ, ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ ΡΡ‚Π°Ρ‚ΡŒ Π±ΠΎΠ»Π΅Π΅ ΠΊΡ€Π΅Π°Ρ‚ΠΈΠ²Π½Ρ‹ΠΌΠΈ.",
6261 "Ban EOS Token": "Π—Π°ΠΏΡ€Π΅Ρ‚ΠΈΡ‚ΡŒ EOS-Ρ‚ΠΎΠΊΠ΅Π½",
6362 "Ban the eos_token. This forces the model to never end the generation prematurely": "Π—Π°ΠΏΡ€Π΅Ρ‚ EOS-Ρ‚ΠΎΠΊΠ΅Π½Π° Π½Π΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π·Π°Π²Π΅Ρ€ΡˆΠΈΡ‚ΡŒ Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΡŽ ΠΏΡ€Π΅ΠΆΠ΄Π΅Π²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΡΠ°ΠΌΠΎΡΡ‚ΠΎΡΡ‚Π΅Π»ΡŒΠ½ΠΎ (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ достиТСнии Π»ΠΈΠΌΠΈΡ‚Π° Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²)",
6463 "Skip Special Tokens": "ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Ρ‚ΡŒ спСц. Ρ‚ΠΎΠΊΠ΅Π½Ρ‹",
6564 "Beam search": "Поиск Beam Search",
66- "Number of Beams": "ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Beam",
6765 "Length Penalty": "Π¨Ρ‚Ρ€Π°Ρ„ Π·Π° Π΄Π»ΠΈΠ½Ρƒ",
6866 "Early Stopping": "ΠŸΡ€Π΅ΠΆΠ΄Π΅Π²Ρ€Π΅ΠΌΠ΅Π½Π½Π°ΡΠŸΡ€Π΅ΠΊΡ€Π°Ρ‰Π°Ρ‚ΡŒ остановкасразу",
69- "Contrastive search": "ΠšΠΎΠ½Ρ‚Ρ€Π°ΡΡ‚Π½Ρ‹ΠΉ поиск",
7067 "Penalty Alpha": "Penalty Alpha",
7168 "Seed": "Π—Π΅Ρ€Π½ΠΎ",
7269 "Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
8986 "Text Completion presets": "ΠŸΡ€Π΅ΡΠ΅Ρ‚Ρ‹ для Text Completion",
9087 "Documentation on sampling parameters": "ДокумСнтация ΠΏΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌ сэмплСров",
9188 "Set all samplers to their neutral/disabled state.": "Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС сэмплСры Π² Π½Π΅ΠΉΡ‚Ρ€Π°Π»ΡŒΠ½ΠΎΠ΅/ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½Π½ΠΎΠ΅ состояниС.",
9289 "Only enable this if your model supports context sizes greater than 8192 tokens": "Π’ΠΊΠ»ΡŽΡ‡Π°ΠΉΡ‚Π΅ эту ΠΎΠΏΡ†ΠΈΡŽ, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ваша модСль ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€ контСкста Π±ΠΎΠ»Π΅Π΅ 8192 Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ².\nΠ£Π²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°ΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Π²Ρ‹ Π·Π½Π°Π΅Ρ‚Π΅ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ‚Π΅, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚Π΅.",
9390 "Wrap in Quotes": "Π—Π°ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ Π² ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ",
9491 "Wrap entire user message in quotes before sending.": "ΠŸΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ Π·Π°ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ всё сообщСниС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π² ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ.",
9592 "Leave off if you use quotes manually for speech.": "ΠžΡΡ‚Π°Π²ΡŒΡ‚Π΅ Π²Ρ‹ΠΊΠ»ΡŽΡ‡Π΅Π½Π½Ρ‹ΠΌ, Ссли Π²Ρ€ΡƒΡ‡Π½ΡƒΡŽ выставляСтС ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ для прямой Ρ€Π΅Ρ‡ΠΈ.",
@@ -109,7 +106,7 @@
109106 "Adjust response length to worker capabilities": "ΠŸΠΎΠ΄ΡΡ‚Ρ€Π°ΠΈΠ²Π°Ρ‚ΡŒ Π΄Π»ΠΈΠ½Ρƒ ΠΎΡ‚Π²Π΅Ρ‚Π° ΠΏΠΎΠ΄ возмоТности Ρ€Π°Π±ΠΎΡ‡ΠΈΡ… машин",
110107 "API key": "API-ΠΊΠ»ΡŽΡ‡",
111108 "Tabby API key": "Tabby API-ΠΊΠ»ΡŽΡ‡",
112109 "Get it here:": "ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚Π΅ здСсь:",
113110 "Register": "Π—Π°Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ",
114111 "TogetherAI Model": "МодСль TogetherAI",
115112 "Example: 127.0.0.1:5001": "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: http://127.0.0.1:5001",
@@ -289,10 +286,10 @@
289286 "Author's Note": "Π—Π°ΠΌΠ΅Ρ‚ΠΊΠΈ Π°Π²Ρ‚ΠΎΡ€Π°",
290287 "Replace empty message": "Π—Π°ΠΌΠ΅Π½ΡΡ‚ΡŒ пустыС сообщСния",
291288 "Send this text instead of nothing when the text box is empty.": "Π­Ρ‚ΠΎΡ‚ тСкст Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡ‚ΠΏΡ€Π°Π²Π»Π΅Π½ Π² случаС отсутствия тСкста Π½Π° ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΡƒ.",
292289 "Unrestricted maximum value for the context slider": "Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΏΠΎΡ‚ΠΎΠ»ΠΎΠΊ для ΠΏΠΎΠ»Π·ΡƒΠ½ΠΊΠ° контСкста. Π’ΠΊΠ»ΡŽΡ‡Π°ΠΉΡ‚Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли Ρ‚ΠΎΡ‡Π½ΠΎ Π·Π½Π°Π΅Ρ‚Π΅ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ‚Π΅, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚Π΅",
293290 "Chat Completion Source": "Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ для Chat Completion",
294291 "Avoid sending sensitive information to the Horde.": "Π˜Π·Π±Π΅Π³Π°ΠΉΡ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ Π»ΠΈΡ‡Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ Horde.",
295292 "Review the Privacy statement": "ΠžΠ·Π½Π°ΠΊΠΎΠΌΠΈΡ‚ΡŒΡΡΠžΠ·Π½Π°ΠΊΠΎΠΌΡŒΡ‚Π΅ΡΡŒ с заявлСниСм ΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ΄Π΅Π½Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ",
296293 "Trusted workers only": "Волько Π΄ΠΎΠ²Π΅Ρ€Π΅Π½Π½Ρ‹Π΅ Ρ€Π°Π±ΠΎΡ‡ΠΈΠ΅ ΠΌΠ°ΡˆΠΈΠ½Ρ‹",
297294 "For privacy reasons, your API key will be hidden after you reload the page.": "Из сообраТСний бСзопасности ваш API-ΠΊΠ»ΡŽΡ‡ Π±ΡƒΠ΄Π΅Ρ‚ скрыт послС ΠΏΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ страницы.",
298295 "-- Horde models not loaded --": "--МодСль Horde Π½Π΅ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π°--",
@@ -699,7 +696,7 @@
699696 "Aggressive": "АгрСссивный",
700697 "Very aggressive": "ΠžΡ‡Π΅Π½ΡŒ агрСссивный",
701698 "Eta_Cutoff_desc": "Eta cutoff - основной ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½ΠΎΠΉ Ρ‚Π΅Ρ…Π½ΠΈΠΊΠΈ сэмплинга ΠΏΠΎΠ΄ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ΠΌ Eta Sampling.&#13;Π’ Π΅Π΄ΠΈΠ½ΠΈΡ†Π°Ρ… 1e-4; Ρ€Π°Π·ΡƒΠΌΠ½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ - 3.&#13;УстановитС Π² 0, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ.&#13;Π‘ΠΌ. ΡΡ‚Π°Ρ‚ΡŒΡŽ Truncation Sampling as Language Model Desmoothing ΠΎΡ‚ Π₯ΡŒΡŽΠΈΡ‚Ρ‚ ΠΈ Π΄Ρ€. (2022) для получСния ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠΉ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ.",
702699 "Learn how to contribute your idle GPU cycles to the Horde": "Π£Π·Π½Π°ΠΉΡ‚Π΅, ΠΊΠ°ΠΊ внСсти свой Π²ΠΊΠ»Π°Π΄ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ вврСмя своипростоя ΡΠ²ΠΎΠ±ΠΎΠ΄Π½Ρ‹Π΅Π²Π°ΡˆΠ΅Π³ΠΎ GPU-Ρ†ΠΈΠΊΠ»Ρ‹ вдля ΠΎΡ€Π΄ΡƒΠΏΠΎΠΌΠΎΡ‰ΠΈ Horde",
703700 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€ для ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Google Ρ‡Π΅Ρ€Π΅Π· ΠΈΡ… API. МСдлСнная ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° подсказок, Π½ΠΎ ΠΏΡ€Π΅Π΄Π»Π°Π³Π°Π΅Ρ‚ Π½Π°ΠΌΠ½ΠΎΠ³ΠΎ Π±ΠΎΠ»Π΅Π΅ Ρ‚ΠΎΡ‡Π½Ρ‹ΠΉ подсчСт Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ².",
704701 "Load koboldcpp order": "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚ΡŒ порядок ΠΈΠ· koboldcpp",
705702 "Use Google Tokenizer": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Ρ‚ΠΎΠΊΠ΅Π½ΠΈΠ·Π°Ρ‚ΠΎΡ€ Google",
@@ -744,7 +741,7 @@
744741 "Last Assistant Prefix": "ПослСдний прСфикс ассистСнта",
745742 "System Instruction Prefix": "ΠŸΡ€Π΅Ρ„ΠΈΠΊΡ систСмной инструкции",
746743 "User Filler Message": "ΠŸΡ€ΠΈΠ½ΡƒΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ сообщСниС ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ",
747744 "Permanent": "пСрманСнтныхпостоянных",
748745 "Alt. Greetings": "Π”Ρ€. Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Ρ‹",
749746 "Smooth Streaming": "ΠŸΠ»Π°Π²Π½Ρ‹ΠΉ стриминг",
750747 "Save checkpoint": "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Ρ‡Π΅ΠΊΠΏΠΎΠΈΠ½Ρ‚",
@@ -1227,7 +1224,6 @@
12271224 "JSON-serialized array of strings.": "Бписок строк Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ JSON.",
12281225 "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.",
12291226 "Helpful tip coming soon.": "ΠŸΠΎΠ΄ΡΠΊΠ°Π·ΠΊΡƒ скоро Π΄ΠΎΠ±Π°Π²ΠΈΠΌ.",
1230- "Temperature_Last_desc": "Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Temperature сэмплСр Π² послСднюю ΠΎΡ‡Π΅Ρ€Π΅Π΄ΡŒ. Π­Ρ‚ΠΎ ΠΏΠΎΡ‡Ρ‚ΠΈ всСгда Ρ€Π°Π·ΡƒΠΌΠ½ΠΎ.\nΠŸΡ€ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ: сначала Π²Ρ‹Π±ΠΎΡ€ΠΊΠ° Π½Π°Π±ΠΎΡ€Π° ΠΏΡ€Π°Π²Π΄ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Π·Π°Ρ‚Π΅ΠΌ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Temperature для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²ΠΊΠΈ ΠΈΡ… ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… вСроятностСй (тСхничСски, Π»ΠΎΠ³ΠΈΡ‚ΠΎΠ²).\nΠŸΡ€ΠΈ ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ: сначала ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ Temperature для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚ΠΈΡ€ΠΎΠ²ΠΊΠΈ ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… вСроятностСй Π’Π‘Π•Π₯ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ², Π·Π°Ρ‚Π΅ΠΌ Π²Ρ‹Π±ΠΎΡ€ΠΊΠ° ΠΏΡ€Π°Π²Π΄ΠΎΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ² ΠΈΠ· этого.\nΠžΡ‚ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ Temperature Last ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ вСроятности Π² хвостС распрСдСлСния, Ρ‡Ρ‚ΠΎ ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ ΡˆΠ°Π½ΡΡ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ нСсогласованный ΠΎΡ‚Π²Π΅Ρ‚.",
12311227 "Speculative Ngram": "Speculative Ngram",
12321228 "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.",
12331229 "Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1734,7 +1730,7 @@
17341730 "markdown_hotkeys_desc": "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ горячиС клавиши для вставки символов Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠΈ Π² Π½Π΅ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… полях Π²Π²ΠΎΠ΄Π°. Π‘ΠΌ. '/help hotkeys'.",
17351731 "Save and Update": "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ",
17361732 "Profile name:": "НазваниС профиля:",
17371733 "API returned an error": "API Π²Π΅Ρ€Π½ΡƒΠ»ΠΎΠΎΡ‚Π²Π΅Ρ‚ΠΈΠ»ΠΎ ΠΎΡˆΠΈΠ±ΠΊΡƒΠΎΡˆΠΈΠ±ΠΊΠΎΠΉ",
17381734 "Failed to save preset": "НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ прСсСт",
17391735 "Preset name should be unique.": "НазваниС прСсСта Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΌ.",
17401736 "Invalid file": "НСвалидный Ρ„Π°ΠΉΠ»",
@@ -1756,8 +1752,7 @@
17561752 "dot quota_error": "имССтся достаточно ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΎΠ².",
17571753 "If you have sufficient credits, please try again later.": "Если ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΎΠ² достаточно, Ρ‚ΠΎ ΠΏΠΎΠ²Ρ‚ΠΎΡ€ΠΈΡ‚Π΅ ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΡƒ ΠΏΠΎΠ·Π΄Π½Π΅Π΅.",
17581754 "Proxy preset '${0}' not found": "ΠŸΡ€Π΅ΡΠ΅Ρ‚ '${0}' Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½",
17591755 "Window.ai returned an error": "Window.ai Π²Π΅Ρ€Π½ΡƒΠ»ΠΎΡ‚Π²Π΅Ρ‚ΠΈΠ» ΠΎΡˆΠΈΠ±ΠΊΡƒΠΎΡˆΠΈΠ±ΠΊΠΎΠΉ",
1760- "Get it here:": "Π—Π°Π³Ρ€ΡƒΠ·ΠΈΡ‚Π΅ здСсь:",
17611756 "Extension is not installed": "Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Π½Π΅ установлСно",
17621757 "Update or remove your reverse proxy settings.": "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚Π΅ ΠΈΠ»ΠΈ ΡƒΠ΄Π°Π»ΠΈΡ‚Π΅ ваши настройки прокси.",
17631758 "An error occurred while importing prompts. More info available in console.": "Π’ процСссС ΠΈΠΌΠΏΠΎΡ€Ρ‚Π° ΠΏΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка. ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΡƒΡŽ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ см. Π² консоли.",
@@ -1866,7 +1861,7 @@
18661861 "Group Chat could not be saved": "НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΠΎΠ²ΠΎΠΉ Ρ‡Π°Ρ‚",
18671862 "Deleted group member swiped. To get a reply, add them back to the group.": "Π’Ρ‹ ΠΏΡ‹Ρ‚Π°Π΅Ρ‚Π΅ΡΡŒ ΡΠ²Π°ΠΉΠΏΠ½ΡƒΡ‚ΡŒ ΡƒΠ΄Π°Π»Ρ‘Π½Π½ΠΎΠ³ΠΎ Ρ‡Π»Π΅Π½Π° Π³Ρ€ΡƒΠΏΠΏΡ‹. Π§Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΎΡ‚Π²Π΅Ρ‚, Π΄ΠΎΠ±Π°Π²ΡŒΡ‚Π΅ этого пСрсонаТа ΠΎΠ±Ρ€Π°Ρ‚Π½ΠΎ Π² Π³Ρ€ΡƒΠΏΠΏΡƒ.",
18681863 "Currently no group selected.": "Π’ Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π½Π΅ Π²Ρ‹Π±Ρ€Π°Π½ΠΎ Π½ΠΈ ΠΎΠ΄Π½ΠΎΠΉ Π³Ρ€ΡƒΠΏΠΏΡ‹.",
18691864 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Π§ΡƒΡ‚ΡŒ ΠΏΠΎΠΌΠ΅Π΄Π»Π΅Π½Π½Π΅Π΅! ΠŸΠ΅Ρ€Π΅Π΄ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ΠΌ Π³Ρ€ΡƒΠΏΠΏΡ‹ Π΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ΡΡŒ, ΠΏΠΎΠΊΠ° пСрсонаТпСрсонаТи Π·Π°ΠΊΠΎΠ½Ρ‡ΠΈΡ‚Π·Π°ΠΊΠΎΠ½Ρ‡Π°Ρ‚ ΠΏΠ΅Ρ‡Π°Ρ‚Π°Ρ‚ΡŒ.",
18701865 "Delete the group?": "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Π³Ρ€ΡƒΠΏΠΏΡƒ?",
18711866 "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.": "ВмСстС с Π½Π΅ΠΉ Π±ΡƒΠ΄ΡƒΡ‚ ΡƒΠ΄Π°Π»Π΅Π½Ρ‹ ΠΈ всС Π΅Ρ‘ Ρ‡Π°Ρ‚Ρ‹. Если трСбуСтся ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½ Ρ‡Π°Ρ‚, Π²ΠΎΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ΡΡŒ ΠΊΠ½ΠΎΠΏΠΊΠΎΠΉ \"ВсС Ρ‡Π°Ρ‚Ρ‹\" Π² мСню Π² Π»Π΅Π²ΠΎΠΌ Π½ΠΈΠΆΠ½Π΅ΠΌ ΡƒΠ³Π»Ρƒ.",
18721867 "Can't peek a character while group reply is being generated": "НСвозмоТно ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΊΠ°Ρ€Ρ‚ΠΎΡ‡ΠΊΡƒ пСрсонаТа Π²ΠΎ врСмя Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ ΠΎΡ‚Π²Π΅Ρ‚Π°",
@@ -1997,7 +1992,7 @@
19971992 "Default persona deleted": "Π£Π΄Π°Π»Π΅Π½Π° пСрсона ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ",
19981993 "The locked persona was deleted. You will need to set a new persona for this chat.": "Π£Π΄Π°Π»Π΅Π½Π° привязанная ΠΊ Ρ‡Π°Ρ‚Ρƒ пСрсона. Π’Π°ΠΌ Π±ΡƒΠ΄Π΅Ρ‚ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ„ΠΈΠΊΡΠΈΡ€ΠΎΠ²Π°Π½Π½ΡƒΡŽ пСрсону для этого Ρ‡Π°Ρ‚Π°.",
19991994 "Persona deleted": "ΠŸΠ΅Ρ€ΡΠΎΠ½Π° ΡƒΠ΄Π°Π»Π΅Π½Π°",
20001995 "You must bind a name to this persona before you can set it as the default.": "ΠŸΡ€Π΅ΠΆΠ΄Π΅ Ρ‡Π΅ΠΌ ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ эту пСрсону Π² качСствС пСрсоны ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ, Π΅ΠΉ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ Π·Π°Π΄Π°Ρ‚ΡŒΠΏΡ€ΠΈΡΠ²ΠΎΠΈΡ‚ΡŒ имя.",
20011996 "Persona name not set": "Π£ пСрсоны отсутствуСт имя",
20021997 "Are you sure you want to remove the default persona?": "Π’Ρ‹ Ρ‚ΠΎΡ‡Π½ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡΠ½ΡΡ‚ΡŒ статус пСрсоны ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ?",
20031998 "This persona will no longer be used by default when you open a new chat.": "Π­Ρ‚Π° пСрсона большС Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ автоматичСски Π²Ρ‹Π±ΠΈΡ€Π°Ρ‚ΡŒΡΡ ΠΏΡ€ΠΈ стартС Π½ΠΎΠ²ΠΎΠ³ΠΎ Ρ‡Π°Ρ‚Π°",
@@ -2203,5 +2198,146 @@
22032198 "Input:": "Π’Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅:",
22042199 "Tokenized text:": "Π’ΠΎΠΊΠ΅Π½ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ тСкст:",
22052200 "Token IDs:": "Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠ²:",
22062201 "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": "Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚"
22072343}
public/script.js+227 -75
@@ -172,6 +172,7 @@ import {
172172 copyText,
173173 escapeHtml,
174174 saveBase64AsFile,
175+ uuidv4,
175176} from './scripts/utils.js';
176177import { debounce_timeout } from './scripts/constants.js';
177178
@@ -494,6 +495,8 @@ export const event_types = {
494495 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
495496 GENERATE_AFTER_DATA: 'generate_after_data',
496497 GROUP_MEMBER_DRAFTED: 'group_member_drafted',
498+ GROUP_WRAPPER_STARTED: 'group_wrapper_started',
499+ GROUP_WRAPPER_FINISHED: 'group_wrapper_finished',
497500 WORLD_INFO_ACTIVATED: 'world_info_activated',
498501 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
499502 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',
@@ -514,6 +517,9 @@ export const event_types = {
514517 ONLINE_STATUS_CHANGED: 'online_status_changed',
515518 IMAGE_SWIPED: 'image_swiped',
516519 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',
517523 TOOL_CALLS_PERFORMED: 'tool_calls_performed',
518524 TOOL_CALLS_RENDERED: 'tool_calls_rendered',
519525};
@@ -589,7 +595,7 @@ let is_delete_mode = false;
589595let fav_ch_checked = false;
590596let scrollLock = false;
591597export let abortStatusCheck = new AbortController();
592598export let charDragDropHandler = null;
593599
594600/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */
595601export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;
@@ -1140,7 +1146,7 @@ export async function clearItemizedPrompts() {
11401146async function getStatusHorde() {
11411147 try {
11421148 const hordeStatus = await checkHordeStatus();
11431149 setOnlineStatus(hordeStatus ? 't`Connected'` : 'no_connection');
11441150 }
11451151 catch {
11461152 setOnlineStatus('no_connection');
@@ -1207,7 +1213,7 @@ async function getStatusTextgen() {
12071213 }
12081214
12091215 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
12101216 setOnlineStatus('t`Status check bypassed'`);
12111217 return resultCheckStatus();
12121218 }
12131219
@@ -1232,7 +1238,7 @@ async function getStatusTextgen() {
12321238 setOnlineStatus(textgen_settings.togetherai_model);
12331239 } else if (textgen_settings.type === textgen_types.OLLAMA) {
12341240 loadOllamaModels(data?.data);
12351241 setOnlineStatus(textgen_settings.ollama_model || 't`Connected'`);
12361242 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
12371243 loadInfermaticAIModels(data?.data);
12381244 setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1256,7 +1262,7 @@ async function getStatusTextgen() {
12561262 setOnlineStatus(textgen_settings.tabby_model || data?.result);
12571263 } else if (textgen_settings.type === textgen_types.GENERIC) {
12581264 loadGenericModels(data?.data);
12591265 setOnlineStatus(textgen_settings.generic_model || data?.result || 't`Connected'`);
12601266 } else {
12611267 setOnlineStatus(data?.result);
12621268 }
@@ -1370,8 +1376,11 @@ export function resultCheckStatus() {
13701376 * If the character ID doesn't exist, if the chat is being saved, or if a group is being generated, this function does nothing.
13711377 * If the character is different from the currently selected one, it will clear the chat and reset any selected character or group.
13721378 * @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.
13731382 */
13741383export async function selectCharacterById(id, { switchMenu = true } = {}) {
13751384 if (characters[id] === undefined) {
13761385 return;
13771386 }
@@ -1400,9 +1409,9 @@ export async function selectCharacterById(id) {
14001409 }
14011410 } else {
14021411 //if clicked on character that was already selected
14031412 switchMenu && (selected_button = 'character_edit');
14041413 await unshallowCharacter(this_chid);
14051414 select_selected_character(this_chid, { switchMenu });
14061415 }
14071416}
14081417
@@ -1787,6 +1796,7 @@ export async function getCharacters() {
17871796 body: JSON.stringify({}),
17881797 });
17891798 if (response.ok === true) {
1799+ const previousAvatar = this_chid !== undefined ? characters[this_chid]?.avatar : null;
17901800 characters.splice(0, characters.length);
17911801 const getData = await response.json();
17921802 for (let i = 0; i < getData.length; i++) {
@@ -1800,8 +1810,16 @@ export async function getCharacters() {
18001810
18011811 characters[i]['chat'] = String(characters[i]['chat']);
18021812 }
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+ }
18051823 }
18061824
18071825 await getGroups();
@@ -2730,6 +2748,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
27302748 environment.mesExamplesRaw = fields.mesExamples || '';
27312749 environment.charVersion = fields.version || '';
27322750 environment.char_version = fields.version || '';
2751+ environment.charDepthPrompt = fields.charDepthPrompt || '';
27332752 }
27342753
27352754 // Must be substituted last so that they're replaced inside {{description}}
@@ -3081,13 +3100,38 @@ export function baseChatReplace(value, name1, name2) {
30813100
30823101/**
30833102 * 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
30853117 */
30863118export 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+ };
30883132 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
30893133
30903134 const character = characters[this_chidcurrentChid];
30913135
30923136 if (!character) {
30933137 return result;
@@ -3101,9 +3145,10 @@ export function getCharacterCardFields() {
31013145 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?.trim(), name1, name2) : '';
31023146 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
31033147 result.version = character.data?.character_version ?? '';
3148+ result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
31043149
31053150 if (selected_group) {
31063151 const groupCards = getGroupCharacterCards(selected_group, Number(this_chidcurrentChid));
31073152
31083153 if (groupCards) {
31093154 result.description = groupCards.description;
@@ -3596,7 +3641,8 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
35963641 throw new Error(data.response);
35973642 }
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
36013647 if (!message) {
36023648 throw new Error('No message generated');
@@ -3905,6 +3951,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
39053951 mesExamples,
39063952 system,
39073953 jailbreak,
3954+ charDepthPrompt,
39083955 } = getCharacterCardFields();
39093956
39103957 if (main_api !== 'openai') {
@@ -3927,7 +3974,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
39273974 setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);
39283975 });
39293976 } else {
3930- const depthPromptText = baseChatReplace(characters[this_chid]?.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2) || '';
3977+ const depthPromptText = charDepthPrompt || '';
39313978 const depthPromptDepth = characters[this_chid]?.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;
39323979 const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid]?.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);
39333980 setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);
@@ -5868,13 +5915,14 @@ function extractMultiSwipes(data, type) {
58685915 return swipes;
58695916}
58705917
58715918export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncompleteSentences = false, stoppingStrings = null, includeUserPromptBias = true) {
58725919 if (!getMessage) {
58735920 return '';
58745921 }
58755922
58765923 // Add the prompt bias before anything else
58775924 if (
5925+ includeUserPromptBias &&
58785926 power_user.user_prompt_bias &&
58795927 !isImpersonate &&
58805928 !isContinue &&
@@ -6261,7 +6309,6 @@ export function syncMesToSwipe(messageId = null) {
62616309 }
62626310
62636311 const targetMessage = chat[targetMessageId];
6264-
62656312 if (!targetMessage) {
62666313 return false;
62676314 }
@@ -6295,6 +6342,68 @@ export function syncMesToSwipe(messageId = null) {
62956342}
62966343
62976344/**
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+ */
6353+export 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+/**
62986407 * Saves the image to the message object.
62996408 * @param {ParsedImage} img Image object
63006409 * @param {object} mes Chat message object
@@ -6527,6 +6636,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65276636
65286637 await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar);
65296638
6639+ // Unload current character
6640+ setCharacterId(undefined);
65306641 // Reload characters list
65316642 await getCharacters();
65326643
@@ -6535,7 +6646,6 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65356646
65366647 if (newChId !== -1) {
65376648 // Select the character after the renaming
6538- setCharacterId(undefined);
65396649 await selectCharacterById(newChId);
65406650
65416651 // Async delay to update UI
@@ -6668,7 +6778,22 @@ export function saveChatDebounced() {
66686778 }, DEFAULT_SAVE_EDIT_TIMEOUT);
66696779}
66706780
6671-export 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+ */
6791+export 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+
66726797 const metadata = { ...chat_metadata, ...(withMetadata || {}) };
66736798 const fileName = chatName ?? characters[this_chid]?.chat;
66746799
@@ -6688,53 +6813,59 @@ export async function saveChat(chatName, withMetadata, mesId) {
66886813 toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);
66896814 throw new Error('Group chat saved from saveChat');
66906815 }
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- */
67016816 });
67026817
67036818 const trimmed_chattrimmedChat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
67046819 ? chat.slice(0, parseIntNumber(mesId) + 1)
67056820 : chat.slice();
67066821
67076822 varconst save_chatchatToSave = [
67086823 {
67096824 user_name: name1,
67106825 character_name: name2,
67116826 create_date: chat_create_date,
67126827 chat_metadata: metadata,
67136828 },
67146829 ...trimmed_chattrimmedChat,
67156830 ];
6716- return jQuery.ajax({
6831+
6717- type: 'POST',
6832+ try {
67186833 url: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({
67206838 ch_name: characters[this_chid].name,
67216839 file_name: fileName,
67226840 chat: save_chatchatToSave,
67236841 avatar_url: characters[this_chid].avatar,
6842+ force: force,
67246843 }),
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);
67336867 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- });
67386869}
67396870
67406871async function read_avatar_load(input) {
@@ -6861,14 +6992,14 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t
68616992 */
68626993export async function unshallowCharacter(characterId) {
68636994 if (characterId === undefined) {
68646995 console.warndebug('Undefined character cannot be unshallowed');
68656996 return;
68666997 }
68676998
68686999 /** @type {import('./scripts/char-data.js').v1CharData} */
68697000 const character = characters[characterId];
68707001 if (!character) {
68717002 console.warndebug('Character not found:', characterId);
68727003 return;
68737004 }
68747005
@@ -6879,7 +7010,7 @@ export async function unshallowCharacter(characterId) {
68797010
68807011 const avatar = character.avatar;
68817012 if (!avatar) {
68827013 console.warndebug('Character has no avatar field:', characterId);
68837014 return;
68847015 }
68857016
@@ -6911,6 +7042,9 @@ export async function getChat() {
69117042 } else {
69127043 chat_create_date = humanizedDateTime();
69137044 }
7045+ if (!chat_metadata['integrity']) {
7046+ chat_metadata['integrity'] = uuidv4();
7047+ }
69147048 await getChatResult();
69157049 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) {
78828016 }
78838017}
78848018
7885-export 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+ */
8025+export function select_selected_character(chid, { switchMenu = true } = {}) {
78868026 //character select
78878027 //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');
78888028 select_rm_create({ switchMenu });
78898029 switchMenu && setMenuType('character_edit');
78908030 $('#delete_button').css('display', 'flex');
78918031 $('#export_button').css('display', 'flex');
7892- var display_name = characters[chid].name;
78938032
78948033 //create text poles
78958034 $('#rm_button_back').css('display', 'none');
@@ -7904,7 +8043,7 @@ export function select_selected_character(chid) {
79048043
79058044 // Don't update the navbar name if we're peeking the group member defs
79068045 if (!selected_group) {
79078046 $('#rm_button_selected_ch').children('h2').text(display_namecharacters[chid].name);
79088047 }
79098048
79108049 $('#add_avatar_button').val('');
@@ -7935,22 +8074,20 @@ export function select_selected_character(chid) {
79358074 $('#chat_import_avatar_url').val(characters[chid].avatar);
79368075 $('#chat_import_character_name').val(characters[chid].name);
79378076 $('#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
79438078 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;
79468081 $('#name_divavatar_load_preview').removeClassattr('displayBlocksrc', avatarUrl);
7947- $('#name_div').addClass('displayNone');
7948- $('#renameCharButton').css('display', '');
79498082 $('.open_alternate_greetings').data('chid', chid);
79508083 $('#set_character_world').data('chid', chid);
79518084 setWorldInfoButtonClass(chid);
79528085 checkEmbeddedWorld(chid);
79538086
8087+ $('#name_div').removeClass('displayBlock');
8088+ $('#name_div').addClass('displayNone');
8089+ $('#renameCharButton').css('display', '');
8090+
79548091 $('#form_create').attr('actiontype', 'editcharacter');
79558092 $('.form_create_bottom_buttons_block .chat_lorebook_button').show();
79568093
@@ -7962,8 +8099,13 @@ export function select_selected_character(chid) {
79628099 saveSettingsDebounced();
79638100}
79648101
7965-function 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+ */
8107+function select_rm_create({ switchMenu = true } = {}) {
8108+ switchMenu && setMenuType('create');
79678109
79688110 //console.log('select_rm_Create() -- selected button: '+selected_button);
79698111 if (selected_button == 'create') {
@@ -7973,7 +8115,7 @@ function select_rm_create() {
79738115 }
79748116 }
79758117
79768118 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
79778119
79788120 $('#set_chat_scenario').hide();
79798121 $('#delete_button_div').css('display', 'none');
@@ -8293,10 +8435,9 @@ export async function deleteSwipe(swipeId = null) {
82938435 lastMessage.swipe_info.splice(swipeId, 1);
82948436 }
82958437
82968438 // Select the next swipswipe, or the one before if it was the last one
82978439 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
83018442 await saveChatConditional();
83028443 await reloadCurrentChat();
@@ -9196,6 +9337,17 @@ function swipe_right(_event, { source, repeated } = {}) {
91969337 }
91979338}
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+ */
91999351export const CONNECT_API_MAP = {
92009352 // Default APIs not contined inside text gen / chat gen
92019353 'kobold': {
@@ -10417,7 +10569,7 @@ jQuery(async function () {
1041710569 e.stopPropagation();
1041810570 chat_file_for_del = $(this).attr('file_name');
1041910571 console.debug('detected cross click for' + chat_file_for_del);
1042010572 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
1042110573 });
1042210574
1042310575 $('#advanced_div').click(function () {
public/scripts/PromptManager.js+1 -1
@@ -4,7 +4,7 @@ import { DOMPurify, Popper } from '../lib.js';
44
55import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';
66import { is_group_generating } from './group-chats.js';
77import { Message, MessageCollection, TokenHandler } from './openai.js';
88import { power_user } from './power-user.js';
99import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
1010import { debounce_timeout } from './constants.js';
public/scripts/authors-note.js+5 -0
@@ -17,6 +17,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
1717import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
1818export { MODULE_NAME as NOTE_MODULE_NAME };
1919import { t } from './i18n.js';
20+import { MacrosParser } from './macros.js';
2021
2122const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
2223
@@ -576,4 +577,8 @@ export function initAuthorsNote() {
576577 `,
577578 }));
578579 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`);
579584}
public/scripts/autocomplete/AutoComplete.js+0 -2
@@ -3,10 +3,8 @@ import { debounce, escapeRegex } from '../utils.js';
33import { AutoCompleteOption } from './AutoCompleteOption.js';
44import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
55import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';
6-// eslint-disable-next-line no-unused-vars
76import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
87import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
9-import { Popup, getTopmostModalLayer } from '../popup.js';
108
119/**@readonly*/
1210/**@enum {Number}*/
public/scripts/autocomplete/AutoCompleteNameResultBase.js+0 -1
@@ -1,4 +1,3 @@
1-import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
21import { AutoCompleteOption } from './AutoCompleteOption.js';
32
43
public/scripts/autocomplete/AutoCompleteOption.js+0 -1
@@ -1,4 +1,3 @@
1-import { SlashCommand } from '../slash-commands/SlashCommand.js';
21import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
32
43
public/scripts/backgrounds.js+2 -1
@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
55import { SlashCommand } from './slash-commands/SlashCommand.js';
66import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
77import { flashHighlight, stringFormat } from './utils.js';
8+import { t } from './i18n.js';
89
910const BG_METADATA_KEY = 'custom_background';
1011const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
243244 const fileExtension = oldBg.split('.').pop();
244245 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
245246 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
246247 const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
247248
248249 if (!newBgExtensionless) {
249250 console.debug('no new_bg_extensionless');
public/scripts/bookmarks.js+4 -5
@@ -1,7 +1,6 @@
11import {
22 characters,
33 saveChat,
4- system_messages,
54 system_message_types,
65 this_chid,
76 openCharacterChat,
@@ -13,7 +12,7 @@ import {
1312 saveChatConditional,
1413 saveItemizedPrompts,
1514} from '../script.js';
1615import { humanizedDateTime, getMessageTimeStamp } from './RossAscends-mods.js';
1716import {
1817 getGroupPastChats,
1918 group_activation_strategy,
@@ -156,7 +155,7 @@ export async function createBranch(mesId) {
156155 if (selected_group) {
157156 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
158157 } else {
159158 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
160159 }
161160 // append to branches list if it exists
162161 // otherwise create it
@@ -212,7 +211,7 @@ export async function createNewBookmark(mesId, { forceName = null } = {}) {
212211 if (selected_group) {
213212 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
214213 } else {
215214 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
216215 }
217216
218217 lastMes.extra['bookmark_link'] = name;
@@ -358,7 +357,7 @@ export async function convertSoloToGroupChat() {
358357 // Click on the freshly selected group to open it
359358 await openGroupById(group.id);
360359
361360 toastr.success('t`The chat has been successfully converted!'`);
362361}
363362
364363/**
public/scripts/custom-request.js+234 -34
@@ -1,20 +1,20 @@
11import { getPresetManager } from './preset-manager.js';
22import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';
33import { getTextGenServer } from './textgen-settings.js';
4+import { extractReasoningFromData } from './reasoning.js';
5+import { formatInstructModeChat, formatInstructModePrompt, names_behavior_types } from './instruct-mode.js';
46
57// #region Type Definitions
68/**
79 * @typedef {Object} TextCompletionRequestBase
8- * @property {string} prompt - The text prompt for completion
910 * @property {number} max_tokens - Maximum number of tokens to generate
1011 * @property {string} [model] - Optional model name
1112 * @property {string} api_type - Type of API to use
1213 * @property {string} [api_server] - Optional API server URL
1314 * @property {number} [temperature] - Optional temperature parameter
15+ * @property {number} [min_p] - Optional min_p parameter
1416 */
1517
16-/** @typedef {Record<string, any> & TextCompletionRequestBase} TextCompletionRequest */
17-
1818/**
1919 * @typedef {Object} TextCompletionPayloadBase
2020 * @property {string} prompt - The text prompt for completion
@@ -41,9 +41,17 @@ import { getTextGenServer } from './textgen-settings.js';
4141 * @property {string} chat_completion_source - Source provider for chat completion
4242 * @property {number} max_tokens - Maximum number of tokens to generate
4343 * @property {number} [temperature] - Optional temperature parameter for response randomness
44+ * @property {string} [custom_url] - Optional custom URL for chat completion
4445 */
4546
4647/** @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+
4755// #endregion
4856
4957/**
@@ -53,11 +61,11 @@ export class TextCompletionService {
5361 static TYPE = 'textgenerationwebui';
5462
5563 /**
56- * @param {TextCompletionRequest} custom
64+ * @param {Record<string, any> & TextCompletionRequestBase & {prompt: string}} custom
5765 * @returns {TextCompletionPayload}
5866 */
5967 static createRequestData({ prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) {
6068 returnconst payload = {
6169 ...props,
6270 prompt,
6371 max_tokens,
@@ -66,15 +74,25 @@ export class TextCompletionService {
6674 api_type,
6775 api_server: api_server ?? getTextGenServer(api_type),
6876 temperature,
77+ min_p,
6978 stream: false,
7079 };
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;
7189 }
7290
7391 /**
7492 * Sends a text completion request to the specified server
7593 * @param {TextCompletionPayload} data Request data
7694 * @param {boolean?} extractData Extract message from the response. Default true
7795 * @returns {Promise<stringExtractedData | any>} Extracted data or the raw response
7896 * @throws {Error}
7997 */
8098 static async sendRequest(data, extractData = true) {
@@ -91,31 +109,150 @@ export class TextCompletionService {
91109 throw json;
92110 }
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+ };
95124 }
96125
97126 /**
98- * @param {string} presetName
127+ * Process and send a text completion request with optional preset & instruct
99- * @param {TextCompletionRequest} custom
128+ * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom
100129 * @param {boolean?Object} extractData Extract message from theoptions response.- DefaultConfiguration trueoptions
101- * @returns {Promise<string | any>} Extracted data or the raw response
130+ * @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
102134 * @throws {Error}
103135 */
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) {
105147 const presetManager = getPresetManager(this.TYPE);
106148 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+ }
108160 }
109161
110- const preset = presetManager.getCompletionPresetByName(presetName);
162+ // Handle instruct formatting if requested
111163 if (!presetArray.isArray(prompt) && instructName) {
112164 throwconst newinstructPresetManager Error= getPresetManager('Preset not foundinstruct');
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');
113218 }
114219
115- const data = this.createRequestData({ ...preset, ...custom });
220+ // @ts-ignore
221+ const data = this.createRequestData(requestData);
116222
117223 return await this.sendRequest(data, extractData);
118224 }
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+ }
119256}
120257
121258/**
@@ -128,23 +265,33 @@ export class ChatCompletionService {
128265 * @param {ChatCompletionPayload} custom
129266 * @returns {ChatCompletionPayload}
130267 */
131268 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, custom_url, ...props }) {
132269 returnconst payload = {
133270 ...props,
134271 messages,
135272 model,
136273 chat_completion_source,
137274 max_tokens,
138275 temperature,
276+ custom_url,
139277 stream: false,
140278 };
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;
141288 }
142289
143290 /**
144291 * Sends a chat completion request
145292 * @param {ChatCompletionPayload} data Request data
146293 * @param {boolean?} extractData Extract message from the response. Default true
147294 * @returns {Promise<stringExtractedData | any>} Extracted data or the raw response
148295 * @throws {Error}
149296 */
150297 static async sendRequest(data, extractData = true) {
@@ -161,29 +308,82 @@ export class ChatCompletionService {
161308 throw json;
162309 }
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+ };
165323 }
166324
167325 /**
168- * @param {string} presetName
326+ * Process and send a chat completion request with optional preset
169327 * @param {ChatCompletionPayload} custom
170328 * @param {booleanObject} extractData Extract message from theoptions response.- DefaultConfiguration trueoptions
171- * @returns {Promise<string | any>} Extracted data or the raw response
329+ * @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
172332 * @throws {Error}
173333 */
174334 static async sendRequestWithPresetprocessRequest(presetNamecustom, customoptions, extractData = true) {
175335 const presetManager{ presetName } = getPresetManager(this.TYPE)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) {
180342 const preset = presetManager.getCompletionPresetByName(presetName);
181343 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+ }
183353 }
184354
185355 const data = this.createRequestData({ ...preset, ...custom }requestData);
186356
187357 return await this.sendRequest(data, extractData);
188358 }
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+ }
189389}
public/scripts/extensions/assets/index.js+1 -1
@@ -424,7 +424,7 @@ jQuery(async () => {
424424 installHintButton.on('click', async function () {
425425 const installButton = $('#third_party_extension_button');
426426 flashHighlight(installButton, 5000);
427427 toastr.info('t`Click the flashing button to install extensions.'`, 't`How to install extensions?'`);
428428 });
429429
430430 const connectButton = windowHtml.find('#assets-connect-button');
public/scripts/extensions/attachments/attach-button.html+1 -1
@@ -1,4 +1,4 @@
11<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.">
22 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
33 <span data-i18n="Attach a File">Attach a File</span>
44</div>
public/scripts/extensions/caption/settings.html+3 -0
@@ -42,6 +42,9 @@
4242 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
4343 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>
4444 <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>
4548 <option data-type="zerooneai" value="yi-vision">yi-vision</option>
4649 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
4750 <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 @@
11import { DOMPurify, Fuse } from '../../../lib.js';
22
33import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
44import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
3939 'proxy',
4040 'stop-strings',
4141 'start-reply-with',
42+ 'reasoning-template',
4243];
4344
4445const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
5455 'tokenizer',
5556 'stop-strings',
5657 'start-reply-with',
58+ 'reasoning-template',
5759];
5860
5961const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
7072 'tokenizer': 'Tokenizer',
7173 'stop-strings': 'Custom Stopping Strings',
7274 'start-reply-with': 'Start Reply With',
75+ 'reasoning-template': 'Reasoning Template',
7376};
7477
7578/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
154157 * @property {string} [tokenizer] Tokenizer
155158 * @property {string} [stop-strings] Custom Stopping Strings
156159 * @property {string} [start-reply-with] Start Reply With
160+ * @property {string} [reasoning-template] Reasoning Template
157161 * @property {string[]} [exclude] Commands to exclude
158162 */
159163
@@ -267,9 +271,14 @@ async function createConnectionProfile(forceName = null) {
267271 });
268272 const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
269273 const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
270274 constlet name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
271-
275+ // If it's cancelled, it will be false
276+ if (!name) {
277+ return null;
278+ }
279+ name = DOMPurify.sanitize(String(name));
272280 if (!name) {
281+ toastr.error('Name cannot be empty.');
273282 return null;
274283 }
275284
@@ -303,7 +312,8 @@ async function deleteConnectionProfile() {
303312 return;
304313 }
305314
306315 const nameprofile = extension_settings.connectionManager.profiles[index].name;
316+ const name = profile.name;
307317 const confirm = await Popup.show.confirm(t`Are you sure you want to delete the selected profile?`, name);
308318
309319 if (!confirm) {
@@ -313,6 +323,8 @@ async function deleteConnectionProfile() {
313323 extension_settings.connectionManager.profiles.splice(index, 1);
314324 extension_settings.connectionManager.selectedProfile = null;
315325 saveSettingsDebounced();
326+
327+ await eventSource.emit(event_types.CONNECTION_PROFILE_DELETED, profile);
316328}
317329
318330/**
@@ -512,6 +524,7 @@ async function renderDetailsContent(detailsContent) {
512524 saveSettingsDebounced();
513525 renderConnectionProfiles(profiles);
514526 await renderDetailsContent(detailsContent);
527+ await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
515528 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
516529 });
517530
@@ -523,9 +536,11 @@ async function renderDetailsContent(detailsContent) {
523536 console.log('No profile selected');
524537 return;
525538 }
539+ const oldProfile = structuredClone(profile);
526540 await updateConnectionProfile(profile);
527541 await renderDetailsContent(detailsContent);
528542 saveSettingsDebounced();
543+ await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
529544 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
530545 toastr.success('Connection profile updated', '', { timeOut: 1500 });
531546 });
@@ -559,7 +574,7 @@ async function renderDetailsContent(detailsContent) {
559574 return acc;
560575 }, {});
561576 const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));
562577 constlet newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
563578 rows: 2,
564579 customButtons: [{
565580 text: t`Save and Update`,
@@ -571,7 +586,13 @@ async function renderDetailsContent(detailsContent) {
571586 }],
572587 });
573588
589+ // If it's cancelled, it will be false
590+ if (!newName) {
591+ return;
592+ }
593+ newName = DOMPurify.sanitize(String(newName));
574594 if (!newName) {
595+ toastr.error('Name cannot be empty.');
575596 return;
576597 }
577598
@@ -584,6 +605,7 @@ async function renderDetailsContent(detailsContent) {
584605 return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];
585606 }).get();
586607
608+ const oldProfile = structuredClone(profile);
587609 if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {
588610 profile.exclude = newExcludeList;
589611 for (const command of newExcludeList) {
@@ -598,10 +620,11 @@ async function renderDetailsContent(detailsContent) {
598620
599621 if (profile.name !== newName) {
600622 toastr.success('Connection profile renamed.');
601623 profile.name = String(newName);
602624 }
603625
604626 saveSettingsDebounced();
627+ await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
605628 renderConnectionProfiles(profiles);
606629 await renderDetailsContent(detailsContent);
607630 });
@@ -704,6 +727,7 @@ async function renderDetailsContent(detailsContent) {
704727 saveSettingsDebounced();
705728 renderConnectionProfiles(profiles);
706729 await renderDetailsContent(detailsContent);
730+ await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
707731 return profile.name;
708732 },
709733 }));
@@ -718,9 +742,11 @@ async function renderDetailsContent(detailsContent) {
718742 toastr.warning('No profile selected.');
719743 return '';
720744 }
745+ const oldProfile = structuredClone(profile);
721746 await updateConnectionProfile(profile);
722747 await renderDetailsContent(detailsContent);
723748 saveSettingsDebounced();
749+ await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
724750 return profile.name;
725751 },
726752 }));
public/scripts/extensions/expressions/index.js+59 -17
@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateRaw, getRequestHeaders, m
44import { dragElement, isMobile } from '../../RossAscends-mods.js';
55import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
66import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
77import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js';
88import { hideMutedSprites, selected_group } from '../../group-chats.js';
99import { isJsonSchemaSupported } from '../../textgen-settings.js';
1010import { debounce_timeout } from '../../constants.js';
@@ -17,6 +17,7 @@ import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandRetur
1717import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
1818import { Popup, POPUP_RESULT } from '../../popup.js';
1919import { t } from '../../i18n.js';
20+import { removeReasoningFromString } from '../../reasoning.js';
2021export { MODULE_NAME };
2122
2223/**
@@ -678,7 +679,7 @@ async function setSpriteFolderCommand(_, folder) {
678679 return '';
679680}
680681
681682async function classifyCallback(/** @type {{api: string?, filter: string?, prompt: string?}} */ { api = null, filter = null, prompt = null }, text) {
682683 if (!text) {
683684 toastr.error('No text provided');
684685 return '';
@@ -689,13 +690,14 @@ async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ {
689690 }
690691
691692 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;
693+ const filterAvailable = !isFalseBoolean(filter);
692694
693695 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {
694696 toastr.warning('Text classification is disabled or not available');
695697 return '';
696698 }
697699
698700 const label = await getExpressionLabel(text, expressionApi, { filterAvailable: filterAvailable, customPrompt: prompt });
699701 console.debug(`Classification result for "${text}": ${label}`);
700702 return label;
701703}
@@ -928,6 +930,9 @@ function parseLlmResponse(emotionResponse, labels) {
928930
929931 return response;
930932 } catch {
933+ // Clean possible reasoning from response
934+ emotionResponse = removeReasoningFromString(emotionResponse);
935+
931936 const fuse = new Fuse(labels, { includeScore: true });
932937 console.debug('Using fuzzy search in labels:', labels);
933938 const result = fuse.search(emotionResponse);
@@ -988,10 +993,11 @@ function onTextGenSettingsReady(args) {
988993 * @param {string} text - The text to classify and retrieve the expression label for.
989994 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
990995 * @param {object} [options={}] - Optional arguments.
996+ * @param {boolean?} [options.filterAvailable=null] - Whether to filter available expressions. If not specified, uses the extension setting.
991997 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
992998 * @returns {Promise<string?>} - The label of the expression.
993999 */
9941000export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { filterAvailable = null, customPrompt = null } = {}) {
9951001 // Return if text is undefined, saving a costly fetch request
9961002 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
9971003 return extension_settings.expressions.fallback_expression;
@@ -1003,6 +1009,11 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10031009
10041010 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+
10061017 try {
10071018 switch (expressionsApi) {
10081019 // Local BERT pipeline
@@ -1027,7 +1038,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10271038 return extension_settings.expressions.fallback_expression;
10281039 }
10291040
10301041 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
10311042 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
10321043 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
10331044 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
@@ -1040,7 +1051,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10401051 return extension_settings.expressions.fallback_expression;
10411052 }
10421053
10431054 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
10441055 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
10451056 const messages = [
10461057 { role: 'user', content: text + '\n\n' + prompt },
@@ -1320,12 +1331,28 @@ function getCachedExpressions() {
13201331 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
13211332}
13221333
13231334export async function getExpressionsList({ filterAvailable = false } = {}) {
13241335 // ReturnIf there is no cached list, ifload availableand cache it
13251336 if (!Array.isArray(expressionsList)) {
13261337 returnexpressionsList getCachedExpressions= await resolveExpressionsList();
13271338 }
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+
13291356 /**
13301357 * Returns the list of expressions from the API or fallback in offline mode.
13311358 * @returns {Promise<string[]>}
@@ -1372,9 +1399,6 @@ export async function getExpressionsList() {
13721399 expressionsList = DEFAULT_EXPRESSIONS.slice();
13731400 return expressionsList;
13741401 }
1375-
1376- const result = await resolveExpressionsList();
1377- return [...result, ...extension_settings.expressions.custom].filter(onlyUnique);
13781402}
13791403
13801404/**
@@ -1810,7 +1834,7 @@ async function onClickExpressionUpload(event) {
18101834 }
18111835 }
18121836 } else {
18131837 spriteName = withoutExtension(clickedFileNameexpression);
18141838 }
18151839
18161840 if (!spriteName) {
@@ -2102,6 +2126,10 @@ function migrateSettings() {
21022126 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');
21032127 saveSettingsDebounced();
21042128 });
2129+ $('#expressions_filter_available').prop('checked', extension_settings.expressions.filterAvailable).on('input', function () {
2130+ extension_settings.expressions.filterAvailable = !!$(this).prop('checked');
2131+ saveSettingsDebounced();
2132+ });
21052133 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
21062134 $(document).on('dragstart', '.expression', (e) => {
21072135 e.preventDefault();
@@ -2154,7 +2182,7 @@ function migrateSettings() {
21542182 imgElement.src = '';
21552183 }
21562184
2157- setExpressionOverrideHtml();
2185+ setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
21582186
21592187 if (isVisualNovelMode()) {
21602188 $('#visual-novel-wrapper').empty();
@@ -2279,13 +2307,13 @@ function migrateSettings() {
22792307 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
22802308 name: 'expression-list',
22812309 aliases: ['expressions'],
22822310 /** @type {(args: {return: string, filter: string}) => Promise<string>} */
22832311 callback: async (args) => {
22842312 let returnType =
22852313 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
22862314 (args.return);
22872315
22882316 const list = await getExpressionsList({ filterAvailable: !isFalseBoolean(args.filter) });
22892317
22902318 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
22912319 },
@@ -2298,6 +2326,13 @@ function migrateSettings() {
22982326 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
22992327 forceEnum: true,
23002328 }),
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+ }),
23012336 ],
23022337 returns: 'The comma-separated list of available expressions, including custom expressions.',
23032338 helpString: 'Returns a list of available expressions, including custom expressions.',
@@ -2314,6 +2349,13 @@ function migrateSettings() {
23142349 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),
23152350 }),
23162351 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({
23172359 name: 'prompt',
23182360 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',
23192361 typeList: [ARGUMENT_TYPE.STRING],
public/scripts/extensions/expressions/settings.html+5 -1
@@ -29,7 +29,11 @@
2929 </select>
3030 </div>
3131 <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">
3337 <span data-i18n="LLM Prompt">LLM Prompt</span>
3438 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
3539 <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
21import { QuickReply } from '../src/QuickReply.js';
32import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';
43import { QuickReplySet } from '../src/QuickReplySet.js';
5-// eslint-disable-next-line no-unused-vars
64import { QuickReplySettings } from '../src/QuickReplySettings.js';
7-// eslint-disable-next-line no-unused-vars
85import { SettingsUi } from '../src/ui/SettingsUi.js';
96import { onlyUnique } from '../../../utils.js';
107
public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js+0 -2
@@ -1,7 +1,5 @@
11import { warn } from '../index.js';
2-// eslint-disable-next-line no-unused-vars
32import { QuickReply } from './QuickReply.js';
4-// eslint-disable-next-line no-unused-vars
53import { QuickReplySettings } from './QuickReplySettings.js';
64
75export class AutoExecuteHandler {
public/scripts/extensions/quick-reply/src/QuickReply.js+0 -2
@@ -635,7 +635,6 @@ export class QuickReply {
635635 }, { passive:true });
636636 const getLineStart = ()=>{
637637 const start = message.selectionStart;
638- const end = message.selectionEnd;
639638 let lineStart;
640639 if (start == 0 || message.value[start - 1] == '\n') {
641640 // cursor is already at beginning of line
@@ -701,7 +700,6 @@ export class QuickReply {
701700 } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {
702701 // new line, keep indent
703702 const start = message.selectionStart;
704- const end = message.selectionEnd;
705703 let lineStart = getLineStart();
706704 const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? '';
707705 if (indent.length) {
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+0 -1
@@ -8,7 +8,6 @@ import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashC
88import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
99import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
1010import { isTrueBoolean } from '../../../utils.js';
11-// eslint-disable-next-line no-unused-vars
1211import { QuickReplyApi } from '../api/QuickReplyApi.js';
1312import { QuickReply } from './QuickReply.js';
1413import { QuickReplySet } from './QuickReplySet.js';
public/scripts/extensions/quick-reply/src/ui/ButtonUi.js+0 -1
@@ -1,7 +1,6 @@
11import { animation_duration } from '../../../../../script.js';
22import { dragElement } from '../../../../RossAscends-mods.js';
33import { loadMovingUIState } from '../../../../power-user.js';
4-// eslint-disable-next-line no-unused-vars
54import { QuickReplySettings } from '../QuickReplySettings.js';
65
76export class ButtonUi {
public/scripts/extensions/quick-reply/src/ui/SettingsUi.js+0 -1
@@ -3,7 +3,6 @@ import { getSortableDelay } from '../../../../utils.js';
33import { log, warn } from '../../index.js';
44import { QuickReply } from '../QuickReply.js';
55import { QuickReplySet } from '../QuickReplySet.js';
6-// eslint-disable-next-line no-unused-vars
76import { QuickReplySettings } from '../QuickReplySettings.js';
87
98export class SettingsUi {
public/scripts/extensions/quick-reply/src/ui/ctx/ContextMenu.js+0 -1
@@ -1,5 +1,4 @@
11import { QuickReply } from '../../QuickReply.js';
2-// eslint-disable-next-line no-unused-vars
32import { QuickReplySet } from '../../QuickReplySet.js';
43import { MenuHeader } from './MenuHeader.js';
54import { MenuItem } from './MenuItem.js';
public/scripts/extensions/regex/editor.html+3 -4
@@ -18,9 +18,8 @@
1818
1919 <div id="regex_info_block_wrapper">
2020 <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>
2423 </a>
2524 </div>
2625
@@ -148,7 +147,7 @@
148147 </label>
149148 <span>
150149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
151150 <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>
152151 </span>
153152 <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.">
154153 <input type="checkbox" name="only_format_display" />
public/scripts/extensions/regex/index.js+1 -1
@@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
398398 for (const script of scripts) {
399399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
400400 if (script.disabled) {
401401 toastr.warning(t`Regex script "${scriptName}" is disabled.`);
402402 return value;
403403 }
404404
public/scripts/extensions/shared.js+309 -1
@@ -1,5 +1,6 @@
11import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';
22import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
3+import { t } from '../i18n.js';
34import { oai_settings } from '../openai.js';
45import { SECRET_KEYS, secret_state } from '../secrets.js';
56import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
@@ -273,3 +274,310 @@ export async function getWebLlmContextSize() {
273274 const model = await engine.getCurrentModelInfo();
274275 return model?.context_size;
275276}
277+
278+/**
279+ * It uses the profiles to send a generate request to the API. Doesn't support streaming.
280+ */
281+export 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() {
37723772 $('#sd_wand_container').append(buttonHtml);
37733773 $(document.body).append(dropdownHtml);
37743774
3775- const messageButton = $('.sd_message_gen');
37763775 const button = $('#sd_gen');
37773776 const dropdown = $('#sd_dropdown');
37783777 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
184184 let prompt_text = replaceSpeaker(voiceId);
185185
186- const streaming = this.settings.streaming;
187-
188186 const params = {
189187 text: inputText,
190188 prompt_text: prompt_text,
public/scripts/extensions/tts/index.js+3 -3
@@ -1,5 +1,5 @@
11import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
22import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules, renderExtensionTemplateAsync } from '../../extensions.js';
33import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';
44import { EdgeTtsProvider } from './edge.js';
55import { ElevenLabsTtsProvider } from './elevenlabs.js';
@@ -1207,8 +1207,8 @@ jQuery(async function () {
12071207 eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
12081208 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
12091209 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
12101210 eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
12111211 eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, (messageId) => onMessageEvent(messageId));
12121212 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
12131213 name: 'speak',
12141214 callback: async (args, value) => {
public/scripts/group-chats.js+11 -9
@@ -46,7 +46,6 @@ import {
4646 hideSwipeButtons,
4747 chat_metadata,
4848 updateChatMetadata,
49- isStreamingEnabled,
5049 getThumbnailUrl,
5150 getRequestHeaders,
5251 setMenuType,
@@ -435,16 +434,18 @@ export function getGroupCharacterCards(groupId, characterId) {
435434 * @param {string} value Value to replace
436435 * @param {string} fieldName Name of the field
437436 * @param {string} characterName Name of the character
437+ * @param {boolean} trim Whether to trim the value
438438 * @returns {string} Replaced text
439439 * */
440440 function customBaseChatReplace(value, fieldName, characterName, trim) {
441441 if (!value) {
442442 return '';
443443 }
444444
445445 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names
446446 value = value.replace(/<FIELDNAME>/gi, fieldName);
447447 returnvalue baseChatReplace(= trim ? value.trim(), name1,: characterName)value;
448+ return baseChatReplace(value, name1, characterName);
448449 }
449450
450451 /**
@@ -467,13 +468,12 @@ export function getGroupCharacterCards(groupId, characterId) {
467468 }
468469
469470 // Prepare and replace prefixes
470471 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName, false);
471472 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName, false);
472- const separator = power_user.instruct.wrap ? '\n' : '';
473473 // Also run the macro replacement on the actual content
474474 value = customBaseChatReplace(value, fieldName, characterName, true);
475475
476476 return `${prefix ? prefix + separator : ''}${value}${suffix ? separator + suffix : ''}`;
477477 }
478478
479479 const scenarioOverride = chat_metadata['scenario'];
@@ -696,7 +696,7 @@ export function getGroupBlock(group) {
696696 template.find('.group_fav_icon').css('display', 'none');
697697 template.addClass(group.fav ? 'is_fav' : '');
698698 template.find('.ch_fav').val(group.fav);
699699 template.find('.group_select_counter').text(`${count} ${+ ' ' + (count != 1 ? 't`characters'` : 't`character'}`));
700700 template.find('.group_select_block_list').text(namesList.join(', '));
701701
702702 // Display inline tags
@@ -904,6 +904,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
904904 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);
905905 }
906906 }
907+ await eventSource.emit(event_types.GROUP_WRAPPER_STARTED, { selected_group, type });
907908 // now the real generation begins: cycle through every activated character
908909 for (const chId of activatedMembers) {
909910 throwIfAborted();
@@ -942,6 +943,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
942943 setCharacterName('');
943944 activateSendButtons();
944945 showSwipeButtons();
946+ await eventSource.emit(event_types.GROUP_WRAPPER_FINISHED, { selected_group, type });
945947 }
946948
947949 return Promise.resolve(textResult);
public/scripts/horde.js+4 -3
@@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
1010import { delay } from './utils.js';
1111import { isMobile } from './RossAscends-mods.js';
1212import { autoSelectInstructPreset } from './instruct-mode.js';
13+import { t } from './i18n.js';
1314
1415export {
1516 horde_settings,
@@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
169170 }
170171 }
171172 console.log(maxContextLength, maxLength);
172173 $('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
173174 return { maxContextLength, maxLength };
174175}
175176
@@ -177,7 +178,7 @@ function setContextSizePreview() {
177178 if (horde_settings.models.length) {
178179 adjustHordeGenerationParams(max_context, amount_gen);
179180 } else {
180181 $('#adjustedHordeParams').text('t`Context` + ': --, ' + t`Response` + ': --');
181182 }
182183}
183184
@@ -404,7 +405,7 @@ jQuery(function () {
404405 if (horde_settings.models.length) {
405406 adjustHordeGenerationParams(max_context, amount_gen);
406407 } else {
407408 $('#adjustedHordeParams').text('t`Context` + ': --, ' + t`Response` + ': --');
408409 }
409410
410411 saveSettingsDebounced();
public/scripts/instruct-mode.js+44 -37
@@ -320,59 +320,61 @@ export const force_output_sequence = {
320320 * @param {string} name1 User name.
321321 * @param {string} name2 Character name.
322322 * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).
323+ * @param {InstructSettings} customInstruct Custom instruct mode settings.
323324 * @returns {string} Formatted instruct mode chat message.
324325 */
325326export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence, customInstruct = null) {
326327 letconst includeNamesinstruct = isNarratorstructuredClone(customInstruct ? false :? power_user.instruct.names_behavior === names_behavior_types.ALWAYS);
328+ let includeNames = isNarrator ? false : instruct.names_behavior === names_behavior_types.ALWAYS;
327329
328330 if (!isNarrator && power_user.instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {
329331 includeNames = true;
330332 }
331333
332334 function getPrefix() {
333335 if (isNarrator) {
334336 return power_user.instruct.system_same_as_user ? power_user.instruct.input_sequence : power_user.instruct.system_sequence;
335337 }
336338
337339 if (isUser) {
338340 if (forceOutputSequence === force_output_sequence.FIRST) {
339341 return power_user.instruct.first_input_sequence || power_user.instruct.input_sequence;
340342 }
341343
342344 if (forceOutputSequence === force_output_sequence.LAST) {
343345 return power_user.instruct.last_input_sequence || power_user.instruct.input_sequence;
344346 }
345347
346348 return power_user.instruct.input_sequence;
347349 }
348350
349351 if (forceOutputSequence === force_output_sequence.FIRST) {
350352 return power_user.instruct.first_output_sequence || power_user.instruct.output_sequence;
351353 }
352354
353355 if (forceOutputSequence === force_output_sequence.LAST) {
354356 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
355357 }
356358
357359 return power_user.instruct.output_sequence;
358360 }
359361
360362 function getSuffix() {
361363 if (isNarrator) {
362364 return power_user.instruct.system_same_as_user ? power_user.instruct.input_suffix : power_user.instruct.system_suffix;
363365 }
364366
365367 if (isUser) {
366368 return power_user.instruct.input_suffix;
367369 }
368370
369371 return power_user.instruct.output_suffix;
370372 }
371373
372374 let prefix = getPrefix() || '';
373375 let suffix = getSuffix() || '';
374376
375377 if (power_user.instruct.macro) {
376378 prefix = substituteParams(prefix, name1, name2);
377379 prefix = prefix.replace(/{{name}}/gi, name || 'System');
378380
@@ -380,11 +382,11 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
380382 suffix = suffix.replace(/{{name}}/gi, name || 'System');
381383 }
382384
383385 if (!suffix && power_user.instruct.wrap) {
384386 suffix = '\n';
385387 }
386388
387389 const separator = power_user.instruct.wrap ? '\n' : '';
388390
389391 // Don't include the name if it's empty
390392 const textArray = includeNames && name ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];
@@ -396,23 +398,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
396398/**
397399 * Formats instruct mode system prompt.
398400 * @param {string} systemPrompt System prompt string.
401+ * @param {InstructSettings} customInstruct Custom instruct mode settings.
399402 * @returns {string} Formatted instruct mode system prompt.
400403 */
401404export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
402405 if (!systemPrompt) {
403406 return '';
404407 }
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) {
409414 // TODO: Replace with a proper 'System' prompt entity name input
410415 const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
411416 systemPrompt = prefix + separator + systemPrompt;
412417 }
413418
414419 if (power_user.instruct.system_sequence_suffix) {
415420 systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;
416421 }
417422
418423 return systemPrompt;
@@ -504,30 +509,32 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
504509 * @param {string} name2 Character name.
505510 * @param {boolean} isQuiet Is quiet mode generation.
506511 * @param {boolean} isQuietToLoud Is quiet to loud generation.
512+ * @param {InstructSettings} customInstruct Custom instruct settings.
507513 * @returns {string} Formatted instruct mode last prompt line.
508514 */
509515export 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
512519 function getSequence() {
513520 // User impersonation prompt
514521 if (isImpersonate) {
515522 return power_user.instruct.input_sequence;
516523 }
517524
518525 // Neutral / system / quiet prompt
519526 // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise
520527 if (isQuiet && !isQuietToLoud) {
521528 return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence;
522529 }
523530
524531 // Quiet in-character prompt
525532 if (isQuiet && isQuietToLoud) {
526533 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
527534 }
528535
529536 // Default AI response
530537 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
531538 }
532539
533540 let sequence = getSequence() || '';
@@ -536,21 +543,21 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
536543 // A hack for Mistral's formatting that has a normal output sequence ending with a space
537544 if (
538545 includeNames &&
539546 power_user.instruct.last_output_sequence &&
540547 power_user.instruct.output_sequence &&
541548 sequence === power_user.instruct.last_output_sequence &&
542549 /\s$/.test(power_user.instruct.output_sequence) &&
543550 !/\s$/.test(power_user.instruct.last_output_sequence)
544551 ) {
545552 nameFiller = power_user.instruct.output_sequence.slice(-1);
546553 }
547554
548555 if (power_user.instruct.macro) {
549556 sequence = substituteParams(sequence, name1, name2);
550557 sequence = sequence.replace(/{{name}}/gi, name || 'System');
551558 }
552559
553560 const separator = power_user.instruct.wrap ? '\n' : '';
554561 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);
555562
556563 // Quiet prompt already has a newline at the end
@@ -562,7 +569,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
562569 text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
563570 }
564571
565572 return (power_user.instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);
566573}
567574
568575/**
public/scripts/nai-settings.js+1 -0
@@ -795,6 +795,7 @@ export function parseNovelAILogprobs(data) {
795795
796796 // Add the chosen token to `merged` if it's not already there. This can
797797 // happen if the chosen token was not among the top 10 most likely ones.
798+ // eslint-disable-next-line no-unused-vars
798799 const [[chosenId], [_, chosenAfter]] = data.chosen[0];
799800 if (!merged.some(([id]) => id === chosenId)) {
800801 merged.push([chosenId, chosenAfter]);
public/scripts/openai.js+14 -12
@@ -705,16 +705,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
705705 return result;
706706}
707707
708-function formatWorldInfo(value) {
708+export function formatWorldInfo(value, { wiFormat = null } = {}) {
709709 if (!value) {
710710 return '';
711711 }
712712
713- if (!oai_settings.wi_format.trim()) {
713+ const format = wiFormat ?? oai_settings.wi_format;
714+
715+ if (!format.trim()) {
714716 return value;
715717 }
716718
717719 return stringFormat(oai_settings.wi_formatformat, value);
718720}
719721
720722/**
@@ -952,7 +954,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
952954 * @param {number} position - Prompt position in the extensions object.
953955 * @returns {string|false} - The prompt position for prompt collection.
954956 */
955957export function getPromptPosition(position) {
956958 if (position == extension_prompt_types.BEFORE_PROMPT) {
957959 return 'start';
958960 }
@@ -969,7 +971,7 @@ function getPromptPosition(position) {
969971 * @param {number} role Role of the prompt.
970972 * @returns {string} Mapped role.
971973 */
972974export function getPromptRole(role) {
973975 switch (role) {
974976 case extension_prompt_roles.SYSTEM:
975977 return 'system';
@@ -2018,6 +2020,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20182020 'reasoning_effort': String(oai_settings.reasoning_effort),
20192021 'enable_web_search': Boolean(oai_settings.enable_web_search),
20202022 'request_images': Boolean(oai_settings.request_images),
2023+ 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,
20212024 };
20222025
20232026 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
@@ -2048,7 +2051,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20482051 delete generate_data.stop;
20492052 delete generate_data.logprobs;
20502053 }
20512054 if (isOAI && oai_settings.openai_model.includes('gpt-4.5-preview') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5-preview')) {
20522055 delete generate_data.logprobs;
20532056 }
20542057
@@ -2098,7 +2101,6 @@ async function sendOpenAIRequest(type, messages, signal) {
20982101 generate_data['custom_include_body'] = oai_settings.custom_include_body;
20992102 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;
21002103 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;
2101- generate_data['custom_prompt_post_processing'] = oai_settings.custom_prompt_post_processing;
21022104 }
21032105
21042106 if (isCohere) {
@@ -3476,7 +3478,7 @@ async function getStatusOpen() {
34763478 let status;
34773479
34783480 if ('ai' in window) {
34793481 status = 't`Valid'`;
34803482 }
34813483 else {
34823484 showWindowExtensionError();
@@ -3525,7 +3527,7 @@ async function getStatusOpen() {
35253527
35263528 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;
35273529 if (canBypass) {
35283530 setOnlineStatus('t`Status check bypassed'`);
35293531 }
35303532
35313533 try {
@@ -3547,7 +3549,7 @@ async function getStatusOpen() {
35473549 saveModelList(responseData.data);
35483550 }
35493551 if (!('error' in responseData)) {
35503552 setOnlineStatus('t`Valid'`);
35513553 }
35523554 } catch (error) {
35533555 console.error(error);
@@ -4435,9 +4437,9 @@ async function onModelChange() {
44354437 if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
44364438 if (oai_settings.max_context_unlocked) {
44374439 $('#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)) {
44394441 $('#openai_max_context').attr('max', max_256k);
44404442 } else if (['mistral-large-2407', 'mistral-large-2411', 'mistral-large-pixtral-2411', 'mistral-large-latest'].includes(oai_settings.mistralai_model)) {
44414443 $('#openai_max_context').attr('max', max_128k);
44424444 } else if (oai_settings.mistralai_model.includes('mistral-nemo')) {
44434445 $('#openai_max_context').attr('max', max_128k);
public/scripts/personas.js+1 -1
@@ -800,7 +800,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
800800 chat_metadata['persona'] = user_avatar;
801801 console.log(`Auto locked persona to ${user_avatar}`);
802802 if (toastPersonaNameChange && power_user.persona_show_notifications) {
803803 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
804804 }
805805 saveMetadataDebounced();
806806 updatePersonaUIStates();
public/scripts/power-user.js+16 -6
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
5555import { loadSystemPrompts } from './sysprompt.js';
5656import { fuzzySearchCategories } from './filters.js';
5757import { accountStorage } from './util/AccountStorage.js';
58+import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
5859
5960export {
6061 loadPowerUserSettings,
@@ -218,7 +219,9 @@ let power_user = {
218219 system_sequence: '',
219220 system_suffix: '',
220221 last_system_sequence: '',
222+ first_input_sequence: '',
221223 first_output_sequence: '',
224+ last_input_sequence: '',
222225 last_output_sequence: '',
223226 system_sequence_prefix: '',
224227 system_sequence_suffix: '',
@@ -255,6 +258,7 @@ let power_user = {
255258 },
256259
257260 reasoning: {
261+ name: DEFAULT_REASONING_TEMPLATE,
258262 auto_parse: false,
259263 add_to_prompts: false,
260264 auto_expand: false,
@@ -1622,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
16221626 await loadInstructMode(data);
16231627 await loadContextSettings();
16241628 await loadSystemPrompts(data);
1629+ await loadReasoningTemplates(data);
16251630 loadMaxContextUnlocked();
16261631 switchWaifuMode();
16271632 switchSpoilerMode();
@@ -1983,15 +1988,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
19831988/**
19841989 * Renders a story string template with the given parameters.
19851990 * @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.
19861994 * @returns {string} The rendered story string.
19871995 */
1988-export function renderStoryString(params) {
1996+export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
19891997 try {
1998+ const storyString = customStoryString ?? power_user.context.story_string;
1999+ const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
2000+
19902001 // Validate and log possible warnings/errors
19912002 validateStoryString(power_user.context.story_stringstoryString, params);
19922003
19932004 // compile the story string template into a function, with no HTML escaping
19942005 const compiledTemplate = Handlebars.compile(power_user.context.story_stringstoryString, { noEscape: true });
19952006
19962007 // render the story string template with the given params
19972008 let output = compiledTemplate(params);
@@ -2004,7 +2015,7 @@ export function renderStoryString(params) {
20042015
20052016 // add a newline to the end of the story string if it doesn't have one
20062017 if (output.length > 0 && !output.endsWith('\n')) {
20072018 if (!power_user.instructinstructSettings.enabled || power_user.instructinstructSettings.wrap) {
20082019 output += '\n';
20092020 }
20102021 }
@@ -4227,14 +4238,13 @@ $(document).ready(() => {
42274238 ],
42284239 callback: (args, value) => {
42294240 const force = isTrueBoolean(String(args?.force ?? false));
4230- value = String(value ?? '').trim();
42314241
42324242 // Skip processing if no value and not forced
42334243 if (!force && !value) {
42344244 return power_user.user_prompt_bias;
42354245 }
42364246
42374247 power_user.user_prompt_bias = String(value ?? '');
42384248 $('#start_reply_with').val(power_user.user_prompt_bias);
42394249 saveSettingsDebounced();
42404250
public/scripts/preset-manager.js+44 -2
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
2121import { instruct_presets } from './instruct-mode.js';
2222import { kai_settings } from './kai-settings.js';
2323import { convertNovelPreset } from './nai-settings.js';
2424import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
2525import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
2626import { context_presets, getContextSettings, power_user } from './power-user.js';
2727import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
3838} from './textgen-settings.js';
3939import { download, parseJsonFile, waitUntilCondition } from './utils.js';
4040import { t } from './i18n.js';
41+import { reasoning_templates } from './reasoning.js';
4142
4243const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168169 },
169170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170171 },
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+ },
171186 };
172187
173188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191206 }
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+
193213 /**
194214 * Imports master settings from JSON data.
195215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228248 }
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+
230256 const validSections = [];
231257 for (const [key, section] of Object.entries(this.masterSections)) {
232258 if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
478504 presets = system_prompts;
479505 preset_names = system_prompts.map(x => x.name);
480506 break;
507+ case 'reasoning':
508+ presets = reasoning_templates;
509+ preset_names = reasoning_templates.map(x => x.name);
510+ break;
481511 default:
482512 console.warn(`Unknown API ID ${api}`);
483513 }
@@ -490,7 +520,7 @@ class PresetManager {
490520 }
491521
492522 isAdvancedFormatting() {
493- return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
523+ return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494524 }
495525
496526 updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
553583 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554584 return sysprompt_preset;
555585 }
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+ }
556591 default:
557592 console.warn(`Unknown API ID ${apiId}`);
558593 return {};
@@ -599,6 +634,13 @@ class PresetManager {
599634 'include_reasoning',
600635 'global_banned_tokens',
601636 'send_banned_tokens',
637+
638+ // Reasoning exclusions
639+ 'auto_parse',
640+ 'add_to_prompts',
641+ 'auto_expand',
642+ 'show_hidden',
643+ 'max_additions',
602644 ];
603645 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';
77import { MacrosParser } from './macros.js';
88import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
99import { Popup } from './popup.js';
1010import { performFuzzySearch, power_user } from './power-user.js';
11+import { getPresetManager } from './preset-manager.js';
1112import { SlashCommand } from './slash-commands/SlashCommand.js';
1213import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
1314import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
1415import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
1516import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
1617import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
1718import { 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+ */
31+export const reasoning_templates = [];
32+
33+export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
34+
35+/**
36+ * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
37+ * @readonly
38+ */
39+const 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
1951/**
2052 * Enum representing the type of the reasoning for a message (where it came from)
@@ -57,19 +89,24 @@ function toggleReasoningAutoExpand() {
5789 * @param {object} data Response data
5890 * @returns {string} Extracted reasoning
5991 */
6092export 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) {
6299 case 'textgenerationwebui':
63100 switch (textGenType ?? textgenerationwebui_settings.type) {
64101 case textgen_types.OPENROUTER:
65102 return data?.choices?.[0]?.reasoning ?? '';
66103 }
67104 break;
68105
69106 case 'openai':
70107 if (!ignoreShowThoughts && !oai_settings.show_thoughts) break;
71108
72109 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
73110 case chat_completion_sources.DEEPSEEK:
74111 return data?.choices?.[0]?.message?.reasoning_content ?? '';
75112 case chat_completion_sources.OPENROUTER:
@@ -664,57 +701,102 @@ export class PromptReasoning {
664701}
665702
666703function loadReasoningSettings() {
667704 UI.$('#reasoning_add_to_prompts')addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
668705 UI.$('#reasoning_add_to_prompts')addToPrompts.on('change', function () {
669706 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
670707 saveSettingsDebounced();
671708 });
672709
673710 UI.$('#reasoning_prefix')prefix.val(power_user.reasoning.prefix);
674711 UI.$('#reasoning_prefix')prefix.on('input', function () {
675712 power_user.reasoning.prefix = String($(this).val());
676713 saveSettingsDebounced();
677714 });
678715
679716 UI.$('#reasoning_suffix')suffix.val(power_user.reasoning.suffix);
680717 UI.$('#reasoning_suffix')suffix.on('input', function () {
681718 power_user.reasoning.suffix = String($(this).val());
682719 saveSettingsDebounced();
683720 });
684721
685722 UI.$('#reasoning_separator')separator.val(power_user.reasoning.separator);
686723 UI.$('#reasoning_separator')separator.on('input', function () {
687724 power_user.reasoning.separator = String($(this).val());
688725 saveSettingsDebounced();
689726 });
690727
691728 UI.$('#reasoning_max_additions')maxAdditions.val(power_user.reasoning.max_additions);
692729 UI.$('#reasoning_max_additions')maxAdditions.on('input', function () {
693730 power_user.reasoning.max_additions = Number($(this).val());
694731 saveSettingsDebounced();
695732 });
696733
697734 UI.$('#reasoning_auto_parse')autoParse.prop('checked', power_user.reasoning.auto_parse);
698735 UI.$('#reasoning_auto_parse')autoParse.on('change', function () {
699736 power_user.reasoning.auto_parse = !!$(this).prop('checked');
700737 saveSettingsDebounced();
701738 });
702739
703740 UI.$('#reasoning_auto_expand')autoExpand.prop('checked', power_user.reasoning.auto_expand);
704741 UI.$('#reasoning_auto_expand')autoExpand.on('change', function () {
705742 power_user.reasoning.auto_expand = !!$(this).prop('checked');
706743 toggleReasoningAutoExpand();
707744 saveSettingsDebounced();
708745 });
709746 toggleReasoningAutoExpand();
710747
711748 UI.$('#reasoning_show_hidden')showHidden.prop('checked', power_user.reasoning.show_hidden);
712749 UI.$('#reasoning_show_hidden')showHidden.on('change', function () {
713750 power_user.reasoning.show_hidden = !!$(this).prop('checked');
714751 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
715752 saveSettingsDebounced();
716753 });
717754 $('#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+
776+function 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+
718800}
719801
720802function registerReasoningSlashCommands() {
@@ -848,6 +930,42 @@ function registerReasoningSlashCommands() {
848930 : parsedReasoning.reasoning;
849931 },
850932 }));
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+ }));
851969}
852970
853971function registerReasoningMacros() {
@@ -1207,6 +1325,53 @@ function registerReasoningAppEvents() {
12071325 }
12081326}
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+ */
1334+export 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+ */
12101375export function initReasoning() {
12111376 loadReasoningSettings();
12121377 setReasoningEventHandlers();
public/scripts/samplerSelect.js+2 -19
@@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
99//import { getSortableDelay, onlyUnique } from './utils.js';
1010//import { getCfgPrompt } from './cfg-scale.js';
1111import { setting_names } from './textgen-settings.js';
12+import { renderTemplateAsync } from './templates.js';
1213
1314
1415const TGsamplerNames = setting_names;
@@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
2526 const html = $(document.createElement('div'));
2627 html.attr('id', 'sampler_view_list')
2728 .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
4831 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
4932 const APISamplers = await listSamplers(main_api);
public/scripts/secrets.js+2 -1
@@ -1,5 +1,6 @@
11import { DOMPurify } from '../lib.js';
22import { callPopup, getRequestHeaders } from '../script.js';
3+import { t } from './i18n.js';
34
45export const SECRET_KEYS = {
56 HORDE: 'api_key_horde',
@@ -104,7 +105,7 @@ async function viewSecrets() {
104105 });
105106
106107 if (response.status == 403) {
107108 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');
108109 return;
109110 }
110111
public/scripts/slash-commands.js+5 -3
@@ -69,12 +69,14 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
6969import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
7070import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js';
7171import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
7272import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
7373import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
7474import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
7575import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
7676import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
7777import { accountStorage } from './util/AccountStorage.js';
78+import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
79+import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
7880export {
7981 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
8082};
@@ -4345,7 +4347,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
43454347 * @prop {boolean} [handleParserErrors] (true) Whether to handle parser errors (show toast on error) or throw.
43464348 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
43474349 * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
43484350 * @prop {{[id:PARSER_FLAG]:boolean}import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
43494351 * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
43504352 * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
43514353 * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
@@ -4355,7 +4357,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
43554357/**
43564358 * @typedef ExecuteSlashCommandsOnChatInputOptions
43574359 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
43584360 * @prop {{[id:PARSER_FLAG]:boolean}import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
43594361 * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
43604362 * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
43614363 */
public/scripts/slash-commands/SlashCommand.js+1 -5
@@ -3,16 +3,12 @@ import { SlashCommandAbortController } from './SlashCommandAbortController.js';
33import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
44import { SlashCommandClosure } from './SlashCommandClosure.js';
55import { SlashCommandDebugController } from './SlashCommandDebugController.js';
6-import { PARSER_FLAG } from './SlashCommandParser.js';
76import { SlashCommandScope } from './SlashCommandScope.js';
87
9-
10-
11-
128/**
139 * @typedef {{
1410 * _scope:SlashCommandScope,
15- * _parserFlags:{[id:PARSER_FLAG]:boolean},
11+ * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
1612 * _abortController:SlashCommandAbortController,
1713 * _debugController:SlashCommandDebugController,
1814 * _hasUnnamedArgument:boolean,
public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js+0 -3
@@ -1,9 +1,6 @@
11import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
2-import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';
32import { AutoCompleteSecondaryNameResult } from '../autocomplete/AutoCompleteSecondaryNameResult.js';
43import { SlashCommand } from './SlashCommand.js';
5-import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
6-import { SlashCommandClosure } from './SlashCommandClosure.js';
74import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAutoCompleteOption.js';
85import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
96import { SlashCommandExecutor } from './SlashCommandExecutor.js';
public/scripts/slash-commands/SlashCommandClosure.js+0 -3
@@ -2,7 +2,6 @@ import { substituteParams } from '../../script.js';
22import { delay, escapeRegex, uuidv4 } from '../utils.js';
33import { SlashCommand } from './SlashCommand.js';
44import { SlashCommandAbortController } from './SlashCommandAbortController.js';
5-import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
65import { SlashCommandBreak } from './SlashCommandBreak.js';
76import { SlashCommandBreakController } from './SlashCommandBreakController.js';
87import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
@@ -16,9 +15,7 @@ import { SlashCommandScope } from './SlashCommandScope.js';
1615export class SlashCommandClosure {
1716 /** @type {SlashCommandScope} */ scope;
1817 /** @type {boolean} */ executeNow = false;
19- // @ts-ignore
2018 /** @type {SlashCommandNamedArgumentAssignment[]} */ argumentList = [];
21- // @ts-ignore
2219 /** @type {SlashCommandNamedArgumentAssignment[]} */ providedArgumentList = [];
2320 /** @type {SlashCommandExecutor[]} */ executorList = [];
2421 /** @type {SlashCommandAbortController} */ abortController;
public/scripts/slash-commands/SlashCommandDebugController.js+0 -0
public/scripts/slash-commands/SlashCommandEnumValue.js+0 -4
@@ -1,7 +1,3 @@
1-import { SlashCommandExecutor } from './SlashCommandExecutor.js';
2-import { SlashCommandScope } from './SlashCommandScope.js';
3-
4-
51/**
62 * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
73 */
public/scripts/slash-commands/SlashCommandExecutor.js+1 -6
@@ -1,11 +1,7 @@
1-// eslint-disable-next-line no-unused-vars
21import { uuidv4 } from '../utils.js';
32import { SlashCommand } from './SlashCommand.js';
4-// eslint-disable-next-line no-unused-vars
53import { SlashCommandClosure } from './SlashCommandClosure.js';
64import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
7-// eslint-disable-next-line no-unused-vars
8-import { PARSER_FLAG } from './SlashCommandParser.js';
95import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
106
117export class SlashCommandExecutor {
@@ -29,10 +25,9 @@ export class SlashCommandExecutor {
2925 }
3026 }
3127 /** @type {SlashCommand} */ command;
32- // @ts-ignore
3328 /** @type {SlashCommandNamedArgumentAssignment[]} */ namedArgumentList = [];
3429 /** @type {SlashCommandUnnamedArgumentAssignment[]} */ unnamedArgumentList = [];
3530 /** @type {{[id:PARSER_FLAG]:boolean}import('./SlashCommandParser.js').ParserFlags} */ parserFlags;
3631
3732 get commandCount() {
3833 return 1
public/scripts/slash-commands/SlashCommandNamedArgumentAssignment.js+0 -0
public/scripts/slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js+0 -1
@@ -1,7 +1,6 @@
11import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';
22import { SlashCommand } from './SlashCommand.js';
33import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
4-import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
54
65export class SlashCommandNamedArgumentAutoCompleteOption extends AutoCompleteOption {
76 /** @type {SlashCommandNamedArgument} */ arg;
public/scripts/slash-commands/SlashCommandParser.js+5 -6
@@ -8,11 +8,9 @@ import { SlashCommandExecutor } from './SlashCommandExecutor.js';
88import { SlashCommandParserError } from './SlashCommandParserError.js';
99import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
1010import { SlashCommandQuickReplyAutoCompleteOption } from './SlashCommandQuickReplyAutoCompleteOption.js';
11-// eslint-disable-next-line no-unused-vars
1211import { SlashCommandScope } from './SlashCommandScope.js';
1312import { SlashCommandVariableAutoCompleteOption } from './SlashCommandVariableAutoCompleteOption.js';
1413import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
15-// eslint-disable-next-line no-unused-vars
1614import { SlashCommandAbortController } from './SlashCommandAbortController.js';
1715import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNameResult.js';
1816import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
@@ -28,15 +26,17 @@ import { t } from '../i18n.js';
2826/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
2927/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
3028
3129/**@readonly*/
3230/* * @enum {Number}*/
31+ * @readonly
32+ * @typedef {{[id:PARSER_FLAG]:boolean}} ParserFlags
33+ */
3334export const PARSER_FLAG = {
3435 'STRICT_ESCAPING': 1,
3536 'REPLACE_GETVAR': 2,
3637};
3738
3839export class SlashCommandParser {
39- // @ts-ignore
4040 /** @type {Object.<string, SlashCommand>} */ static commands = {};
4141
4242 /**
@@ -101,7 +101,6 @@ export class SlashCommandParser {
101101 get commands() {
102102 return SlashCommandParser.commands;
103103 }
104- // @ts-ignore
105104 /** @type {Object.<string, string>} */ helpStrings = {};
106105 /** @type {boolean} */ verifyCommandNames = true;
107106 /** @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 {
4949 clearChat,
5050 unshallowCharacter,
5151 deleteLastMessage,
52+ getCharacterCardFields,
5253} from '../script.js';
5354import {
5455 extension_settings,
@@ -78,8 +79,9 @@ import { ToolManager } from './tool-calling.js';
7879import { accountStorage } from './util/AccountStorage.js';
7980import { timestampToMoment, uuidv4 } from './utils.js';
8081import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
8182import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
8283import { ChatCompletionService, TextCompletionService } from './custom-request.js';
84+import { ConnectionManagerRequestService } from './extensions/shared.js';
8385import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
8486
8587export function getContext() {
@@ -188,6 +190,7 @@ export function getContext() {
188190 textCompletionSettings: textgenerationwebui_settings,
189191 powerUserSettings: power_user,
190192 getCharacters,
193+ getCharacterCardFields,
191194 uuidv4,
192195 humanizedDateTime,
193196 updateMessageBlock,
@@ -206,6 +209,7 @@ export function getContext() {
206209 saveWorldInfo,
207210 updateWorldInfoList,
208211 convertCharacterBook,
212+ getWorldInfoPrompt,
209213 CONNECT_API_MAP,
210214 getTextGenServer,
211215 extractMessageFromData,
@@ -215,6 +219,7 @@ export function getContext() {
215219 clearChat,
216220 ChatCompletionService,
217221 TextCompletionService,
222+ ConnectionManagerRequestService,
218223 updateReasoningUI,
219224 parseReasoningFromString,
220225 unshallowCharacter,
public/scripts/tags.js+0 -1
@@ -13,7 +13,6 @@ import {
1313 DEFAULT_PRINT_TIMEOUT,
1414 printCharacters,
1515} from '../script.js';
16-// eslint-disable-next-line no-unused-vars
1716import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
1817
1918import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
public/scripts/templates/assistantNote.html+2 -2
@@ -1,9 +1,9 @@
11<div data-type="assistant_note">
22 <div>
33 <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>
44 <span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
55 </div>
66 <div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
77 <i class="fa-solid fa-file-export"></i>
88 </div>
99</div>
public/scripts/templates/macros.html+2 -1
@@ -20,7 +20,8 @@
2020 <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>
2121 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>
2222 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>
2323 <li><tt>&lcub;&lcub;char_versionversion&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>
2425 <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>
2526 <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>
2627 <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>
18 \ No newline at end of file
public/scripts/textgen-settings.js+1 -1
@@ -86,7 +86,7 @@ const OOBA_DEFAULT_ORDER = [
8686 'encoder_repetition_penalty',
8787 'no_repeat_ngram',
8888];
8989export const APHRODITE_DEFAULT_ORDER = [
9090 'dry',
9191 'penalties',
9292 'no_repeat_ngram',
public/scripts/variables.js+2 -2
@@ -9,7 +9,7 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
99import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
1010import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
1111import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
1212import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
1313import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
1414import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
1515import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
@@ -583,7 +583,7 @@ export function evalBoolean(rule, a, b) {
583583 * Executes a slash command from a string (may be enclosed in quotes) and returns the result.
584584 * @param {string} command Command to execute. May contain escaped macro and batch separators.
585585 * @param {SlashCommandScope} [scope] The scope to use.
586586 * @param {{[id:PARSER_FLAG]:boolean}import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] The parser flags to use.
587587 * @param {SlashCommandAbortController} [abortController] The abort controller to use.
588588 * @returns {Promise<SlashCommandClosureResult>} Closure execution result
589589 */
public/scripts/world-info.js+24 -8
@@ -1,6 +1,6 @@
11import { Fuse } from '../lib.js';
22
33import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
44import { 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';
55import { extension_settings, getContext } from './extensions.js';
66import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
@@ -753,10 +753,17 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
753753
754754/**
755755 * Gets the world info based on chat messages.
756756 * @param {string[]} chat - The chat messages to scan, in reverse order.
757757 * @param {number} maxContext - The maximum context size of the generation.
758758 * @param {boolean} isDryRun - If true, the function will not emit any events.
759- * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
759+ * @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
760767 * @returns {Promise<WIPromptResult>} The world info string and depth.
761768 */
762769export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -778,6 +785,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
778785 worldInfoAfter,
779786 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
780787 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
788+ anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
789+ anAfter: activatedWorldInfo.ANAfterEntries ?? [],
781790 };
782791}
783792
@@ -3862,7 +3871,14 @@ function parseDecorators(content) {
38623871 * @param {string[]} chat The chat messages to scan, in reverse order.
38633872 * @param {number} maxContext The maximum context size of the generation.
38643873 * @param {boolean} isDryRun Whether to perform a dry run.
3865- * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
3874+ * @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.
38663882 * @returns {Promise<WIActivated>} The world info activated.
38673883 */
38683884export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +3922,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39063922 timedEffects.checkTimedEffects();
39073923
39083924 if (sortedEntries.length === 0) {
39093925 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
39103926 }
39113927
39123928 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4371,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
43554371 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
43564372 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
43574373
43584374 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
43594375}
43604376
43614377/**
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
4343 vectors: 'vectors',
4444 backups: 'backups',
4545 sysprompt: 'sysprompt',
46+ reasoning: 'reasoning',
4647});
4748
4849/**
src/endpoints/backends/chat-completions.js+10 -9
@@ -49,7 +49,7 @@ const API_COHERE_V2 = 'https://api.cohere.ai/v2';
4949const API_PERPLEXITY = 'https://api.perplexity.ai';
5050const API_GROQ = 'https://api.groq.com/openai/v1';
5151const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
5252const API_01AI = 'https://api.01lingyiwanwu.aicom/v1';
5353const API_BLOCKENTROPY = 'https://api.blockentropy.ai/v1';
5454const API_AI21 = 'https://api.ai21.com/studio/v1';
5555const API_NANOGPT = 'https://nano-gpt.com/api/v1';
@@ -1048,6 +1048,15 @@ router.post('/generate', function (request, response) {
10481048 let bodyParams;
10491049 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+
10511060 if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENAI) {
10521061 apiUrl = new URL(request.body.reverse_proxy || API_OPENAI).toString();
10531062 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
11221131 mergeObjectWithYaml(bodyParams, request.body.custom_include_body);
11231132 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- }
11321133 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.PERPLEXITY) {
11331134 apiUrl = API_PERPLEXITY;
11341135 apiKey = readSecret(request.user.directories, SECRET_KEYS.PERPLEXITY);
src/endpoints/chats.js+72 -6
@@ -21,6 +21,7 @@ import {
2121const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true, 'boolean');
2222const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1, 'number'));
2323const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000, 'number'));
24+const checkIntegrity = !!getConfigValue('backups.chat.checkIntegrity', true, 'boolean');
2425
2526/**
2627 * Saves a chat to the backups directory.
@@ -292,15 +293,81 @@ function importRisuChat(userName, characterName, jsonData) {
292293 return chat.map(obj => JSON.stringify(obj)).join('\n');
293294}
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+ */
301+function 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+ */
334+async 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+
295354export const router = express.Router();
296355
297356router.post('/save', validateAvatarUrlMiddleware, async function (request, response) {
298357 try {
299358 const directoryName = String(request.body.avatar_url).replace('.png', '');
300359 const chatData = request.body.chat;
301360 const jsonlData = chatData.map(JSON.stringify).join('\n');
302361 const fileName = `${String(request.body.file_name)}.jsonl`;
303362 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+ }
304371 writeFileAtomicSync(filePath, jsonlData, 'utf8');
305372 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);
306373 return response.send({ result: 'ok' });
@@ -716,12 +783,11 @@ router.post('/search', validateAvatarUrlMiddleware, function (request, response)
716783 continue;
717784 }
718785
719786 // Search through title and messages of the chat
720787 const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);
721- const hasMatch = messages.some(message => {
788+ const text = [path.parse(chatFile.path).name,
722789 const text...messages.map(message => message?.mes?)].join('\n').toLowerCase();
723790 return const texthasMatch &&= fragments.every(fragment => text.includes(fragment));
724- });
725791
726792 if (hasMatch) {
727793 results.push({
src/endpoints/content-manager.js+4 -1
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
4848 MOVING_UI: 'moving_ui',
4949 QUICK_REPLIES: 'quick_replies',
5050 SYSPROMPT: 'sysprompt',
51+ REASONING: 'reasoning',
5152};
5253
5354/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
6162 const presets = [];
6263
6364 for (const contentItem of contentIndex) {
6465 if (contentItem.type.endsWith('_preset') || contentItem.type === ['instruct' || contentItem.type ===, 'context', ||'sysprompt', 'reasoning'].includes(contentItem.type === 'sysprompt')) {
6566 contentItem.name = path.parse(contentItem.filename).name;
6667 contentItem.folder = getTargetByType(contentItem.type, directories);
6768 presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
299300 return directories.quickreplies;
300301 case CONTENT_TYPES.SYSPROMPT:
301302 return directories.sysprompt;
303+ case CONTENT_TYPES.REASONING:
304+ return directories.reasoning;
302305 default:
303306 return null;
304307 }
src/endpoints/openai.js+1 -1
@@ -116,7 +116,7 @@ router.post('/caption-image', async (request, response) => {
116116 }
117117
118118 if (request.body.api === 'zerooneai') {
119119 apiUrl = 'https://api.01lingyiwanwu.aicom/v1/chat/completions';
120120 }
121121
122122 if (request.body.api === 'groq') {
src/endpoints/presets.js+2 -0
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
3030 return { folder: directories.context, extension: '.json' };
3131 case 'sysprompt':
3232 return { folder: directories.sysprompt, extension: '.json' };
33+ case 'reasoning':
34+ return { folder: directories.reasoning, extension: '.json' };
3335 default:
3436 return { folder: null, extension: null };
3537 }
src/endpoints/settings.js+2 -0
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
254254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
255255 const context = readAndParseFromDirectory(request.user.directories.context);
256256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
257+ const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
257258
258259 response.send({
259260 settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
272273 instruct,
273274 context,
274275 sysprompt,
276+ reasoning,
275277 enable_extensions: ENABLE_EXTENSIONS,
276278 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
277279 enable_accounts: ENABLE_ACCOUNTS,
src/endpoints/tokenizers.js+1 -1
@@ -411,7 +411,7 @@ export function getTokenizerModel(requestModel) {
411411 return 'gpt-4o';
412412 }
413413
414414 if (requestModel.includes('gpt-4.5-preview')) {
415415 return 'gpt-4o';
416416 }
417417
src/users.js+1 -0
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
9595 * @property {string} vectors - The directory where the vectors are stored
9696 * @property {string} backups - The directory where the backups are stored
9797 * @property {string} sysprompt - The directory where the system prompt data is stored
98+ * @property {string} reasoning - The directory where the reasoning templates are stored
9899 */
99100
100101/**