Merge pull request #3891 from SillyTavern/staging Staging

689637b36c178478545b46a4bec0f25ae3b97471

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

Signed
151 files changed, +4311 -1293Showing 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/workflows/issues-auto-manager.yml+7 -7
@@ -32,7 +32,7 @@ jobs:
3232 with:
3333 configuration-path: .github/issues-auto-labels.yml
3434 enable-versioned-regex: 0
3535 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
3636
3737 label-on-labels:
3838 name: 🏷️ Label Issues by Labels
@@ -46,7 +46,7 @@ jobs:
4646 uses: actions-cool/issues-helper@v3.6.0
4747 with:
4848 actions: 'add-labels'
4949 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
5050 labels: '👍 Approved'
5151
5252 - name: ❌ Remove progress labels when issue is marked done or stale
@@ -56,7 +56,7 @@ jobs:
5656 uses: actions-cool/issues-helper@v3.6.0
5757 with:
5858 actions: 'remove-labels'
5959 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
6060 labels: '🧑‍💻 In Progress,🤔 Unsure,🤔 Under Consideration'
6161
6262 - name: ❌ Remove temporary labels when confirmed labels are added
@@ -66,7 +66,7 @@ jobs:
6666 uses: actions-cool/issues-helper@v3.6.0
6767 with:
6868 actions: 'remove-labels'
6969 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
7070 labels: '🤔 Unsure,🤔 Under Consideration'
7171
7272 - name: ❌ Remove no bug labels when "🪲 Confirmed" is added
@@ -76,7 +76,7 @@ jobs:
7676 uses: actions-cool/issues-helper@v3.6.0
7777 with:
7878 actions: 'remove-labels'
7979 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
8080 labels: '✖️ Not Reproducible,✖️ Not A Bug'
8181
8282 remove-stale-label:
@@ -92,7 +92,7 @@ jobs:
9292 uses: actions-cool/issues-helper@v3.6.0
9393 with:
9494 actions: 'remove-labels'
9595 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
9696 issue-number: ${{ github.event.issue.number }}
9797 labels: '⚰️ Stale,🕸️ Inactive,🚏 Awaiting User Response,🛑 No Response'
9898
@@ -113,4 +113,4 @@ jobs:
113113 uses: peaceiris/actions-label-commenter@v1.10.0
114114 with:
115115 config_file: .github/issues-auto-comments.yml
116116 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
.github/workflows/issues-updates-on-merge.yml+4 -4
@@ -31,15 +31,15 @@ jobs:
3131 - name: Label Linked Issues
3232 id: label_linked_issues
3333 env:
3434 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
3535 run: |
3636 for ISSUE in $(echo $issues | jq -r '.[]'); do
3737 if [ "${{ github.ref }}" == "refs/heads/staging" ]; then
3838 LABEL="✅ Done (staging)"
3939 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL" --remove-label "🧑‍💻 In Progress"
4040 elif [ "${{ github.ref }}" == "refs/heads/release" ]; then
4141 LABEL="✅ Done"
4242 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL" --remove-label "🧑‍💻 In Progress"
4343 fi
4444 echo "Added label '$LABEL' to(and removed '🧑‍💻 In Progress' if present) in issue #$ISSUE"
4545 done
.github/workflows/job-close-stale.yml+3 -3
@@ -22,7 +22,7 @@ jobs:
2222 # https://github.com/marketplace/actions/close-stale-issues
2323 uses: actions/stale@v9.1.0
2424 with:
2525 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2626 days-before-stale: 183
2727 days-before-close: 7
2828 operations-per-run: 30
@@ -56,7 +56,7 @@ jobs:
5656 # https://github.com/marketplace/actions/close-stale-issues
5757 uses: actions/stale@v9.1.0
5858 with:
5959 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
6060 days-before-stale: 7
6161 days-before-close: 7
6262 operations-per-run: 30
@@ -83,7 +83,7 @@ jobs:
8383 # https://github.com/marketplace/actions/close-stale-issues
8484 uses: actions/stale@v9.1.0
8585 with:
8686 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
8787 days-before-stale: 7
8888 days-before-close: 7
8989 operations-per-run: 30
.github/workflows/on-close-handler.yml+1 -1
@@ -23,6 +23,6 @@ jobs:
2323 uses: actions-cool/issues-helper@v3.6.0
2424 with:
2525 actions: remove-labels
2626 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2727 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
2828 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+1 -1
@@ -24,6 +24,6 @@ jobs:
2424 uses: actions-cool/issues-helper@v3.6.0
2525 with:
2626 actions: 'add-labels'
2727 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2828 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
2929 labels: '👷 Maintainer'
.github/workflows/pr-auto-manager.yml+84 -26
@@ -1,6 +1,7 @@
11name: 🔀 Pull Request Manager
22
33on:
4+ workflow_dispatch: # Allow to manually call this workflow
45 pull_request_target:
56 types: [opened, synchronize, reopened, edited, labeled, unlabeled, closed]
67 pull_request_review_comment:
@@ -11,9 +12,63 @@ permissions:
1112 pull-requests: write
1213
1314jobs:
15+ run-eslint:
16+ name: ✅ Check ESLint on PR
17+ runs-on: ubuntu-latest
18+ # Only needs to run when code is changed
19+ if: github.event.action == 'opened' || github.event.action == 'synchronize'
20+
21+ # Override permissions, linter likely needs write access to issues
22+ permissions:
23+ contents: read
24+ issues: write
25+ pull-requests: write
26+
27+ steps:
28+ - name: Checkout Repository
29+ # Checkout
30+ # https://github.com/marketplace/actions/checkout
31+ uses: actions/checkout@v4.2.2
32+ with:
33+ ref: ${{ github.event.pull_request.head.sha }}
34+ repository: ${{ github.event.pull_request.head.repo.full_name }}
35+
36+ - name: Setup Node.js
37+ # Setup Node.js environment
38+ # https://github.com/marketplace/actions/setup-node-js-environment
39+ uses: actions/setup-node@v4.3.0
40+ with:
41+ node-version: 20
42+
43+ - name: Run npm install
44+ run: npm ci
45+
46+ - name: Run ESLint
47+ # Action ESLint
48+ # https://github.com/marketplace/actions/action-eslint
49+ uses: sibiraj-s/action-eslint@v3.0.1
50+ with:
51+ token: ${{ secrets.GITHUB_TOKEN }}
52+ eslint-args: '--ignore-path=.gitignore --quiet'
53+ extensions: 'js'
54+ annotations: true
55+ ignore-patterns: |
56+ dist/
57+ lib/
58+
1459 label-by-size:
1560 name: 🏷️ Label PR by Size
61+ # This job should run after all others, to prevent possible concurrency issues
62+ needs: [label-by-branches, label-by-files, remove-stale-label, check-merge-blocking-labels, write-auto-comments]
1663 runs-on: ubuntu-latest
64+ # Only needs to run when code is changed
65+ if: always() && (github.event.action == 'opened' || github.event.action == 'synchronize')
66+
67+ # Override permissions, the labeler needs issues write access
68+ permissions:
69+ contents: read
70+ issues: write
71+ pull-requests: write
1772
1873 steps:
1974 - name: Label PR Size
@@ -21,7 +76,7 @@ jobs:
2176 # https://github.com/marketplace/actions/pull-request-size-labeler
2277 uses: codelytv/pr-size-labeler@v1.10.2
2378 with:
2479 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2580 xs_label: '🟩 ⬤○○○○'
2681 xs_max_size: '20'
2782 s_label: '🟩 ⬤⬤○○○'
@@ -32,20 +87,15 @@ jobs:
3287 l_max_size: '1000'
3388 xl_label: '🟥 ⬤⬤⬤⬤⬤'
3489 fail_if_xl: 'false'
35- github_api_url: 'https://api.github.com'
3690 files_to_ignore: |
3791 "package-lock.json"
3892 "public/lib/*"
3993
4094 label-by-branches:
4195 name: 🏷️ Label PR by Branches
42- needs: [label-by-size]
4396 runs-on: ubuntu-latest
44- # Run, even if the previous jobs were skipped/failed
97+ # Only label once when PR is created or when base branch is changed, to allow manual label removal
45- # Only label once when PR is created or branches are changed, to allow manual label removal
98+ if: github.event.action == 'opened' || (github.event.action == 'synchronize' && github.event.changes.base)
46- if: |
47- always()
48- && github.event.action == 'opened' || (github.event.action == 'synchronize' && (github.event.changes.base || github.event.changes.head))
4999
50100 steps:
51101 - name: Checkout Repository
@@ -59,14 +109,13 @@ jobs:
59109 uses: actions/labeler@v5.0.0
60110 with:
61111 configuration-path: .github/pr-auto-labels-by-branch.yml
62112 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
63113
64114 label-by-files:
65115 name: 🏷️ Label PR by Files
66- needs: [label-by-branches]
67116 runs-on: ubuntu-latest
68117 # Run,Only evenneeds ifto therun previouswhen jobscode wereis skipped/failedchanged
69- if: always()
118+ if: github.event.action == 'opened' || github.event.action == 'synchronize'
70119
71120 steps:
72121 - name: Checkout Repository
@@ -80,16 +129,19 @@ jobs:
80129 uses: actions/labeler@v5.0.0
81130 with:
82131 configuration-path: .github/pr-auto-labels-by-files.yml
83132 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
84133
85134 remove-stale-label:
86135 name: 🗑️ Remove Stale Label on Comment
87- needs: [label-by-files]
88136 runs-on: ubuntu-latest
89137 # Only runs when thison iscomments not done by the github actions bot
90- if: |
138+ if: github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
91- always()
139+
92- && github.event_name == 'pull_request_review_comment' && github.actor != 'github-actions[bot]'
140+ # Override permissions, issue labeler needs issues write access
141+ permissions:
142+ contents: read
143+ issues: write
144+ pull-requests: write
93145
94146 steps:
95147 - name: Remove Stale Label
@@ -98,13 +150,13 @@ jobs:
98150 uses: actions-cool/issues-helper@v3.6.0
99151 with:
100152 actions: 'remove-labels'
101153 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
102154 issue-number: ${{ github.event.pull_request.number }}
103155 labels: '⚰️ Stale'
104156
105157 check-merge-blocking-labels:
106158 name: 🚫 Check Merge Blocking Labels
107159 needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]
108160 runs-on: ubuntu-latest
109161 # Run, even if the previous jobs were skipped/failed
110162 if: always()
@@ -154,7 +206,7 @@ jobs:
154206
155207 write-auto-comments:
156208 name: 💬 Post PR Comments Based on Labels
157209 needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]
158210 runs-on: ubuntu-latest
159211 # Run, even if the previous jobs were skipped/failed
160212 if: always()
@@ -171,7 +223,7 @@ jobs:
171223 uses: peaceiris/actions-label-commenter@v1.10.0
172224 with:
173225 config_file: .github/pr-auto-comments.yml
174226 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
175227
176228 # 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.
177229 update-linked-issues:
@@ -179,6 +231,12 @@ jobs:
179231 runs-on: ubuntu-latest
180232 if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'
181233
234+ # Override permissions, We need to be able to write to issues
235+ permissions:
236+ contents: read
237+ issues: write
238+ pull-requests: write
239+
182240 steps:
183241 - name: Extract Linked Issues From PR Description
184242 id: extract_issues
@@ -192,7 +250,7 @@ jobs:
192250 PR_NUMBER=${{ github.event.pull_request.number }}
193251 REPO=${{ github.repository }}
194252 API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"
195253 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]')
196254 echo "linked_issues=$ISSUES" >> $GITHUB_ENV
197255
198256 - name: Merge Issue Lists
@@ -204,9 +262,9 @@ jobs:
204262 - name: Label Linked Issues
205263 id: label_linked_issues
206264 env:
207265 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
208266 run: |
209267 for ISSUE in $(echo $final_issues | jq -r '.[]'); do
210268 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "✅ Done (staging)" --remove-label "🧑‍💻 In Progress"
211269 echo "Added label '✅ Done (staging)' to(and removed '🧑‍💻 In Progress' if present) in issue #$ISSUE"
212270 done
.github/workflows/pr-check-merge-conflicts.yaml+1 -1
@@ -23,6 +23,6 @@ jobs:
2323 uses: eps1lon/actions-label-merge-conflict@v3.0.3
2424 with:
2525 dirtyLabel: '🚫 Merge Conflicts'
2626 repoToken: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
2727 commentOnDirty: >
2828 ⚠️ This PR has conflicts that need to be resolved before it can be merged.
default/config.yaml+5 -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
@@ -140,6 +142,8 @@ performance:
140142 lazyLoadCharacters: false
141143 # The maximum amount of memory that parsed character cards can use. Set to 0 to disable memory caching.
142144 memoryCacheCapacity: '100mb'
145+ # Enables disk caching for character cards. Improves performances with large card libraries.
146+ useDiskCache: true
143147
144148# Allow secret keys exposure via API
145149allowKeysExposure: false
@@ -151,6 +155,7 @@ whitelistImportDomains:
151155 - cdn.discordapp.com
152156 - files.catbox.moe
153157 - raw.githubusercontent.com
158+ - char-archive.evulid.cc
154159# API request overrides (for KoboldAI and Text Completion APIs)
155160## Note: host includes the port number if it's not the default (80 or 443)
156161## Format is an array of objects:
default/content/index.json+16 -0
@@ -564,6 +564,10 @@
564564 "type": "context"
565565 },
566566 {
567+ "filename": "presets/context/Llama 4 Instruct.json",
568+ "type": "context"
569+ },
570+ {
567571 "filename": "presets/context/Phi.json",
568572 "type": "context"
569573 },
@@ -664,6 +668,10 @@
664668 "type": "instruct"
665669 },
666670 {
671+ "filename": "presets/instruct/Llama 4 Instruct.json",
672+ "type": "instruct"
673+ },
674+ {
667675 "filename": "presets/instruct/Phi.json",
668676 "type": "instruct"
669677 },
@@ -786,5 +794,13 @@
786794 {
787795 "filename": "presets/context/DeepSeek-V2.5.json",
788796 "type": "context"
797+ },
798+ {
799+ "filename": "presets/reasoning/DeepSeek.json",
800+ "type": "reasoning"
801+ },
802+ {
803+ "filename": "presets/reasoning/Blank.json",
804+ "type": "reasoning"
789805 }
790806]
default/content/presets/context/Llama 4 Instruct.json+11 -0
@@ -0,0 +1,11 @@
1+{
2+ "story_string": "<|begin_of_text|><|header_start|>system<|header_end|>\n\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|eot|>",
3+ "example_separator": "",
4+ "chat_start": "",
5+ "use_stop_strings": false,
6+ "allow_jailbreak": false,
7+ "always_force_name2": true,
8+ "trim_sentences": false,
9+ "single_line": false,
10+ "name": "Llama 4 Instruct"
11+}
default/content/presets/instruct/Llama 3 Instruct.json+1 -1
@@ -16,7 +16,7 @@
1616 "input_suffix": "<|eot_id|>",
1717 "system_suffix": "<|eot_id|>",
1818 "user_alignment_message": "",
1919 "system_same_as_user": truefalse,
2020 "last_system_sequence": "",
2121 "name": "Llama 3 Instruct"
2222}
default/content/presets/instruct/Llama 4 Instruct.json+22 -0
@@ -0,0 +1,22 @@
1+{
2+ "input_sequence": "<|header_start|>user<|header_end|>\n\n",
3+ "output_sequence": "<|header_start|>assistant<|header_end|>\n\n",
4+ "last_output_sequence": "",
5+ "system_sequence": "<|header_start|>system<|header_end|>\n\n",
6+ "stop_sequence": "<|eot|>",
7+ "wrap": false,
8+ "macro": true,
9+ "names_behavior": "always",
10+ "activation_regex": "",
11+ "system_sequence_prefix": "",
12+ "system_sequence_suffix": "",
13+ "first_output_sequence": "",
14+ "skip_examples": false,
15+ "output_suffix": "<|eot|>",
16+ "input_suffix": "<|eot|>",
17+ "system_suffix": "<|eot|>",
18+ "user_alignment_message": "",
19+ "system_same_as_user": false,
20+ "last_system_sequence": "",
21+ "name": "Llama 4 Instruct"
22+}
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+}
jsconfig.json+1 -1
@@ -2,7 +2,7 @@
22 "compilerOptions": {
33 "module": "ESNext",
44 "target": "ES2023",
55 "moduleResolution": "NodeBundler",
66 "strictNullChecks": true,
77 "strictFunctionTypes": true,
88 "checkJs": true,
package-lock.json+1081 -333
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.12.1314",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.12.1314",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
@@ -14,6 +14,26 @@
1414 "@agnai/sentencepiece-js": "^1.1.1",
1515 "@agnai/web-tokenizers": "^0.1.3",
1616 "@iconfu/svg-inject": "^1.2.3",
17+ "@jimp/core": "^1.6.0",
18+ "@jimp/js-bmp": "^1.6.0",
19+ "@jimp/js-gif": "^1.6.0",
20+ "@jimp/js-tiff": "^1.6.0",
21+ "@jimp/plugin-circle": "^1.6.0",
22+ "@jimp/plugin-color": "^1.6.0",
23+ "@jimp/plugin-contain": "^1.6.0",
24+ "@jimp/plugin-cover": "^1.6.0",
25+ "@jimp/plugin-crop": "^1.6.0",
26+ "@jimp/plugin-displace": "^1.6.0",
27+ "@jimp/plugin-fisheye": "^1.6.0",
28+ "@jimp/plugin-flip": "^1.6.0",
29+ "@jimp/plugin-mask": "^1.6.0",
30+ "@jimp/plugin-quantize": "^1.6.0",
31+ "@jimp/plugin-rotate": "^1.6.0",
32+ "@jimp/plugin-threshold": "^1.6.0",
33+ "@jimp/wasm-avif": "^1.6.0",
34+ "@jimp/wasm-jpeg": "^1.6.0",
35+ "@jimp/wasm-png": "^1.6.0",
36+ "@jimp/wasm-webp": "^1.6.0",
1737 "@mozilla/readability": "^0.6.0",
1838 "@popperjs/core": "^2.11.8",
1939 "@zeldafan0225/ai_horde": "^5.2.0",
@@ -28,6 +48,7 @@
2848 "cookie-parser": "^1.4.6",
2949 "cookie-session": "^2.1.0",
3050 "cors": "^2.8.5",
51+ "crc": "^4.3.2",
3152 "csrf-sync": "^4.0.3",
3253 "diff-match-patch": "^1.0.5",
3354 "dompurify": "^3.2.4",
@@ -46,7 +67,6 @@
4667 "ip-regex": "^5.0.0",
4768 "ipaddr.js": "^2.2.0",
4869 "is-docker": "^3.0.0",
49- "jimp": "^0.22.10",
5070 "localforage": "^1.10.0",
5171 "lodash": "^4.17.21",
5272 "mime-types": "^2.1.35",
@@ -57,7 +77,6 @@
5777 "node-persist": "^4.0.4",
5878 "open": "^8.4.2",
5979 "png-chunk-text": "^1.0.0",
60- "png-chunks-encode": "^1.0.0",
6180 "png-chunks-extract": "^1.0.0",
6281 "proxy-agent": "^6.5.0",
6382 "rate-limiter-flexible": "^5.0.5",
@@ -102,7 +121,6 @@
102121 "@types/node": "^18.19.80",
103122 "@types/node-persist": "^3.1.8",
104123 "@types/png-chunk-text": "^1.0.3",
105- "@types/png-chunks-encode": "^1.0.2",
106124 "@types/png-chunks-extract": "^1.0.2",
107125 "@types/response-time": "^2.3.8",
108126 "@types/select2": "^4.0.63",
@@ -110,7 +128,8 @@
110128 "@types/write-file-atomic": "^4.0.3",
111129 "@types/yargs": "^17.0.33",
112130 "@types/yauzl": "^2.10.3",
113131 "eslint": "^8.57.1",
132+ "eslint-plugin-jsdoc": "^48.10.0"
114133 },
115134 "engines": {
116135 "node": ">= 18"
@@ -147,6 +166,21 @@
147166 "integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA==",
148167 "license": "Apache-2.0"
149168 },
169+ "node_modules/@es-joy/jsdoccomment": {
170+ "version": "0.46.0",
171+ "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz",
172+ "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==",
173+ "dev": true,
174+ "license": "MIT",
175+ "dependencies": {
176+ "comment-parser": "1.4.1",
177+ "esquery": "^1.6.0",
178+ "jsdoc-type-pratt-parser": "~4.0.0"
179+ },
180+ "engines": {
181+ "node": ">=16"
182+ }
183+ },
150184 "node_modules/@eslint-community/eslint-utils": {
151185 "version": "4.4.0",
152186 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -412,7 +446,55 @@
412446 "@jimp/custom": ">=0.3.5"
413447 }
414448 },
449+ "node_modules/@jimp/bmp/node_modules/@jimp/utils": {
450+ "version": "0.22.12",
451+ "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
452+ "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
453+ "license": "MIT",
454+ "dependencies": {
455+ "regenerator-runtime": "^0.13.3"
456+ }
457+ },
415458 "node_modules/@jimp/core": {
459+ "version": "1.6.0",
460+ "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz",
461+ "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==",
462+ "license": "MIT",
463+ "dependencies": {
464+ "@jimp/file-ops": "1.6.0",
465+ "@jimp/types": "1.6.0",
466+ "@jimp/utils": "1.6.0",
467+ "await-to-js": "^3.0.0",
468+ "exif-parser": "^0.1.12",
469+ "file-type": "^16.0.0",
470+ "mime": "3"
471+ },
472+ "engines": {
473+ "node": ">=18"
474+ }
475+ },
476+ "node_modules/@jimp/core/node_modules/mime": {
477+ "version": "3.0.0",
478+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
479+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
480+ "license": "MIT",
481+ "bin": {
482+ "mime": "cli.js"
483+ },
484+ "engines": {
485+ "node": ">=10.0.0"
486+ }
487+ },
488+ "node_modules/@jimp/custom": {
489+ "version": "0.22.12",
490+ "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",
491+ "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",
492+ "license": "MIT",
493+ "dependencies": {
494+ "@jimp/core": "^0.22.12"
495+ }
496+ },
497+ "node_modules/@jimp/custom/node_modules/@jimp/core": {
416498 "version": "0.22.12",
417499 "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz",
418500 "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==",
@@ -428,13 +510,67 @@
428510 "tinycolor2": "^1.6.0"
429511 }
430512 },
431513 "node_modules/@jimp/custom/node_modules/@jimp/utils": {
432514 "version": "0.22.12",
433515 "resolved": "https://registry.npmjs.org/@jimp/customutils/-/customutils-0.22.12.tgz",
434516 "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHVyJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1QnMKXk8TrSwAE71zt716Q==",
435517 "license": "MIT",
436518 "dependencies": {
437519 "@jimp/coreregenerator-runtime": "^0.2213.123"
520+ }
521+ },
522+ "node_modules/@jimp/custom/node_modules/buffer": {
523+ "version": "5.7.1",
524+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
525+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
526+ "funding": [
527+ {
528+ "type": "github",
529+ "url": "https://github.com/sponsors/feross"
530+ },
531+ {
532+ "type": "patreon",
533+ "url": "https://www.patreon.com/feross"
534+ },
535+ {
536+ "type": "consulting",
537+ "url": "https://feross.org/support"
538+ }
539+ ],
540+ "license": "MIT",
541+ "dependencies": {
542+ "base64-js": "^1.3.1",
543+ "ieee754": "^1.1.13"
544+ }
545+ },
546+ "node_modules/@jimp/custom/node_modules/pixelmatch": {
547+ "version": "4.0.2",
548+ "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz",
549+ "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==",
550+ "license": "ISC",
551+ "dependencies": {
552+ "pngjs": "^3.0.0"
553+ },
554+ "bin": {
555+ "pixelmatch": "bin/pixelmatch"
556+ }
557+ },
558+ "node_modules/@jimp/custom/node_modules/pngjs": {
559+ "version": "3.4.0",
560+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
561+ "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
562+ "license": "MIT",
563+ "engines": {
564+ "node": ">=4.0.0"
565+ }
566+ },
567+ "node_modules/@jimp/file-ops": {
568+ "version": "1.6.0",
569+ "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz",
570+ "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==",
571+ "license": "MIT",
572+ "engines": {
573+ "node": ">=18"
438574 }
439575 },
440576 "node_modules/@jimp/gif": {
@@ -451,6 +587,15 @@
451587 "@jimp/custom": ">=0.3.5"
452588 }
453589 },
590+ "node_modules/@jimp/gif/node_modules/@jimp/utils": {
591+ "version": "0.22.12",
592+ "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
593+ "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
594+ "license": "MIT",
595+ "dependencies": {
596+ "regenerator-runtime": "^0.13.3"
597+ }
598+ },
454599 "node_modules/@jimp/jpeg": {
455600 "version": "0.22.12",
456601 "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz",
@@ -464,10 +609,237 @@
464609 "@jimp/custom": ">=0.3.5"
465610 }
466611 },
612+ "node_modules/@jimp/jpeg/node_modules/@jimp/utils": {
613+ "version": "0.22.12",
614+ "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
615+ "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
616+ "license": "MIT",
617+ "dependencies": {
618+ "regenerator-runtime": "^0.13.3"
619+ }
620+ },
621+ "node_modules/@jimp/js-bmp": {
622+ "version": "1.6.0",
623+ "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz",
624+ "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==",
625+ "license": "MIT",
626+ "dependencies": {
627+ "@jimp/core": "1.6.0",
628+ "@jimp/types": "1.6.0",
629+ "@jimp/utils": "1.6.0",
630+ "bmp-ts": "^1.0.9"
631+ },
632+ "engines": {
633+ "node": ">=18"
634+ }
635+ },
636+ "node_modules/@jimp/js-gif": {
637+ "version": "1.6.0",
638+ "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz",
639+ "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==",
640+ "license": "MIT",
641+ "dependencies": {
642+ "@jimp/core": "1.6.0",
643+ "@jimp/types": "1.6.0",
644+ "gifwrap": "^0.10.1",
645+ "omggif": "^1.0.10"
646+ },
647+ "engines": {
648+ "node": ">=18"
649+ }
650+ },
651+ "node_modules/@jimp/js-jpeg": {
652+ "version": "1.6.0",
653+ "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz",
654+ "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==",
655+ "license": "MIT",
656+ "dependencies": {
657+ "@jimp/core": "1.6.0",
658+ "@jimp/types": "1.6.0",
659+ "jpeg-js": "^0.4.4"
660+ },
661+ "engines": {
662+ "node": ">=18"
663+ }
664+ },
665+ "node_modules/@jimp/js-png": {
666+ "version": "1.6.0",
667+ "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz",
668+ "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==",
669+ "license": "MIT",
670+ "dependencies": {
671+ "@jimp/core": "1.6.0",
672+ "@jimp/types": "1.6.0",
673+ "pngjs": "^7.0.0"
674+ },
675+ "engines": {
676+ "node": ">=18"
677+ }
678+ },
679+ "node_modules/@jimp/js-tiff": {
680+ "version": "1.6.0",
681+ "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz",
682+ "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==",
683+ "license": "MIT",
684+ "dependencies": {
685+ "@jimp/core": "1.6.0",
686+ "@jimp/types": "1.6.0",
687+ "utif2": "^4.1.0"
688+ },
689+ "engines": {
690+ "node": ">=18"
691+ }
692+ },
467693 "node_modules/@jimp/plugin-blit": {
694+ "version": "1.6.0",
695+ "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz",
696+ "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==",
697+ "license": "MIT",
698+ "dependencies": {
699+ "@jimp/types": "1.6.0",
700+ "@jimp/utils": "1.6.0",
701+ "zod": "^3.23.8"
702+ },
703+ "engines": {
704+ "node": ">=18"
705+ }
706+ },
707+ "node_modules/@jimp/plugin-blur": {
708+ "version": "1.6.0",
709+ "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz",
710+ "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==",
711+ "license": "MIT",
712+ "peer": true,
713+ "dependencies": {
714+ "@jimp/core": "1.6.0",
715+ "@jimp/utils": "1.6.0"
716+ },
717+ "engines": {
718+ "node": ">=18"
719+ }
720+ },
721+ "node_modules/@jimp/plugin-circle": {
722+ "version": "1.6.0",
723+ "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz",
724+ "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==",
725+ "license": "MIT",
726+ "dependencies": {
727+ "@jimp/types": "1.6.0",
728+ "zod": "^3.23.8"
729+ },
730+ "engines": {
731+ "node": ">=18"
732+ }
733+ },
734+ "node_modules/@jimp/plugin-color": {
735+ "version": "1.6.0",
736+ "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz",
737+ "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==",
738+ "license": "MIT",
739+ "dependencies": {
740+ "@jimp/core": "1.6.0",
741+ "@jimp/types": "1.6.0",
742+ "@jimp/utils": "1.6.0",
743+ "tinycolor2": "^1.6.0",
744+ "zod": "^3.23.8"
745+ },
746+ "engines": {
747+ "node": ">=18"
748+ }
749+ },
750+ "node_modules/@jimp/plugin-contain": {
751+ "version": "1.6.0",
752+ "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz",
753+ "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==",
754+ "license": "MIT",
755+ "dependencies": {
756+ "@jimp/core": "1.6.0",
757+ "@jimp/plugin-blit": "1.6.0",
758+ "@jimp/plugin-resize": "1.6.0",
759+ "@jimp/types": "1.6.0",
760+ "@jimp/utils": "1.6.0",
761+ "zod": "^3.23.8"
762+ },
763+ "engines": {
764+ "node": ">=18"
765+ }
766+ },
767+ "node_modules/@jimp/plugin-cover": {
768+ "version": "1.6.0",
769+ "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz",
770+ "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==",
771+ "license": "MIT",
772+ "dependencies": {
773+ "@jimp/core": "1.6.0",
774+ "@jimp/plugin-crop": "1.6.0",
775+ "@jimp/plugin-resize": "1.6.0",
776+ "@jimp/types": "1.6.0",
777+ "zod": "^3.23.8"
778+ },
779+ "engines": {
780+ "node": ">=18"
781+ }
782+ },
783+ "node_modules/@jimp/plugin-crop": {
784+ "version": "1.6.0",
785+ "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz",
786+ "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==",
787+ "license": "MIT",
788+ "dependencies": {
789+ "@jimp/core": "1.6.0",
790+ "@jimp/types": "1.6.0",
791+ "@jimp/utils": "1.6.0",
792+ "zod": "^3.23.8"
793+ },
794+ "engines": {
795+ "node": ">=18"
796+ }
797+ },
798+ "node_modules/@jimp/plugin-displace": {
799+ "version": "1.6.0",
800+ "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz",
801+ "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==",
802+ "license": "MIT",
803+ "dependencies": {
804+ "@jimp/types": "1.6.0",
805+ "@jimp/utils": "1.6.0",
806+ "zod": "^3.23.8"
807+ },
808+ "engines": {
809+ "node": ">=18"
810+ }
811+ },
812+ "node_modules/@jimp/plugin-fisheye": {
813+ "version": "1.6.0",
814+ "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz",
815+ "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==",
816+ "license": "MIT",
817+ "dependencies": {
818+ "@jimp/types": "1.6.0",
819+ "@jimp/utils": "1.6.0",
820+ "zod": "^3.23.8"
821+ },
822+ "engines": {
823+ "node": ">=18"
824+ }
825+ },
826+ "node_modules/@jimp/plugin-flip": {
827+ "version": "1.6.0",
828+ "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz",
829+ "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==",
830+ "license": "MIT",
831+ "dependencies": {
832+ "@jimp/types": "1.6.0",
833+ "zod": "^3.23.8"
834+ },
835+ "engines": {
836+ "node": ">=18"
837+ }
838+ },
839+ "node_modules/@jimp/plugin-gaussian": {
468840 "version": "0.22.12",
469841 "resolved": "https://registry.npmjs.org/@jimp/plugin-blitgaussian/-/plugin-blitgaussian-0.22.12.tgz",
470842 "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQsBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==",
471843 "license": "MIT",
472844 "dependencies": {
473845 "@jimp/utils": "^0.22.12"
@@ -476,10 +848,40 @@
476848 "@jimp/custom": ">=0.3.5"
477849 }
478850 },
479851 "node_modules/@jimp/plugin-blurgaussian/node_modules/@jimp/utils": {
480852 "version": "0.22.12",
481853 "resolved": "https://registry.npmjs.org/@jimp/plugin-blurutils/-/plugin-blurutils-0.22.12.tgz",
482854 "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9tyJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uwnMKXk8TrSwAE71zt716Q==",
855+ "license": "MIT",
856+ "dependencies": {
857+ "regenerator-runtime": "^0.13.3"
858+ }
859+ },
860+ "node_modules/@jimp/plugin-hash": {
861+ "version": "1.6.0",
862+ "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz",
863+ "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==",
864+ "license": "MIT",
865+ "dependencies": {
866+ "@jimp/core": "1.6.0",
867+ "@jimp/js-bmp": "1.6.0",
868+ "@jimp/js-jpeg": "1.6.0",
869+ "@jimp/js-png": "1.6.0",
870+ "@jimp/js-tiff": "1.6.0",
871+ "@jimp/plugin-color": "1.6.0",
872+ "@jimp/plugin-resize": "1.6.0",
873+ "@jimp/types": "1.6.0",
874+ "@jimp/utils": "1.6.0",
875+ "any-base": "^1.1.0"
876+ },
877+ "engines": {
878+ "node": ">=18"
879+ }
880+ },
881+ "node_modules/@jimp/plugin-invert": {
882+ "version": "0.22.12",
883+ "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz",
884+ "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==",
483885 "license": "MIT",
484886 "dependencies": {
485887 "@jimp/utils": "^0.22.12"
@@ -488,10 +890,32 @@
488890 "@jimp/custom": ">=0.3.5"
489891 }
490892 },
491893 "node_modules/@jimp/plugin-circleinvert/node_modules/@jimp/utils": {
492894 "version": "0.22.12",
493895 "resolved": "https://registry.npmjs.org/@jimp/plugin-circleutils/-/plugin-circleutils-0.22.12.tgz",
494896 "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTgyJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
897+ "license": "MIT",
898+ "dependencies": {
899+ "regenerator-runtime": "^0.13.3"
900+ }
901+ },
902+ "node_modules/@jimp/plugin-mask": {
903+ "version": "1.6.0",
904+ "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz",
905+ "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==",
906+ "license": "MIT",
907+ "dependencies": {
908+ "@jimp/types": "1.6.0",
909+ "zod": "^3.23.8"
910+ },
911+ "engines": {
912+ "node": ">=18"
913+ }
914+ },
915+ "node_modules/@jimp/plugin-normalize": {
916+ "version": "0.22.12",
917+ "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz",
918+ "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==",
495919 "license": "MIT",
496920 "dependencies": {
497921 "@jimp/utils": "^0.22.12"
@@ -500,65 +924,158 @@
500924 "@jimp/custom": ">=0.3.5"
501925 }
502926 },
503927 "node_modules/@jimp/plugin-colornormalize/node_modules/@jimp/utils": {
504928 "version": "0.22.12",
505929 "resolved": "https://registry.npmjs.org/@jimp/plugin-colorutils/-/plugin-colorutils-0.22.12.tgz",
506930 "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIAyJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
507931 "license": "MIT",
508932 "dependencies": {
509933 "@jimp/utilsregenerator-runtime": "^0.2213.123",
510- "tinycolor2": "^1.6.0"
934+ }
511935 },
512- "peerDependencies": {
936+ "node_modules/@jimp/plugin-quantize": {
513937 "@jimp/customversion": ">=01.36.50",
938+ "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz",
939+ "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==",
940+ "license": "MIT",
941+ "dependencies": {
942+ "image-q": "^4.0.0",
943+ "zod": "^3.23.8"
944+ },
945+ "engines": {
946+ "node": ">=18"
514947 }
515948 },
516949 "node_modules/@jimp/plugin-containresize": {
950+ "version": "1.6.0",
951+ "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",
952+ "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",
953+ "license": "MIT",
954+ "dependencies": {
955+ "@jimp/core": "1.6.0",
956+ "@jimp/types": "1.6.0",
957+ "zod": "^3.23.8"
958+ },
959+ "engines": {
960+ "node": ">=18"
961+ }
962+ },
963+ "node_modules/@jimp/plugin-rotate": {
964+ "version": "1.6.0",
965+ "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz",
966+ "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==",
967+ "license": "MIT",
968+ "dependencies": {
969+ "@jimp/core": "1.6.0",
970+ "@jimp/plugin-crop": "1.6.0",
971+ "@jimp/plugin-resize": "1.6.0",
972+ "@jimp/types": "1.6.0",
973+ "@jimp/utils": "1.6.0",
974+ "zod": "^3.23.8"
975+ },
976+ "engines": {
977+ "node": ">=18"
978+ }
979+ },
980+ "node_modules/@jimp/plugin-scale": {
517981 "version": "0.22.12",
518982 "resolved": "https://registry.npmjs.org/@jimp/plugin-containscale/-/plugin-containscale-0.22.12.tgz",
519983 "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQdghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
520984 "license": "MIT",
521985 "dependencies": {
522986 "@jimp/utils": "^0.22.12"
523987 },
524988 "peerDependencies": {
525989 "@jimp/custom": ">=0.3.5",
526990 "@jimp/plugin-blitresize": ">=0.3.5",
527- "@jimp/plugin-resize": ">=0.3.5",
528- "@jimp/plugin-scale": ">=0.3.5"
529991 }
530992 },
531993 "node_modules/@jimp/plugin-coverscale/node_modules/@jimp/utils": {
532994 "version": "0.22.12",
533995 "resolved": "https://registry.npmjs.org/@jimp/plugin-coverutils/-/plugin-coverutils-0.22.12.tgz",
534996 "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/W56+4nJ1brhSd233HBJCo01BXAnMKXk8TrSwAE71zt716Q==",
997+ "license": "MIT",
998+ "dependencies": {
999+ "regenerator-runtime": "^0.13.3"
1000+ }
1001+ },
1002+ "node_modules/@jimp/plugin-shadow": {
1003+ "version": "0.22.12",
1004+ "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz",
1005+ "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==",
5351006 "license": "MIT",
5361007 "dependencies": {
5371008 "@jimp/utils": "^0.22.12"
5381009 },
5391010 "peerDependencies": {
5401011 "@jimp/custom": ">=0.3.5",
5411012 "@jimp/plugin-cropblur": ">=0.3.5",
5421013 "@jimp/plugin-resize": ">=0.3.5",
543- "@jimp/plugin-scale": ">=0.3.5"
5441014 }
5451015 },
5461016 "node_modules/@jimp/plugin-cropshadow/node_modules/@jimp/utils": {
5471017 "version": "0.22.12",
5481018 "resolved": "https://registry.npmjs.org/@jimp/plugin-croputils/-/plugin-croputils-0.22.12.tgz",
5491019 "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVwyJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
5501020 "license": "MIT",
5511021 "dependencies": {
5521022 "@jimp/utilsregenerator-runtime": "^0.2213.123"
1023+ }
1024+ },
1025+ "node_modules/@jimp/plugin-threshold": {
1026+ "version": "1.6.0",
1027+ "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz",
1028+ "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==",
1029+ "license": "MIT",
1030+ "dependencies": {
1031+ "@jimp/core": "1.6.0",
1032+ "@jimp/plugin-color": "1.6.0",
1033+ "@jimp/plugin-hash": "1.6.0",
1034+ "@jimp/types": "1.6.0",
1035+ "@jimp/utils": "1.6.0",
1036+ "zod": "^3.23.8"
1037+ },
1038+ "engines": {
1039+ "node": ">=18"
1040+ }
1041+ },
1042+ "node_modules/@jimp/plugins": {
1043+ "version": "0.22.12",
1044+ "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz",
1045+ "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==",
1046+ "license": "MIT",
1047+ "dependencies": {
1048+ "@jimp/plugin-blit": "^0.22.12",
1049+ "@jimp/plugin-blur": "^0.22.12",
1050+ "@jimp/plugin-circle": "^0.22.12",
1051+ "@jimp/plugin-color": "^0.22.12",
1052+ "@jimp/plugin-contain": "^0.22.12",
1053+ "@jimp/plugin-cover": "^0.22.12",
1054+ "@jimp/plugin-crop": "^0.22.12",
1055+ "@jimp/plugin-displace": "^0.22.12",
1056+ "@jimp/plugin-dither": "^0.22.12",
1057+ "@jimp/plugin-fisheye": "^0.22.12",
1058+ "@jimp/plugin-flip": "^0.22.12",
1059+ "@jimp/plugin-gaussian": "^0.22.12",
1060+ "@jimp/plugin-invert": "^0.22.12",
1061+ "@jimp/plugin-mask": "^0.22.12",
1062+ "@jimp/plugin-normalize": "^0.22.12",
1063+ "@jimp/plugin-print": "^0.22.12",
1064+ "@jimp/plugin-resize": "^0.22.12",
1065+ "@jimp/plugin-rotate": "^0.22.12",
1066+ "@jimp/plugin-scale": "^0.22.12",
1067+ "@jimp/plugin-shadow": "^0.22.12",
1068+ "@jimp/plugin-threshold": "^0.22.12",
1069+ "timm": "^1.6.1"
5531070 },
5541071 "peerDependencies": {
5551072 "@jimp/custom": ">=0.3.5"
5561073 }
5571074 },
5581075 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-displaceblit": {
5591076 "version": "0.22.12",
5601077 "resolved": "https://registry.npmjs.org/@jimp/plugin-displaceblit/-/plugin-displaceblit-0.22.12.tgz",
5611078 "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CAxslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
5621079 "license": "MIT",
5631080 "dependencies": {
5641081 "@jimp/utils": "^0.22.12"
@@ -567,10 +1084,10 @@
5671084 "@jimp/custom": ">=0.3.5"
5681085 }
5691086 },
5701087 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-ditherblur": {
5711088 "version": "0.22.12",
5721089 "resolved": "https://registry.npmjs.org/@jimp/plugin-ditherblur/-/plugin-ditherblur-0.22.12.tgz",
5731090 "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89sS0vJADTuh1Q9F+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLwcXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==",
5741091 "license": "MIT",
5751092 "dependencies": {
5761093 "@jimp/utils": "^0.22.12"
@@ -579,10 +1096,10 @@
5791096 "@jimp/custom": ">=0.3.5"
5801097 }
5811098 },
5821099 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-fisheyecircle": {
5831100 "version": "0.22.12",
5841101 "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheyecircle/-/plugin-fisheyecircle-0.22.12.tgz",
5851102 "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+QSWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==",
5861103 "license": "MIT",
5871104 "dependencies": {
5881105 "@jimp/utils": "^0.22.12"
@@ -591,23 +1108,53 @@
5911108 "@jimp/custom": ">=0.3.5"
5921109 }
5931110 },
5941111 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-flipcolor": {
5951112 "version": "0.22.12",
5961113 "resolved": "https://registry.npmjs.org/@jimp/plugin-flipcolor/-/plugin-flipcolor-0.22.12.tgz",
5971114 "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxtxImhTE5BpS8xa+inBIb7nlaLLxnvHX8QmAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",
1115+ "license": "MIT",
1116+ "dependencies": {
1117+ "@jimp/utils": "^0.22.12",
1118+ "tinycolor2": "^1.6.0"
1119+ },
1120+ "peerDependencies": {
1121+ "@jimp/custom": ">=0.3.5"
1122+ }
1123+ },
1124+ "node_modules/@jimp/plugins/node_modules/@jimp/plugin-contain": {
1125+ "version": "0.22.12",
1126+ "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz",
1127+ "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==",
5981128 "license": "MIT",
5991129 "dependencies": {
6001130 "@jimp/utils": "^0.22.12"
6011131 },
6021132 "peerDependencies": {
6031133 "@jimp/custom": ">=0.3.5",
6041134 "@jimp/plugin-rotateblit": ">=0.3.5",
1135+ "@jimp/plugin-resize": ">=0.3.5",
1136+ "@jimp/plugin-scale": ">=0.3.5"
6051137 }
6061138 },
6071139 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-gaussiancover": {
6081140 "version": "0.22.12",
6091141 "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussiancover/-/plugin-gaussiancover-0.22.12.tgz",
6101142 "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53z0w/1xH/v/knZkpTNx+n3c8Y0P2HWlUjflEla2hE8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/vWShvhgW56+4nJ1brhSd233HBJCo01BXA==",
1143+ "license": "MIT",
1144+ "dependencies": {
1145+ "@jimp/utils": "^0.22.12"
1146+ },
1147+ "peerDependencies": {
1148+ "@jimp/custom": ">=0.3.5",
1149+ "@jimp/plugin-crop": ">=0.3.5",
1150+ "@jimp/plugin-resize": ">=0.3.5",
1151+ "@jimp/plugin-scale": ">=0.3.5"
1152+ }
1153+ },
1154+ "node_modules/@jimp/plugins/node_modules/@jimp/plugin-crop": {
1155+ "version": "0.22.12",
1156+ "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",
1157+ "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",
6111158 "license": "MIT",
6121159 "dependencies": {
6131160 "@jimp/utils": "^0.22.12"
@@ -616,10 +1163,10 @@
6161163 "@jimp/custom": ">=0.3.5"
6171164 }
6181165 },
6191166 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-invertdisplace": {
6201167 "version": "0.22.12",
6211168 "resolved": "https://registry.npmjs.org/@jimp/plugin-invertdisplace/-/plugin-invertdisplace-0.22.12.tgz",
6221169 "integrity": "sha512-NqpRM8JRicxfK6aPPqKZA6+6rwxdBGzBwUIitiHaZw0QrJ64Ygd3+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6AsTc7BXr+xQ37k2x7QcyCvmKXY4haUrSIsBug4S3CA==",
6231170 "license": "MIT",
6241171 "dependencies": {
6251172 "@jimp/utils": "^0.22.12"
@@ -628,10 +1175,10 @@
6281175 "@jimp/custom": ">=0.3.5"
6291176 }
6301177 },
6311178 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-maskdither": {
6321179 "version": "0.22.12",
6331180 "resolved": "https://registry.npmjs.org/@jimp/plugin-maskdither/-/plugin-maskdither-0.22.12.tgz",
6341181 "integrity": "sha512-4AWZgjYgGdSdSKl1UUEanX8A85v4+DomtpUA099jRV8IEZUfn1wLv6QUm+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NAPE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==",
6351182 "license": "MIT",
6361183 "dependencies": {
6371184 "@jimp/utils": "^0.22.12"
@@ -640,10 +1187,10 @@
6401187 "@jimp/custom": ">=0.3.5"
6411188 }
6421189 },
6431190 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-normalizefisheye": {
6441191 "version": "0.22.12",
6451192 "resolved": "https://registry.npmjs.org/@jimp/plugin-normalizefisheye/-/plugin-normalizefisheye-0.22.12.tgz",
6461193 "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGCLGuUTsFg+As1FtAfOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==",
6471194 "license": "MIT",
6481195 "dependencies": {
6491196 "@jimp/utils": "^0.22.12"
@@ -652,24 +1199,23 @@
6521199 "@jimp/custom": ">=0.3.5"
6531200 }
6541201 },
6551202 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-printflip": {
6561203 "version": "0.22.12",
6571204 "resolved": "https://registry.npmjs.org/@jimp/plugin-printflip/-/plugin-printflip-0.22.12.tgz",
6581205 "integrity": "sha512-c7TnhHlxm87DJeSnwrm251Rop7GN8W0Yo/XOLjJUrF9LWk6kNclngyjIJs/whoiKYY7r21SbuJ5nuHVXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQinBIb7nlaLLxnvHX8Q==",
6591206 "license": "MIT",
6601207 "dependencies": {
6611208 "@jimp/utils": "^0.22.12",
662- "load-bmfont": "^1.4.1"
6631209 },
6641210 "peerDependencies": {
6651211 "@jimp/custom": ">=0.3.5",
6661212 "@jimp/plugin-blitrotate": ">=0.3.5"
6671213 }
6681214 },
6691215 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-resizemask": {
6701216 "version": "0.22.12",
6711217 "resolved": "https://registry.npmjs.org/@jimp/plugin-resizemask/-/plugin-resizemask-0.22.12.tgz",
6721218 "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX/4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/i82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/sXgNA==",
6731219 "license": "MIT",
6741220 "dependencies": {
6751221 "@jimp/utils": "^0.22.12"
@@ -678,49 +1224,48 @@
6781224 "@jimp/custom": ">=0.3.5"
6791225 }
6801226 },
6811227 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-rotateprint": {
6821228 "version": "0.22.12",
6831229 "resolved": "https://registry.npmjs.org/@jimp/plugin-rotateprint/-/plugin-rotateprint-0.22.12.tgz",
6841230 "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvRc7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+emmmPPWGgaYNYt1gA7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==",
6851231 "license": "MIT",
6861232 "dependencies": {
6871233 "@jimp/utils": "^0.22.12",
1234+ "load-bmfont": "^1.4.1"
6881235 },
6891236 "peerDependencies": {
6901237 "@jimp/custom": ">=0.3.5",
6911238 "@jimp/plugin-blit": ">=0.3.5",
692- "@jimp/plugin-crop": ">=0.3.5",
693- "@jimp/plugin-resize": ">=0.3.5"
6941239 }
6951240 },
6961241 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-scaleresize": {
6971242 "version": "0.22.12",
6981243 "resolved": "https://registry.npmjs.org/@jimp/plugin-scaleresize/-/plugin-scaleresize-0.22.12.tgz",
6991244 "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
7001245 "license": "MIT",
7011246 "dependencies": {
7021247 "@jimp/utils": "^0.22.12"
7031248 },
7041249 "peerDependencies": {
7051250 "@jimp/custom": ">=0.3.5",
706- "@jimp/plugin-resize": ">=0.3.5"
7071251 }
7081252 },
7091253 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-shadowrotate": {
7101254 "version": "0.22.12",
7111255 "resolved": "https://registry.npmjs.org/@jimp/plugin-shadowrotate/-/plugin-shadowrotate-0.22.12.tgz",
7121256 "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV39YNEt7BPAFfTls2FGfKBVgwwLUuKqy+WWwTgE8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
7131257 "license": "MIT",
7141258 "dependencies": {
7151259 "@jimp/utils": "^0.22.12"
7161260 },
7171261 "peerDependencies": {
7181262 "@jimp/custom": ">=0.3.5",
7191263 "@jimp/plugin-blurblit": ">=0.3.5",
1264+ "@jimp/plugin-crop": ">=0.3.5",
7201265 "@jimp/plugin-resize": ">=0.3.5"
7211266 }
7221267 },
7231268 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-threshold": {
7241269 "version": "0.22.12",
7251270 "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz",
7261271 "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==",
@@ -734,37 +1279,13 @@
7341279 "@jimp/plugin-resize": ">=0.8.0"
7351280 }
7361281 },
7371282 "node_modules/@jimp/plugins/node_modules/@jimp/utils": {
7381283 "version": "0.22.12",
7391284 "resolved": "https://registry.npmjs.org/@jimp/pluginsutils/-/pluginsutils-0.22.12.tgz",
7401285 "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/XWILoTZsjTUASQcGoH0TuC0N7xm3wwnMKXk8TrSwAE71zt716Q==",
7411286 "license": "MIT",
7421287 "dependencies": {
7431288 "@jimp/pluginregenerator-blitruntime": "^0.2213.123",
744- "@jimp/plugin-blur": "^0.22.12",
745- "@jimp/plugin-circle": "^0.22.12",
746- "@jimp/plugin-color": "^0.22.12",
747- "@jimp/plugin-contain": "^0.22.12",
748- "@jimp/plugin-cover": "^0.22.12",
749- "@jimp/plugin-crop": "^0.22.12",
750- "@jimp/plugin-displace": "^0.22.12",
751- "@jimp/plugin-dither": "^0.22.12",
752- "@jimp/plugin-fisheye": "^0.22.12",
753- "@jimp/plugin-flip": "^0.22.12",
754- "@jimp/plugin-gaussian": "^0.22.12",
755- "@jimp/plugin-invert": "^0.22.12",
756- "@jimp/plugin-mask": "^0.22.12",
757- "@jimp/plugin-normalize": "^0.22.12",
758- "@jimp/plugin-print": "^0.22.12",
759- "@jimp/plugin-resize": "^0.22.12",
760- "@jimp/plugin-rotate": "^0.22.12",
761- "@jimp/plugin-scale": "^0.22.12",
762- "@jimp/plugin-shadow": "^0.22.12",
763- "@jimp/plugin-threshold": "^0.22.12",
764- "timm": "^1.6.1"
765- },
766- "peerDependencies": {
767- "@jimp/custom": ">=0.3.5"
7681289 }
7691290 },
7701291 "node_modules/@jimp/png": {
@@ -780,6 +1301,24 @@
7801301 "@jimp/custom": ">=0.3.5"
7811302 }
7821303 },
1304+ "node_modules/@jimp/png/node_modules/@jimp/utils": {
1305+ "version": "0.22.12",
1306+ "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
1307+ "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
1308+ "license": "MIT",
1309+ "dependencies": {
1310+ "regenerator-runtime": "^0.13.3"
1311+ }
1312+ },
1313+ "node_modules/@jimp/png/node_modules/pngjs": {
1314+ "version": "6.0.0",
1315+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
1316+ "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
1317+ "license": "MIT",
1318+ "engines": {
1319+ "node": ">=12.13.0"
1320+ }
1321+ },
7831322 "node_modules/@jimp/tiff": {
7841323 "version": "0.22.12",
7851324 "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz",
@@ -793,29 +1332,81 @@
7931332 }
7941333 },
7951334 "node_modules/@jimp/types": {
7961335 "version": "01.226.120",
7971336 "resolved": "https://registry.npmjs.org/@jimp/types/-/types-01.226.120.tgz",
7981337 "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==",
7991338 "license": "MIT",
8001339 "dependencies": {
8011340 "@jimp/bmpzod": "^03.2223.128",
802- "@jimp/gif": "^0.22.12",
803- "@jimp/jpeg": "^0.22.12",
804- "@jimp/png": "^0.22.12",
805- "@jimp/tiff": "^0.22.12",
806- "timm": "^1.6.1"
8071341 },
8081342 "peerDependenciesengines": {
8091343 "@jimp/customnode": ">=0.3.518"
8101344 }
8111345 },
8121346 "node_modules/@jimp/utils": {
8131347 "version": "01.226.120",
8141348 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-01.226.120.tgz",
8151349 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHWgqFTGEosKbOkYF/nMKXk8TrSwAE71zt716QWFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==",
8161350 "license": "MIT",
8171351 "dependencies": {
8181352 "regenerator-runtime@jimp/types": "^01.136.30",
1353+ "tinycolor2": "^1.6.0"
1354+ },
1355+ "engines": {
1356+ "node": ">=18"
1357+ }
1358+ },
1359+ "node_modules/@jimp/wasm-avif": {
1360+ "version": "1.6.0",
1361+ "resolved": "https://registry.npmjs.org/@jimp/wasm-avif/-/wasm-avif-1.6.0.tgz",
1362+ "integrity": "sha512-OjiqqtD71MTLVQgB/oUOZ8PD+nxLQtl/Lyf/P/hhL56L6iCJbcciA3xO0Y9OJept7+R34ORY4f7RwWwt535DHg==",
1363+ "license": "MIT",
1364+ "dependencies": {
1365+ "@jsquash/avif": "^1.3.0",
1366+ "zod": "^3.23.8"
1367+ },
1368+ "engines": {
1369+ "node": ">=18"
1370+ }
1371+ },
1372+ "node_modules/@jimp/wasm-jpeg": {
1373+ "version": "1.6.0",
1374+ "resolved": "https://registry.npmjs.org/@jimp/wasm-jpeg/-/wasm-jpeg-1.6.0.tgz",
1375+ "integrity": "sha512-zURjiESa79XXpMHzvvLMD3RC+wzFevS8lcTgyoZq4/y7qE5WrrUTWUBhr1UMFDueQ4gmOUf6EZ8L3/jkS/6nKQ==",
1376+ "license": "MIT",
1377+ "dependencies": {
1378+ "@jsquash/jpeg": "^1.4.0",
1379+ "zod": "^3.23.8"
1380+ },
1381+ "engines": {
1382+ "node": ">=18"
1383+ }
1384+ },
1385+ "node_modules/@jimp/wasm-png": {
1386+ "version": "1.6.0",
1387+ "resolved": "https://registry.npmjs.org/@jimp/wasm-png/-/wasm-png-1.6.0.tgz",
1388+ "integrity": "sha512-D1xTVXpErApFH4YiOWZDoF8S8B6I3PgoCe38WsnTxDLfple8KGZMBdNjv3UO7vczsdlg5rATwWMBbNwxK03jNQ==",
1389+ "license": "MIT",
1390+ "dependencies": {
1391+ "@jsquash/oxipng": "^2.3.0",
1392+ "@jsquash/png": "^3.0.1",
1393+ "zod": "^3.23.8"
1394+ },
1395+ "engines": {
1396+ "node": ">=18"
1397+ }
1398+ },
1399+ "node_modules/@jimp/wasm-webp": {
1400+ "version": "1.6.0",
1401+ "resolved": "https://registry.npmjs.org/@jimp/wasm-webp/-/wasm-webp-1.6.0.tgz",
1402+ "integrity": "sha512-P0zUpK6n2XIAn8bt0F6rhSn1+FgteBTrL+TBb6Oqw8v5qEDJoNYkd6LlfZYN8YwtRBTBdZ8GFnWsg2Sar+qOkA==",
1403+ "license": "MIT",
1404+ "dependencies": {
1405+ "@jsquash/webp": "^1.4.0",
1406+ "zod": "^3.23.8"
1407+ },
1408+ "engines": {
1409+ "node": ">=18"
8191410 }
8201411 },
8211412 "node_modules/@jridgewell/gen-mapping": {
@@ -876,6 +1467,45 @@
8761467 "@jridgewell/sourcemap-codec": "^1.4.14"
8771468 }
8781469 },
1470+ "node_modules/@jsquash/avif": {
1471+ "version": "1.3.0",
1472+ "resolved": "https://registry.npmjs.org/@jsquash/avif/-/avif-1.3.0.tgz",
1473+ "integrity": "sha512-N6zH27O/AioCPNGxaf33PYnUEQZmAjUz0JwwAf9eMHRdYItn+CxwxlsHSSOkFmZKW+v9uVX6c7ZPQ4RTXArL7A==",
1474+ "license": "Apache-2.0",
1475+ "dependencies": {
1476+ "wasm-feature-detect": "^1.2.11"
1477+ }
1478+ },
1479+ "node_modules/@jsquash/jpeg": {
1480+ "version": "1.5.0",
1481+ "resolved": "https://registry.npmjs.org/@jsquash/jpeg/-/jpeg-1.5.0.tgz",
1482+ "integrity": "sha512-Jam3X9BhbP1f+d58TfYobV/ZpYhCnawBF7YMS0Vt5Gi1v5Rk+xUdqYiZarAn0PwAzy2Zm2pPU/VzXHsLhwT9QQ==",
1483+ "license": "Apache-2.0"
1484+ },
1485+ "node_modules/@jsquash/oxipng": {
1486+ "version": "2.3.0",
1487+ "resolved": "https://registry.npmjs.org/@jsquash/oxipng/-/oxipng-2.3.0.tgz",
1488+ "integrity": "sha512-aQ8wiEp6ztlTMXc+RMt/CG8crU3mEHDU+h+JYkIi6ctMhlh8+Ltj5XwQFfBuyzKYrp8NxaFW80Dp824bqjr+zA==",
1489+ "license": "Apache-2.0",
1490+ "dependencies": {
1491+ "wasm-feature-detect": "^1.2.11"
1492+ }
1493+ },
1494+ "node_modules/@jsquash/png": {
1495+ "version": "3.0.1",
1496+ "resolved": "https://registry.npmjs.org/@jsquash/png/-/png-3.0.1.tgz",
1497+ "integrity": "sha512-Bnvv93Y5LL92cuk2r2gpV+9JKuDo2/w7bOODw1iPxk8VARknky0sS1tSDgMosUdhNb4CdMlcCm3TMzTaqa3zZw==",
1498+ "license": "Apache-2.0"
1499+ },
1500+ "node_modules/@jsquash/webp": {
1501+ "version": "1.4.0",
1502+ "resolved": "https://registry.npmjs.org/@jsquash/webp/-/webp-1.4.0.tgz",
1503+ "integrity": "sha512-yKJb6Hilq+qV/4C4qTDEalBobNwJO09LeHHtilmWg5mYHFUDwMunfeAap/r3cL5KsHkGOoI0IjY2nKbTaHq9Bw==",
1504+ "license": "Apache-2.0",
1505+ "dependencies": {
1506+ "wasm-feature-detect": "^1.2.11"
1507+ }
1508+ },
8791509 "node_modules/@kwsites/file-exists": {
8801510 "version": "1.1.1",
8811511 "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@@ -970,6 +1600,19 @@
9701600 "node": ">=14"
9711601 }
9721602 },
1603+ "node_modules/@pkgr/core": {
1604+ "version": "0.1.1",
1605+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
1606+ "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
1607+ "dev": true,
1608+ "license": "MIT",
1609+ "engines": {
1610+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
1611+ },
1612+ "funding": {
1613+ "url": "https://opencollective.com/unts"
1614+ }
1615+ },
9731616 "node_modules/@popperjs/core": {
9741617 "version": "2.11.8",
9751618 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1389,13 +2032,6 @@
13892032 "dev": true,
13902033 "license": "MIT"
13912034 },
1392- "node_modules/@types/png-chunks-encode": {
1393- "version": "1.0.2",
1394- "resolved": "https://registry.npmjs.org/@types/png-chunks-encode/-/png-chunks-encode-1.0.2.tgz",
1395- "integrity": "sha512-Dxn0aXEcSg1wVeHjvNlygm/+fKBDzWMCdxJYhjGUTeefFW/jYxWcrg+W7ppLBfH44iJMqeVBHtHBwtYQUeYvgw==",
1396- "dev": true,
1397- "license": "MIT"
1398- },
13992035 "node_modules/@types/png-chunks-extract": {
14002036 "version": "1.0.2",
14012037 "resolved": "https://registry.npmjs.org/@types/png-chunks-extract/-/png-chunks-extract-1.0.2.tgz",
@@ -1926,29 +2562,6 @@
19262562 "balanced-match": "^1.0.0"
19272563 }
19282564 },
1929- "node_modules/archiver-utils/node_modules/buffer": {
1930- "version": "6.0.3",
1931- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
1932- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
1933- "funding": [
1934- {
1935- "type": "github",
1936- "url": "https://github.com/sponsors/feross"
1937- },
1938- {
1939- "type": "patreon",
1940- "url": "https://www.patreon.com/feross"
1941- },
1942- {
1943- "type": "consulting",
1944- "url": "https://feross.org/support"
1945- }
1946- ],
1947- "dependencies": {
1948- "base64-js": "^1.3.1",
1949- "ieee754": "^1.2.1"
1950- }
1951- },
19522565 "node_modules/archiver-utils/node_modules/glob": {
19532566 "version": "10.3.12",
19542567 "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
@@ -2026,29 +2639,6 @@
20262639 "safe-buffer": "~5.2.0"
20272640 }
20282641 },
2029- "node_modules/archiver/node_modules/buffer": {
2030- "version": "6.0.3",
2031- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
2032- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
2033- "funding": [
2034- {
2035- "type": "github",
2036- "url": "https://github.com/sponsors/feross"
2037- },
2038- {
2039- "type": "patreon",
2040- "url": "https://www.patreon.com/feross"
2041- },
2042- {
2043- "type": "consulting",
2044- "url": "https://feross.org/support"
2045- }
2046- ],
2047- "dependencies": {
2048- "base64-js": "^1.3.1",
2049- "ieee754": "^1.2.1"
2050- }
2051- },
20522642 "node_modules/archiver/node_modules/buffer-crc32": {
20532643 "version": "1.0.0",
20542644 "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
@@ -2099,6 +2689,16 @@
20992689 "safe-buffer": "~5.2.0"
21002690 }
21012691 },
2692+ "node_modules/are-docs-informative": {
2693+ "version": "0.0.2",
2694+ "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
2695+ "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
2696+ "dev": true,
2697+ "license": "MIT",
2698+ "engines": {
2699+ "node": ">=14"
2700+ }
2701+ },
21022702 "node_modules/argparse": {
21032703 "version": "2.0.1",
21042704 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2135,6 +2735,15 @@
21352735 "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
21362736 "license": "MIT"
21372737 },
2738+ "node_modules/await-to-js": {
2739+ "version": "3.0.0",
2740+ "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
2741+ "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
2742+ "license": "MIT",
2743+ "engines": {
2744+ "node": ">=6.0.0"
2745+ }
2746+ },
21382747 "node_modules/axios": {
21392748 "version": "1.8.3",
21402749 "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
@@ -2212,6 +2821,12 @@
22122821 "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
22132822 "license": "MIT"
22142823 },
2824+ "node_modules/bmp-ts": {
2825+ "version": "1.0.9",
2826+ "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
2827+ "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==",
2828+ "license": "MIT"
2829+ },
22152830 "node_modules/body-parser": {
22162831 "version": "1.20.3",
22172832 "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -2304,9 +2919,9 @@
23042919 }
23052920 },
23062921 "node_modules/buffer": {
23072922 "version": "56.70.13",
23082923 "resolved": "https://registry.npmjs.org/buffer/-/buffer-56.70.13.tgz",
23092924 "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHYFTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQpDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
23102925 "funding": [
23112926 {
23122927 "type": "github",
@@ -2324,7 +2939,7 @@
23242939 "license": "MIT",
23252940 "dependencies": {
23262941 "base64-js": "^1.3.1",
23272942 "ieee754": "^1.12.131"
23282943 }
23292944 },
23302945 "node_modules/buffer-crc32": {
@@ -2614,6 +3229,16 @@
26143229 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
26153230 "license": "MIT"
26163231 },
3232+ "node_modules/comment-parser": {
3233+ "version": "1.4.1",
3234+ "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
3235+ "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
3236+ "dev": true,
3237+ "license": "MIT",
3238+ "engines": {
3239+ "node": ">= 12.0.0"
3240+ }
3241+ },
26173242 "node_modules/compress-commons": {
26183243 "version": "6.0.2",
26193244 "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
@@ -2629,29 +3254,6 @@
26293254 "node": ">= 14"
26303255 }
26313256 },
2632- "node_modules/compress-commons/node_modules/buffer": {
2633- "version": "6.0.3",
2634- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
2635- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
2636- "funding": [
2637- {
2638- "type": "github",
2639- "url": "https://github.com/sponsors/feross"
2640- },
2641- {
2642- "type": "patreon",
2643- "url": "https://www.patreon.com/feross"
2644- },
2645- {
2646- "type": "consulting",
2647- "url": "https://feross.org/support"
2648- }
2649- ],
2650- "dependencies": {
2651- "base64-js": "^1.3.1",
2652- "ieee754": "^1.2.1"
2653- }
2654- },
26553257 "node_modules/compress-commons/node_modules/crc-32": {
26563258 "version": "1.2.2",
26573259 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -2928,8 +3530,25 @@
29283530 "object-assign": "^4",
29293531 "vary": "^1"
29303532 },
29313533 "engines": {
29323534 "node": ">= 0.10"
3535+ }
3536+ },
3537+ "node_modules/crc": {
3538+ "version": "4.3.2",
3539+ "resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz",
3540+ "integrity": "sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==",
3541+ "license": "MIT",
3542+ "engines": {
3543+ "node": ">=12"
3544+ },
3545+ "peerDependencies": {
3546+ "buffer": ">=6.0.3"
3547+ },
3548+ "peerDependenciesMeta": {
3549+ "buffer": {
3550+ "optional": true
3551+ }
29333552 }
29343553 },
29353554 "node_modules/crc-32": {
@@ -2953,29 +3572,6 @@
29533572 "node": ">= 14"
29543573 }
29553574 },
2956- "node_modules/crc32-stream/node_modules/buffer": {
2957- "version": "6.0.3",
2958- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
2959- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
2960- "funding": [
2961- {
2962- "type": "github",
2963- "url": "https://github.com/sponsors/feross"
2964- },
2965- {
2966- "type": "patreon",
2967- "url": "https://www.patreon.com/feross"
2968- },
2969- {
2970- "type": "consulting",
2971- "url": "https://feross.org/support"
2972- }
2973- ],
2974- "dependencies": {
2975- "base64-js": "^1.3.1",
2976- "ieee754": "^1.2.1"
2977- }
2978- },
29793575 "node_modules/crc32-stream/node_modules/crc-32": {
29803576 "version": "1.2.2",
29813577 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -3560,6 +4156,69 @@
35604156 "url": "https://opencollective.com/eslint"
35614157 }
35624158 },
4159+ "node_modules/eslint-plugin-jsdoc": {
4160+ "version": "48.10.0",
4161+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.10.0.tgz",
4162+ "integrity": "sha512-BEli0k8E0dzhJairAllwlkGnyYDZVKNn4WDmyKy+v6J5qGNuofjzxwNUi+55BOGmyO9mKBhqaidwGy+dxndn/Q==",
4163+ "dev": true,
4164+ "license": "BSD-3-Clause",
4165+ "dependencies": {
4166+ "@es-joy/jsdoccomment": "~0.46.0",
4167+ "are-docs-informative": "^0.0.2",
4168+ "comment-parser": "1.4.1",
4169+ "debug": "^4.3.5",
4170+ "escape-string-regexp": "^4.0.0",
4171+ "esquery": "^1.6.0",
4172+ "parse-imports": "^2.1.1",
4173+ "semver": "^7.6.3",
4174+ "spdx-expression-parse": "^4.0.0",
4175+ "synckit": "^0.9.1"
4176+ },
4177+ "engines": {
4178+ "node": ">=18"
4179+ },
4180+ "peerDependencies": {
4181+ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
4182+ }
4183+ },
4184+ "node_modules/eslint-plugin-jsdoc/node_modules/debug": {
4185+ "version": "4.4.0",
4186+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
4187+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
4188+ "dev": true,
4189+ "license": "MIT",
4190+ "dependencies": {
4191+ "ms": "^2.1.3"
4192+ },
4193+ "engines": {
4194+ "node": ">=6.0"
4195+ },
4196+ "peerDependenciesMeta": {
4197+ "supports-color": {
4198+ "optional": true
4199+ }
4200+ }
4201+ },
4202+ "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": {
4203+ "version": "4.0.0",
4204+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
4205+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
4206+ "dev": true,
4207+ "license": "MIT",
4208+ "engines": {
4209+ "node": ">=10"
4210+ },
4211+ "funding": {
4212+ "url": "https://github.com/sponsors/sindresorhus"
4213+ }
4214+ },
4215+ "node_modules/eslint-plugin-jsdoc/node_modules/ms": {
4216+ "version": "2.1.3",
4217+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
4218+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
4219+ "dev": true,
4220+ "license": "MIT"
4221+ },
35634222 "node_modules/eslint-scope": {
35644223 "version": "7.2.2",
35654224 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -3690,9 +4349,9 @@
36904349 }
36914350 },
36924351 "node_modules/esquery": {
36934352 "version": "1.56.0",
36944353 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.56.0.tgz",
36954354 "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idISca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/9fIU5GjG73IgjKMVg5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
36964355 "dev": true,
36974356 "license": "BSD-3-Clause",
36984357 "dependencies": {
@@ -4978,18 +5637,6 @@
49785637 "url": "https://github.com/chalk/supports-color?sponsor=1"
49795638 }
49805639 },
4981- "node_modules/jimp": {
4982- "version": "0.22.12",
4983- "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz",
4984- "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==",
4985- "license": "MIT",
4986- "dependencies": {
4987- "@jimp/custom": "^0.22.12",
4988- "@jimp/plugins": "^0.22.12",
4989- "@jimp/types": "^0.22.12",
4990- "regenerator-runtime": "^0.13.3"
4991- }
4992- },
49935640 "node_modules/jpeg-js": {
49945641 "version": "0.4.4",
49955642 "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
@@ -5015,6 +5662,16 @@
50155662 "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
50165663 "license": "MIT"
50175664 },
5665+ "node_modules/jsdoc-type-pratt-parser": {
5666+ "version": "4.0.0",
5667+ "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz",
5668+ "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==",
5669+ "dev": true,
5670+ "license": "MIT",
5671+ "engines": {
5672+ "node": ">=12.0.0"
5673+ }
5674+ },
50185675 "node_modules/json-buffer": {
50195676 "version": "3.0.1",
50205677 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5886,11 +6543,25 @@
58866543 }
58876544 },
58886545 "node_modules/parse-headers": {
58896546 "version": "2.0.56",
58906547 "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.56.tgz",
58916548 "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1ZauTz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/HwI75HA5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==",
58926549 "license": "MIT"
58936550 },
6551+ "node_modules/parse-imports": {
6552+ "version": "2.2.1",
6553+ "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz",
6554+ "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==",
6555+ "dev": true,
6556+ "license": "Apache-2.0 AND MIT",
6557+ "dependencies": {
6558+ "es-module-lexer": "^1.5.3",
6559+ "slashes": "^3.0.12"
6560+ },
6561+ "engines": {
6562+ "node": ">= 18"
6563+ }
6564+ },
58946565 "node_modules/parse5": {
58956566 "version": "7.1.2",
58966567 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@@ -6012,27 +6683,6 @@
60126683 "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
60136684 "license": "ISC"
60146685 },
6015- "node_modules/pixelmatch": {
6016- "version": "4.0.2",
6017- "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz",
6018- "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==",
6019- "license": "ISC",
6020- "dependencies": {
6021- "pngjs": "^3.0.0"
6022- },
6023- "bin": {
6024- "pixelmatch": "bin/pixelmatch"
6025- }
6026- },
6027- "node_modules/pixelmatch/node_modules/pngjs": {
6028- "version": "3.4.0",
6029- "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
6030- "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
6031- "license": "MIT",
6032- "engines": {
6033- "node": ">=4.0.0"
6034- }
6035- },
60366686 "node_modules/platform": {
60376687 "version": "1.3.6",
60386688 "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
@@ -6045,16 +6695,6 @@
60456695 "integrity": "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==",
60466696 "license": "MIT"
60476697 },
6048- "node_modules/png-chunks-encode": {
6049- "version": "1.0.0",
6050- "resolved": "https://registry.npmjs.org/png-chunks-encode/-/png-chunks-encode-1.0.0.tgz",
6051- "integrity": "sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA==",
6052- "license": "MIT",
6053- "dependencies": {
6054- "crc-32": "^0.3.0",
6055- "sliced": "^1.0.1"
6056- }
6057- },
60586698 "node_modules/png-chunks-extract": {
60596699 "version": "1.0.0",
60606700 "resolved": "https://registry.npmjs.org/png-chunks-extract/-/png-chunks-extract-1.0.0.tgz",
@@ -6065,12 +6705,12 @@
60656705 }
60666706 },
60676707 "node_modules/pngjs": {
60686708 "version": "67.0.0",
60696709 "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-67.0.0.tgz",
60706710 "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28KLKWqWJRhstyYo9pGvgor/m5bjBWKsC29KyoMfHbypaygivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
60716711 "license": "MIT",
60726712 "engines": {
60736713 "node": ">=1214.1319.0"
60746714 }
60756715 },
60766716 "node_modules/prelude-ls": {
@@ -6342,12 +6982,12 @@
63426982 }
63436983 },
63446984 "node_modules/readable-web-to-node-stream": {
63456985 "version": "3.0.24",
63466986 "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.24.tgz",
63476987 "integrity": "sha512-ePeK6cc1EcKLEhJFt9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/AebMCLL+GgSKhuygrZCVPtb/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGwXHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==",
63486988 "license": "MIT",
63496989 "dependencies": {
63506990 "readable-stream": "^34.67.0"
63516991 },
63526992 "engines": {
63536993 "node": ">=8"
@@ -6358,17 +6998,48 @@
63586998 }
63596999 },
63607000 "node_modules/readable-web-to-node-stream/node_modules/readable-stream": {
63617001 "version": "34.67.20",
63627002 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-34.67.20.tgz",
63637003 "integrity": "sha512-9uoIGGmcpTLwPga8Bn6/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoAZ75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
63647004 "license": "MIT",
63657005 "dependencies": {
63667006 "inheritsabort-controller": "^23.0.30",
63677007 "string_decoderbuffer": "^16.10.13",
63687008 "util-deprecateevents": "^13.03.10",
7009+ "process": "^0.11.10",
7010+ "string_decoder": "^1.3.0"
63697011 },
63707012 "engines": {
6371- "node": ">= 6"
7013+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
7014+ }
7015+ },
7016+ "node_modules/readable-web-to-node-stream/node_modules/safe-buffer": {
7017+ "version": "5.2.1",
7018+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
7019+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
7020+ "funding": [
7021+ {
7022+ "type": "github",
7023+ "url": "https://github.com/sponsors/feross"
7024+ },
7025+ {
7026+ "type": "patreon",
7027+ "url": "https://www.patreon.com/feross"
7028+ },
7029+ {
7030+ "type": "consulting",
7031+ "url": "https://feross.org/support"
7032+ }
7033+ ],
7034+ "license": "MIT"
7035+ },
7036+ "node_modules/readable-web-to-node-stream/node_modules/string_decoder": {
7037+ "version": "1.3.0",
7038+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
7039+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
7040+ "license": "MIT",
7041+ "dependencies": {
7042+ "safe-buffer": "~5.2.0"
63727043 }
63737044 },
63747045 "node_modules/readdir-glob": {
@@ -6600,6 +7271,19 @@
66007271 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
66017272 "license": "MIT"
66027273 },
7274+ "node_modules/semver": {
7275+ "version": "7.7.1",
7276+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
7277+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
7278+ "dev": true,
7279+ "license": "ISC",
7280+ "bin": {
7281+ "semver": "bin/semver.js"
7282+ },
7283+ "engines": {
7284+ "node": ">=10"
7285+ }
7286+ },
66037287 "node_modules/send": {
66047288 "version": "0.19.0",
66057289 "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
@@ -6773,6 +7457,35 @@
67737457 "onnxruntime-web": "1.14.0"
67747458 }
67757459 },
7460+ "node_modules/sillytavern-transformers/node_modules/@jimp/types": {
7461+ "version": "0.22.12",
7462+ "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz",
7463+ "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==",
7464+ "license": "MIT",
7465+ "dependencies": {
7466+ "@jimp/bmp": "^0.22.12",
7467+ "@jimp/gif": "^0.22.12",
7468+ "@jimp/jpeg": "^0.22.12",
7469+ "@jimp/png": "^0.22.12",
7470+ "@jimp/tiff": "^0.22.12",
7471+ "timm": "^1.6.1"
7472+ },
7473+ "peerDependencies": {
7474+ "@jimp/custom": ">=0.3.5"
7475+ }
7476+ },
7477+ "node_modules/sillytavern-transformers/node_modules/jimp": {
7478+ "version": "0.22.12",
7479+ "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz",
7480+ "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==",
7481+ "license": "MIT",
7482+ "dependencies": {
7483+ "@jimp/custom": "^0.22.12",
7484+ "@jimp/plugins": "^0.22.12",
7485+ "@jimp/types": "^0.22.12",
7486+ "regenerator-runtime": "^0.13.3"
7487+ }
7488+ },
67767489 "node_modules/simple-git": {
67777490 "version": "3.27.0",
67787491 "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",
@@ -6811,11 +7524,12 @@
68117524 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
68127525 "license": "MIT"
68137526 },
68147527 "node_modules/slicedslashes": {
68157528 "version": "13.0.112",
68167529 "resolved": "https://registry.npmjs.org/slicedslashes/-/slicedslashes-13.0.112.tgz",
68177530 "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2XxzmQ9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yAkWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==",
68187531 "licensedev": "MIT"true,
7532+ "license": "ISC"
68197533 },
68207534 "node_modules/slidetoggle": {
68217535 "version": "4.0.0",
@@ -6903,6 +7617,31 @@
69037617 "source-map": "^0.6.0"
69047618 }
69057619 },
7620+ "node_modules/spdx-exceptions": {
7621+ "version": "2.5.0",
7622+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
7623+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
7624+ "dev": true,
7625+ "license": "CC-BY-3.0"
7626+ },
7627+ "node_modules/spdx-expression-parse": {
7628+ "version": "4.0.0",
7629+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
7630+ "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
7631+ "dev": true,
7632+ "license": "MIT",
7633+ "dependencies": {
7634+ "spdx-exceptions": "^2.1.0",
7635+ "spdx-license-ids": "^3.0.0"
7636+ }
7637+ },
7638+ "node_modules/spdx-license-ids": {
7639+ "version": "3.0.21",
7640+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
7641+ "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
7642+ "dev": true,
7643+ "license": "CC0-1.0"
7644+ },
69067645 "node_modules/sprintf-js": {
69077646 "version": "1.1.3",
69087647 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@@ -7042,6 +7781,23 @@
70427781 "node": ">=8"
70437782 }
70447783 },
7784+ "node_modules/synckit": {
7785+ "version": "0.9.2",
7786+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
7787+ "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
7788+ "dev": true,
7789+ "license": "MIT",
7790+ "dependencies": {
7791+ "@pkgr/core": "^0.1.0",
7792+ "tslib": "^2.6.2"
7793+ },
7794+ "engines": {
7795+ "node": "^14.18.0 || >=16.0.0"
7796+ },
7797+ "funding": {
7798+ "url": "https://opencollective.com/unts"
7799+ }
7800+ },
70457801 "node_modules/tapable": {
70467802 "version": "2.2.1",
70477803 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -7391,6 +8147,12 @@
73918147 "vectra": "bin/vectra.js"
73928148 }
73938149 },
8150+ "node_modules/wasm-feature-detect": {
8151+ "version": "1.8.0",
8152+ "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz",
8153+ "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==",
8154+ "license": "Apache-2.0"
8155+ },
73948156 "node_modules/watchpack": {
73958157 "version": "2.4.2",
73968158 "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
@@ -7757,29 +8519,6 @@
77578519 "node": ">= 14"
77588520 }
77598521 },
7760- "node_modules/zip-stream/node_modules/buffer": {
7761- "version": "6.0.3",
7762- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
7763- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
7764- "funding": [
7765- {
7766- "type": "github",
7767- "url": "https://github.com/sponsors/feross"
7768- },
7769- {
7770- "type": "patreon",
7771- "url": "https://www.patreon.com/feross"
7772- },
7773- {
7774- "type": "consulting",
7775- "url": "https://feross.org/support"
7776- }
7777- ],
7778- "dependencies": {
7779- "base64-js": "^1.3.1",
7780- "ieee754": "^1.2.1"
7781- }
7782- },
77838522 "node_modules/zip-stream/node_modules/readable-stream": {
77848523 "version": "4.5.2",
77858524 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
@@ -7821,6 +8560,15 @@
78218560 "dependencies": {
78228561 "safe-buffer": "~5.2.0"
78238562 }
8563+ },
8564+ "node_modules/zod": {
8565+ "version": "3.24.2",
8566+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
8567+ "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
8568+ "license": "MIT",
8569+ "funding": {
8570+ "url": "https://github.com/sponsors/colinhacks"
8571+ }
78248572 }
78258573 }
78268574}
package.json+24 -5
@@ -4,6 +4,26 @@
44 "@agnai/sentencepiece-js": "^1.1.1",
55 "@agnai/web-tokenizers": "^0.1.3",
66 "@iconfu/svg-inject": "^1.2.3",
7+ "@jimp/core": "^1.6.0",
8+ "@jimp/js-bmp": "^1.6.0",
9+ "@jimp/js-gif": "^1.6.0",
10+ "@jimp/js-tiff": "^1.6.0",
11+ "@jimp/plugin-circle": "^1.6.0",
12+ "@jimp/plugin-color": "^1.6.0",
13+ "@jimp/plugin-contain": "^1.6.0",
14+ "@jimp/plugin-cover": "^1.6.0",
15+ "@jimp/plugin-crop": "^1.6.0",
16+ "@jimp/plugin-displace": "^1.6.0",
17+ "@jimp/plugin-fisheye": "^1.6.0",
18+ "@jimp/plugin-flip": "^1.6.0",
19+ "@jimp/plugin-mask": "^1.6.0",
20+ "@jimp/plugin-quantize": "^1.6.0",
21+ "@jimp/plugin-rotate": "^1.6.0",
22+ "@jimp/plugin-threshold": "^1.6.0",
23+ "@jimp/wasm-avif": "^1.6.0",
24+ "@jimp/wasm-jpeg": "^1.6.0",
25+ "@jimp/wasm-png": "^1.6.0",
26+ "@jimp/wasm-webp": "^1.6.0",
727 "@mozilla/readability": "^0.6.0",
828 "@popperjs/core": "^2.11.8",
929 "@zeldafan0225/ai_horde": "^5.2.0",
@@ -18,6 +38,7 @@
1838 "cookie-parser": "^1.4.6",
1939 "cookie-session": "^2.1.0",
2040 "cors": "^2.8.5",
41+ "crc": "^4.3.2",
2142 "csrf-sync": "^4.0.3",
2243 "diff-match-patch": "^1.0.5",
2344 "dompurify": "^3.2.4",
@@ -36,7 +57,6 @@
3657 "ip-regex": "^5.0.0",
3758 "ipaddr.js": "^2.2.0",
3859 "is-docker": "^3.0.0",
39- "jimp": "^0.22.10",
4060 "localforage": "^1.10.0",
4161 "lodash": "^4.17.21",
4262 "mime-types": "^2.1.35",
@@ -47,7 +67,6 @@
4767 "node-persist": "^4.0.4",
4868 "open": "^8.4.2",
4969 "png-chunk-text": "^1.0.0",
50- "png-chunks-encode": "^1.0.0",
5170 "png-chunks-extract": "^1.0.0",
5271 "proxy-agent": "^6.5.0",
5372 "rate-limiter-flexible": "^5.0.5",
@@ -90,7 +109,7 @@
90109 "type": "git",
91110 "url": "https://github.com/SillyTavern/SillyTavern.git"
92111 },
93112 "version": "1.12.1314",
94113 "scripts": {
95114 "start": "node server.js",
96115 "debug": "node --inspect server.js",
@@ -132,7 +151,6 @@
132151 "@types/node": "^18.19.80",
133152 "@types/node-persist": "^3.1.8",
134153 "@types/png-chunk-text": "^1.0.3",
135- "@types/png-chunks-encode": "^1.0.2",
136154 "@types/png-chunks-extract": "^1.0.2",
137155 "@types/response-time": "^2.3.8",
138156 "@types/select2": "^4.0.63",
@@ -140,6 +158,7 @@
140158 "@types/write-file-atomic": "^4.0.3",
141159 "@types/yargs": "^17.0.33",
142160 "@types/yauzl": "^2.10.3",
143161 "eslint": "^8.57.1",
162+ "eslint-plugin-jsdoc": "^48.10.0"
144163 }
145164}
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/css/extensions-panel.css+12 -0
@@ -146,3 +146,15 @@ input.extension_missing[type="checkbox"] {
146146.extensions_info .extension_actions {
147147 flex-wrap: nowrap;
148148}
149+
150+.extensions_toolbar {
151+ top: 0;
152+ position: sticky;
153+ display: flex;
154+ flex-direction: row;
155+ background-color: var(--SmartThemeBlurTintColor);
156+ gap: 5px;
157+ z-index: 1;
158+ margin-bottom: 10px;
159+ padding: 5px;
160+}
public/css/select2-overrides.css+1 -0
@@ -13,6 +13,7 @@
1313 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
1414 color: var(--SmartThemeBodyColor);
1515 z-index: 40000;
16+ user-select: none;
1617}
1718
1819.select2-container .select2-selection .select2-selection__clear {
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/img/xai.svg+46 -0
@@ -0,0 +1,46 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3+
4+<svg
5+ version="1.1"
6+ id="katman_1"
7+ x="0px"
8+ y="0px"
9+ viewBox="0 0 438.67001 481.44999"
10+ xml:space="preserve"
11+ sodipodi:docname="XAI_Logo.svg"
12+ width="438.67001"
13+ height="481.45001"
14+ inkscape:version="1.3 (0e150ed, 2023-07-21)"
15+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
16+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
17+ xmlns="http://www.w3.org/2000/svg"
18+ xmlns:svg="http://www.w3.org/2000/svg"><defs
19+ id="defs4" /><sodipodi:namedview
20+ id="namedview4"
21+ pagecolor="#ffffff"
22+ bordercolor="#000000"
23+ borderopacity="0.25"
24+ inkscape:showpageshadow="2"
25+ inkscape:pageopacity="0.0"
26+ inkscape:pagecheckerboard="0"
27+ inkscape:deskcolor="#d1d1d1"
28+ inkscape:zoom="0.39645207"
29+ inkscape:cx="219.44645"
30+ inkscape:cy="238.36425"
31+ inkscape:window-width="1512"
32+ inkscape:window-height="856"
33+ inkscape:window-x="0"
34+ inkscape:window-y="38"
35+ inkscape:window-maximized="1"
36+ inkscape:current-layer="katman_1" />&#10;<g
37+ id="g4"
38+ transform="translate(-201.61,-56.91)">&#10; <polygon
39+ points="631.96,538.36 640.28,93.18 557.09,211.99 565.4,538.36 "
40+ id="polygon1" />&#10; <polygon
41+ points="379.35,284.53 430.13,357.05 640.28,56.91 538.72,56.91 "
42+ id="polygon2" />&#10; <polygon
43+ points="353.96,465.84 303.17,393.31 201.61,538.36 303.17,538.36 "
44+ id="polygon3" />&#10; <polygon
45+ points="531.69,538.36 303.17,211.99 201.61,211.99 430.13,538.36 "
46+ id="polygon4" />&#10;</g>&#10;</svg>
public/index.html+126 -64
@@ -197,6 +197,9 @@
197197 <div id="update_oai_preset" class="menu_button menu_button_icon" title="Update current preset" data-i18n="[title]Update current preset">
198198 <i class="fa-fw fa-solid fa-save"></i>
199199 </div>
200+ <div data-preset-manager-rename="openai" class="menu_button menu_button_icon" title="Rename current preset" data-i18n="[title]Rename current preset">
201+ <i class="fa-fw fa-solid fa-pencil"></i>
202+ </div>
200203 <div id="new_oai_preset" class="menu_button menu_button_icon" title="Save preset as" data-i18n="[title]Save preset as">
201204 <i class="fa-fw fa-solid fa-file-circle-plus"></i>
202205 </div>
@@ -643,7 +646,7 @@
643646 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
644647 </div>
645648 </div>
646649 <div class="range-block" data-source="openai,custom,xai">
647650 <div class="range-block-title" data-i18n="Multiple swipes per generation">
648651 Multiple swipes per generation
649652 </div>
@@ -682,7 +685,7 @@
682685 </span>
683686 </div>
684687 </div>
685688 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
686689 <div class="range-block-title" data-i18n="Temperature">
687690 Temperature
688691 </div>
@@ -695,7 +698,7 @@
695698 </div>
696699 </div>
697700 </div>
698701 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
699702 <div class="range-block-title" data-i18n="Frequency Penalty">
700703 Frequency Penalty
701704 </div>
@@ -708,7 +711,7 @@
708711 </div>
709712 </div>
710713 </div>
711714 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
712715 <div class="range-block-title" data-i18n="Presence Penalty">
713716 Presence Penalty
714717 </div>
@@ -734,7 +737,7 @@
734737 </div>
735738 </div>
736739 </div>
737740 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
738741 <div class="range-block-title" data-i18n="Top P">
739742 Top P
740743 </div>
@@ -971,7 +974,7 @@
971974 </div>
972975 </div>
973976 </div>
974977 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai">
975978 <div class="range-block-title justifyLeft" data-i18n="Seed">
976979 Seed
977980 </div>
@@ -1416,7 +1419,7 @@
14161419 </div>
14171420 </div>
14181421
14191422 <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp, dreamgen" id="dryBlock" class="wide100p">
14201423 <h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable." data-i18n="[title]DRY_Repetition_Penalty_desc">
14211424 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
14221425 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -1571,7 +1574,7 @@
15711574 <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative" title="Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative."></div>
15721575 </label>
15731576 </label>
15741577 <label data-tg-type="ooba, llamacpp, tabby, koboldcpp, dreamgen" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui">
15751578 <input type="checkbox" id="ban_eos_token_textgenerationwebui" />
15761579 <label>
15771580 <small data-i18n="Ban EOS Token">Ban EOS Token</small>
@@ -1957,12 +1960,15 @@
19571960 <span data-i18n="Enable web search">Enable web search</span>
19581961 </label>
19591962 <div class="flexBasis100p toggle-description justifyLeft">
1960- <span>
1963+ <span data-i18n="Use search capabilities provided by the backend.">
19611964 Use search capabilities provided by the backend.
19621965 </span>
1966+ <b data-source="openrouter" data-i18n="openrouter_web_search_fee">
1967+ Not free, adds a $0.02 fee to each prompt.
1968+ </b>
19631969 </div>
19641970 </div>
19651971 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai">
19661972 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
19671973 <input id="openai_function_calling" type="checkbox" />
19681974 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1972,7 +1978,7 @@
19721978 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
19731979 </div>
19741980 </div>
19751981 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai">
19761982 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
19771983 <input id="openai_image_inlining" type="checkbox" />
19781984 <span data-i18n="Send inline images">Send inline images</span>
@@ -1984,7 +1990,7 @@
19841990 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
19851991 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
19861992 </div>
19871993 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai">
19881994 <div class="flex-container oneline-dropdown">
19891995 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
19901996 Inline Image Quality
@@ -2028,7 +2034,7 @@
20282034 </span>
20292035 </div>
20302036 </div>
20312037 <div class="range-block" data-source="deepseek,openrouter,custom,claude,xai">
20322038 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
20332039 <input id="openai_show_thoughts" type="checkbox" />
20342040 <span>
@@ -2042,7 +2048,7 @@
20422048 </span>
20432049 </div>
20442050 </div>
20452051 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai">
20462052 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Currently supported values are low, medium, and high.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
20472053 <label for="openai_reasoning_effort">
20482054 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2188,7 +2194,7 @@
21882194 <input id="horde_trusted_workers_only" type="checkbox" />
21892195 <span data-i18n="Trusted workers only">Trusted workers only</span>
21902196 </label>
21912197 <small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
21922198 <h4 data-i18n="API key">API key</h4>
21932199 <small>
21942200 <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>
@@ -2429,7 +2435,7 @@
24292435 </div>
24302436 <div class="flex1">
24312437 <h4 data-i18n="Server url">Server URL</h4>
24322438 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
24332439 <input id="generic_api_url_text" name="generic_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="generic">
24342440 </div>
24352441 <datalist id="generic_model_fill"></datalist>
@@ -2458,7 +2464,7 @@
24582464 </div>
24592465 <div class="flex1">
24602466 <h4 data-i18n="Server url">Server URL</h4>
24612467 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
24622468 <input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ooba_blocking">
24632469 </div>
24642470 <input id="custom_model_textgenerationwebui" class="text_pole wide100p" placeholder="Custom model (optional)" data-i18n="[placeholder]Custom model (optional)" type="text">
@@ -2531,7 +2537,7 @@
25312537 </div>
25322538 <div class="flex1">
25332539 <h4 data-i18n="API url">API URL</h4>
25342540 <small data-i18n="Example: http://127.0.0.1:8000">Example: http://127.0.0.1:8000</small>
25352541 <input id="vllm_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="vllm">
25362542 </div>
25372543 <div>
@@ -2577,7 +2583,7 @@
25772583 </div>
25782584 <div class="flex1">
25792585 <h4 data-i18n="API url">API URL</h4>
25802586 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
25812587 <input id="aphrodite_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="aphrodite">
25822588 </div>
25832589 <div>
@@ -2606,7 +2612,7 @@
26062612 </div>
26072613 <div class="flex1">
26082614 <h4 data-i18n="API url">API URL</h4>
26092615 <small data-i18n="Example: http://127.0.0.1:8080">Example: http://127.0.0.1:8080</small>
26102616 <input id="llamacpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="llamacpp">
26112617 </div>
26122618 </div>
@@ -2618,7 +2624,7 @@
26182624 </div>
26192625 <div class="flex1">
26202626 <h4 data-i18n="API url">API URL</h4>
26212627 <small data-i18n="Example: http://127.0.0.1:11434">Example: http://127.0.0.1:11434</small>
26222628 <input id="ollama_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ollama">
26232629 </div>
26242630 <div class="flex1">
@@ -2653,7 +2659,7 @@
26532659 </div>
26542660 <div class="flex1">
26552661 <h4 data-i18n="API url">API URL</h4>
26562662 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
26572663 <input id="tabby_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="tabby">
26582664 </div>
26592665 <div class="flex1">
@@ -2705,7 +2711,7 @@
27052711 </div>
27062712 <div class="flex1">
27072713 <h4 data-i18n="API url">API URL</h4>
27082714 <small data-i18n="Example: http://127.0.0.1:5001">Example: http://127.0.0.1:5001</small>
27092715 <input id="koboldcpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="koboldcpp">
27102716 </div>
27112717 </div>
@@ -2742,7 +2748,6 @@
27422748 <optgroup>
27432749 <option value="01ai">01.AI (Yi)</option>
27442750 <option value="ai21">AI21</option>
2745- <option value="blockentropy">Block Entropy</option>
27462751 <option value="claude">Claude</option>
27472752 <option value="cohere">Cohere</option>
27482753 <option value="deepseek">DeepSeek</option>
@@ -2754,9 +2759,10 @@
27542759 <option value="perplexity">Perplexity</option>
27552760 <option value="scale">Scale</option>
27562761 <option value="windowai">Window AI</option>
2762+ <option value="xai">xAI (Grok)</option>
27572763 </optgroup>
27582764 </select>
27592765 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek,xai">
27602766 <div class="inline-drawer-toggle inline-drawer-header">
27612767 <b data-i18n="Reverse Proxy">Reverse Proxy</b>
27622768 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -2819,7 +2825,7 @@
28192825 </div>
28202826 </div>
28212827 </div>
28222828 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek,xai">
28232829 <div class="reverse_proxy_warning">
28242830 <b>
28252831 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">
@@ -2883,7 +2889,15 @@
28832889 <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
28842890 <option value="chatgpt-4o-latest">chatgpt-4o-latest</option>
28852891 </optgroup>
28862892 <optgroup label="o1 and o1GPT-mini4.1">
2893+ <option value="gpt-4.1">gpt-4.1</option>
2894+ <option value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option>
2895+ <option value="gpt-4.1-mini">gpt-4.1-mini</option>
2896+ <option value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option>
2897+ <option value="gpt-4.1-nano">gpt-4.1-nano</option>
2898+ <option value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option>
2899+ </optgroup>
2900+ <optgroup label="o1">
28872901 <option value="o1">o1</option>
28882902 <option value="o1-2024-12-17">o1-2024-12-17</option>
28892903 <option value="o1-mini">o1-mini</option>
@@ -2892,9 +2906,15 @@
28922906 <option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option>
28932907 </optgroup>
28942908 <optgroup label="o3">
2909+ <option value="o3">o3</option>
2910+ <option value="o3-2025-04-16">o3-2025-04-16</option>
28952911 <option value="o3-mini">o3-mini</option>
28962912 <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option>
28972913 </optgroup>
2914+ <optgroup label="o4">
2915+ <option value="o4-mini">o4-mini</option>
2916+ <option value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
2917+ </optgroup>
28982918 <optgroup label="GPT-4.5">
28992919 <option value="gpt-4.5-preview">gpt-4.5-preview</option>
29002920 <option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
@@ -3138,8 +3158,11 @@
31383158 <option value="gemma-3-27b-it">Gemma 3 27B</option>
31393159 </optgroup>
31403160 <optgroup label="Subversions">
3161+ <option value="gemini-2.5-pro-preview-03-25">Gemini 2.5 Pro Preview 2025-03-25</option>
3162+ <option value="gemini-2.5-pro-exp-03-25">Gemini 2.5 Pro Experimental 2025-03-25</option>
31413163 <option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option>
31423164 <option value="gemini-2.0-pro-exp-02-05">Gemini 2.0 Pro Experimental 2025-02-05</option>
3165+ <option value="gemini-2.5-flash-preview-04-17">Gemini 2.5 Flash Preview 2025-04-17</option>
31433166 <option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option>
31443167 <option value="gemini-2.0-flash-lite-preview-02-05">Gemini 2.0 Flash-Lite Preview 2025-02-05</option>
31453168 <option value="gemini-2.0-flash-001">Gemini 2.0 Flash [001]</option>
@@ -3193,6 +3216,7 @@
31933216 <option value="mistral-small-latest">mistral-small-latest</option>
31943217 <option value="mistral-medium-latest">mistral-medium-latest</option>
31953218 <option value="mistral-large-latest">mistral-large-latest</option>
3219+ <option value="mistral-saba-latest">mistral-saba-latest</option>
31963220 <option value="codestral-latest">codestral-latest</option>
31973221 <option value="codestral-mamba-latest">codestral-mamba-latest</option>
31983222 <option value="pixtral-12b-latest">pixtral-12b-latest</option>
@@ -3208,13 +3232,20 @@
32083232 <option value="mistral-small-2312">mistral-small-2312</option>
32093233 <option value="mistral-small-2402">mistral-small-2402</option>
32103234 <option value="mistral-small-2409">mistral-small-2409</option>
3235+ <option value="mistral-small-2501">mistral-small-2501</option>
3236+ <option value="mistral-small-2503">mistral-small-2503</option>
32113237 <option value="mistral-medium-2312">mistral-medium-2312</option>
32123238 <option value="mistral-large-2402">mistral-large-2402</option>
32133239 <option value="mistral-large-2407">mistral-large-2407</option>
32143240 <option value="mistral-large-2411">mistral-large-2411</option>
3241+ <option value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option>
3242+ <option value="mistral-saba-2502">mistral-saba-2502</option>
32153243 <option value="codestral-2405">codestral-2405</option>
32163244 <option value="codestral-2405-blue">codestral-2405-blue</option>
32173245 <option value="codestral-mamba-2407">codestral-mamba-2407</option>
3246+ <option value="codestral-2411-rc5">codestral-2411-rc5</option>
3247+ <option value="codestral-2412">codestral-2412</option>
3248+ <option value="codestral-2501">codestral-2501</option>
32183249 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
32193250 <option value="pixtral-large-2411">pixtral-large-2411</option>
32203251 </optgroup>
@@ -3237,16 +3268,16 @@
32373268 <option value="qwen-2.5-32b">qwen-2.5-32b</option>
32383269 <option value="qwen-2.5-coder-32b">qwen-2.5-coder-32b</option>
32393270 </optgroup>
32403271 <optgroup label="DeepSeek / Alibaba Cloud">
32413272 <option value="deepseek-r1-distill-qwen-32b">deepseek-r1-distill-qwen-32b</option>
3242- </optgroup>
3243- <optgroup label="DeepSeek / Meta">
32443273 <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option>
32453274 </optgroup>
32463275 <optgroup label="Google">
32473276 <option value="gemma2-9b-it">gemma2-9b-it</option>
32483277 </optgroup>
32493278 <optgroup label="Meta">
3279+ <option value="meta-llama/llama-4-scout-17b-16e-instruct">meta-llama/llama-4-scout-17b-16e-instruct</option>
3280+ <option value="meta-llama/llama-4-maverick-17b-128e-instruct">meta-llama/llama-4-maverick-17b-128e-instruct</option>
32503281 <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>
32513282 <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview </option>
32523283 <option value="llama-3.2-1b-preview">llama-3.2-1b-preview </option>
@@ -3259,6 +3290,7 @@
32593290 <option value="llama3-8b-8192">llama3-8b-8192</option>
32603291 </optgroup>
32613292 <optgroup label="Mistral AI">
3293+ <option value="mistral-saba-24b">mistral-saba-24b</option>
32623294 <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>
32633295 </optgroup>
32643296 </select>
@@ -3363,20 +3395,6 @@
33633395 </select>
33643396 </div>
33653397 </form>
3366- <form id="blockentropy_form" data-source="blockentropy">
3367- <h4 data-i18n="Block Entropy API Key">Block Entropy API Key</h4>
3368- <div class="flex-container">
3369- <input id="api_key_blockentropy" name="api_key_blockentropy" class="text_pole flex1" value="" type="text" autocomplete="off">
3370- <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_blockentropy"></div>
3371- </div>
3372- <div data-for="api_key_blockentropy" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
3373- For privacy reasons, your API key will be hidden after you reload the page.
3374- </div>
3375- <h4 data-i18n="Select a Model">Select a Model</h4>
3376- <div class="flex-container">
3377- <select id="model_blockentropy_select" class="text_pole"></select>
3378- </div>
3379- </form>
33803398 <form id="custom_form" data-source="custom">
33813399 <h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
33823400 <div class="flex-container">
@@ -3407,17 +3425,10 @@
34073425 <div class="flex-container">
34083426 <select id="model_custom_select" class="text_pole model_custom_select"></select>
34093427 </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>
34173428 </form>
34183429 <div id="01ai_form" data-source="01ai">
34193430 <h4>
34203431 <a data-i18n="01.AI API Key" href="https://platform.01lingyiwanwu.aicom/" target="_blank" rel="noopener noreferrer">
34213432 01.AI API Key
34223433 </a>
34233434 </h4>
@@ -3432,6 +3443,40 @@
34323443 <select id="model_01ai_select">
34333444 </select>
34343445 </div>
3446+ <div id="xai_form" data-source="xai">
3447+ <h4>
3448+ <a data-i18n="xAI API Key" href="https://console.x.ai/" target="_blank" rel="noopener noreferrer">
3449+ xAI API Key
3450+ </a>
3451+ </h4>
3452+ <div class="flex-container">
3453+ <input id="api_key_xai" name="api_key_xai" class="text_pole flex1" value="" type="text" autocomplete="off">
3454+ <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_xai"></div>
3455+ </div>
3456+ <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
3457+ For privacy reasons, your API key will be hidden after you reload the page.
3458+ </div>
3459+ <h4 data-i18n="xAI Model">xAI Model</h4>
3460+ <select id="model_xai_select">
3461+ <option value="grok-3-beta">grok-3-beta</option>
3462+ <option value="grok-3-fast-beta">grok-3-fast-beta</option>
3463+ <option value="grok-3-mini-beta">grok-3-mini-beta</option>
3464+ <option value="grok-3-mini-fast-beta">grok-3-mini-fast-beta</option>
3465+ <option value="grok-2-vision-1212">grok-2-vision-1212</option>
3466+ <option value="grok-2-1212">grok-2-1212</option>
3467+ <option value="grok-vision-beta">grok-vision-beta</option>
3468+ <option value="grok-beta">grok-beta</option>
3469+ </select>
3470+ </div>
3471+ <div id="prompt_post_porcessing_form" data-source="custom,openrouter">
3472+ <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
3473+ <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.">
3474+ <option data-i18n="prompt_post_processing_none" value="">None</option>
3475+ <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
3476+ <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
3477+ <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>
3478+ </select>
3479+ </div>
34353480 <div class="flex-container flex">
34363481 <div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
34373482 <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>
@@ -3917,6 +3962,19 @@
39173962 <summary data-i18n="Reasoning Formatting">
39183963 Reasoning Formatting
39193964 </summary>
3965+ <div class="flex-container" title="Select your current Reasoning Template" data-i18n="[title]Select your current Reasoning Template">
3966+ <select id="reasoning_select" data-preset-manager-for="reasoning" class="flex1 text_pole"></select>
3967+ <div class="flex-container margin0 justifyCenter gap3px">
3968+ <input type="file" hidden data-preset-manager-file="reasoning" accept=".json, .settings">
3969+ <i data-preset-manager-update="reasoning" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3970+ <i data-preset-manager-rename="reasoning" class="menu_button fa-pencil fa-solid" title="Rename current template" data-i18n="[title]Rename current template"></i>
3971+ <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>
3972+ <i data-preset-manager-import="reasoning" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3973+ <i data-preset-manager-export="reasoning" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3974+ <i data-preset-manager-restore="reasoning" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3975+ <i data-preset-manager-delete="reasoning" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3976+ </div>
3977+ </div>
39203978 <div class="flex-container">
39213979 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
39223980 <small data-i18n="Prefix">Prefix</small>
@@ -5099,7 +5157,7 @@
50995157 <div id="persona_depth_position_settings" class="flex-container">
51005158 <div class="flex1">
51015159 <label for="persona_depth_value" data-i18n="Depth:">Depth:</label>
51025160 <input id="persona_depth_value" class="text_pole" type="number" min="0" max="9999999" step="1">
51035161 </div>
51045162 <div class="flex1">
51055163 <label for="persona_depth_role" data-i18n="Role:">Role:</label>
@@ -5752,7 +5810,7 @@
57525810 @ Depth
57535811 </span>
57545812 </h4>
57555813 <input id="depth_prompt_depth" name="depth_prompt_depth" class="text_pole textarea_compact m-t-0" type="number" min="0" max="9999999" value="4" form="form_create" />
57565814 <h4>
57575815 <span data-i18n="Role">
57585816 Role
@@ -5973,11 +6031,11 @@
59736031 </div>
59746032 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
59756033 <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
59766034 <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999999" />
59776035 </div>
59786036 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
59796037 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
59806038 <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999999" />
59816039 </div>
59826040 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
59836041 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
@@ -5986,6 +6044,7 @@
59866044 </div>
59876045 </div>
59886046 </div>
6047+ <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></i>
59896048 <i class="menu_button duplicate_entry_button fa-solid fa-paste" title="Duplicate world info entry" data-i18n="[title]Duplicate world info entry" type="submit" value=""></i>
59906049 <i class="menu_button delete_entry_button fa-solid fa-trash-can" title="Delete world info entry" data-i18n="[title]Delete world info entry" type="submit" value=""></i>
59916050 </div>
@@ -6074,9 +6133,12 @@
60746133 <label for="content ">
60756134 <small>
60766135 <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
60776136 <span data-i18n="Content" class="alignitemscenter flex-container flexNoGap mdhotkey_location">
6137+ <span data-i18n="Content" class="mdhotkey_location">
60786138 Content
60796139 </span>
6140+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
6141+ </span>
60806142 <span>
60816143 (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
60826144 <span class="world_entry_form_uid_value" data-first-run="true"></span>
@@ -6335,7 +6397,7 @@
63356397 <span data-i18n="prompt_manager_depth">Depth</span>
63366398 </label>
63376399 <div class="text_muted" data-i18n="Injection depth. 0 = after the last message, 1 = before the last message, etc.">Injection depth. 0 = after the last message, 1 = before the last message, etc.</div>
63386400 <input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="9999999" value="4" />
63396401 </div>
63406402 </div>
63416403 <div class="completion_prompt_manager_popup_entry_form_control">
@@ -6434,7 +6496,7 @@
64346496 <div class="mes_text"></div>
64356497 <div class="mes_img_container">
64366498 <div class="mes_img_controls">
64376499 <div title="EnlargeExpand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge" data-i18n="[title]EnlargeExpand and zoom"></div>
64386500 <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
64396501 <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
64406502 </div>
@@ -6563,7 +6625,7 @@
65636625 <div class="ch_name"></div>
65646626 <small class="ch_additional_info group_select_counter"></small>
65656627 </div>
65666628 <small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
65676629 <i class='group_fav_icon fa-solid fa-star'></i>
65686630 <input class="ch_fav" value="" hidden />
65696631 <div class="group_select_block_list ch_description"></div>
@@ -6695,7 +6757,7 @@
66956757 <label class="checkbox_label alignItemsCenter" for="extension_floating_position_depth">
66966758 <input type="radio" id="extension_floating_position_depth" name="extension_floating_position" value="1" />
66976759 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
66986760 <input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999999" />
66996761 <span data-i18n="as">as</span>
67006762 <select id="extension_floating_role" class="text_pole widthNatural">
67016763 <option data-i18n="System" value="0">System</option>
@@ -6710,7 +6772,7 @@
67106772 <span data-i18n="Insertion Frequency">Insertion Frequency</span>
67116773 <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>
67126774 </label>
67136775 <input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="9999999" />
67146776 </div>
67156777 <br>
67166778 <span><span data-i18n="User inputs until next insertion:">User inputs until next insertion:</span> <span id="extension_floating_counter">(disabled)</span></span>
@@ -6780,7 +6842,7 @@
67806842 <label class="checkbox_label alignItemsCenter" for="extension_default_position_depth">
67816843 <input type="radio" id="extension_default_position_depth" name="extension_default_position" value="1" />
67826844 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
67836845 <input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999999" />
67846846 <span data-i18n="as">as</span>
67856847 <select id="extension_default_role" class="text_pole widthNatural">
67866848 <option data-i18n="System" value="0">System</option>
@@ -6794,7 +6856,7 @@
67946856 <span data-i18n="Insertion Frequency">Insertion Frequency</span>
67956857 <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>
67966858 </label>
67976859 <input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="9999999" />
67986860 </div>
67996861 </div>
68006862 </div>
public/locales/ar-sa.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "وضع علامة",
319319 "API key (optional)": "مفتاح API (اختياري)",
320320 "Server url": "رابط الخادم",
321321 "Example: http://127.0.0.1:5000": "مثال: http://127.0.0.1:5000",
322322 "Custom model (optional)": "نموذج مخصص (اختياري)",
323323 "vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",
324324 "vLLM API key": "مفتاح واجهة برمجة التطبيقات vLLM",
325325 "Example: http://127.0.0.1:8000": "مثال: http://127.0.0.1:8000",
326326 "vLLM Model": "نموذج vLLM",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (وضع التغليف لواجهة برمجة التطبيقات OpenAI)",
328328 "Aphrodite API key": "مفتاح واجهة برمجة التطبيقات Aphrodite",
329329 "Aphrodite Model": "نموذج أفروديت",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (خادم إخراج)",
331331 "Example: http://127.0.0.1:8080": "مثال: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "مثال: http://127.0.0.1:11434",
333333 "Ollama Model": "نموذج Ollama",
334334 "Download": "تحميل",
335335 "Tabby API key": "مفتاح API لـ Tabby",
336336 "koboldcpp API key (optional)": "مفتاح koboldcpp API (اختياري)",
337337 "Example: http://127.0.0.1:5001": "مثال: http://127.0.0.1:5001",
338338 "Authorize": "تفويض",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "احصل على رمز واجهة برمجة التطبيقات الخاص بك لموزع الاتصالات باستخدام تدفق OAuth. سيتم توجيهك إلى openrouter.ai",
340340 "Bypass status check": "تجاوز فحص الحالة",
public/locales/de-de.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "Flagge",
319319 "API key (optional)": "API-Schlüssel (optional)",
320320 "Server url": "Server-URL",
321321 "Example: http://127.0.0.1:5000": "Beispiel: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Benutzerdefiniertes Modell (optional)",
323323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",
324324 "vLLM API key": "vLLM-API-Schlüssel",
325325 "Example: http://127.0.0.1:8000": "Beispiel: http://127.0.0.1:8000",
326326 "vLLM Model": "vLLM-Modell",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus für OpenAI API)",
328328 "Aphrodite API key": "Aphrodite API-Schlüssel",
329329 "Aphrodite Model": "Aphrodite-Modell",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-Server)",
331331 "Example: http://127.0.0.1:8080": "Beispiel: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Beispiel: http://127.0.0.1:11434",
333333 "Ollama Model": "Ollama-Modell",
334334 "Download": "Herunterladen",
335335 "Tabby API key": "Tabby API-Schlüssel",
336336 "koboldcpp API key (optional)": "koboldcpp API-Schlüssel (optional)",
337337 "Example: http://127.0.0.1:5001": "Beispiel: http://127.0.0.1:5001",
338338 "Authorize": "Autorisieren",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Hole dein OpenRouter-API-Token mit OAuth-Fluss. Du wirst zu openrouter.ai weitergeleitet",
340340 "Bypass status check": "Umgehe Statusüberprüfung",
public/locales/es-es.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "bandera",
319319 "API key (optional)": "Clave API (opcional)",
320320 "Server url": "URL del servidor",
321321 "Example: http://127.0.0.1:5000": "Ejemplo: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Modelo personalizado (opcional)",
323323 "vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",
324324 "vLLM API key": "Clave API vLLM",
325325 "Example: http://127.0.0.1:8000": "Ejemplo: http://127.0.0.1:8000",
326326 "vLLM Model": "Modelo vLLM",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo envolvente para API de OpenAI)",
328328 "Aphrodite API key": "Clave de API de Aphrodite",
329329 "Aphrodite Model": "Modelo Afrodita",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de salida)",
331331 "Example: http://127.0.0.1:8080": "Ejemplo: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Ejemplo: http://127.0.0.1:11434",
333333 "Ollama Model": "Modelo Ollama",
334334 "Download": "Descargar",
335335 "Tabby API key": "Clave API de Tabby",
336336 "koboldcpp API key (optional)": "Clave API de koboldcpp (opcional)",
337337 "Example: http://127.0.0.1:5001": "Ejemplo: http://127.0.0.1:5001",
338338 "Authorize": "Autorizar",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenga su token de API de OpenRouter utilizando el flujo OAuth. Será redirigido a openrouter.ai",
340340 "Bypass status check": "Saltar la verificación del estado",
public/locales/fr-fr.json+5 -5
@@ -301,23 +301,23 @@
301301 "flag": "fanion",
302302 "API key (optional)": "Clé API (optionnelle)",
303303 "Server url": "URL du serveur",
304304 "Example: http://127.0.0.1:5000": "Exemple : http://127.0.0.1:5000",
305305 "Custom model (optional)": "Modèle personnalisé (optionnel)",
306306 "vllm-project/vllm": "vllm-project/vllm (mode wrapper de l'API OpenAI)",
307307 "vLLM API key": "Clé API vLLM",
308308 "Example: http://127.0.0.1:8000": "Exemple : http://127.0.0.1:8000",
309309 "vLLM Model": "Modèle vLLM",
310310 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (mode wrapper pour l'API OpenAI)",
311311 "Aphrodite API key": "Clé API Aphrodite",
312312 "Aphrodite Model": "Modèle Aphrodite",
313313 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
314314 "Example: http://127.0.0.1:8080": "Exemple : http://127.0.0.1:8080",
315315 "Example: http://127.0.0.1:11434": "Exemple : http://127.0.0.1:11434",
316316 "Ollama Model": "Modèle Ollama",
317317 "Download": "Télécharger",
318318 "Tabby API key": "Clé API de Tabby",
319319 "koboldcpp API key (optional)": "Clé API koboldcpp (facultatif)",
320320 "Example: http://127.0.0.1:5001": "Exemple : http://127.0.0.1:5001",
321321 "Authorize": "Autoriser",
322322 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenez votre jeton API OpenRouter en utilisant le flux OAuth. Vous serez redirigé vers openrouter.ai",
323323 "Bypass status check": "Contourner la vérification de l'état",
public/locales/is-is.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "merki",
319319 "API key (optional)": "API lykill (valkvæmt)",
320320 "Server url": "URL þjóns",
321321 "Example: http://127.0.0.1:5000": "Dæmi: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Sérsniðið módel (valkvæmt)",
323323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API umbúðastilling)",
324324 "vLLM API key": "vLLM API lykill",
325325 "Example: http://127.0.0.1:8000": "Dæmi: http://127.0.0.1:8000",
326326 "vLLM Model": "vLLM líkan",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI forritunargrensl)",
328328 "Aphrodite API key": "Aphrodite API lykill",
329329 "Aphrodite Model": "Afródíta fyrirmynd",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (úttak þjónn)",
331331 "Example: http://127.0.0.1:8080": "Dæmi: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Dæmi: http://127.0.0.1:11434",
333333 "Ollama Model": "Ollama módel",
334334 "Download": "Niðurhal",
335335 "Tabby API key": "Tabby API lykill",
336336 "koboldcpp API key (optional)": "koboldcpp API lykill (valfrjálst)",
337337 "Example: http://127.0.0.1:5001": "Dæmi: http://127.0.0.1:5001",
338338 "Authorize": "Heimild",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Fáðu API lykilinn þinn fyrir OpenRouter með því að nota OAuth strauminn. Þú verður endurvísað(ð/ur) á openrouter.ai",
340340 "Bypass status check": "Hlaupa framhjá stöðutík",
public/locales/it-it.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "bandiera",
319319 "API key (optional)": "Chiave API (opzionale)",
320320 "Server url": "URL del server",
321321 "Example: http://127.0.0.1:5000": "Esempio: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Modello personalizzato (opzionale)",
323323 "vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",
324324 "vLLM API key": "Chiave API vLLM",
325325 "Example: http://127.0.0.1:8000": "Esempio: http://127.0.0.1:8000",
326326 "vLLM Model": "Modello vLLM",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modalità wrapper per l'API OpenAI)",
328328 "Aphrodite API key": "Chiave API di Aphrodite",
329329 "Aphrodite Model": "Modello di Afrodite",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Server di output)",
331331 "Example: http://127.0.0.1:8080": "Esempio: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Esempio: http://127.0.0.1:11434",
333333 "Ollama Model": "Modello Ollama",
334334 "Download": "Scarica",
335335 "Tabby API key": "Chiave API di Tabby",
336336 "koboldcpp API key (optional)": "Chiave API koboldcpp (opzionale)",
337337 "Example: http://127.0.0.1:5001": "Esempio: http://127.0.0.1:5001",
338338 "Authorize": "Autorizzare",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Ottieni il tuo token API di OpenRouter utilizzando il flusso OAuth. Sarai reindirizzato su openrouter.ai",
340340 "Bypass status check": "Ignora controllo stato",
public/locales/ja-jp.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "フラグ",
319319 "API key (optional)": "APIキー(オプション)",
320320 "Server url": "サーバーURL",
321321 "Example: http://127.0.0.1:5000": "例: http://127.0.0.1:5000",
322322 "Custom model (optional)": "カスタムモデル(オプション)",
323323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",
324324 "vLLM API key": "vLLM API キー",
325325 "Example: http://127.0.0.1:8000": "例: http://127.0.0.1:8000",
326326 "vLLM Model": "vLLM モデル",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(OpenAI APIエンドポイントのパッケージングモード)",
328328 "Aphrodite API key": "アフロディーテAPIキー",
329329 "Aphrodite Model": "アフロディーテモデル",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp(出力サーバー)",
331331 "Example: http://127.0.0.1:8080": "例: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "例: http://127.0.0.1:11434",
333333 "Ollama Model": "Ollamaモデル",
334334 "Download": "ダウンロード",
335335 "Tabby API key": "TabbyのAPIキー",
336336 "koboldcpp API key (optional)": "koboldcpp API キー (オプション)",
337337 "Example: http://127.0.0.1:5001": "例: http://127.0.0.1:5001",
338338 "Authorize": "承認",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuthフローを使用してOpenRouter APIトークンを取得します。 openrouter.aiにリダイレクトされます",
340340 "Bypass status check": "ステータスのチェックをバイパスする",
public/locales/ko-kr.json+5 -5
@@ -320,23 +320,23 @@
320320 "flag": "깃발",
321321 "API key (optional)": "API 키 (선택 사항)",
322322 "Server url": "서버 URL",
323323 "Example: http://127.0.0.1:5000": "예시: http://127.0.0.1:5000",
324324 "Custom model (optional)": "사용자 정의 모델 (선택 사항)",
325325 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",
326326 "vLLM API key": "vLLM API 키",
327327 "Example: http://127.0.0.1:8000": "예: http://127.0.0.1:8000",
328328 "vLLM Model": "vLLM 모델",
329329 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI API의 래퍼 모드)",
330330 "Aphrodite API key": "Aphrodite API 키",
331331 "Aphrodite Model": "Aphrodite 모델",
332332 "ggerganov/llama.cpp": "ggerganov/llama.cpp (출력 서버)",
333333 "Example: http://127.0.0.1:8080": "예: http://127.0.0.1:8080",
334334 "Example: http://127.0.0.1:11434": "예: http://127.0.0.1:11434",
335335 "Ollama Model": "Ollama 모델",
336336 "Download": "다운로드",
337337 "Tabby API key": "Tabby API 키",
338338 "koboldcpp API key (optional)": "koboldcpp API 키(선택사항)",
339339 "Example: http://127.0.0.1:5001": "예: http://127.0.0.1:5001",
340340 "Authorize": "승인하기",
341341 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuth 플로우를 사용하여 OpenRouter API 토큰을 가져옵니다. openrouter.ai로 리디렉션됩니다.",
342342 "Legacy API (pre-OAI, no streaming)": "레거시 API (OAI 이전, 스트리밍 없음)",
public/locales/nl-nl.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "vlag",
319319 "API key (optional)": "API-sleutel (optioneel)",
320320 "Server url": "Server-URL",
321321 "Example: http://127.0.0.1:5000": "Voorbeeld: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Aangepast model (optioneel)",
323323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-wrappermodus)",
324324 "vLLM API key": "vLLM API-sleutel",
325325 "Example: http://127.0.0.1:8000": "Voorbeeld: http://127.0.0.1:8000",
326326 "vLLM Model": "vLLM-model",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus voor OpenAI API)",
328328 "Aphrodite API key": "Aphrodite API-sleutel",
329329 "Aphrodite Model": "Aphrodite-model",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-server)",
331331 "Example: http://127.0.0.1:8080": "Voorbeeld: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Voorbeeld: http://127.0.0.1:11434",
333333 "Ollama Model": "Ollama-model",
334334 "Download": "Downloaden",
335335 "Tabby API key": "Tabby API-sleutel",
336336 "koboldcpp API key (optional)": "koboldcpp API-sleutel (optioneel)",
337337 "Example: http://127.0.0.1:5001": "Voorbeeld: http://127.0.0.1:5001",
338338 "Authorize": "Toestemming geven",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Haal uw OpenRouter API-token op met behulp van OAuth-flow. U wordt doorgestuurd naar openrouter.ai",
340340 "Bypass status check": "Omzeil statuscontrole",
public/locales/pt-pt.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "bandeira",
319319 "API key (optional)": "Chave da API (opcional)",
320320 "Server url": "URL do servidor",
321321 "Example: http://127.0.0.1:5000": "Exemplo: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Modelo personalizado (opcional)",
323323 "vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",
324324 "vLLM API key": "Chave de API vLLM",
325325 "Example: http://127.0.0.1:8000": "Exemplo: http://127.0.0.1:8000",
326326 "vLLM Model": "Modelo vLLM",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo Wrapper para API OpenAI)",
328328 "Aphrodite API key": "Chave da API Aphrodite",
329329 "Aphrodite Model": "Modelo Afrodite",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de Saída)",
331331 "Example: http://127.0.0.1:8080": "Exemplo: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Exemplo: http://127.0.0.1:11434",
333333 "Ollama Model": "Modelo Ollama",
334334 "Download": "Baixar",
335335 "Tabby API key": "Chave da API do Tabby",
336336 "koboldcpp API key (optional)": "Chave API koboldcpp (opcional)",
337337 "Example: http://127.0.0.1:5001": "Exemplo: http://127.0.0.1:5001",
338338 "Authorize": "Autorizar",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenha seu token da API do OpenRouter usando o fluxo OAuth. Você será redirecionado para openrouter.ai",
340340 "Bypass status check": "Ignorar verificação de status",
public/locales/ru-ru.json+162 -27
@@ -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,13 +106,13 @@
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: http://127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
116113 "ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер вывода)",
117114 "Example: http://127.0.0.1:8080": "Пример: http://127.0.0.1:8080",
118115 "Example: http://127.0.0.1:11434": "Пример: http://127.0.0.1:11434",
119116 "Ollama Model": "Модель Ollama",
120117 "Download": "Скачать",
121118 "TogetherAI API Key": "TogetherAI API-ключ",
@@ -136,7 +133,7 @@
136133 "Server url": "URL-адрес сервера",
137134 "Custom model (optional)": "Пользовательская модель (необязательно)",
138135 "Bypass API status check": "Обход проверки статуса API",
139136 "Example: http://127.0.0.1:5000": "Пример: http://127.0.0.1:5000",
140137 "Bypass status check": "Обход проверки статуса",
141138 "Mancer API key": "Ключ от Mancer API",
142139 "to get your OpenAI API key.": "для получения ключа от API OpenAI",
@@ -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",
@@ -1256,7 +1252,6 @@
12561252 "DreamGen Model": "Модель DreamGen",
12571253 "vllm-project/vllm": "vllm-project/vllm (режим враппера OpenAI API)",
12581254 "vLLM API key": "Ключ от API vLLM",
1259- "Example: 127.0.0.1:8000": "Example: http://127.0.0.1:8000",
12601255 "vLLM Model": "Модель vLLM",
12611256 "Aphrodite Model": "Модель Aphrodite",
12621257 "Peek a password": "Посмотреть пароль",
@@ -1734,7 +1729,7 @@
17341729 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
17351730 "Save and Update": "Сохранить и обновить",
17361731 "Profile name:": "Название профиля:",
17371732 "API returned an error": "API вернулоответило ошибкуошибкой",
17381733 "Failed to save preset": "Не удалось сохранить пресет",
17391734 "Preset name should be unique.": "Название пресета должно быть уникальным.",
17401735 "Invalid file": "Невалидный файл",
@@ -1756,8 +1751,7 @@
17561751 "dot quota_error": "имеется достаточно кредитов.",
17571752 "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
17581753 "Proxy preset '${0}' not found": "Пресет '${0}' не найден",
17591754 "Window.ai returned an error": "Window.ai вернулответил ошибкуошибкой",
1760- "Get it here:": "Загрузите здесь:",
17611755 "Extension is not installed": "Расширение не установлено",
17621756 "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
17631757 "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@@ -1866,7 +1860,7 @@
18661860 "Group Chat could not be saved": "Не удалось сохранить групповой чат",
18671861 "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
18681862 "Currently no group selected.": "В данный момент не выбрано ни одной группы.",
18691863 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажперсонажи закончитзакончат печатать.",
18701864 "Delete the group?": "Удалить группу?",
18711865 "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.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
18721866 "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@@ -1997,7 +1991,7 @@
19971991 "Default persona deleted": "Удалена персона по умолчанию",
19981992 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
19991993 "Persona deleted": "Персона удалена",
20001994 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задатьприсвоить имя.",
20011995 "Persona name not set": "У персоны отсутствует имя",
20021996 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
20031997 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@@ -2038,7 +2032,7 @@
20382032 "[Currently loaded]": "[Загруженная сейчас]",
20392033 "Search providers...": "Искать по провайдерам...",
20402034 "Automatically chooses an alternative provider if chosen providers can't serve your request.": "Автоматически переключаться на другого провайдера, если текущий не может обслужить запрос.",
20412035 "Example: http://127.0.0.1:8000": "Пример: http://127.0.0.1:8000",
20422036 "Edit a connection profile": "Редактировать профиль соединения",
20432037 "System Prompt Name": "Название системного промпта",
20442038 "Use System Prompt": "Использовать системный промпт",
@@ -2203,5 +2197,146 @@
22032197 "Input:": "Входные данные:",
22042198 "Tokenized text:": "Токенизированный текст:",
22052199 "Token IDs:": "Идентификаторы токенов:",
22062200 "Tokens:": "Токенов:",
2201+ "Max prompt cost:": "Макс. стоимость промпта:",
2202+ "Reset custom sampler selection": "Сбросить подборку семплеров",
2203+ "Here you can toggle the display of individual samplers. (WIP)": "Здесь можно включить или выключить отображение каждого из сэмплеров отдельно. (WIP)",
2204+ "Request Model Reasoning": "Запрашивать цепочку рассуждений",
2205+ "Reasoning": "Рассуждения / Reasoning",
2206+ "Auto-Parse": "Авто-парсинг",
2207+ "reasoning_auto_parse": "Автоматически считывать блоки рассуждений, расположенные между префиксом и суффиксом рассуждений. Для работы должно быть указано и то, и другое.",
2208+ "Auto-Expand": "Разворачивать",
2209+ "reasoning_auto_expand": "Автоматически разворачивать блоки рассуждений.",
2210+ "Show Hidden": "Показывать время",
2211+ "reasoning_show_hidden": "Отображать затраченное на рассуждения время для моделей со скрытой цепочкой рассуждений",
2212+ "Add to Prompts": "Добавлять в промпт",
2213+ "reasoning_add_to_prompts": "Добавлять существующие блоки рассуждений в промпт. Для добавления новых используйте меню редактирования сообщений.",
2214+ "reasoning_max_additions": "Макс. кол-во блоков рассуждений в промпте, считается от последнего сообщения",
2215+ "Max": "Макс.",
2216+ "Reasoning Formatting": "Форматирование рассуждений",
2217+ "Prefix": "Префикс",
2218+ "Suffix": "Постфикс",
2219+ "Separator": "Разделитель",
2220+ "reasoning_separator": "Вставляется между рассуждениями и содержанием самого сообщения.",
2221+ "reasoning_prefix": "Вставляется перед рассуждениями.",
2222+ "reasoning_suffix": "Вставляется после рассуждений.",
2223+ "Seed_desc": "Фиксированное значение зерна позволяет получать предсказуемые, одинаковые результаты на одинаковых настройках. Поставьте -1 для рандомного зерна.",
2224+ "# of Beams": "Кол-во лучей",
2225+ "The number of sequences generated at each step with Beam Search.": "Кол-во вариантов, генерируемых Beam Search на каждом шаге работы.",
2226+ "Penalize sequences based on their length.": "Штрафует строки в зависимости от длины",
2227+ "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. Поставив галочку, вы укажете поиску остановиться тогда, когда будет достигнуто кол-во лучей из соответствующего поля. Если галочку не отмечать, то генерация остановится тогда, когда сочтёт, что дальше найти лучших кандидатов слишком маловероятно.",
2228+ "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-сэмплинга, подбирающий наиболее вероятную последовательность слов или токенов путём исследования и расширения сразу нескольких вариантов. На каждом шаге он удерживает фиксированное кол-во самых подходящих вариантов (ширина луча).",
2229+ "Smooth_Sampling_desc": "Изменяет распределение с помощью квадратичных и кубических преобразований. Снижение Коэффициента сглаживания даёт более креативные ответы, обычно идеальное значение находится в диапазоне 0.2-0.3 (при кривой сглаживания=1.0). Повышение значения Кривой сглаживания сделает кривую круче, что приведёт к более агрессивной фильтрации маловероятных вариантов. Установив Кривую сглаживания = 1.0, вы фактически нейтрализуете этот параметр и будете работать только с Коэффициентом",
2230+ "Temperature_Last_desc": "Применять сэмплер Температуры в последнюю очередь. Почти всегда оправдано.\nПри включении: сначала все токены семплируются, и затем температура регулирует распределение у оставшихся (технически, у оставшихся логитов).\nПри выключении: сначала температура настраивает распределение ВСЕХ токенов, и потом они семплируются уже с этим обновлённым распределением.\nПри отключении этой опции токены в хвосте получают больше шансов попасть в итоговую последовательность, что может привести к менее связным и логичным ответам.",
2231+ "Swipe # for All Messages": "Номер свайпа на всех сообщениях",
2232+ "Display swipe numbers for all messages, not just the last.": "Отображать номер свайпа для всех сообщений, а не только для последнего.",
2233+ "Penalty Range": "Окно для штрафа",
2234+ "Never": "Никогда",
2235+ "Groups and Past Personas": "Для групп и прошлых персон",
2236+ "Always": "Всегда",
2237+ "Request model reasoning": "Запрашивать рассуждения",
2238+ "Allows the model to return its thinking process.": "Позволяет модели высылать в ответе свою цепочку рассуждений.",
2239+ "Rename Persona": "Переименовать персону",
2240+ "Change Persona Image": "Изменить изображение персоны",
2241+ "Duplicate Persona": "Клонировать персону",
2242+ "Delete Persona": "Удалить персону",
2243+ "Enter a new name for this persona:": "Введите новое имя персоны:",
2244+ "Connections": "Связи",
2245+ "Click to select this as default persona for the new chats. Click again to remove it.": "Нажмите, чтобы установить эту персону стандартной для всех новых чатов. Нажмите ещё раз, чтобы отключить.",
2246+ "Character": "Персонаж",
2247+ "Click to lock your selected persona to the current character. Click again to remove the lock.": "Нажмите, чтобы закрепить эту персону для текущего персонажа. Нажмите ещё раз, чтобы открепить.",
2248+ "Chat": "Чат",
2249+ "[No character connections. Click one of the buttons above to connect this persona.]": "[Связи отсутствуют. Нажмите на одну из кнопок выше, чтобы создать.]",
2250+ "Global Settings": "Общие настройки",
2251+ "Allow multiple persona connections per character": "Разрешить привязывать несколько персон к одному персонажу",
2252+ "When multiple personas are connected to a character, a popup will appear to select which one to use": "При связывании нескольких персон с персонажем, будет появляться окошко с предложением выбрать нужную.",
2253+ "Auto-lock a chosen persona to the chat": "Автоматически привязывать выбранную персону к чату",
2254+ "Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.": "При выборе новой персоны она автоматически будет привязана к текущему чату, и будет выбираться при его открытии.",
2255+ "Current Persona": "Текущая персона",
2256+ "The chat has been successfully converted!": "Чат успешно преобразован!",
2257+ "Manual": "Когда вы скажете",
2258+ "Auto Mode delay": "Задержка авто-режима",
2259+ "Use tag as folder": "Тег-папка",
2260+ "All connections to ${0} have been removed.": "Все связи с персонажем ${0} были удалены.",
2261+ "Personas Unlocked": "Персоны отвязаны",
2262+ "Remove All Connections": "Удалить все связи",
2263+ "Persona ${0} selected and auto-locked to current chat": "Персона ${0} выбрана и автоматически закреплена за этим чатом",
2264+ "This persona is only temporarily chosen. Click for more info.": "Данная персона выбрана лишь временно. Нажмите, чтобы узнать больше.",
2265+ "Temporary Persona": "Временная персона",
2266+ "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.": "К этому чату уже привязана иная персона, либо у вас выбрана иная персона по-умолчанию. Выбранная в данный момент персона будет временной, и сбросится после перезагрузки. Если хотите всегда использовать её в этом чате, советуем её прикрепить.",
2267+ "Current Persona: ${0}": "Выбранная персона: ${0}",
2268+ "Chat persona: ${0}": "Персона для этого чата: ${0}",
2269+ "Default persona: ${0}": "Персона по умолчанию (стандартная): ${0}",
2270+ "Persona ${0} is now unlocked from this chat.": "Персона ${0} отвязана от этого чата.",
2271+ "Persona Unlocked": "Персона отвязана",
2272+ "Persona ${0} is now unlocked from character ${1}.": "Персона ${0} отвязана от персонажа ${1}.",
2273+ "Persona Not Found": "Персона не найдена",
2274+ "Persona Locked": "Персона закреплена",
2275+ "User persona ${0} is locked to character ${1}${2}": "Персона ${0} прикреплена к персонажу ${1}${2}",
2276+ "Persona Name Not Set": "У персоны отсутствует имя",
2277+ "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
2278+ "Default Persona Removed": "Персона по умолчанию снята",
2279+ "Persona is locked to the current character": "Персона закреплена за этим персонажем",
2280+ "Persona is locked to the current chat": "Персона закреплена за этим чатом",
2281+ "characters": "перс.",
2282+ "character": "персонаж",
2283+ "in this group": "в группе",
2284+ "Chatting Since": "Первая беседа",
2285+ "Context": "Контекст",
2286+ "Response": "Ответ",
2287+ "Connected": "Подключено",
2288+ "Enter new background name:": "Введите новое название для фона:",
2289+ "AI Horde Website": "Сайт AI Horde",
2290+ "Enable web search": "Включить поиск в Интернете",
2291+ "Use search capabilities provided by the backend.": "Разрешить использование предоставляемых бэкендом функций поиска.",
2292+ "Request inline images": "Запрашивать inline-изображения",
2293+ "Allows the model to return image attachments.": "Разрешить модели отправлять вложения в виде картинок.",
2294+ "Request inline images_desc_2": "Не совместимо со следующим функционалом: вызов функций, поиск в Интернете, системный промпт.",
2295+ "Connected Personas": "Связанные персоны",
2296+ "[Currently no personas connected]": "[Связанных персон нет]",
2297+ "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 + ЛКМ, чтобы её отвязать.",
2298+ "Persona Connections": "Связи с персонами",
2299+ "Pooled order": "Если уже давно не отвечали",
2300+ "Attach a File": "Приложить файл",
2301+ "Attach a file or image to a current chat.": "Приложить файл или изображение к текущему чату",
2302+ "Remove the file": "Удалить файл",
2303+ "Delete the Chat File?": "Удалить чат?",
2304+ "Forbidden": "Доступ запрещён",
2305+ "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.",
2306+ "Invalid endpoint URL. Requests may fail.": "Некорректный адрес эндпоинта. Запросы могут не проходить.",
2307+ "How to install extensions?": "Как устанавливать расширения?",
2308+ "Click the flashing button to install extensions.": "Чтобы их установить, нажмите на мигающую кнопку.",
2309+ "ext_regex_reasoning_desc": "Содержимое блоков рассуждений. При отмеченной галочке \"Только промпт\" будут также обработаны добавленные в промпт рассуждения.",
2310+ "Macro in Find Regex": "Макросы в рег. выражении",
2311+ "Don't substitute": "Не заменять",
2312+ "Substitute (raw)": "Заменять в \"чистом\" виде",
2313+ "Substitute (escaped)": "Заменять после экранирования",
2314+ "ext_regex_other_options_desc": "По умолчанию, расширение вносит изменения в сам файл чата.\nПри включении одной из опций (или обеих), файл чата останется нетронутым, при этом сами изменения по-прежнему будут действовать.",
2315+ "ext_regex_flags_help": "Нажмите, чтобы узнать больше о флагах в рег. выражениях.",
2316+ "Applies to all matches": "Заменяет все вхождения",
2317+ "Applies to the first match": "Заменяет первое вхождение",
2318+ "Case insensitive": "Не чувствительно к регистру",
2319+ "Case sensitive": "Чувствительно к регистру",
2320+ "Find Regex is empty": "Рег. выражение не указано",
2321+ "Click the button to save it as a file.": "Нажмите на кнопку справа, чтобы сохранить его в файл.",
2322+ "Export as JSONL": "Экспорт в формате JSONL",
2323+ "Thought for some time": "Какое-то время заняли размышления",
2324+ "Thinking...": "В раздумьях...",
2325+ "Thought for ${0}": "Размышления заняли ${0}",
2326+ "Hidden reasoning - Add reasoning block": "Рассуждения скрыты - Добавить блок рассуждений",
2327+ "Add reasoning block": "Добавить блок рассуждений",
2328+ "Edit reasoning": "Редактировать рассуждения",
2329+ "Copy reasoning": "Скопировать рассуждения",
2330+ "Confirm Edit": "Подтвердить",
2331+ "Remove reasoning": "Удалить рассуждения",
2332+ "Cancel edit": "Отменить редактирование",
2333+ "Remove Reasoning": "Удалить рассуждения",
2334+ "Are you sure you want to clear the reasoning?<br />Visible message contents will stay intact.": "Вы точно хотите удалить блок рассуждений?<br />Основное сообщение останется на месте.",
2335+ "Reasoning Parse": "Парсинг рассуждений",
2336+ "Both prefix and suffix must be set in the Reasoning Formatting settings.": "В настройках форматирования рассуждений должны быть заданы префикс и суффикс.",
2337+ "Invalid return type '${0}', defaulting to 'reasoning'.": "Некорректный возвращаемый тип, используем стандартный 'reasoning'.",
2338+ "Reasoning already exists.": "Рассуждения уже присутствуют.",
2339+ "Edit Message": "Редактирование",
2340+ "Status check bypassed": "Проверка статуса отключена",
2341+ "Valid": "Работает"
22072342}
public/locales/uk-ua.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "прапорцем",
319319 "API key (optional)": "Ключ API (необов'язково)",
320320 "Server url": "URL-адреса сервера",
321321 "Example: http://127.0.0.1:5000": "Приклад: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Власна модель (необов'язково)",
323323 "vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",
324324 "vLLM API key": "Ключ API vLLM",
325325 "Example: http://127.0.0.1:8000": "Приклад: http://127.0.0.1:8000",
326326 "vLLM Model": "Модель vLLM",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (режим OpenAI API)",
328328 "Aphrodite API key": "Ключ API для Aphrodite",
329329 "Aphrodite Model": "Модель Афродіта",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер виведення)",
331331 "Example: http://127.0.0.1:8080": "Приклад: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Приклад: http://127.0.0.1:11434",
333333 "Ollama Model": "Модель Ollama",
334334 "Download": "Завантажити",
335335 "Tabby API key": "Ключ API для Tabby",
336336 "koboldcpp API key (optional)": "API-ключ koboldcpp (необов’язково)",
337337 "Example: http://127.0.0.1:5001": "Приклад: http://127.0.0.1:5001",
338338 "Authorize": "Авторизувати",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Отримайте свій токен API OpenRouter за допомогою OAuth. Вас буде перенаправлено на openrouter.ai",
340340 "Bypass status check": "Обійти перевірку статусу",
public/locales/vi-vn.json+5 -5
@@ -318,23 +318,23 @@
318318 "flag": "cờ",
319319 "API key (optional)": "Key API (tùy chọn)",
320320 "Server url": "URL máy chủ",
321321 "Example: http://127.0.0.1:5000": "Ví dụ: http://127.0.0.1:5000",
322322 "Custom model (optional)": "Model tùy chỉnh (tùy chọn)",
323323 "vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",
324324 "vLLM API key": "Key API vLLM",
325325 "Example: http://127.0.0.1:8000": "Ví dụ: http://127.0.0.1:8000",
326326 "vLLM Model": "Model vLLM",
327327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Chế độ đóng gói cho Giao diện lập trình ứng dụng OpenAI)",
328328 "Aphrodite API key": "Key API Aphrodite",
329329 "Aphrodite Model": "Moddel cho Aphrodite",
330330 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
331331 "Example: http://127.0.0.1:8080": "Ví dụ: http://127.0.0.1:8080",
332332 "Example: http://127.0.0.1:11434": "Ví dụ: http://127.0.0.1:11434",
333333 "Ollama Model": "Model Ollama",
334334 "Download": "Tải xuống",
335335 "Tabby API key": "Key API Tabby",
336336 "koboldcpp API key (optional)": "Key API koboldcpp (tùy chọn)",
337337 "Example: http://127.0.0.1:5001": "Ví dụ: http://127.0.0.1:5001",
338338 "Cho phép": "Ủy quyền",
339339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Nhận mã thông báo API OpenRouter của bạn bằng cách sử dụng luồng OAuth. Bạn sẽ được chuyển hướng đến openrouter.ai",
340340 "Bypass status check": "Bỏ qua check trạng thái",
public/locales/zh-cn.json+5 -5
@@ -347,7 +347,7 @@
347347 "Mancer Model": "Mancer 模型",
348348 "API key (optional)": "API密钥(可选)",
349349 "Server url": "服务器URL",
350350 "Example: http://127.0.0.1:5000": "示例:http://127.0.0.1:5000",
351351 "Model ID (optional)": "模型 ID(可选)",
352352 "Make sure you run it with": "确保您在运行时加上",
353353 "flag": "标志",
@@ -364,7 +364,7 @@
364364 "No model description": "[无描述]",
365365 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 包装器模式)",
366366 "vLLM API key": "vLLM API 密钥",
367367 "Example: http://127.0.0.1:8000": "示例:http://127.0.0.1:8000",
368368 "vLLM Model": "vLLM 模型",
369369 "HuggingFace Token": "HuggingFace 代币",
370370 "Endpoint URL": "端点 URL",
@@ -373,8 +373,8 @@
373373 "Aphrodite API key": "Aphrodite API 密钥",
374374 "Aphrodite Model": "Aphrodite 模型",
375375 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
376376 "Example: http://127.0.0.1:8080": "示例:http://127.0.0.1:8080",
377377 "Example: http://127.0.0.1:11434": "示例:http://127.0.0.1:11434",
378378 "Ollama Model": "Ollama 模型",
379379 "Download": "下载",
380380 "Tabby API key": "Tabby API 密钥",
@@ -382,7 +382,7 @@
382382 "must be set in Tabby's config.yml to switch models.": "必须在Tabby的config.yml内设置以切换模型",
383383 "Use an admin API key.": "使用管理员API密钥。",
384384 "koboldcpp API key (optional)": "koboldcpp API 密钥(可选)",
385385 "Example: http://127.0.0.1:5001": "示例:http://127.0.0.1:5001",
386386 "Bypass status check": "跳过状态检查",
387387 "Derive context size from backend": "从后端获取上下文长度",
388388 "Authorize": "授权",
public/locales/zh-tw.json+5 -5
@@ -319,23 +319,23 @@
319319 "flag": "旗標",
320320 "API key (optional)": "API 金鑰(可選)",
321321 "Server url": "伺服器 URL",
322322 "Example: http://127.0.0.1:5000": "範例:http://127.0.0.1:5000",
323323 "Custom model (optional)": "自訂模型(選填)",
324324 "vllm-project/vllm": "vllm-project/vllm",
325325 "vLLM API key": "vLLM API 金鑰",
326326 "Example: http://127.0.0.1:8000": "範例:http://127.0.0.1:8000",
327327 "vLLM Model": "vLLM 模型",
328328 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite 引擎",
329329 "Aphrodite API key": "Aphrodite API 金鑰",
330330 "Aphrodite Model": "Aphrodite 模型",
331331 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
332332 "Example: http://127.0.0.1:8080": "範例:http://127.0.0.1:8080",
333333 "Example: http://127.0.0.1:11434": "範例:http://127.0.0.1:11434",
334334 "Ollama Model": "Ollama 模型",
335335 "Download": "下載",
336336 "Tabby API key": "Tabby API 金鑰",
337337 "koboldcpp API key (optional)": "KoboldCpp API 金鑰(可選)",
338338 "Example: http://127.0.0.1:5001": "範例:http://127.0.0.1:5001",
339339 "Authorize": "授權",
340340 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "使用 OAuth 流程取得您的 OpenRouter API 符元。您將被重新導向到 openrouter.ai",
341341 "Bypass status check": "繞過狀態檢查",
public/script.js+306 -96
@@ -172,8 +172,10 @@ import {
172172 copyText,
173173 escapeHtml,
174174 saveBase64AsFile,
175+ uuidv4,
176+ equalsIgnoreCaseAndAccents,
175177} from './scripts/utils.js';
176178import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js';
177179
178180import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
179181import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';
@@ -494,6 +496,8 @@ export const event_types = {
494496 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
495497 GENERATE_AFTER_DATA: 'generate_after_data',
496498 GROUP_MEMBER_DRAFTED: 'group_member_drafted',
499+ GROUP_WRAPPER_STARTED: 'group_wrapper_started',
500+ GROUP_WRAPPER_FINISHED: 'group_wrapper_finished',
497501 WORLD_INFO_ACTIVATED: 'world_info_activated',
498502 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
499503 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',
@@ -514,6 +518,9 @@ export const event_types = {
514518 ONLINE_STATUS_CHANGED: 'online_status_changed',
515519 IMAGE_SWIPED: 'image_swiped',
516520 CONNECTION_PROFILE_LOADED: 'connection_profile_loaded',
521+ CONNECTION_PROFILE_CREATED: 'connection_profile_created',
522+ CONNECTION_PROFILE_DELETED: 'connection_profile_deleted',
523+ CONNECTION_PROFILE_UPDATED: 'connection_profile_updated',
517524 TOOL_CALLS_PERFORMED: 'tool_calls_performed',
518525 TOOL_CALLS_RENDERED: 'tool_calls_rendered',
519526};
@@ -589,7 +596,7 @@ let is_delete_mode = false;
589596let fav_ch_checked = false;
590597let scrollLock = false;
591598export let abortStatusCheck = new AbortController();
592599export let charDragDropHandler = null;
593600
594601/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */
595602export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;
@@ -644,7 +651,7 @@ export const extension_prompt_roles = {
644651 ASSISTANT: 2,
645652};
646653
647654export const MAX_INJECTION_DEPTH = 100010000;
648655
649656const SAFETY_CHAT = [
650657 {
@@ -1140,7 +1147,7 @@ export async function clearItemizedPrompts() {
11401147async function getStatusHorde() {
11411148 try {
11421149 const hordeStatus = await checkHordeStatus();
11431150 setOnlineStatus(hordeStatus ? 't`Connected'` : 'no_connection');
11441151 }
11451152 catch {
11461153 setOnlineStatus('no_connection');
@@ -1207,7 +1214,7 @@ async function getStatusTextgen() {
12071214 }
12081215
12091216 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
12101217 setOnlineStatus('t`Status check bypassed'`);
12111218 return resultCheckStatus();
12121219 }
12131220
@@ -1232,7 +1239,7 @@ async function getStatusTextgen() {
12321239 setOnlineStatus(textgen_settings.togetherai_model);
12331240 } else if (textgen_settings.type === textgen_types.OLLAMA) {
12341241 loadOllamaModels(data?.data);
12351242 setOnlineStatus(textgen_settings.ollama_model || 't`Connected'`);
12361243 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
12371244 loadInfermaticAIModels(data?.data);
12381245 setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1256,7 +1263,7 @@ async function getStatusTextgen() {
12561263 setOnlineStatus(textgen_settings.tabby_model || data?.result);
12571264 } else if (textgen_settings.type === textgen_types.GENERIC) {
12581265 loadGenericModels(data?.data);
12591266 setOnlineStatus(textgen_settings.generic_model || data?.result || 't`Connected'`);
12601267 } else {
12611268 setOnlineStatus(data?.result);
12621269 }
@@ -1370,8 +1377,11 @@ export function resultCheckStatus() {
13701377 * If the character ID doesn't exist, if the chat is being saved, or if a group is being generated, this function does nothing.
13711378 * If the character is different from the currently selected one, it will clear the chat and reset any selected character or group.
13721379 * @param {number} id The ID of the character to switch to.
1380+ * @param {object} [options] Options for the switch.
1381+ * @param {boolean} [options.switchMenu=true] Whether to switch the right menu to the character edit menu if the character is already selected.
1382+ * @returns {Promise<void>} A promise that resolves when the character is switched.
13731383 */
13741384export async function selectCharacterById(id, { switchMenu = true } = {}) {
13751385 if (characters[id] === undefined) {
13761386 return;
13771387 }
@@ -1400,9 +1410,9 @@ export async function selectCharacterById(id) {
14001410 }
14011411 } else {
14021412 //if clicked on character that was already selected
14031413 switchMenu && (selected_button = 'character_edit');
14041414 await unshallowCharacter(this_chid);
14051415 select_selected_character(this_chid, { switchMenu });
14061416 }
14071417}
14081418
@@ -1787,6 +1797,7 @@ export async function getCharacters() {
17871797 body: JSON.stringify({}),
17881798 });
17891799 if (response.ok === true) {
1800+ const previousAvatar = this_chid !== undefined ? characters[this_chid]?.avatar : null;
17901801 characters.splice(0, characters.length);
17911802 const getData = await response.json();
17921803 for (let i = 0; i < getData.length; i++) {
@@ -1800,8 +1811,16 @@ export async function getCharacters() {
18001811
18011812 characters[i]['chat'] = String(characters[i]['chat']);
18021813 }
1803- if (this_chid !== undefined) {
1814+
1804- $('#avatar_url_pole').val(characters[this_chid].avatar);
1815+ if (previousAvatar) {
1816+ const newCharacterId = characters.findIndex(x => x.avatar === previousAvatar);
1817+ if (newCharacterId >= 0) {
1818+ setCharacterId(newCharacterId);
1819+ await selectCharacterById(newCharacterId, { switchMenu: false });
1820+ } else {
1821+ 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.`);
1822+ return location.reload();
1823+ }
18051824 }
18061825
18071826 await getGroups();
@@ -2051,8 +2070,9 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
20512070 }
20522071
20532072 // Prompt bias replacement should be applied on the raw message
2054- if (!power_user.show_user_prompt_bias && ch_name && !isUser && !isSystem) {
2073+ const replacedPromptBias = power_user.user_prompt_bias && substituteParams(power_user.user_prompt_bias);
2055- mes = mes.replaceAll(substituteParams(power_user.user_prompt_bias), '');
2074+ if (!power_user.show_user_prompt_bias && ch_name && !isUser && !isSystem && replacedPromptBias && mes.startsWith(replacedPromptBias)) {
2075+ mes = mes.slice(replacedPromptBias.length);
20562076 }
20572077
20582078 if (!isSystem) {
@@ -2114,7 +2134,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
21142134 }
21152135
21162136 mes = mes.replace(
21172137 /<style>[\s\S]*?<\/style>|```[\s\S]*?```|~~~[\s\S]*?~~~|``[\s\S]*?``|`[\s\S]*?`|(".*?")|(\u201C.*?\u201D)|(\u00AB.*?\u00BB)|(\u300C.*?\u300D)|(\u300E.*?\u300F)|(\uFF02.*?\uFF02)/gmgim,
21182138 function (match, p1, p2, p3, p4, p5, p6) {
21192139 if (p1) {
21202140 // English double quotes
@@ -2730,6 +2750,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
27302750 environment.mesExamplesRaw = fields.mesExamples || '';
27312751 environment.charVersion = fields.version || '';
27322752 environment.char_version = fields.version || '';
2753+ environment.charDepthPrompt = fields.charDepthPrompt || '';
27332754 }
27342755
27352756 // Must be substituted last so that they're replaced inside {{description}}
@@ -3026,6 +3047,20 @@ export async function getExtensionPromptByName(moduleName) {
30263047}
30273048
30283049/**
3050+ * Gets the maximum depth of extension prompts.
3051+ * @returns {number} Maximum depth of extension prompts
3052+ */
3053+export function getExtensionPromptMaxDepth() {
3054+ return MAX_INJECTION_DEPTH;
3055+ /*
3056+ const prompts = Object.values(extension_prompts);
3057+ const maxDepth = Math.max(...prompts.map(x => x.depth ?? 0));
3058+ // Clamp to 1 <= depth <= MAX_INJECTION_DEPTH
3059+ return Math.max(Math.min(maxDepth, MAX_INJECTION_DEPTH), 1);
3060+ */
3061+}
3062+
3063+/**
30293064 * Returns the extension prompt for the given position, depth, and role.
30303065 * If multiple prompts are found, they are joined with a separator.
30313066 * @param {number} [position] Position of the prompt
@@ -3081,13 +3116,38 @@ export function baseChatReplace(value, name1, name2) {
30813116
30823117/**
30833118 * 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}}
3119+ * @param {object} [options]
3120+ * @param {number} [options.chid] Optional character index
3121+ *
3122+ * @typedef {object} CharacterCardFields
3123+ * @property {string} system System prompt
3124+ * @property {string} mesExamples Message examples
3125+ * @property {string} description Description
3126+ * @property {string} personality Personality
3127+ * @property {string} persona Persona
3128+ * @property {string} scenario Scenario
3129+ * @property {string} jailbreak Jailbreak instructions
3130+ * @property {string} version Character version
3131+ * @property {string} charDepthPrompt Character depth note
3132+ * @returns {CharacterCardFields} Character card fields
30853133 */
30863134export function getCharacterCardFields({ chid = null } = {}) {
3087- const result = { system: '', mesExamples: '', description: '', personality: '', persona: '', scenario: '', jailbreak: '', version: '' };
3135+ const currentChid = chid ?? this_chid;
3136+
3137+ const result = {
3138+ system: '',
3139+ mesExamples: '',
3140+ description: '',
3141+ personality: '',
3142+ persona: '',
3143+ scenario: '',
3144+ jailbreak: '',
3145+ version: '',
3146+ charDepthPrompt: '',
3147+ };
30883148 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
30893149
30903150 const character = characters[this_chidcurrentChid];
30913151
30923152 if (!character) {
30933153 return result;
@@ -3101,9 +3161,10 @@ export function getCharacterCardFields() {
31013161 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?.trim(), name1, name2) : '';
31023162 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
31033163 result.version = character.data?.character_version ?? '';
3164+ result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
31043165
31053166 if (selected_group) {
31063167 const groupCards = getGroupCharacterCards(selected_group, Number(this_chidcurrentChid));
31073168
31083169 if (groupCards) {
31093170 result.description = groupCards.description;
@@ -3269,13 +3330,25 @@ class StreamingProcessor {
32693330
32703331 if (!isImpersonate && !isContinue && Array.isArray(this.swipes) && this.swipes.length > 0) {
32713332 for (let i = 0; i < this.swipes.length; i++) {
32723333 this.swipes[i] = cleanUpMessage(this.swipes[i], false, false, true, this.stoppingStrings);{
3334+ getMessage: this.swipes[i],
3335+ isImpersonate: false,
3336+ isContinue: false,
3337+ displayIncompleteSentences: true,
3338+ stoppingStrings: this.stoppingStrings,
3339+ });
32733340 }
32743341 }
32753342
3276- let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal, this.stoppingStrings);
3343+ let processedText = cleanUpMessage({
3344+ getMessage: text,
3345+ isImpersonate: isImpersonate,
3346+ isContinue: isContinue,
3347+ displayIncompleteSentences: !isFinal,
3348+ stoppingStrings: this.stoppingStrings,
3349+ });
32773350
32783351 const charsToBalance = ['*', '"', '```', '~~~'];
32793352 for (const char of charsToBalance) {
32803353 if (!isFinal && isOdd(countOccurrences(processedText, char))) {
32813354 const separator = char.length > 1 ? '\n' : '';
@@ -3505,9 +3578,10 @@ class StreamingProcessor {
35053578 * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode
35063579 * @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI.
35073580 * @param {number} [responseLength] Maximum response length. If unset, the global default value is used.
3581+ * @param {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response.
35083582 * @returns {Promise<string>} Generated message
35093583 */
35103584export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt, responseLength, trimNames = true) {
35113585 if (!api) {
35123586 api = main_api;
35133587 }
@@ -3596,7 +3670,16 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
35963670 throw new Error(data.response);
35973671 }
35983672
3599- const message = cleanUpMessage(extractMessageFromData(data), false, false, true);
3673+ // format result, exclude user prompt bias
3674+ const message = cleanUpMessage({
3675+ getMessage: extractMessageFromData(data),
3676+ isImpersonate: false,
3677+ isContinue: false,
3678+ displayIncompleteSentences: true,
3679+ includeUserPromptBias: false,
3680+ trimNames: trimNames,
3681+ trimWrongNames: trimNames,
3682+ });
36003683
36013684 if (!message) {
36023685 throw new Error('No message generated');
@@ -3905,6 +3988,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
39053988 mesExamples,
39063989 system,
39073990 jailbreak,
3991+ charDepthPrompt,
39083992 } = getCharacterCardFields();
39093993
39103994 if (main_api !== 'openai') {
@@ -3927,7 +4011,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
39274011 setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);
39284012 });
39294013 } else {
3930- const depthPromptText = baseChatReplace(characters[this_chid]?.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2) || '';
4014+ const depthPromptText = charDepthPrompt || '';
39314015 const depthPromptDepth = characters[this_chid]?.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;
39324016 const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid]?.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);
39334017 setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);
@@ -4796,7 +4880,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
47964880
47974881 hideSwipeButtons();
47984882 let getMessage = await streamingProcessor.generate();
47994883 let messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);{
4884+ getMessage: getMessage,
4885+ isImpersonate: isImpersonate,
4886+ isContinue: isContinue,
4887+ displayIncompleteSentences: false,
4888+ });
48004889
48014890 if (isContinue) {
48024891 getMessage = continue_mag + getMessage;
@@ -4880,7 +4969,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
48804969
48814970 const swipes = extractMultiSwipes(data, type);
48824971
48834972 messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);{
4973+ getMessage: getMessage,
4974+ isImpersonate: isImpersonate,
4975+ isContinue: isContinue,
4976+ displayIncompleteSentences: false,
4977+ });
4978+
4979+
48844980 reasoning = getRegexedString(reasoning, regex_placement.REASONING);
48854981
48864982 if (power_user.trim_spaces) {
@@ -4894,7 +4990,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
48944990
48954991 //Formating
48964992 const displayIncomplete = type === 'quiet' && !quietToLoud;
48974993 getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete);{
4994+ getMessage: getMessage,
4995+ isImpersonate: isImpersonate,
4996+ isContinue: isContinue,
4997+ displayIncompleteSentences: displayIncomplete,
4998+ });
48984999
48995000 if (isImpersonate) {
49005001 $('#send_textarea').val(getMessage)[0].dispatchEvent(new Event('input', { bubbles: true }));
@@ -5012,7 +5113,8 @@ async function doChatInject(messages, isContinue) {
50125113 let totalInsertedMessages = 0;
50135114 messages.reverse();
50145115
5015- for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {
5116+ const maxDepth = getExtensionPromptMaxDepth();
5117+ for (let i = 0; i <= maxDepth; i++) {
50165118 // Order of priority (most important go lower)
50175119 const roles = [extension_prompt_roles.SYSTEM, extension_prompt_roles.USER, extension_prompt_roles.ASSISTANT];
50185120 const names = {
@@ -5215,6 +5317,12 @@ function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
52155317 const itemName = chatItem.is_user ? chatItem['name'] : characterName;
52165318 const shouldPrependName = !isNarratorType;
52175319
5320+ // If this symbol flag is set, completely ignore the message.
5321+ // This can be used to hide messages without affecting the number of messages in the chat.
5322+ if (chatItem.extra?.[IGNORE_SYMBOL]) {
5323+ return '';
5324+ }
5325+
52185326 // Don't include a name if it's empty
52195327 let textResult = chatItem?.name && shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`;
52205328
@@ -5860,7 +5968,13 @@ function extractMultiSwipes(data, type) {
58605968
58615969 for (let i = 1; i < data.choices.length; i++) {
58625970 const text = data?.choices[i]?.message?.content ?? data?.choices[i]?.text ?? '';
58635971 const cleanedText = cleanUpMessage(text, false, false, false);{
5972+ getMessage: text,
5973+ isImpersonate: false,
5974+ isContinue: false,
5975+ displayIncompleteSentences: false,
5976+ });
5977+
58645978 swipes.push(cleanedText);
58655979 }
58665980 }
@@ -5868,13 +5982,33 @@ function extractMultiSwipes(data, type) {
58685982 return swipes;
58695983}
58705984
5871-export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncompleteSentences = false, stoppingStrings = null) {
5985+/**
5986+ * Formats a message according to user settings
5987+ * @param {object} [options] - Additional options.
5988+ * @param {string} [options.getMessage] The message to clean up
5989+ * @param {boolean} [options.isImpersonate] Whether this is an impersonated message
5990+ * @param {boolean} [options.isContinue] Whether this is a continued message
5991+ * @param {boolean} [options.displayIncompleteSentences] Whether to keep incomplete sentences at the end.
5992+ * @param {array} [options.stoppingStrings] Array of stopping strings.
5993+ * @param {boolean} [options.includeUserPromptBias] Whether to permit prepending the user prompt bias at the beginning.
5994+ * @param {boolean} [options.trimNames] Whether to allow trimming "{{char}}:" or "{{user}}:" from the beginning.
5995+ * @param {boolean} [options.trimWrongNames] Whether to allow deleting responses prefixed by the incorrect name, depending on isImpersonate
5996+ *
5997+ * @returns {string} The formatted message
5998+ */
5999+export function cleanUpMessage({ getMessage, isImpersonate, isContinue, displayIncompleteSentences = false, stoppingStrings = null, includeUserPromptBias = true, trimNames = true, trimWrongNames = true } = {}) {
6000+ if (arguments.length > 0 && typeof arguments[0] !== 'object') {
6001+ console.trace('cleanUpMessage called with positional arguments. Please use an object instead.');
6002+ [getMessage, isImpersonate, isContinue, displayIncompleteSentences, stoppingStrings, includeUserPromptBias, trimNames, trimWrongNames] = arguments;
6003+ }
6004+
58726005 if (!getMessage) {
58736006 return '';
58746007 }
58756008
58766009 // Add the prompt bias before anything else
58776010 if (
6011+ includeUserPromptBias &&
58786012 power_user.user_prompt_bias &&
58796013 !isImpersonate &&
58806014 !isContinue &&
@@ -5912,21 +6046,32 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
59126046 // "trailing whitespace on newlines\nevery line of the string\nsample text"
59136047 getMessage = getMessage.replace(/[^\S\r\n]+$/gm, '');
59146048
5915- let nameToTrim = isImpersonate ? name2 : name1;
6049+ if (trimWrongNames) {
6050+ // If this is an impersonation, delete the entire response if it starts with "{{char}}:"
6051+ // If this isn't an impersonation, delete the entire response if it starts with "{{user}}:"
6052+ // Also delete any trailing text that starts with the wrong name.
6053+ // This only occurs if the corresponding "power_user.allow_nameX_display" is false.
59166054
5917- if (isImpersonate) {
6055+ let wrongName = isImpersonate
5918- nameToTrim = power_user.allow_name2_display ? '' : name2;
6056+ ? (!power_user.allow_name2_display ? name2 : '') // char
5919- }
6057+ : (!power_user.allow_name1_display ? name1 : ''); // user
5920- else {
6058+
5921- nameToTrim = power_user.allow_name1_display ? '' : name1;
6059+ if (wrongName) {
6060+ // If the message starts with the wrong name, delete the entire response
6061+ let startIndex = getMessage.indexOf(`${wrongName}:`);
6062+ if (startIndex === 0) {
6063+ getMessage = '';
6064+ console.debug(`Message started with the wrong name: "${wrongName}" - response was deleted.`);
59226065 }
59236066
5924- if (nameToTrim && getMessage.indexOf(`${nameToTrim}:`) == 0) {
6067+ // If there is trailing text starting with the wrong name, trim it off.
59256068 getMessage startIndex = getMessage.substring(0, getMessage.indexOf(`\n${nameToTrimwrongName}:`));
6069+ if (startIndex >= 0) {
6070+ getMessage = getMessage.substring(0, startIndex);
59266071 }
5927- if (nameToTrim && getMessage.indexOf(`\n${nameToTrim}:`) >= 0) {
5928- getMessage = getMessage.substring(0, getMessage.indexOf(`\n${nameToTrim}:`));
59296072 }
6073+ }
6074+
59306075 if (getMessage.indexOf('<|endoftext|>') != -1) {
59316076 getMessage = getMessage.substring(0, getMessage.indexOf('<|endoftext|>'));
59326077 }
@@ -5986,14 +6131,19 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
59866131 getMessage = fixMarkdown(getMessage, false);
59876132 }
59886133
6134+ if (trimNames) {
6135+ // If this is an impersonation, trim "{{user}}:" from the beginning
6136+ // If this isn't an impersonation, trim "{{char}}:" from the beginning.
6137+ // Only applied when the corresponding "power_user.allow_nameX_display" is false.
59896138 const nameToTrim2 = isImpersonate
59906139 ? (!power_user.allow_name1_display ? name1 : '') // user
59916140 : (!power_user.allow_name2_display ? name2 : ''); // char
59926141
59936142 if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) {
59946143 getMessage = getMessage.replace(nameToTrim2 + ':', '');
59956144 getMessage = getMessage.trimStart();
59966145 }
6146+ }
59976147
59986148 if (isImpersonate) {
59996149 getMessage = getMessage.trim();
@@ -6588,6 +6738,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65886738
65896739 await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar);
65906740
6741+ // Unload current character
6742+ setCharacterId(undefined);
65916743 // Reload characters list
65926744 await getCharacters();
65936745
@@ -6596,7 +6748,6 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65966748
65976749 if (newChId !== -1) {
65986750 // Select the character after the renaming
6599- setCharacterId(undefined);
66006751 await selectCharacterById(newChId);
66016752
66026753 // Async delay to update UI
@@ -6729,7 +6880,22 @@ export function saveChatDebounced() {
67296880 }, DEFAULT_SAVE_EDIT_TIMEOUT);
67306881}
67316882
6732-export async function saveChat(chatName, withMetadata, mesId) {
6883+/**
6884+ * Saves the chat to the server.
6885+ * @param {object} [options] - Additional options.
6886+ * @param {string} [options.chatName] The name of the chat file to save to
6887+ * @param {object} [options.withMetadata] Additional metadata to save with the chat
6888+ * @param {number} [options.mesId] The message ID to save the chat up to
6889+ * @param {boolean} [options.force] Force the saving despire the integrity check result
6890+ *
6891+ * @returns {Promise<void>}
6892+ */
6893+export async function saveChat({ chatName, withMetadata, mesId, force = false } = {}) {
6894+ if (arguments.length > 0 && typeof arguments[0] !== 'object') {
6895+ console.trace('saveChat called with positional arguments. Please use an object instead.');
6896+ [chatName, withMetadata, mesId, force] = arguments;
6897+ }
6898+
67336899 const metadata = { ...chat_metadata, ...(withMetadata || {}) };
67346900 const fileName = chatName ?? characters[this_chid]?.chat;
67356901
@@ -6749,53 +6915,59 @@ export async function saveChat(chatName, withMetadata, mesId) {
67496915 toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);
67506916 throw new Error('Group chat saved from saveChat');
67516917 }
6752- /*
6753- if (item.is_user) {
6754- //var str = item.mes.replace(`${name1}:`, `${name1}:`);
6755- //chat[i].mes = str;
6756- //chat[i].name = name1;
6757- } else if (i !== chat.length - 1 && chat[i].swipe_id !== undefined) {
6758- // delete chat[i].swipes;
6759- // delete chat[i].swipe_id;
6760- }
6761- */
67626918 });
67636919
67646920 const trimmed_chattrimmedChat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
67656921 ? chat.slice(0, parseIntNumber(mesId) + 1)
67666922 : chat.slice();
67676923
67686924 varconst save_chatchatToSave = [
67696925 {
67706926 user_name: name1,
67716927 character_name: name2,
67726928 create_date: chat_create_date,
67736929 chat_metadata: metadata,
67746930 },
67756931 ...trimmed_chattrimmedChat,
67766932 ];
6777- return jQuery.ajax({
6933+
6778- type: 'POST',
6934+ try {
67796935 url:const result = await fetch('/api/chats/save', {
6780- data: JSON.stringify({
6936+ method: 'POST',
6937+ cache: 'no-cache',
6938+ headers: getRequestHeaders(),
6939+ body: JSON.stringify({
67816940 ch_name: characters[this_chid].name,
67826941 file_name: fileName,
67836942 chat: save_chatchatToSave,
67846943 avatar_url: characters[this_chid].avatar,
6944+ force: force,
67856945 }),
6786- beforeSend: function () {
6946+ });
67876947
6788- },
6948+ if (result.ok) {
6789- cache: false,
6949+ return;
6790- dataType: 'json',
6950+ }
6791- contentType: 'application/json',
6951+
6792- success: function (data) { },
6952+ const errorData = await result.json();
6793- error: function (jqXHR, exception) {
6953+ const isIntegrityError = errorData?.error === 'integrity' && !force;
6954+ if (!isIntegrityError) {
6955+ throw new Error(result.statusText);
6956+ }
6957+
6958+ const forceSaveConfirmed = await Popup.show.confirm(
6959+ t`ERROR: Chat integrity check failed.`,
6960+ 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.`,
6961+ { okButton: t`Yes, overwrite`, cancelButton: t`No, cancel` },
6962+ ) === POPUP_RESULT.AFFIRMATIVE;
6963+
6964+ if (forceSaveConfirmed) {
6965+ await saveChat({ chatName, withMetadata, mesId, force: true });
6966+ }
6967+ } catch (error) {
6968+ console.error(error);
67946969 toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`);
6795- console.log(exception);
6970+ }
6796- console.log(jqXHR);
6797- },
6798- });
67996971}
68006972
68016973async function read_avatar_load(input) {
@@ -6972,6 +7144,9 @@ export async function getChat() {
69727144 } else {
69737145 chat_create_date = humanizedDateTime();
69747146 }
7147+ if (!chat_metadata['integrity']) {
7148+ chat_metadata['integrity'] = uuidv4();
7149+ }
69757150 await getChatResult();
69767151 eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
69777152
@@ -7943,14 +8118,19 @@ export function select_rm_info(type, charId, previousCharId = null) {
79438118 }
79448119}
79458120
7946-export function select_selected_character(chid) {
8121+/**
8122+ * Selects the right menu for displaying the character editor.
8123+ * @param {number|string} chid Character array index
8124+ * @param {object} [param1] Options for the switch
8125+ * @param {boolean} [param1.switchMenu=true] Whether to switch the menu
8126+ */
8127+export function select_selected_character(chid, { switchMenu = true } = {}) {
79478128 //character select
79488129 //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');
79498130 select_rm_create({ switchMenu });
79508131 switchMenu && setMenuType('character_edit');
79518132 $('#delete_button').css('display', 'flex');
79528133 $('#export_button').css('display', 'flex');
7953- var display_name = characters[chid].name;
79548134
79558135 //create text poles
79568136 $('#rm_button_back').css('display', 'none');
@@ -7965,7 +8145,7 @@ export function select_selected_character(chid) {
79658145
79668146 // Don't update the navbar name if we're peeking the group member defs
79678147 if (!selected_group) {
79688148 $('#rm_button_selected_ch').children('h2').text(display_namecharacters[chid].name);
79698149 }
79708150
79718151 $('#add_avatar_button').val('');
@@ -7996,22 +8176,20 @@ export function select_selected_character(chid) {
79968176 $('#chat_import_avatar_url').val(characters[chid].avatar);
79978177 $('#chat_import_character_name').val(characters[chid].name);
79988178 $('#character_json_data').val(characters[chid].json_data);
7999- let this_avatar = default_avatar;
8000- if (characters[chid].avatar != 'none') {
8001- this_avatar = getThumbnailUrl('avatar', characters[chid].avatar);
8002- }
80038179
80048180 updateFavButtonState(characters[chid].fav || characters[chid].fav == 'true');
80058181
8006- $('#avatar_load_preview').attr('src', this_avatar);
8182+ const avatarUrl = characters[chid].avatar != 'none' ? getThumbnailUrl('avatar', characters[chid].avatar) : default_avatar;
80078183 $('#name_divavatar_load_preview').removeClassattr('displayBlocksrc', avatarUrl);
8008- $('#name_div').addClass('displayNone');
8009- $('#renameCharButton').css('display', '');
80108184 $('.open_alternate_greetings').data('chid', chid);
80118185 $('#set_character_world').data('chid', chid);
80128186 setWorldInfoButtonClass(chid);
80138187 checkEmbeddedWorld(chid);
80148188
8189+ $('#name_div').removeClass('displayBlock');
8190+ $('#name_div').addClass('displayNone');
8191+ $('#renameCharButton').css('display', '');
8192+
80158193 $('#form_create').attr('actiontype', 'editcharacter');
80168194 $('.form_create_bottom_buttons_block .chat_lorebook_button').show();
80178195
@@ -8023,8 +8201,13 @@ export function select_selected_character(chid) {
80238201 saveSettingsDebounced();
80248202}
80258203
8026-function select_rm_create() {
8204+/**
8027- setMenuType('create');
8205+ * Selects the right menu for creating a new character.
8206+ * @param {object} [options] Options for the switch
8207+ * @param {boolean} [options.switchMenu=true] Whether to switch the menu
8208+ */
8209+function select_rm_create({ switchMenu = true } = {}) {
8210+ switchMenu && setMenuType('create');
80288211
80298212 //console.log('select_rm_Create() -- selected button: '+selected_button);
80308213 if (selected_button == 'create') {
@@ -8034,7 +8217,7 @@ function select_rm_create() {
80348217 }
80358218 }
80368219
80378220 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
80388221
80398222 $('#set_chat_scenario').hide();
80408223 $('#delete_button_div').css('display', 'none');
@@ -9256,6 +9439,17 @@ function swipe_right(_event, { source, repeated } = {}) {
92569439 }
92579440}
92589441
9442+/**
9443+ * @typedef {object} ConnectAPIMap
9444+ * @property {string} selected - API name (e.g. "textgenerationwebui", "openai")
9445+ * @property {string?} [button] - CSS selector for the API button
9446+ * @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter")
9447+ * @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai")
9448+ */
9449+
9450+/**
9451+ * @type {Record<string, ConnectAPIMap>}
9452+ */
92599453export const CONNECT_API_MAP = {
92609454 // Default APIs not contined inside text gen / chat gen
92619455 'kobold': {
@@ -9700,6 +9894,15 @@ export async function renameChat(oldFileName, newName) {
97009894 renamed_file: `${newName.trim()}.jsonl`,
97019895 };
97029896
9897+ if (body.original_file === body.renamed_file) {
9898+ console.debug('Chat rename cancelled, old and new names are the same');
9899+ return;
9900+ }
9901+ if (equalsIgnoreCaseAndAccents(body.original_file, body.renamed_file)) {
9902+ toastr.warning(t`Name not accepted, as it is the same as before (ignoring case and accents).`, t`Rename Chat`);
9903+ return;
9904+ }
9905+
97039906 try {
97049907 showLoader();
97059908 const response = await fetch('/api/chats/rename', {
@@ -10477,7 +10680,7 @@ jQuery(async function () {
1047710680 e.stopPropagation();
1047810681 chat_file_for_del = $(this).attr('file_name');
1047910682 console.debug('detected cross click for' + chat_file_for_del);
1048010683 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
1048110684 });
1048210685
1048310686 $('#advanced_div').click(function () {
@@ -11644,8 +11847,8 @@ jQuery(async function () {
1164411847 return;
1164511848 }
1164611849 const drawer = $(this).closest('.inline-drawer');
1164711850 const icon = drawer.find('>.inline-drawer-header .inline-drawer-icon');
1164811851 const drawerContent = drawer.find('>.inline-drawer-content');
1164911852 icon.toggleClass('down up');
1165011853 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
1165111854 drawerContent.stop().slideToggle({
@@ -12031,4 +12234,11 @@ jQuery(async function () {
1203112234 });
1203212235
1203312236 initCustomSelectedSamplers();
12237+
12238+ window.addEventListener('beforeunload', (e) => {
12239+ if (isChatSaving) {
12240+ e.preventDefault();
12241+ e.returnValue = true;
12242+ }
12243+ });
1203412244});
public/scripts/PromptManager.js+3 -31
@@ -1,10 +1,10 @@
11'use strict';
22
33import { 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';
@@ -1440,36 +1440,8 @@ class PromptManager {
14401440 footerDiv.querySelector('select').selectedIndex = selectedPromptIndex;
14411441
14421442 // Add prompt export dialogue and options
1443-
1444- const exportForCharacter = await renderTemplateAsync('promptManagerExportForCharacter');
1445- const exportPopup = await renderTemplateAsync('promptManagerExportPopup', { isGlobalStrategy: 'global' === this.configuration.promptOrder.strategy, exportForCharacter });
1446- rangeBlockDiv.insertAdjacentHTML('beforeend', exportPopup);
1447-
1448- // Destroy previous popper instance if it exists
1449- if (this.exportPopper) {
1450- this.exportPopper.destroy();
1451- }
1452-
1453- this.exportPopper = Popper.createPopper(
1454- document.getElementById('prompt-manager-export'),
1455- document.getElementById('prompt-manager-export-format-popup'),
1456- { placement: 'bottom' },
1457- );
1458-
1459- const showExportSelection = () => {
1460- const popup = document.getElementById('prompt-manager-export-format-popup');
1461- const show = popup.hasAttribute('data-show');
1462-
1463- if (show) popup.removeAttribute('data-show');
1464- else popup.setAttribute('data-show', '');
1465-
1466- this.exportPopper.update();
1467- };
1468-
14691443 footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport);
14701444 footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelectionthis.handleFullExport);
1471- rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport);
1472- rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
14731445 }
14741446 }
14751447
public/scripts/RossAscends-mods.js+1 -1
@@ -407,9 +407,9 @@ function RA_autoconnect(PrevApi) {
407407 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
408408 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
409409 || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)
410- || (secret_state[SECRET_KEYS.BLOCKENTROPY] && oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY)
411410 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
412411 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
412+ || (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
413413 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
414414 ) {
415415 $('#api_button_openai').trigger('click');
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/chats.js+11 -5
@@ -459,7 +459,7 @@ export async function appendFileContent(message, messageText) {
459459 * @copyright https://github.com/kwaroran/risuAI
460460 */
461461export function encodeStyleTags(text) {
462462 const styleRegex = /<style>(.+?)<\/style>/gmsgims;
463463 return text.replaceAll(styleRegex, (_, match) => {
464464 return `<custom-style>${escape(match)}</custom-style>`;
465465 });
@@ -575,8 +575,8 @@ export function isExternalMediaAllowed() {
575575 return !power_user.forbid_external_media;
576576}
577577
578578async function enlargeMessageImageexpandMessageImage(event) {
579579 const mesBlock = $(thisevent.currentTarget).closest('.mes');
580580 const mesId = mesBlock.attr('mesid');
581581 const message = chat[mesId];
582582 const imgSrc = message?.extra?.image;
@@ -620,7 +620,12 @@ async function enlargeMessageImage() {
620620 popup.completeCancelled();
621621 });
622622
623623 await popup.show();
624+ return img;
625+}
626+
627+function expandAndZoomMessageImage(event) {
628+ expandMessageImage(event).click();
624629}
625630
626631async function deleteMessageImage() {
@@ -1603,7 +1608,8 @@ jQuery(function () {
16031608 reloadCurrentChat();
16041609 });
16051610
16061611 $(document).on('click', '.mes_img_enlargemes_img', enlargeMessageImageexpandMessageImage);
1612+ $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage);
16071613 $(document).on('click', '.mes_img_delete', deleteMessageImage);
16081614
16091615 $('#file_form_input').on('change', async () => {
public/scripts/constants.js+8 -0
@@ -14,3 +14,11 @@ export const debounce_timeout = {
1414 /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */
1515 extended: 5000,
1616};
17+
18+/**
19+ * Used as an ephemeral key in message extra metadata.
20+ * When set, the message will be excluded from generation
21+ * prompts without affecting the number of chat messages,
22+ * which is needed to preserve world info timed effects.
23+ */
24+export const IGNORE_SYMBOL = Symbol.for('ignore');
public/scripts/custom-request.js+415 -46
@@ -1,22 +1,26 @@
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, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js';
6+import { getStreamingReply, tryParseStreamingError } from './openai.js';
7+import EventSourceStream from './sse-stream.js';
48
59// #region Type Definitions
610/**
711 * @typedef {Object} TextCompletionRequestBase
812 * @property {stringboolean?} prompt[stream=false] - TheWhether textto promptstream forthe completionresponse
913 * @property {number} max_tokens - Maximum number of tokens to generate
1014 * @property {string} [model] - Optional model name
1115 * @property {string} api_type - Type of API to use
1216 * @property {string} [api_server] - Optional API server URL
1317 * @property {number} [temperature] - Optional temperature parameter
18+ * @property {number} [min_p] - Optional min_p parameter
1419 */
1520
16-/** @typedef {Record<string, any> & TextCompletionRequestBase} TextCompletionRequest */
17-
1821/**
1922 * @typedef {Object} TextCompletionPayloadBase
23+ * @property {boolean?} [stream=false] - Whether to stream the response
2024 * @property {string} prompt - The text prompt for completion
2125 * @property {number} max_tokens - Maximum number of tokens to generate
2226 * @property {number} max_new_tokens - Alias for max_tokens
@@ -36,29 +40,49 @@ import { getTextGenServer } from './textgen-settings.js';
3640
3741/**
3842 * @typedef {Object} ChatCompletionPayloadBase
43+ * @property {boolean?} [stream=false] - Whether to stream the response
3944 * @property {ChatCompletionMessage[]} messages - Array of chat messages
4045 * @property {string} [model] - Optional model name to use for completion
4146 * @property {string} chat_completion_source - Source provider for chat completion
4247 * @property {number} max_tokens - Maximum number of tokens to generate
4348 * @property {number} [temperature] - Optional temperature parameter for response randomness
49+ * @property {string} [custom_url] - Optional custom URL
50+ * @property {string} [reverse_proxy] - Optional reverse proxy URL
51+ * @property {string} [proxy_password] - Optional proxy password
4452 */
4553
4654/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
55+
56+/**
57+ * @typedef {Object} ExtractedData
58+ * @property {string} content - Extracted content.
59+ * @property {string} reasoning - Extracted reasoning.
60+ */
61+
62+/**
63+ * @typedef {Object} StreamResponse
64+ * @property {string} text - Generated text.
65+ * @property {string[]} swipes - Generated swipes
66+ * @property {Object} state - Generated state
67+ * @property {string?} [state.reasoning] - Generated reasoning
68+ * @property {string?} [state.image] - Generated image
69+ */
70+
4771// #endregion
4872
4973/**
5074 * Creates & sends a text completion request. Streaming is not supported.
5175 */
5276export class TextCompletionService {
5377 static TYPE = 'textgenerationwebui';
5478
5579 /**
56- * @param {TextCompletionRequest} custom
80+ * @param {Record<string, any> & TextCompletionRequestBase & {prompt: string}} custom
5781 * @returns {TextCompletionPayload}
5882 */
5983 static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) {
6084 returnconst payload = {
61- ...props,
85+ stream,
6286 prompt,
6387 max_tokens,
6488 max_new_tokens: max_tokens,
@@ -66,24 +90,36 @@ export class TextCompletionService {
6690 api_type,
6791 api_server: api_server ?? getTextGenServer(api_type),
6892 temperature,
69- stream: false,
93+ min_p,
94+ ...props,
7095 };
96+
97+ // Remove undefined values to avoid API errors
98+ Object.keys(payload).forEach(key => {
99+ if (payload[key] === undefined) {
100+ delete payload[key];
101+ }
102+ });
103+
104+ return payload;
71105 }
72106
73107 /**
74108 * Sends a text completion request to the specified server
75109 * @param {TextCompletionPayload} data Request data
76110 * @param {boolean?} extractData Extract message from the response. Default true
77- * @returns {Promise<string | any>} Extracted data or the raw response
111+ * @param {AbortSignal?} signal
112+ * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
78113 * @throws {Error}
79114 */
80115 static async sendRequest(data, extractData = true, signal = null) {
116+ if (!data.stream) {
81117 const response = await fetch(getGenerateUrl(this.TYPE), {
82118 method: 'POST',
83119 headers: getRequestHeaders(),
84120 cache: 'no-cache',
85121 body: JSON.stringify(data),
86122 signal: signal ?? new AbortController().signal,
87123 });
88124
89125 const json = await response.json();
@@ -91,35 +127,260 @@ export class TextCompletionService {
91127 throw json;
92128 }
93129
94- return extractData ? extractMessageFromData(json, this.TYPE) : json;
130+ if (!extractData) {
131+ return json;
132+ }
133+
134+ return {
135+ content: extractMessageFromData(json, this.TYPE),
136+ reasoning: extractReasoningFromData(json, {
137+ mainApi: this.TYPE,
138+ textGenType: data.api_type,
139+ ignoreShowThoughts: true,
140+ }),
141+ };
142+ }
143+
144+ const response = await fetch('/api/backends/text-completions/generate', {
145+ method: 'POST',
146+ headers: getRequestHeaders(),
147+ cache: 'no-cache',
148+ body: JSON.stringify(data),
149+ signal: signal ?? new AbortController().signal,
150+ });
151+
152+ if (!response.ok) {
153+ const text = await response.text();
154+ tryParseStreamingError(response, text, { quiet: true });
155+
156+ throw new Error(`Got response status ${response.status}`);
157+ }
158+
159+ const eventStream = new EventSourceStream();
160+ response.body.pipeThrough(eventStream);
161+ const reader = eventStream.readable.getReader();
162+ return async function* streamData() {
163+ let text = '';
164+ const swipes = [];
165+ const state = { reasoning: '' };
166+ while (true) {
167+ const { done, value } = await reader.read();
168+ if (done) return;
169+ if (value.data === '[DONE]') return;
170+
171+ tryParseStreamingError(response, value.data, { quiet: true });
172+
173+ let data = JSON.parse(value.data);
174+
175+ if (data?.choices?.[0]?.index > 0) {
176+ const swipeIndex = data.choices[0].index - 1;
177+ swipes[swipeIndex] = (swipes[swipeIndex] || '') + data.choices[0].text;
178+ } else {
179+ const newText = data?.choices?.[0]?.text || data?.content || '';
180+ text += newText;
181+ state.reasoning += data?.choices?.[0]?.reasoning ?? '';
182+ }
183+
184+ yield { text, swipes, state };
185+ }
186+ };
95187 }
96188
97189 /**
98- * @param {string} presetName
190+ * Process and send a text completion request with optional preset & instruct
99- * @param {TextCompletionRequest} custom
191+ * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom
100192 * @param {boolean?Object} extractData Extract message from theoptions response.- DefaultConfiguration trueoptions
101- * @returns {Promise<string | any>} Extracted data or the raw response
193+ * @param {string?} [options.presetName] - Name of the preset to use for generation settings
194+ * @param {string?} [options.instructName] - Name of instruct preset for message formatting
195+ * @param {Partial<InstructSettings>?} [options.instructSettings] - Override instruct settings
196+ * @param {boolean} extractData - Whether to extract structured data from response
197+ * @param {AbortSignal?} [signal]
198+ * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
102199 * @throws {Error}
103200 */
104- static async sendRequestWithPreset(presetName, custom, extractData = true) {
201+ static async processRequest(
202+ custom,
203+ options = {},
204+ extractData = true,
205+ signal = null,
206+ ) {
207+ const { presetName, instructName } = options;
208+ let requestData = { ...custom };
209+ const prompt = custom.prompt;
210+
211+ // Apply generation preset if specified
212+ if (presetName) {
105213 const presetManager = getPresetManager(this.TYPE);
106214 if (!presetManager) {
107- throw new Error('Preset manager not found');
215+ const preset = presetManager.getCompletionPresetByName(presetName);
216+ if (preset) {
217+ // Convert preset to payload and merge with custom parameters
218+ const presetPayload = this.presetToGeneratePayload(preset, {});
219+ requestData = { ...presetPayload, ...requestData };
220+ } else {
221+ console.warn(`Preset "${presetName}" not found, continuing with default settings`);
222+ }
223+ } else {
224+ console.warn('Preset manager not found, continuing with default settings');
225+ }
108226 }
109227
110- const preset = presetManager.getCompletionPresetByName(presetName);
228+
111- if (!preset) {
229+ /** @type {InstructSettings | undefined} */
112- throw new Error('Preset not found');
230+ let instructPreset;
231+ // Handle instruct formatting if requested
232+ if (Array.isArray(prompt) && instructName) {
233+ const instructPresetManager = getPresetManager('instruct');
234+ instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
235+ if (instructPreset) {
236+ // Clone the preset to avoid modifying the original
237+ instructPreset = structuredClone(instructPreset);
238+ instructPreset.names_behavior = names_behavior_types.NONE;
239+ if (options.instructSettings) {
240+ Object.assign(instructPreset, options.instructSettings);
241+ }
242+
243+ // Format messages using instruct formatting
244+ const formattedMessages = [];
245+ for (const message of prompt) {
246+ let messageContent = message.content;
247+ if (!message.ignoreInstruct) {
248+ messageContent = formatInstructModeChat(
249+ message.role,
250+ message.content,
251+ message.role === 'user',
252+ false,
253+ undefined,
254+ undefined,
255+ undefined,
256+ undefined,
257+ instructPreset,
258+ );
259+
260+ // Add prompt formatting for the last message
261+ if (message === prompt[prompt.length - 1]) {
262+ messageContent += formatInstructModePrompt(
263+ undefined,
264+ false,
265+ undefined,
266+ undefined,
267+ undefined,
268+ false,
269+ false,
270+ instructPreset,
271+ );
272+ }
273+ }
274+ formattedMessages.push(messageContent);
113275 }
276+ requestData.prompt = formattedMessages.join('');
277+ const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false });
278+ requestData.stop = stoppingStrings;
279+ requestData.stopping_strings = stoppingStrings;
280+ } else {
281+ console.warn(`Instruct preset "${instructName}" not found, using basic formatting`);
282+ requestData.prompt = prompt.map(x => x.content).join('\n\n');
283+ }
284+ } else if (typeof prompt === 'string') {
285+ requestData.prompt = prompt;
286+ } else {
287+ requestData.prompt = prompt.map(x => x.content).join('\n\n');
288+ }
289+
290+ // @ts-ignore
291+ const data = this.createRequestData(requestData);
292+
293+ const response = await this.sendRequest(data, extractData, signal);
294+ // Remove stopping strings from the end
295+ if (!data.stream && extractData) {
296+ /** @type {ExtractedData} */
297+ // @ts-ignore
298+ const extractedData = response;
114299
115- const data = this.createRequestData({ ...preset, ...custom });
300+ let message = extractedData.content;
116301
117- return await this.sendRequest(data, extractData);
302+ message = message.replace(/[^\S\r\n]+$/gm, '');
303+
304+ if (requestData.stopping_strings) {
305+ for (const stoppingString of requestData.stopping_strings) {
306+ if (stoppingString.length) {
307+ for (let j = stoppingString.length; j > 0; j--) {
308+ if (message.slice(-j) === stoppingString.slice(0, j)) {
309+ message = message.slice(0, -j);
310+ break;
311+ }
312+ }
313+ }
314+ }
315+ }
316+
317+ if (instructPreset) {
318+ [
319+ instructPreset.stop_sequence,
320+ instructPreset.input_sequence,
321+ ].forEach(sequence => {
322+ if (sequence?.trim()) {
323+ const index = message.indexOf(sequence);
324+ if (index !== -1) {
325+ message = message.substring(0, index);
326+ }
327+ }
328+ });
329+
330+ [
331+ instructPreset.output_sequence,
332+ instructPreset.last_output_sequence,
333+ ].forEach(sequences => {
334+ if (sequences) {
335+ sequences.split('\n')
336+ .filter(line => line.trim() !== '')
337+ .forEach(line => {
338+ message = message.replaceAll(line, '');
339+ });
340+ }
341+ });
342+ }
343+
344+ extractedData.content = message;
345+ }
346+
347+ return response;
348+ }
349+
350+ /**
351+ * Converts a preset to a valid text completion payload.
352+ * Only supports temperature.
353+ * @param {Object} preset - The preset configuration
354+ * @param {Object} customPreset - Additional parameters to override preset values
355+ * @returns {Object} - Formatted payload for text completion API
356+ */
357+ static presetToGeneratePayload(preset, customPreset = {}) {
358+ if (!preset || typeof preset !== 'object') {
359+ throw new Error('Invalid preset: must be an object');
360+ }
361+
362+ // Merge preset with custom parameters
363+ const settings = { ...preset, ...customPreset };
364+
365+ // Initialize base payload with common parameters
366+ let payload = {
367+ 'temperature': settings.temp ? Number(settings.temp) : undefined,
368+ 'min_p': settings.min_p ? Number(settings.min_p) : undefined,
369+ };
370+
371+ // Remove undefined values to avoid API errors
372+ Object.keys(payload).forEach(key => {
373+ if (payload[key] === undefined) {
374+ delete payload[key];
375+ }
376+ });
377+
378+ return payload;
118379 }
119380}
120381
121382/**
122383 * Creates & sends a chat completion request. Streaming is not supported.
123384 */
124385export class ChatCompletionService {
125386 static TYPE = 'openai';
@@ -128,62 +389,170 @@ export class ChatCompletionService {
128389 * @param {ChatCompletionPayload} custom
129390 * @returns {ChatCompletionPayload}
130391 */
131392 static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, ...props }) {
132393 returnconst payload = {
133- ...props,
394+ stream,
134395 messages,
135396 model,
136397 chat_completion_source,
137398 max_tokens,
138399 temperature,
139- stream: false,
400+ custom_url,
401+ reverse_proxy,
402+ proxy_password,
403+ use_makersuite_sysprompt: true,
404+ claude_use_sysprompt: true,
405+ ...props,
140406 };
407+
408+ // Remove undefined values to avoid API errors
409+ Object.keys(payload).forEach(key => {
410+ if (payload[key] === undefined) {
411+ delete payload[key];
412+ }
413+ });
414+
415+ return payload;
141416 }
142417
143418 /**
144419 * Sends a chat completion request
145420 * @param {ChatCompletionPayload} data Request data
146421 * @param {boolean?} extractData Extract message from the response. Default true
147- * @returns {Promise<string | any>} Extracted data or the raw response
422+ * @param {AbortSignal?} signal Abort signal
423+ * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
148424 * @throws {Error}
149425 */
150426 static async sendRequest(data, extractData = true, signal = null) {
151427 const response = await fetch('/api/backends/chat-completions/generate', {
152428 method: 'POST',
153429 headers: getRequestHeaders(),
154430 cache: 'no-cache',
155431 body: JSON.stringify(data),
156432 signal: signal ?? new AbortController().signal,
157433 });
158434
435+ if (!data.stream) {
159436 const json = await response.json();
160437 if (!response.ok || json.error) {
161438 throw json;
162439 }
163440
164- return extractData ? extractMessageFromData(json, this.TYPE) : json;
441+ if (!extractData) {
442+ return json;
443+ }
444+
445+ return {
446+ content: extractMessageFromData(json, this.TYPE),
447+ reasoning: extractReasoningFromData(json, {
448+ mainApi: this.TYPE,
449+ textGenType: data.chat_completion_source,
450+ ignoreShowThoughts: true,
451+ }),
452+ };
453+ }
454+
455+ if (!response.ok) {
456+ const text = await response.text();
457+ tryParseStreamingError(response, text, { quiet: true });
458+
459+ throw new Error(`Got response status ${response.status}`);
460+ }
461+
462+ const eventStream = new EventSourceStream();
463+ response.body.pipeThrough(eventStream);
464+ const reader = eventStream.readable.getReader();
465+ return async function* streamData() {
466+ let text = '';
467+ const swipes = [];
468+ const state = { reasoning: '', image: '' };
469+ while (true) {
470+ const { done, value } = await reader.read();
471+ if (done) return;
472+ const rawData = value.data;
473+ if (rawData === '[DONE]') return;
474+ tryParseStreamingError(response, rawData, { quiet: true });
475+ const parsed = JSON.parse(rawData);
476+
477+ const reply = getStreamingReply(parsed, state, {
478+ chatCompletionSource: data.chat_completion_source,
479+ overrideShowThoughts: true,
480+ });
481+ if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
482+ const swipeIndex = parsed.choices[0].index - 1;
483+ swipes[swipeIndex] = (swipes[swipeIndex] || '') + reply;
484+ } else {
485+ text += reply;
486+ }
487+
488+ yield { text, swipes: swipes, state };
489+ }
490+ };
165491 }
166492
167493 /**
168- * @param {string} presetName
494+ * Process and send a chat completion request with optional preset
169495 * @param {ChatCompletionPayload} custom
170496 * @param {booleanObject} extractData Extract message from theoptions response.- DefaultConfiguration trueoptions
171- * @returns {Promise<string | any>} Extracted data or the raw response
497+ * @param {string?} [options.presetName] - Name of the preset to use for generation settings
498+ * @param {boolean} [extractData=true] - Whether to extract structured data from response
499+ * @param {AbortSignal?} [signal] - Abort signal
500+ * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
172501 * @throws {Error}
173502 */
174503 static async sendRequestWithPresetprocessRequest(presetNamecustom, customoptions, extractData = true, signal = null) {
504+ const { presetName } = options;
505+ let requestData = { ...custom };
506+
507+ // Apply generation preset if specified
508+ if (presetName) {
175509 const presetManager = getPresetManager(this.TYPE);
176510 if (!presetManager) {
177- throw new Error('Preset manager not found');
511+ const preset = presetManager.getCompletionPresetByName(presetName);
512+ if (preset) {
513+ // Convert preset to payload and merge with custom parameters
514+ const presetPayload = this.presetToGeneratePayload(preset, {});
515+ requestData = { ...presetPayload, ...requestData };
516+ } else {
517+ console.warn(`Preset "${presetName}" not found, continuing with default settings`);
518+ }
519+ } else {
520+ console.warn('Preset manager not found, continuing with default settings');
521+ }
178522 }
179523
180524 const presetdata = presetManagerthis.getCompletionPresetByNamecreateRequestData(presetNamerequestData);
181- if (!preset) {
525+
182- throw new Error('Preset not found');
526+ return await this.sendRequest(data, extractData, signal);
183527 }
184528
185- const data = this.createRequestData({ ...preset, ...custom });
529+ /**
530+ * Converts a preset to a valid chat completion payload
531+ * Only supports temperature.
532+ * @param {Object} preset - The preset configuration
533+ * @param {Object} customParams - Additional parameters to override preset values
534+ * @returns {Object} - Formatted payload for chat completion API
535+ */
536+ static presetToGeneratePayload(preset, customParams = {}) {
537+ if (!preset || typeof preset !== 'object') {
538+ throw new Error('Invalid preset: must be an object');
539+ }
540+
541+ // Merge preset with custom parameters
542+ const settings = { ...preset, ...customParams };
543+
544+ // Initialize base payload with common parameters
545+ const payload = {
546+ temperature: settings.temperature ? Number(settings.temperature) : undefined,
547+ };
548+
549+ // Remove undefined values to avoid API errors
550+ Object.keys(payload).forEach(key => {
551+ if (payload[key] === undefined) {
552+ delete payload[key];
553+ }
554+ });
186555
187- return await this.sendRequest(data, extractData);
556+ return payload;
188557 }
189558}
public/scripts/extensions.js+42 -16
@@ -783,25 +783,41 @@ async function showExtensionsDetails() {
783783 .append(htmlExternal)
784784 .append(getModuleInformation());
785785
786- /** @type {import('./popup.js').CustomPopupButton} */
786+ {
787- const updateAllButton = {
787+ const updateAction = async (force) => {
788- text: t`Update all`,
789- action: async () => {
790788 requiresReload = true;
791789 await autoUpdateExtensions(trueforce);
792790 await popup.complete(POPUP_RESULT.AFFIRMATIVE);
793- },
794791 };
795792
796- /** @type {import('./popup.js').CustomPopupButton} */
793+ const toolbar = document.createElement('div');
797- const sortOrderButton = {
794+ toolbar.classList.add('extensions_toolbar');
798- text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`,
795+
799- action: async () => {
796+ const updateAllButton = document.createElement('button');
797+ updateAllButton.classList.add('menu_button', 'menu_button_icon');
798+ updateAllButton.textContent = t`Update all`;
799+ updateAllButton.addEventListener('click', () => updateAction(true));
800+
801+ const updateEnabledOnlyButton = document.createElement('button');
802+ updateEnabledOnlyButton.classList.add('menu_button', 'menu_button_icon');
803+ updateEnabledOnlyButton.textContent = t`Update enabled`;
804+ updateEnabledOnlyButton.addEventListener('click', () => updateAction(false));
805+
806+ const flexExpander = document.createElement('div');
807+ flexExpander.classList.add('expander');
808+
809+ const sortOrderButton = document.createElement('button');
810+ sortOrderButton.classList.add('menu_button', 'menu_button_icon');
811+ sortOrderButton.textContent = sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`;
812+ sortOrderButton.addEventListener('click', async () => {
800813 abortController.abort();
801814 accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
802815 await showExtensionsDetails();
803816 },);
804- };
817+
818+ toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton);
819+ html.prepend(toolbar);
820+ }
805821
806822 let waitingForSave = false;
807823
@@ -809,7 +825,7 @@ async function showExtensionsDetails() {
809825 okButton: t`Close`,
810826 wide: true,
811827 large: true,
812828 customButtons: [sortOrderButton, updateAllButton],
813829 allowVerticalScrolling: true,
814830 onClosing: async () => {
815831 if (waitingForSave) {
@@ -1196,7 +1212,7 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
11961212}
11971213
11981214/**
11991215 * Checks if there are updates available for enabled 3rd-party extensions.
12001216 * @param {boolean} force Skip nag check
12011217 * @returns {Promise<any>}
12021218 */
@@ -1218,6 +1234,11 @@ async function checkForExtensionUpdates(force) {
12181234 const promises = [];
12191235
12201236 for (const [id, manifest] of Object.entries(manifests)) {
1237+ const isDisabled = extension_settings.disabledExtensions.includes(id);
1238+ if (isDisabled) {
1239+ console.debug(`Skipping extension: ${manifest.display_name} (${id}) for non-admin user`);
1240+ continue;
1241+ }
12211242 const isGlobal = getExtensionType(id) === 'global';
12221243 if (isGlobal && !isCurrentUserAdmin) {
12231244 console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);
@@ -1247,8 +1268,8 @@ async function checkForExtensionUpdates(force) {
12471268}
12481269
12491270/**
12501271 * Updates all enabled 3rd-party extensions that have auto-update enabled.
12511272 * @param {boolean} forceAll Force update allInclude evendisabled ifand not auto-updating
12521273 * @returns {Promise<void>}
12531274 */
12541275async function autoUpdateExtensions(forceAll) {
@@ -1260,6 +1281,11 @@ async function autoUpdateExtensions(forceAll) {
12601281 const isCurrentUserAdmin = isAdmin();
12611282 const promises = [];
12621283 for (const [id, manifest] of Object.entries(manifests)) {
1284+ const isDisabled = extension_settings.disabledExtensions.includes(id);
1285+ if (!forceAll && isDisabled) {
1286+ console.debug(`Skipping extension: ${manifest.display_name} (${id}) for non-admin user`);
1287+ continue;
1288+ }
12631289 const isGlobal = getExtensionType(id) === 'global';
12641290 if (isGlobal && !isCurrentUserAdmin) {
12651291 console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);
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/assets/window.html+1 -1
@@ -39,7 +39,7 @@ To install a single 3rd party extension, use the &quot;Install Extensions&quot;
3939 <span data-i18n="Characters">Characters</span>
4040 </div>
4141 </div>
4242 <div class="inline-drawer-content" id="assets_menu">
4343 </div>
4444 </div>
4545 </div>
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/index.js+1 -0
@@ -428,6 +428,7 @@ jQuery(async function () {
428428 'zerooneai': SECRET_KEYS.ZEROONEAI,
429429 'groq': SECRET_KEYS.GROQ,
430430 'cohere': SECRET_KEYS.COHERE,
431+ 'xai': SECRET_KEYS.XAI,
431432 };
432433
433434 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+19 -0
@@ -31,6 +31,7 @@
3131 <option value="openrouter">OpenRouter</option>
3232 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
3333 <option value="vllm">vLLM</option>
34+ <option value="xai">xAI (Grok)</option>
3435 </select>
3536 </div>
3637 <div class="flex1 flex-container flexFlowColumn flexNoGap">
@@ -42,7 +43,16 @@
4243 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
4344 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>
4445 <option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option>
46+ <option data-type="mistral" value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option>
47+ <option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option>
48+ <option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option>
4549 <option data-type="zerooneai" value="yi-vision">yi-vision</option>
50+ <option data-type="openai" value="gpt-4.1">gpt-4.1</option>
51+ <option data-type="openai" value="gpt-4.1-2025-04-14">gpt-4.1-2025-04-14</option>
52+ <option data-type="openai" value="gpt-4.1-mini">gpt-4.1-mini</option>
53+ <option data-type="openai" value="gpt-4.1-mini-2025-04-14">gpt-4.1-mini-2025-04-14</option>
54+ <option data-type="openai" value="gpt-4.1-nano">gpt-4.1-nano</option>
55+ <option data-type="openai" value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option>
4656 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
4757 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
4858 <option data-type="openai" value="gpt-4o">gpt-4o</option>
@@ -50,6 +60,10 @@
5060 <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
5161 <option data-type="openai" value="o1">o1</option>
5262 <option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option>
63+ <option data-type="openai" value="o3">o3</option>
64+ <option data-type="openai" value="o3-2025-04-16">o3-2025-04-16</option>
65+ <option data-type="openai" value="o4-mini">o4-mini</option>
66+ <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
5367 <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>
5468 <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
5569 <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>
@@ -62,8 +76,11 @@
6276 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
6377 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
6478 <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
79+ <option data-type="google" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
80+ <option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>
6581 <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option>
6682 <option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option>
83+ <option data-type="google" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
6784 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
6885 <option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option>
6986 <option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>
@@ -129,6 +146,8 @@
129146 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
130147 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
131148 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
149+ <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option>
150+ <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option>
132151 </select>
133152 </div>
134153 <div data-type="ollama">
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+77 -20
@@ -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/**
@@ -82,6 +83,7 @@ const EXPRESSION_API = {
8283 extras: 1,
8384 llm: 2,
8485 webllm: 3,
86+ none: 99,
8587};
8688
8789let expressionsList = null;
@@ -273,7 +275,7 @@ async function getLastMessageSprite(avatar) {
273275 return null;
274276}
275277
276278export async function visualNovelUpdateLayers(container) {
277279 const context = getContext();
278280 const group = context.groups.find(x => x.id == context.groupId);
279281 const recentMessages = context.chat.map(x => x.original_avatar).filter(x => x).reverse().filter(onlyUnique);
@@ -678,7 +680,7 @@ async function setSpriteFolderCommand(_, folder) {
678680 return '';
679681}
680682
681683async function classifyCallback(/** @type {{api: string?, filter: string?, prompt: string?}} */ { api = null, filter = null, prompt = null }, text) {
682684 if (!text) {
683685 toastr.error('No text provided');
684686 return '';
@@ -689,13 +691,19 @@ async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ {
689691 }
690692
691693 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;
694+ const filterAvailable = !isFalseBoolean(filter);
695+
696+ if (expressionApi === EXPRESSION_API.none) {
697+ toastr.warning('No classifier API selected');
698+ return '';
699+ }
692700
693701 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {
694702 toastr.warning('Text classification is disabled or not available');
695703 return '';
696704 }
697705
698706 const label = await getExpressionLabel(text, expressionApi, { filterAvailable: filterAvailable, customPrompt: prompt });
699707 console.debug(`Classification result for "${text}": ${label}`);
700708 return label;
701709}
@@ -928,6 +936,9 @@ function parseLlmResponse(emotionResponse, labels) {
928936
929937 return response;
930938 } catch {
939+ // Clean possible reasoning from response
940+ emotionResponse = removeReasoningFromString(emotionResponse);
941+
931942 const fuse = new Fuse(labels, { includeScore: true });
932943 console.debug('Using fuzzy search in labels:', labels);
933944 const result = fuse.search(emotionResponse);
@@ -988,10 +999,11 @@ function onTextGenSettingsReady(args) {
988999 * @param {string} text - The text to classify and retrieve the expression label for.
9891000 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
9901001 * @param {object} [options={}] - Optional arguments.
1002+ * @param {boolean?} [options.filterAvailable=null] - Whether to filter available expressions. If not specified, uses the extension setting.
9911003 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
9921004 * @returns {Promise<string?>} - The label of the expression.
9931005 */
9941006export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { filterAvailable = null, customPrompt = null } = {}) {
9951007 // Return if text is undefined, saving a costly fetch request
9961008 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
9971009 return extension_settings.expressions.fallback_expression;
@@ -1003,6 +1015,11 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10031015
10041016 text = sampleClassifyText(text);
10051017
1018+ filterAvailable ??= extension_settings.expressions.filterAvailable;
1019+ if (filterAvailable && ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(expressionsApi)) {
1020+ console.debug('Filter available is only supported for LLM and WebLLM expressions');
1021+ }
1022+
10061023 try {
10071024 switch (expressionsApi) {
10081025 // Local BERT pipeline
@@ -1027,7 +1044,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10271044 return extension_settings.expressions.fallback_expression;
10281045 }
10291046
10301047 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
10311048 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
10321049 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
10331050 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
@@ -1040,7 +1057,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10401057 return extension_settings.expressions.fallback_expression;
10411058 }
10421059
10431060 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
10441061 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
10451062 const messages = [
10461063 { role: 'user', content: text + '\n\n' + prompt },
@@ -1050,7 +1067,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10501067 return parseLlmResponse(emotionResponse, expressionsList);
10511068 }
10521069 // Extras
10531070 defaultcase EXPRESSION_API.extras: {
10541071 const url = new URL(getApiUrl());
10551072 url.pathname = '/api/classify';
10561073
@@ -1068,6 +1085,15 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10681085 return data.classification[0].label;
10691086 }
10701087 } break;
1088+ // None
1089+ case EXPRESSION_API.none: {
1090+ // Return empty, the fallback expression will be used
1091+ return '';
1092+ }
1093+ default: {
1094+ toastr.error('Invalid API selected');
1095+ return '';
1096+ }
10711097 }
10721098 } catch (error) {
10731099 toastr.error('Could not classify expression. Check the console or your backend for more information.');
@@ -1320,12 +1346,28 @@ function getCachedExpressions() {
13201346 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
13211347}
13221348
13231349export async function getExpressionsList({ filterAvailable = false } = {}) {
13241350 // ReturnIf there is no cached list, ifload availableand cache it
13251351 if (!Array.isArray(expressionsList)) {
13261352 returnexpressionsList getCachedExpressions= await resolveExpressionsList();
13271353 }
13281354
1355+ const expressions = getCachedExpressions();
1356+
1357+ // Filtering is only available for llm and webllm APIs
1358+ if (!filterAvailable || ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api)) {
1359+ return expressions;
1360+ }
1361+
1362+ // Get expressions with available sprites
1363+ const currentLastMessage = selected_group ? getLastCharacterMessage() : null;
1364+ const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage?.name);
1365+
1366+ return expressions.filter(label => {
1367+ const expression = spriteCache[spriteFolderName]?.find(x => x.label === label);
1368+ return (expression?.files.length ?? 0) > 0;
1369+ });
1370+
13291371 /**
13301372 * Returns the list of expressions from the API or fallback in offline mode.
13311373 * @returns {Promise<string[]>}
@@ -1372,9 +1414,6 @@ export async function getExpressionsList() {
13721414 expressionsList = DEFAULT_EXPRESSIONS.slice();
13731415 return expressionsList;
13741416 }
1375-
1376- const result = await resolveExpressionsList();
1377- return [...result, ...extension_settings.expressions.custom].filter(onlyUnique);
13781417}
13791418
13801419/**
@@ -2036,7 +2075,7 @@ async function fetchImagesNoCache() {
20362075
20372076function migrateSettings() {
20382077 if (extension_settings.expressions.api === undefined) {
20392078 extension_settings.expressions.api = EXPRESSION_API.extrasnone;
20402079 saveSettingsDebounced();
20412080 }
20422081
@@ -2102,6 +2141,10 @@ function migrateSettings() {
21022141 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');
21032142 saveSettingsDebounced();
21042143 });
2144+ $('#expressions_filter_available').prop('checked', extension_settings.expressions.filterAvailable).on('input', function () {
2145+ extension_settings.expressions.filterAvailable = !!$(this).prop('checked');
2146+ saveSettingsDebounced();
2147+ });
21052148 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
21062149 $(document).on('dragstart', '.expression', (e) => {
21072150 e.preventDefault();
@@ -2114,7 +2157,7 @@ function migrateSettings() {
21142157 $('#open_chat_expressions').hide();
21152158
21162159 await renderAdditionalExpressionSettings();
21172160 $('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.extrasnone);
21182161 $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));
21192162 $('#expression_llm_prompt').val(extension_settings.expressions.llmPrompt ?? '');
21202163 $('#expression_llm_prompt').on('input', function () {
@@ -2154,7 +2197,7 @@ function migrateSettings() {
21542197 imgElement.src = '';
21552198 }
21562199
2157- setExpressionOverrideHtml();
2200+ setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
21582201
21592202 if (isVisualNovelMode()) {
21602203 $('#visual-novel-wrapper').empty();
@@ -2279,13 +2322,13 @@ function migrateSettings() {
22792322 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
22802323 name: 'expression-list',
22812324 aliases: ['expressions'],
22822325 /** @type {(args: {return: string, filter: string}) => Promise<string>} */
22832326 callback: async (args) => {
22842327 let returnType =
22852328 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
22862329 (args.return);
22872330
22882331 const list = await getExpressionsList({ filterAvailable: !isFalseBoolean(args.filter) });
22892332
22902333 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
22912334 },
@@ -2298,6 +2341,13 @@ function migrateSettings() {
22982341 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
22992342 forceEnum: true,
23002343 }),
2344+ SlashCommandNamedArgument.fromProps({
2345+ name: 'filter',
2346+ description: 'Filter the list to only include expressions that have available sprites for the current character.',
2347+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2348+ enumList: commonEnumProviders.boolean('trueFalse')(),
2349+ defaultValue: 'true',
2350+ }),
23012351 ],
23022352 returns: 'The comma-separated list of available expressions, including custom expressions.',
23032353 helpString: 'Returns a list of available expressions, including custom expressions.',
@@ -2314,6 +2364,13 @@ function migrateSettings() {
23142364 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),
23152365 }),
23162366 SlashCommandNamedArgument.fromProps({
2367+ name: 'filter',
2368+ description: 'Filter the list to only include expressions that have available sprites for the current character.',
2369+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2370+ enumList: commonEnumProviders.boolean('trueFalse')(),
2371+ defaultValue: 'true',
2372+ }),
2373+ SlashCommandNamedArgument.fromProps({
23172374 name: 'prompt',
23182375 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',
23192376 typeList: [ARGUMENT_TYPE.STRING],
public/scripts/extensions/expressions/settings.html+6 -1
@@ -22,6 +22,7 @@
2222 <label for="expression_api" data-i18n="Classifier API">Classifier API</label>
2323 <small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small>
2424 <select id="expression_api" class="flex1 margin0">
25+ <option value="99" data-i18n="[ None ]">[ None ]</option>
2526 <option value="0" data-i18n="Local">Local</option>
2627 <option value="1" data-i18n="Extras">Extras (deprecated)</option>
2728 <option value="2" data-i18n="Main API">Main API</option>
@@ -29,7 +30,11 @@
2930 </select>
3031 </div>
3132 <div class="expression_llm_prompt_block m-b-1 m-t-1">
32- <label for="expression_llm_prompt" class="title_restorable">
33+ <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.">
34+ <input id="expressions_filter_available" type="checkbox">
35+ <span data-i18n="Filter expressions for available sprites">Filter expressions for available sprites</span>
36+ </label>
37+ <label for="expression_llm_prompt" class="title_restorable m-t-1">
3338 <span data-i18n="LLM Prompt">LLM Prompt</span>
3439 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
3540 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>
public/scripts/extensions/memory/settings.html+1 -1
@@ -132,7 +132,7 @@
132132 </label>
133133 <label class="flex-container alignItemsCenter" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
134134 <input type="radio" name="memory_position" value="1" />
135135 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="9999999" />
136136 <span data-i18n="as">as</span>
137137 <select id="memory_role" class="text_pole widthNatural">
138138 <option value="0" data-i18n="System">System</option>
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+5 -6
@@ -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
@@ -114,14 +113,14 @@
114113 <span data-i18n="Min Depth">Min Depth</span>
115114 <span class="fa-solid fa-circle-question note-link-span"></span>
116115 </small>
117116 <input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="9999999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
118117 </div>
119118 <div class="flex1 flex-container flexNoGap">
120119 <small data-i18n="[title]ext_regex_max_depth_desc" title="When applied to prompts or display, only affect messages no more than N levels deep. 0 = last message, 1 = penultimate message, etc. System prompt and utility prompts are not affected. Max must be greater than Min for regex to apply.">
121120 <span data-i18n="Max Depth">Max Depth</span>
122121 <span class="fa-solid fa-circle-question note-link-span"></span>
123122 </small>
124123 <input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="9999999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
125124 </div>
126125 </div>
127126 </div>
@@ -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+333 -2
@@ -1,6 +1,7 @@
11import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';
22import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
33import { oai_settingst } from '../openaii18n.js';
4+import { oai_settings, proxies } from '../openai.js';
45import { SECRET_KEYS, secret_state } from '../secrets.js';
56import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
67import { getTokenCountAsync } from '../tokenizers.js';
@@ -152,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) {
152153 throw new Error('Cohere API key is not set.');
153154 }
154155
156+ if (extension_settings.caption.multimodal_api === 'xai' && !secret_state[SECRET_KEYS.XAI]) {
157+ throw new Error('xAI API key is not set.');
158+ }
159+
155160 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
156161 throw new Error('Ollama server URL is not set.');
157162 }
@@ -273,3 +278,329 @@ export async function getWebLlmContextSize() {
273278 const model = await engine.getCurrentModelInfo();
274279 return model?.context_size;
275280}
281+
282+/**
283+ * It uses the profiles to send a generate request to the API.
284+ */
285+export class ConnectionManagerRequestService {
286+ static defaultSendRequestParams = {
287+ stream: false,
288+ signal: null,
289+ extractData: true,
290+ includePreset: true,
291+ includeInstruct: true,
292+ instructSettings: {},
293+ };
294+
295+ static getAllowedTypes() {
296+ return {
297+ openai: t`Chat Completion`,
298+ textgenerationwebui: t`Text Completion`,
299+ };
300+ }
301+
302+ /**
303+ * @param {string} profileId
304+ * @param {string | (import('../custom-request.js').ChatCompletionMessage & {ignoreInstruct?: boolean})[]} prompt
305+ * @param {number} maxTokens
306+ * @param {Object} custom
307+ * @param {boolean?} [custom.stream=false]
308+ * @param {AbortSignal?} [custom.signal]
309+ * @param {boolean?} [custom.extractData=true]
310+ * @param {boolean?} [custom.includePreset=true]
311+ * @param {boolean?} [custom.includeInstruct=true]
312+ * @param {Partial<InstructSettings>?} [custom.instructSettings] Override instruct settings
313+ * @param {Record<string, any>} [overridePayload] - Override payload for the request
314+ * @returns {Promise<import('../custom-request.js').ExtractedData | (() => AsyncGenerator<import('../custom-request.js').StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
315+ */
316+ static async sendRequest(profileId, prompt, maxTokens, custom = this.defaultSendRequestParams, overridePayload = {}) {
317+ const { stream, signal, extractData, includePreset, includeInstruct, instructSettings } = { ...this.defaultSendRequestParams, ...custom };
318+
319+ const context = SillyTavern.getContext();
320+ if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
321+ throw new Error('Connection Manager is not available');
322+ }
323+
324+ const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
325+ const selectedApiMap = this.validateProfile(profile);
326+
327+ try {
328+ switch (selectedApiMap.selected) {
329+ case 'openai': {
330+ if (!selectedApiMap.source) {
331+ throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`);
332+ }
333+
334+ const proxyPreset = proxies.find((p) => p.name === profile.proxy);
335+
336+ const messages = Array.isArray(prompt) ? prompt : [{ role: 'user', content: prompt }];
337+ return await context.ChatCompletionService.processRequest({
338+ stream,
339+ messages,
340+ max_tokens: maxTokens,
341+ model: profile.model,
342+ chat_completion_source: selectedApiMap.source,
343+ custom_url: profile['api-url'],
344+ reverse_proxy: proxyPreset?.url,
345+ proxy_password: proxyPreset?.password,
346+ ...overridePayload,
347+ }, {
348+ presetName: includePreset ? profile.preset : undefined,
349+ }, extractData, signal);
350+ }
351+ case 'textgenerationwebui': {
352+ if (!selectedApiMap.type) {
353+ throw new Error(`API type ${selectedApiMap.selected} does not support text completions`);
354+ }
355+
356+ return await context.TextCompletionService.processRequest({
357+ stream,
358+ prompt,
359+ max_tokens: maxTokens,
360+ model: profile.model,
361+ api_type: selectedApiMap.type,
362+ api_server: profile['api-url'],
363+ ...overridePayload,
364+ }, {
365+ instructName: includeInstruct ? profile.instruct : undefined,
366+ presetName: includePreset ? profile.preset : undefined,
367+ instructSettings: includeInstruct ? instructSettings : undefined,
368+ }, extractData, signal);
369+ }
370+ default: {
371+ throw new Error(`Unknown API type ${selectedApiMap.selected}`);
372+ }
373+ }
374+ } catch (error) {
375+ throw new Error('API request failed', { cause: error });
376+ }
377+ }
378+
379+ /**
380+ * Respects allowed types.
381+ * @returns {import('./connection-manager/index.js').ConnectionProfile[]}
382+ */
383+ static getSupportedProfiles() {
384+ const context = SillyTavern.getContext();
385+ if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
386+ throw new Error('Connection Manager is not available');
387+ }
388+
389+ const profiles = context.extensionSettings.connectionManager.profiles;
390+ return profiles.filter((p) => this.isProfileSupported(p));
391+ }
392+
393+ /**
394+ * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
395+ * @returns {boolean}
396+ */
397+ static isProfileSupported(profile) {
398+ if (!profile || !profile.api) {
399+ return false;
400+ }
401+
402+ const apiMap = CONNECT_API_MAP[profile.api];
403+ if (!Object.hasOwn(this.getAllowedTypes(), apiMap.selected)) {
404+ return false;
405+ }
406+
407+ // Some providers not need model, like koboldcpp. But I don't want to check by provider.
408+ switch (apiMap.selected) {
409+ case 'openai':
410+ return !!apiMap.source;
411+ case 'textgenerationwebui':
412+ return !!apiMap.type;
413+ }
414+
415+ return false;
416+ }
417+
418+ /**
419+ * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
420+ * @return {import('../../script.js').ConnectAPIMap}
421+ * @throws {Error}
422+ */
423+ static validateProfile(profile) {
424+ if (!profile) {
425+ throw new Error('Could not find profile.');
426+ }
427+ if (!profile.api) {
428+ throw new Error('Select a connection profile that has an API');
429+ }
430+
431+ const context = SillyTavern.getContext();
432+ const selectedApiMap = context.CONNECT_API_MAP[profile.api];
433+ if (!selectedApiMap) {
434+ throw new Error(`Unknown API type ${profile.api}`);
435+ }
436+ if (!Object.hasOwn(this.getAllowedTypes(), selectedApiMap.selected)) {
437+ throw new Error(`API type ${selectedApiMap.selected} is not supported. Supported types: ${Object.values(this.getAllowedTypes()).join(', ')}`);
438+ }
439+
440+ return selectedApiMap;
441+ }
442+
443+ /**
444+ * Create profiles dropdown and updates select element accordingly. Use onChange, onCreate, unUpdate, onDelete callbacks for custom behaviour. e.g updating extension settings.
445+ * @param {string} selector
446+ * @param {string} initialSelectedProfileId
447+ * @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.
448+ * @param {(profile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onCreate
449+ * @param {(oldProfile: import('./connection-manager/index.js').ConnectionProfile, newProfile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} unUpdate
450+ * @param {(profile: import('./connection-manager/index.js').ConnectionProfile) => Promise<void> | void} onDelete
451+ */
452+ static handleDropdown(
453+ selector,
454+ initialSelectedProfileId,
455+ onChange = () => { },
456+ onCreate = () => { },
457+ unUpdate = () => { },
458+ onDelete = () => { },
459+ ) {
460+ const context = SillyTavern.getContext();
461+ if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
462+ throw new Error('Connection Manager is not available');
463+ }
464+
465+ /**
466+ * @type {JQuery<HTMLSelectElement>}
467+ */
468+ const dropdown = $(selector);
469+
470+ if (!dropdown || !dropdown.length) {
471+ throw new Error(`Could not find dropdown with selector ${selector}`);
472+ }
473+
474+ dropdown.empty();
475+
476+ // Create default option using document.createElement
477+ const defaultOption = document.createElement('option');
478+ defaultOption.value = '';
479+ defaultOption.textContent = 'Select a Connection Profile';
480+ defaultOption.dataset.i18n = 'Select a Connection Profile';
481+ dropdown.append(defaultOption);
482+
483+ const profiles = context.extensionSettings.connectionManager.profiles;
484+
485+ // Create optgroups using document.createElement
486+ const groups = {};
487+ for (const [apiType, groupLabel] of Object.entries(this.getAllowedTypes())) {
488+ const optgroup = document.createElement('optgroup');
489+ optgroup.label = groupLabel;
490+ groups[apiType] = optgroup;
491+ }
492+
493+ const sortedProfilesByGroup = {};
494+ for (const apiType of Object.keys(this.getAllowedTypes())) {
495+ sortedProfilesByGroup[apiType] = [];
496+ }
497+
498+ for (const profile of profiles) {
499+ if (this.isProfileSupported(profile)) {
500+ const apiMap = CONNECT_API_MAP[profile.api];
501+ if (sortedProfilesByGroup[apiMap.selected]) {
502+ sortedProfilesByGroup[apiMap.selected].push(profile);
503+ }
504+ }
505+ }
506+
507+ // Sort each group alphabetically and add to dropdown
508+ for (const [apiType, groupProfiles] of Object.entries(sortedProfilesByGroup)) {
509+ if (groupProfiles.length === 0) continue;
510+
511+ groupProfiles.sort((a, b) => a.name.localeCompare(b.name));
512+
513+ const group = groups[apiType];
514+ for (const profile of groupProfiles) {
515+ const option = document.createElement('option');
516+ option.value = profile.id;
517+ option.textContent = profile.name;
518+ group.appendChild(option);
519+ }
520+ }
521+
522+ for (const group of Object.values(groups)) {
523+ if (group.children.length > 0) {
524+ dropdown.append(group);
525+ }
526+ }
527+
528+ const selectedProfile = profiles.find((p) => p.id === initialSelectedProfileId);
529+ if (selectedProfile) {
530+ dropdown.val(selectedProfile.id);
531+ }
532+
533+ context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_CREATED, async (profile) => {
534+ const isSupported = this.isProfileSupported(profile);
535+ if (!isSupported) {
536+ return;
537+ }
538+
539+ const group = groups[CONNECT_API_MAP[profile.api].selected];
540+ const option = document.createElement('option');
541+ option.value = profile.id;
542+ option.textContent = profile.name;
543+ group.appendChild(option);
544+
545+ await onCreate(profile);
546+ });
547+
548+ context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_UPDATED, async (oldProfile, newProfile) => {
549+ const currentSelected = dropdown.val();
550+ const isSelectedProfile = currentSelected === oldProfile.id;
551+ await unUpdate(oldProfile, newProfile);
552+
553+ if (!this.isProfileSupported(newProfile)) {
554+ if (isSelectedProfile) {
555+ dropdown.val('');
556+ dropdown.trigger('change');
557+ }
558+ return;
559+ }
560+
561+ const group = groups[CONNECT_API_MAP[newProfile.api].selected];
562+ const oldOption = group.querySelector(`option[value="${oldProfile.id}"]`);
563+ if (oldOption) {
564+ oldOption.remove();
565+ }
566+
567+ const option = document.createElement('option');
568+ option.value = newProfile.id;
569+ option.textContent = newProfile.name;
570+ group.appendChild(option);
571+
572+ if (isSelectedProfile) {
573+ // Ackchyually, we don't need to reselect but what if id changes? It is not possible for now I couldn't stop myself.
574+ dropdown.val(newProfile.id);
575+ dropdown.trigger('change');
576+ }
577+ });
578+
579+ context.eventSource.on(context.eventTypes.CONNECTION_PROFILE_DELETED, async (profile) => {
580+ const currentSelected = dropdown.val();
581+ const isSelectedProfile = currentSelected === profile.id;
582+ if (!this.isProfileSupported(profile)) {
583+ return;
584+ }
585+
586+ const group = groups[CONNECT_API_MAP[profile.api].selected];
587+ const optionToRemove = group.querySelector(`option[value="${profile.id}"]`);
588+ if (optionToRemove) {
589+ optionToRemove.remove();
590+ }
591+
592+ if (isSelectedProfile) {
593+ dropdown.val('');
594+ dropdown.trigger('change');
595+ }
596+
597+ await onDelete(profile);
598+ });
599+
600+ dropdown.on('change', async () => {
601+ const profileId = dropdown.val();
602+ const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
603+ await onChange(profile);
604+ });
605+ }
606+}
public/scripts/extensions/stable-diffusion/index.js+55 -74
@@ -77,11 +77,11 @@ const sources = {
7777 drawthings: 'drawthings',
7878 pollinations: 'pollinations',
7979 stability: 'stability',
80- blockentropy: 'blockentropy',
8180 huggingface: 'huggingface',
8281 nanogpt: 'nanogpt',
8382 bfl: 'bfl',
8483 falai: 'falai',
84+ xai: 'xai',
8585};
8686
8787const initiators = {
@@ -1300,11 +1300,11 @@ async function onModelChange() {
13001300 sources.togetherai,
13011301 sources.pollinations,
13021302 sources.stability,
1303- sources.blockentropy,
13041303 sources.huggingface,
13051304 sources.nanogpt,
13061305 sources.bfl,
13071306 sources.falai,
1307+ sources.xai,
13081308 ];
13091309
13101310 if (cloudSources.includes(extension_settings.sd.source)) {
@@ -1511,9 +1511,6 @@ async function loadSamplers() {
15111511 case sources.stability:
15121512 samplers = ['N/A'];
15131513 break;
1514- case sources.blockentropy:
1515- samplers = ['N/A'];
1516- break;
15171514 case sources.huggingface:
15181515 samplers = ['N/A'];
15191516 break;
@@ -1523,6 +1520,9 @@ async function loadSamplers() {
15231520 case sources.bfl:
15241521 samplers = ['N/A'];
15251522 break;
1523+ case sources.xai:
1524+ samplers = ['N/A'];
1525+ break;
15261526 }
15271527
15281528 for (const sampler of samplers) {
@@ -1701,9 +1701,6 @@ async function loadModels() {
17011701 case sources.stability:
17021702 models = await loadStabilityModels();
17031703 break;
1704- case sources.blockentropy:
1705- models = await loadBlockEntropyModels();
1706- break;
17071704 case sources.huggingface:
17081705 models = [{ value: '', text: '<Enter Model ID above>' }];
17091706 break;
@@ -1716,6 +1713,9 @@ async function loadModels() {
17161713 case sources.falai:
17171714 models = await loadFalaiModels();
17181715 break;
1716+ case sources.xai:
1717+ models = await loadXAIModels();
1718+ break;
17191719 }
17201720
17211721 for (const model of models) {
@@ -1768,6 +1768,12 @@ async function loadFalaiModels() {
17681768 return [];
17691769}
17701770
1771+async function loadXAIModels() {
1772+ return [
1773+ { value: 'grok-2-image-1212', text: 'grok-2-image-1212' },
1774+ ];
1775+}
1776+
17711777async function loadPollinationsModels() {
17721778 const result = await fetch('/api/sd/pollinations/models', {
17731779 method: 'POST',
@@ -1799,26 +1805,6 @@ async function loadTogetherAIModels() {
17991805 return [];
18001806}
18011807
1802-async function loadBlockEntropyModels() {
1803- if (!secret_state[SECRET_KEYS.BLOCKENTROPY]) {
1804- console.debug('Block Entropy API key is not set.');
1805- return [];
1806- }
1807-
1808- const result = await fetch('/api/sd/blockentropy/models', {
1809- method: 'POST',
1810- headers: getRequestHeaders(),
1811- });
1812- console.log(result);
1813- if (result.ok) {
1814- const data = await result.json();
1815- console.log(data);
1816- return data;
1817- }
1818-
1819- return [];
1820-}
1821-
18221808async function loadNanoGPTModels() {
18231809 if (!secret_state[SECRET_KEYS.NANOGPT]) {
18241810 console.debug('NanoGPT API key is not set.');
@@ -2097,9 +2083,6 @@ async function loadSchedulers() {
20972083 case sources.stability:
20982084 schedulers = ['N/A'];
20992085 break;
2100- case sources.blockentropy:
2101- schedulers = ['N/A'];
2102- break;
21032086 case sources.huggingface:
21042087 schedulers = ['N/A'];
21052088 break;
@@ -2112,6 +2095,9 @@ async function loadSchedulers() {
21122095 case sources.falai:
21132096 schedulers = ['N/A'];
21142097 break;
2098+ case sources.xai:
2099+ schedulers = ['N/A'];
2100+ break;
21152101 }
21162102
21172103 for (const scheduler of schedulers) {
@@ -2188,9 +2174,6 @@ async function loadVaes() {
21882174 case sources.stability:
21892175 vaes = ['N/A'];
21902176 break;
2191- case sources.blockentropy:
2192- vaes = ['N/A'];
2193- break;
21942177 case sources.huggingface:
21952178 vaes = ['N/A'];
21962179 break;
@@ -2200,6 +2183,12 @@ async function loadVaes() {
22002183 case sources.bfl:
22012184 vaes = ['N/A'];
22022185 break;
2186+ case sources.falai:
2187+ vaes = ['N/A'];
2188+ break;
2189+ case sources.xai:
2190+ vaes = ['N/A'];
2191+ break;
22032192 }
22042193
22052194 for (const vae of vaes) {
@@ -2757,9 +2746,6 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
27572746 case sources.stability:
27582747 result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
27592748 break;
2760- case sources.blockentropy:
2761- result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);
2762- break;
27632749 case sources.huggingface:
27642750 result = await generateHuggingFaceImage(prefixedPrompt, signal);
27652751 break;
@@ -2772,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
27722758 case sources.falai:
27732759 result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);
27742760 break;
2761+ case sources.xai:
2762+ result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);
2763+ break;
27752764 }
27762765
27772766 if (!result.data) {
@@ -2828,40 +2817,6 @@ async function generateTogetherAIImage(prompt, negativePrompt, signal) {
28282817 }
28292818}
28302819
2831-async function generateBlockEntropyImage(prompt, negativePrompt, signal) {
2832- const result = await fetch('/api/sd/blockentropy/generate', {
2833- method: 'POST',
2834- headers: getRequestHeaders(),
2835- signal: signal,
2836- body: JSON.stringify({
2837- prompt: prompt,
2838- negative_prompt: negativePrompt,
2839- model: extension_settings.sd.model,
2840- steps: extension_settings.sd.steps,
2841- width: extension_settings.sd.width,
2842- height: extension_settings.sd.height,
2843- seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
2844- }),
2845- });
2846-
2847- if (result.ok) {
2848- const data = await result.json();
2849-
2850- // Default format is 'jpg'
2851- let format = 'jpg';
2852-
2853- // Check if a format is specified in the result
2854- if (data.format) {
2855- format = data.format.toLowerCase();
2856- }
2857-
2858- return { format: format, data: data.images[0] };
2859- } else {
2860- const text = await result.text();
2861- throw new Error(text);
2862- }
2863-}
2864-
28652820/**
28662821 * Generates an image using the Pollinations API.
28672822 * @param {string} prompt - The main instruction used to guide the image generation.
@@ -3535,6 +3490,33 @@ async function generateBflImage(prompt, signal) {
35353490}
35363491
35373492/**
3493+ * Generates an image using the xAI API.
3494+ * @param {string} prompt The main instruction used to guide the image generation.
3495+ * @param {string} _negativePrompt Negative prompt is not used in this API
3496+ * @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request.
3497+ * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.
3498+ */
3499+async function generateXAIImage(prompt, _negativePrompt, signal) {
3500+ const result = await fetch('/api/sd/xai/generate', {
3501+ method: 'POST',
3502+ headers: getRequestHeaders(),
3503+ signal: signal,
3504+ body: JSON.stringify({
3505+ prompt: prompt,
3506+ model: extension_settings.sd.model,
3507+ }),
3508+ });
3509+
3510+ if (result.ok) {
3511+ const data = await result.json();
3512+ return { format: 'jpg', data: data.image };
3513+ } else {
3514+ const text = await result.text();
3515+ throw new Error(text);
3516+ }
3517+}
3518+
3519+/**
35383520 * Generates an image using the FAL.AI API.
35393521 * @param {string} prompt - The main instruction used to guide the image generation.
35403522 * @param {string} negativePrompt - The negative prompt used to guide the image generation.
@@ -3772,7 +3754,6 @@ async function addSDGenButtons() {
37723754 $('#sd_wand_container').append(buttonHtml);
37733755 $(document.body).append(dropdownHtml);
37743756
3775- const messageButton = $('.sd_message_gen');
37763757 const button = $('#sd_gen');
37773758 const dropdown = $('#sd_dropdown');
37783759 dropdown.hide();
@@ -3846,8 +3827,6 @@ function isValidState() {
38463827 return true;
38473828 case sources.stability:
38483829 return secret_state[SECRET_KEYS.STABILITY];
3849- case sources.blockentropy:
3850- return secret_state[SECRET_KEYS.BLOCKENTROPY];
38513830 case sources.huggingface:
38523831 return secret_state[SECRET_KEYS.HUGGINGFACE];
38533832 case sources.nanogpt:
@@ -3856,6 +3835,8 @@ function isValidState() {
38563835 return secret_state[SECRET_KEYS.BFL];
38573836 case sources.falai:
38583837 return secret_state[SECRET_KEYS.FALAI];
3838+ case sources.xai:
3839+ return secret_state[SECRET_KEYS.XAI];
38593840 }
38603841}
38613842
public/scripts/extensions/stable-diffusion/settings.html+2 -2
@@ -38,7 +38,6 @@
3838 <label for="sd_source" data-i18n="Source">Source</label>
3939 <select id="sd_source">
4040 <option value="bfl">BFL (Black Forest Labs)</option>
41- <option value="blockentropy">Block Entropy</option>
4241 <option value="comfy">ComfyUI</option>
4342 <option value="drawthings">DrawThings HTTP API</option>
4443 <option value="extras">Extras API (deprecated)</option>
@@ -53,6 +52,7 @@
5352 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
5453 <option value="horde">Stable Horde</option>
5554 <option value="togetherai">TogetherAI</option>
55+ <option value="xai">xAI (Grok)</option>
5656 </select>
5757 <div data-sd-source="auto">
5858 <label for="sd_auto_url">SD Web UI URL</label>
@@ -422,7 +422,7 @@
422422 </label>
423423 </div>
424424
425425 <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,blockentropy,bfl" class="marginTop5">
426426 <label for="sd_seed">
427427 <span data-i18n="Seed">Seed</span>
428428 <small data-i18n="(-1 for random)">(-1 for random)</small>
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+2 -1
@@ -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';
@@ -471,6 +471,7 @@ async function processTtsQueue() {
471471 if (extension_settings.tts.skip_codeblocks) {
472472 text = text.replace(/^\s{4}.*$/gm, '').trim();
473473 text = text.replace(/```.*?```/gs, '').trim();
474+ text = text.replace(/~~~.*?~~~/gs, '').trim();
474475 }
475476
476477 if (extension_settings.tts.skip_tags) {
public/scripts/extensions/tts/settings.html+1 -1
@@ -76,7 +76,7 @@
7676 <div id="tts_voicemap_block">
7777 </div>
7878 <hr>
7979 <form id="tts_provider_settings" class="inline-drawer-content">
8080 </form>
8181 <div class="tts_buttons">
8282 <input id="tts_voices" class="menu_button" type="submit" value="Available voices" />
public/scripts/extensions/tts/system.js+63 -14
@@ -79,6 +79,10 @@ class SystemTtsProvider {
7979 // Config //
8080 //########//
8181
82+ // Static constants for the simulated default voice
83+ static BROWSER_DEFAULT_VOICE_ID = '__browser_default__';
84+ static BROWSER_DEFAULT_VOICE_NAME = 'System Default Voice';
85+
8286 settings;
8387 ready = false;
8488 voices = [];
@@ -168,51 +172,97 @@ class SystemTtsProvider {
168172 //#################//
169173 fetchTtsVoiceObjects() {
170174 if (!('speechSynthesis' in window)) {
171175 return Promise.resolve([]);
172176 }
173177
174178 return new Promise((resolve) => {
175179 setTimeout(() => {
176180 constlet voices = speechSynthesis.getVoices();
177- .getVoices()
181+
182+ if (voices.length === 0) {
183+ // Edge compat: Provide default when voices empty
184+ console.warn('SystemTTS: getVoices() returned empty list. Providing browser default option.');
185+ const defaultVoice = {
186+ name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
187+ voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
188+ preview_url: false,
189+ lang: navigator.language || 'en-US',
190+ };
191+ resolve([defaultVoice]);
192+ } else {
193+ const mappedVoices = voices
178194 .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
179195 .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
180-
196+ resolve(mappedVoices);
181- resolve(voices);
197+ }
182198 }, 150);
183199 });
184200 }
185201
186202 previewTtsVoice(voiceId) {
187203 if (!('speechSynthesis' in window)) {
188204 throw new Error('Speech synthesis API is not supported');
189205 }
190206
191- const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);
207+ let voice = null;
208+ if (voiceId !== SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID) {
209+ const voices = speechSynthesis.getVoices();
210+ voice = voices.find(x => x.voiceURI === voiceId);
192211
193212 if (!voice && voices.length > 0) {
194- throw `TTS Voice id ${voiceId} not found`;
213+ console.warn(`SystemTTS Preview: Voice ID "${voiceId}" not found among available voices. Using browser default.`);
214+ } else if (!voice && voices.length === 0) {
215+ console.warn('SystemTTS Preview: Voice list is empty. Using browser default.');
216+ }
217+ } else {
218+ console.log('SystemTTS Preview: Using browser default voice as requested.');
195219 }
196220
197221 speechSynthesis.cancel();
198- const text = getPreviewString(voice.lang);
222+ const langForPreview = voice ? voice.lang : (navigator.language || 'en-US');
223+ const text = getPreviewString(langForPreview);
199224 const utterance = new SpeechSynthesisUtterance(text);
225+
226+ if (voice) {
200227 utterance.voice = voice;
228+ }
229+
201230 utterance.rate = this.settings.rate || 1;
202231 utterance.pitch = this.settings.pitch || 1;
232+
233+ utterance.onerror = (event) => {
234+ console.error(`SystemTTS Preview Error: ${event.error}`, event);
235+ };
236+
203237 speechSynthesis.speak(utterance);
204238 }
205239
206240 async getVoice(voiceName) {
207241 if (!('speechSynthesis' in window)) {
208242 return { voice_id: null, name: 'API Not Supported' };
243+ }
244+
245+ if (voiceName === SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME) {
246+ return {
247+ voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
248+ name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
249+ };
209250 }
210251
211252 const voices = speechSynthesis.getVoices();
253+
254+ if (voices.length === 0) {
255+ console.warn('SystemTTS: Empty voice list, using default fallback');
256+ return {
257+ voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
258+ name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
259+ };
260+ }
261+
212262 const match = voices.find(x => x.name == voiceName);
213263
214264 if (!match) {
215265 throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`);
216266 }
217267
218268 return { voice_id: match.voiceURI, name: match.name };
@@ -237,7 +287,6 @@ class SystemTtsProvider {
237287 speechUtteranceChunker(utterance, {
238288 chunkLength: 200,
239289 }, function () {
240- //some code to execute when done
241290 resolve(silence);
242291 console.log('System TTS done');
243292 });
public/scripts/extensions/vectors/index.js+86 -4
@@ -55,6 +55,8 @@ const getBatchSize = () => ['transformers', 'palm', 'ollama'].includes(settings.
5555const settings = {
5656 // For both
5757 source: 'transformers',
58+ alt_endpoint_url: '',
59+ use_alt_endpoint: false,
5860 include_wi: false,
5961 togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval',
6062 openai_model: 'text-embedding-ada-002',
@@ -63,6 +65,7 @@ const settings = {
6365 ollama_keep: false,
6466 vllm_model: '',
6567 webllm_model: '',
68+ google_model: 'text-embedding-004',
6669 summarize: false,
6770 summarize_sent: false,
6871 summary_source: 'main',
@@ -108,6 +111,7 @@ const settings = {
108111const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
109112const webllmProvider = new WebLlmVectorProvider();
110113const cachedSummaries = new Map();
114+const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];
111115
112116/**
113117 * Gets the Collection ID for a file embedded in the chat.
@@ -565,6 +569,8 @@ async function retrieveFileChunks(queryText, collectionId) {
565569 * @returns {Promise<boolean>} True if successful, false if not
566570 */
567571async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overlapPercent) {
572+ let toast = jQuery();
573+
568574 try {
569575 if (settings.translate_files && typeof globalThis.translate === 'function') {
570576 console.log(`Vectors: Translating file ${fileName} to English...`);
@@ -574,7 +580,7 @@ async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overla
574580
575581 const batchSize = getBatchSize();
576582 const toastBody = $('<span>').text('This may take a while. Please wait...');
577583 const toast = toastr.info(toastBody, `Ingesting file ${escapeHtml(fileName)}`, { closeButton: false, escapeHtml: false, timeOut: 0, extendedTimeOut: 0 });
578584 const overlapSize = Math.round(chunkSize * overlapPercent / 100);
579585 const delimiters = getChunkDelimiters();
580586 // Overlap should not be included in chunk size. It will be later compensated by overlapChunks
@@ -596,6 +602,7 @@ async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overla
596602 console.log(`Vectors: Inserted ${chunks.length} vector items for file ${fileName} into ${collectionId}`);
597603 return true;
598604 } catch (error) {
605+ toastr.clear(toast);
599606 toastr.error(String(error), 'Failed to vectorize file', { preventDuplicates: true });
600607 console.error('Vectors: Failed to vectorize file', error);
601608 return false;
@@ -773,19 +780,22 @@ function getVectorsRequestBody(args = {}) {
773780 break;
774781 case 'ollama':
775782 body.model = extension_settings.vectors.ollama_model;
776783 body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
777784 body.keep = !!extension_settings.vectors.ollama_keep;
778785 break;
779786 case 'llamacpp':
780787 body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
781788 break;
782789 case 'vllm':
783790 body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.VLLM];
784791 body.model = extension_settings.vectors.vllm_model;
785792 break;
786793 case 'webllm':
787794 body.model = extension_settings.vectors.webllm_model;
788795 break;
796+ case 'palm':
797+ body.model = extension_settings.vectors.google_model;
798+ break;
789799 default:
790800 break;
791801 }
@@ -803,6 +813,12 @@ async function getAdditionalArgs(items) {
803813 case 'webllm':
804814 args.embeddings = await createWebLlmEmbeddings(items);
805815 break;
816+ case 'koboldcpp': {
817+ const { embeddings, model } = await createKoboldCppEmbeddings(items);
818+ args.embeddings = embeddings;
819+ args.model = model;
820+ break;
821+ }
806822 }
807823 return args;
808824}
@@ -870,11 +886,19 @@ function throwIfSourceInvalid() {
870886 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
871887 }
872888
889+ if (vectorApiRequiresUrl.includes(settings.source) && settings.use_alt_endpoint) {
890+ if (!settings.alt_endpoint_url) {
891+ throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
892+ }
893+ }
894+ else {
873895 if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||
874896 settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||
897+ settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] ||
875898 settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {
876899 throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
877900 }
901+ }
878902
879903 if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) {
880904 throw new Error('Vectors: API model missing', { cause: 'api_model_missing' });
@@ -1071,6 +1095,9 @@ function toggleSettings() {
10711095 $('#vllm_vectorsModel').toggle(settings.source === 'vllm');
10721096 $('#nomicai_apiKey').toggle(settings.source === 'nomicai');
10731097 $('#webllm_vectorsModel').toggle(settings.source === 'webllm');
1098+ $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');
1099+ $('#google_vectorsModel').toggle(settings.source === 'palm');
1100+ $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));
10741101 if (settings.source === 'webllm') {
10751102 loadWebLlmModels();
10761103 }
@@ -1138,6 +1165,45 @@ async function createWebLlmEmbeddings(items) {
11381165 });
11391166}
11401167
1168+/**
1169+ * Creates KoboldCpp embeddings for a list of items.
1170+ * @param {string[]} items Items to embed
1171+ * @returns {Promise<{embeddings: Record<string, number[]>, model: string}>} Calculated embeddings
1172+ */
1173+async function createKoboldCppEmbeddings(items) {
1174+ const response = await fetch('/api/backends/kobold/embed', {
1175+ method: 'POST',
1176+ headers: getRequestHeaders(),
1177+ body: JSON.stringify({
1178+ items: items,
1179+ server: settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP],
1180+ }),
1181+ });
1182+
1183+ if (!response.ok) {
1184+ throw new Error('Failed to get KoboldCpp embeddings');
1185+ }
1186+
1187+ const data = await response.json();
1188+ if (!Array.isArray(data.embeddings) || !data.model || data.embeddings.length !== items.length) {
1189+ throw new Error('Invalid response from KoboldCpp embeddings');
1190+ }
1191+
1192+ const embeddings = /** @type {Record<string, number[]>} */ ({});
1193+ for (let i = 0; i < data.embeddings.length; i++) {
1194+ if (!Array.isArray(data.embeddings[i]) || data.embeddings[i].length === 0) {
1195+ throw new Error('KoboldCpp returned an empty embedding. Reduce the chunk size and/or size threshold and try again.');
1196+ }
1197+
1198+ embeddings[items[i]] = data.embeddings[i];
1199+ }
1200+
1201+ return {
1202+ embeddings: embeddings,
1203+ model: data.model,
1204+ };
1205+}
1206+
11411207async function onPurgeClick() {
11421208 const chatId = getCurrentChatId();
11431209 if (!chatId) {
@@ -1412,6 +1478,16 @@ jQuery(async () => {
14121478 saveSettingsDebounced();
14131479 toggleSettings();
14141480 });
1481+ $('#vector_altEndpointUrl_enabled').prop('checked', settings.use_alt_endpoint).on('input', () => {
1482+ settings.use_alt_endpoint = $('#vector_altEndpointUrl_enabled').prop('checked');
1483+ Object.assign(extension_settings.vectors, settings);
1484+ saveSettingsDebounced();
1485+ });
1486+ $('#vector_altEndpoint_address').val(settings.alt_endpoint_url).on('change', () => {
1487+ settings.alt_endpoint_url = String($('#vector_altEndpoint_address').val());
1488+ Object.assign(extension_settings.vectors, settings);
1489+ saveSettingsDebounced();
1490+ });
14151491 $('#api_key_nomicai').on('click', async () => {
14161492 const popupText = 'NomicAI API Key:';
14171493 const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {
@@ -1688,6 +1764,12 @@ jQuery(async () => {
16881764 toastr.success('WebLLM model loaded');
16891765 });
16901766
1767+ $('#vectors_google_model').val(settings.google_model).on('input', () => {
1768+ settings.google_model = String($('#vectors_google_model').val());
1769+ Object.assign(extension_settings.vectors, settings);
1770+ saveSettingsDebounced();
1771+ });
1772+
16911773 $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]);
16921774
16931775 toggleSettings();
public/scripts/extensions/vectors/settings.html+31 -3
@@ -13,6 +13,7 @@
1313 <option value="cohere">Cohere</option>
1414 <option value="extras">Extras (deprecated)</option>
1515 <option value="palm">Google AI Studio</option>
16+ <option value="koboldcpp">KoboldCpp</option>
1617 <option value="llamacpp">llama.cpp</option>
1718 <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
1819 <option value="mistral">MistralAI</option>
@@ -24,6 +25,16 @@
2425 <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
2526 </select>
2627 </div>
28+ <div class="flex-container flexFlowColumn" id="vector_altEndpointUrl">
29+ <label class="checkbox_label" for="vector_altEndpointUrl_enabled" title="Enable secondary endpoint URL usage, instead of the main one.">
30+ <input id="vector_altEndpointUrl_enabled" type="checkbox" class="checkbox">
31+ <span data-i18n="Use secondary URL">Use secondary URL</span>
32+ </label>
33+ <label for="vector_altEndpoint_address" data-i18n="Secondary Embedding endpoint URL">
34+ Secondary Embedding endpoint URL
35+ </label>
36+ <input id="vector_altEndpoint_address" class="text_pole" type="text" placeholder="e.g. http://localhost:5001" />
37+ </div>
2738 <div class="flex-container flexFlowColumn" id="webllm_vectorsModel">
2839 <label for="vectors_webllm_model" data-i18n="Vectorization Model">
2940 Vectorization Model
@@ -55,6 +66,14 @@
5566 Hint: Set the URL in the API connection settings.
5667 </i>
5768 </div>
69+ <div class="flex-container flexFlowColumn" id="koboldcpp_vectorsModel">
70+ <span>
71+ Set the KoboldCpp URL in the Text Completion API connection settings.
72+ </span>
73+ <span>
74+ Must use version 1.87 or higher and have an embedding model loaded.
75+ </span>
76+ </div>
5877 <div class="flex-container flexFlowColumn" id="llamacpp_vectorsModel">
5978 <span data-i18n="The server MUST be started with the --embedding flag to use this feature!">
6079 The server MUST be started with the <code>--embedding</code> flag to use this feature!
@@ -111,7 +130,16 @@
111130 Hint: Set the URL in the API connection settings.
112131 </i>
113132 </div>
114-
133+ <div class="flex-container flexFlowColumn" id="google_vectorsModel">
134+ <label for="vectors_google_model" data-i18n="Vectorization Model">
135+ Vectorization Model
136+ </label>
137+ <select id="vectors_google_model" class="text_pole">
138+ <option value="gemini-embedding-exp-03-07">gemini-embedding-exp-03-07</option>
139+ <option value="text-embedding-004">text-embedding-004</option>
140+ <option value="embedding-001">embedding-001</option>
141+ </select>
142+ </div>
115143 <div class="flex-container alignItemsCenter" id="nomicai_apiKey">
116144 <label for="api_key_nomicai" class="flex1">
117145 <span data-i18n="NomicAI API Key">NomicAI API Key</span>
@@ -292,7 +320,7 @@
292320 <label for="vectors_file_depth_db" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
293321 <input type="radio" name="vectors_file_position_db" value="1" />
294322 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
295323 <input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="9999999" />
296324 <span>as</span>
297325 <select id="vectors_file_depth_role_db" class="text_pole widthNatural">
298326 <option value="0" data-i18n="System">System</option>
@@ -344,7 +372,7 @@
344372 <label for="vectors_depth" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
345373 <input type="radio" name="vectors_position" value="1" />
346374 <span data-i18n="In-chat @ Depth">In-chat @ Depth </span>
347375 <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="9999999" />
348376 </label>
349377 </div>
350378 <div class="flex-container">
public/scripts/group-chats.js+15 -12
@@ -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
@@ -882,7 +882,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
882882 activatedMembers = activateListOrder(enabledMembers);
883883 }
884884 else if (activationStrategy === group_activation_strategy.POOLED) {
885885 activatedMembers = activatePooledOrder(enabledMembers, lastMessage, isUserInput);
886886 }
887887 else if (activationStrategy === group_activation_strategy.MANUAL && !isUserInput) {
888888 activatedMembers = shuffle(enabledMembers).slice(0, 1).map(x => characters.findIndex(y => y.avatar === x)).filter(x => x !== -1);
@@ -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);
@@ -1028,16 +1030,17 @@ function activateListOrder(members) {
10281030 * Activate group members based on the last message.
10291031 * @param {string[]} members List of member avatars
10301032 * @param {Object} lastMessage Last message
1033+ * @param {boolean} isUserInput Whether the user has input text
10311034 * @returns {number[]} List of character ids
10321035 */
10331036function activatePooledOrder(members, lastMessage, isUserInput) {
10341037 /** @type {string} */
10351038 let activatedMember = null;
10361039 /** @type {string[]} */
10371040 const spokenSinceUser = [];
10381041
10391042 for (const message of chat.slice().reverse()) {
10401043 if (message.is_user || isUserInput) {
10411044 break;
10421045 }
10431046
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+62 -49
@@ -243,9 +243,14 @@ export function autoSelectInstructPreset(modelId) {
243243
244244/**
245245 * Converts instruct mode sequences to an array of stopping strings.
246+ * @param {Object} options
247+ * @param {InstructSettings?} [options.customInstruct=null] - Custom instruct settings.
248+ * @param {boolean?} [options.useStopStrings] - Decides whether to use "Chat Start" and "Example Separator"
246249 * @returns {string[]} Array of instruct mode stopping strings.
247250 */
248-export function getInstructStoppingSequences() {
251+export function getInstructStoppingSequences({ customInstruct = null, useStopStrings = null } = {}) {
252+ const instruct = structuredClone(customInstruct ?? power_user.instruct);
253+
249254 /**
250255 * Adds instruct mode sequence to the result array.
251256 * @param {string} sequence Sequence string.
@@ -254,7 +259,7 @@ export function getInstructStoppingSequences() {
254259 function addInstructSequence(sequence) {
255260 // Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
256261 // But it's a problem for Metharme which doesn't use newlines to separate them.
257262 const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
258263 // Sequence must be a non-empty string
259264 if (typeof sequence === 'string' && sequence.length > 0) {
260265 // If sequence is just a whitespace or newline - we don't want to make it a stopping string
@@ -262,7 +267,7 @@ export function getInstructStoppingSequences() {
262267 if (sequence.trim().length > 0) {
263268 const wrappedSequence = wrap(sequence);
264269 // Need to respect "insert macro" setting
265270 const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
266271 result.push(stopString);
267272 }
268273 }
@@ -270,14 +275,15 @@ export function getInstructStoppingSequences() {
270275
271276 const result = [];
272277
273- if (power_user.instruct.enabled) {
278+ // Since preset's don't have "enabled", we assume it's always enabled
274- const stop_sequence = power_user.instruct.stop_sequence || '';
279+ if (customInstruct ?? instruct.enabled) {
275280 const input_sequencestop_sequence = power_user.instruct.input_sequence?.replace(/{{name}}/gi, name1)stop_sequence || '';
276281 const output_sequenceinput_sequence = power_user.instruct.output_sequenceinput_sequence?.replace(/{{name}}/gi, name2name1) || '';
277282 const first_output_sequenceoutput_sequence = power_user.instruct.first_output_sequenceoutput_sequence?.replace(/{{name}}/gi, name2) || '';
278283 const last_output_sequencefirst_output_sequence = power_user.instruct.last_output_sequencefirst_output_sequence?.replace(/{{name}}/gi, name2) || '';
279284 const system_sequencelast_output_sequence = power_user.instruct.system_sequencelast_output_sequence?.replace(/{{name}}/gi, 'System'name2) || '';
280285 const last_system_sequencesystem_sequence = power_user.instruct.last_system_sequencesystem_sequence?.replace(/{{name}}/gi, 'System') || '';
286+ const last_system_sequence = instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';
281287
282288 const combined_sequence = [
283289 stop_sequence,
@@ -292,7 +298,7 @@ export function getInstructStoppingSequences() {
292298 combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
293299 }
294300
295301 if (useStopStrings ?? power_user.context.use_stop_strings) {
296302 if (power_user.context.chat_start) {
297303 result.push(`\n${substituteParams(power_user.context.chat_start)}`);
298304 }
@@ -320,59 +326,61 @@ export const force_output_sequence = {
320326 * @param {string} name1 User name.
321327 * @param {string} name2 Character name.
322328 * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).
329+ * @param {InstructSettings} customInstruct Custom instruct mode settings.
323330 * @returns {string} Formatted instruct mode chat message.
324331 */
325332export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence, customInstruct = null) {
326333 letconst includeNamesinstruct = isNarratorstructuredClone(customInstruct ? false :? power_user.instruct.names_behavior === names_behavior_types.ALWAYS);
334+ let includeNames = isNarrator ? false : instruct.names_behavior === names_behavior_types.ALWAYS;
327335
328336 if (!isNarrator && power_user.instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {
329337 includeNames = true;
330338 }
331339
332340 function getPrefix() {
333341 if (isNarrator) {
334342 return power_user.instruct.system_same_as_user ? power_user.instruct.input_sequence : power_user.instruct.system_sequence;
335343 }
336344
337345 if (isUser) {
338346 if (forceOutputSequence === force_output_sequence.FIRST) {
339347 return power_user.instruct.first_input_sequence || power_user.instruct.input_sequence;
340348 }
341349
342350 if (forceOutputSequence === force_output_sequence.LAST) {
343351 return power_user.instruct.last_input_sequence || power_user.instruct.input_sequence;
344352 }
345353
346354 return power_user.instruct.input_sequence;
347355 }
348356
349357 if (forceOutputSequence === force_output_sequence.FIRST) {
350358 return power_user.instruct.first_output_sequence || power_user.instruct.output_sequence;
351359 }
352360
353361 if (forceOutputSequence === force_output_sequence.LAST) {
354362 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
355363 }
356364
357365 return power_user.instruct.output_sequence;
358366 }
359367
360368 function getSuffix() {
361369 if (isNarrator) {
362370 return power_user.instruct.system_same_as_user ? power_user.instruct.input_suffix : power_user.instruct.system_suffix;
363371 }
364372
365373 if (isUser) {
366374 return power_user.instruct.input_suffix;
367375 }
368376
369377 return power_user.instruct.output_suffix;
370378 }
371379
372380 let prefix = getPrefix() || '';
373381 let suffix = getSuffix() || '';
374382
375383 if (power_user.instruct.macro) {
376384 prefix = substituteParams(prefix, name1, name2);
377385 prefix = prefix.replace(/{{name}}/gi, name || 'System');
378386
@@ -380,11 +388,11 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
380388 suffix = suffix.replace(/{{name}}/gi, name || 'System');
381389 }
382390
383391 if (!suffix && power_user.instruct.wrap) {
384392 suffix = '\n';
385393 }
386394
387395 const separator = power_user.instruct.wrap ? '\n' : '';
388396
389397 // Don't include the name if it's empty
390398 const textArray = includeNames && name ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];
@@ -396,23 +404,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
396404/**
397405 * Formats instruct mode system prompt.
398406 * @param {string} systemPrompt System prompt string.
407+ * @param {InstructSettings} customInstruct Custom instruct mode settings.
399408 * @returns {string} Formatted instruct mode system prompt.
400409 */
401410export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
402411 if (!systemPrompt) {
403412 return '';
404413 }
405414
406- const separator = power_user.instruct.wrap ? '\n' : '';
415+ const instruct = structuredClone(customInstruct ?? power_user.instruct);
416+
417+ const separator = instruct.wrap ? '\n' : '';
407418
408419 if (power_user.instruct.system_sequence_prefix) {
409420 // TODO: Replace with a proper 'System' prompt entity name input
410421 const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
411422 systemPrompt = prefix + separator + systemPrompt;
412423 }
413424
414425 if (power_user.instruct.system_sequence_suffix) {
415426 systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;
416427 }
417428
418429 return systemPrompt;
@@ -504,30 +515,32 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
504515 * @param {string} name2 Character name.
505516 * @param {boolean} isQuiet Is quiet mode generation.
506517 * @param {boolean} isQuietToLoud Is quiet to loud generation.
518+ * @param {InstructSettings} customInstruct Custom instruct settings.
507519 * @returns {string} Formatted instruct mode last prompt line.
508520 */
509521export 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);
522+ const instruct = structuredClone(customInstruct ?? power_user.instruct);
523+ const includeNames = name && (instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && instruct.names_behavior === names_behavior_types.FORCE)) && !(isQuiet && !isQuietToLoud);
511524
512525 function getSequence() {
513526 // User impersonation prompt
514527 if (isImpersonate) {
515528 return power_user.instruct.input_sequence;
516529 }
517530
518531 // Neutral / system / quiet prompt
519532 // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise
520533 if (isQuiet && !isQuietToLoud) {
521534 return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence;
522535 }
523536
524537 // Quiet in-character prompt
525538 if (isQuiet && isQuietToLoud) {
526539 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
527540 }
528541
529542 // Default AI response
530543 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
531544 }
532545
533546 let sequence = getSequence() || '';
@@ -536,21 +549,21 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
536549 // A hack for Mistral's formatting that has a normal output sequence ending with a space
537550 if (
538551 includeNames &&
539552 power_user.instruct.last_output_sequence &&
540553 power_user.instruct.output_sequence &&
541554 sequence === power_user.instruct.last_output_sequence &&
542555 /\s$/.test(power_user.instruct.output_sequence) &&
543556 !/\s$/.test(power_user.instruct.last_output_sequence)
544557 ) {
545558 nameFiller = power_user.instruct.output_sequence.slice(-1);
546559 }
547560
548561 if (power_user.instruct.macro) {
549562 sequence = substituteParams(sequence, name1, name2);
550563 sequence = sequence.replace(/{{name}}/gi, name || 'System');
551564 }
552565
553566 const separator = power_user.instruct.wrap ? '\n' : '';
554567 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);
555568
556569 // Quiet prompt already has a newline at the end
@@ -562,7 +575,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
562575 text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
563576 }
564577
565578 return (power_user.instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);
566579}
567580
568581/**
public/scripts/login.js+10 -5
@@ -180,13 +180,18 @@ function displayError(message) {
180180 * Preserves the query string.
181181 */
182182function redirectToHome() {
183183 // AfterCreate a loginURL theresobject nobased needon tothe preservecurrent thelocation
184- // noauto (if present)
184+ const currentUrl = new URL(window.location.href);
185- const urlParams = new URLSearchParams(window.location.search);
186185
187- urlParams.delete('noauto');
186+ // After a login there's no need to preserve the
187+ // noauto parameter (if present)
188+ currentUrl.searchParams.delete('noauto');
188189
189- window.location.href = '/' + urlParams.toString();
190+ // Set the pathname to root and keep the updated query string
191+ currentUrl.pathname = '/';
192+
193+ // Redirect to the new URL
194+ window.location.href = currentUrl.toString();
190195}
191196
192197/**
public/scripts/logprobs.js+6 -1
@@ -368,7 +368,12 @@ function onToggleLogprobsPanel() {
368368function createSwipe(messageId, prompt) {
369369 // need to call `cleanUpMessage` on our new prompt, because we were working
370370 // with raw model output and our new prompt is missing trimming/macro replacements
371371 const cleanedPrompt = cleanUpMessage(prompt, false, false, true);{
372+ getMessage: prompt,
373+ isImpersonate: false,
374+ isContinue: false,
375+ displayIncompleteSentences: true,
376+ });
372377
373378 const msg = chat[messageId];
374379 const newSwipeInfo = {
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+248 -140
@@ -15,12 +15,12 @@ import {
1515 extension_prompt_types,
1616 Generate,
1717 getExtensionPrompt,
18+ getExtensionPromptMaxDepth,
1819 getNextMessageId,
1920 getRequestHeaders,
2021 getStoppingStrings,
2122 is_send_press,
2223 main_api,
23- MAX_INJECTION_DEPTH,
2424 name1,
2525 name2,
2626 replaceItemizedPromptText,
@@ -75,6 +75,7 @@ import { Popup, POPUP_RESULT } from './popup.js';
7575import { t } from './i18n.js';
7676import { ToolManager } from './tool-calling.js';
7777import { accountStorage } from './util/AccountStorage.js';
78+import { IGNORE_SYMBOL } from './constants.js';
7879
7980export {
8081 openai_messages_count,
@@ -119,7 +120,6 @@ const default_bias_presets = {
119120const max_2k = 2047;
120121const max_4k = 4095;
121122const max_8k = 8191;
122-const max_12k = 12287;
123123const max_16k = 16383;
124124const max_32k = 32767;
125125const max_64k = 65535;
@@ -182,9 +182,9 @@ export const chat_completion_sources = {
182182 PERPLEXITY: 'perplexity',
183183 GROQ: 'groq',
184184 ZEROONEAI: '01ai',
185- BLOCKENTROPY: 'blockentropy',
186185 NANOGPT: 'nanogpt',
187186 DEEPSEEK: 'deepseek',
187+ XAI: 'xai',
188188};
189189
190190const character_names_behavior = {
@@ -258,7 +258,7 @@ export const settingsToUpdate = {
258258 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],
259259 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
260260 zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
261261 blockentropy_modelxai_model: ['#model_blockentropy_selectmodel_xai_select', 'blockentropy_modelxai_model', false],
262262 custom_model: ['#custom_model_id', 'custom_model', false],
263263 custom_url: ['#custom_api_url_text', 'custom_url', false],
264264 custom_include_body: ['#custom_include_body', 'custom_include_body', false],
@@ -346,8 +346,8 @@ const default_settings = {
346346 groq_model: 'llama-3.3-70b-versatile',
347347 nanogpt_model: 'gpt-4o-mini',
348348 zerooneai_model: 'yi-large',
349- blockentropy_model: 'be-70b-base-llama3.1',
350349 deepseek_model: 'deepseek-chat',
350+ xai_model: 'grok-3-beta',
351351 custom_model: '',
352352 custom_url: '',
353353 custom_include_body: '',
@@ -427,8 +427,8 @@ const oai_settings = {
427427 groq_model: 'llama-3.1-70b-versatile',
428428 nanogpt_model: 'gpt-4o-mini',
429429 zerooneai_model: 'yi-large',
430- blockentropy_model: 'be-70b-base-llama3.1',
431430 deepseek_model: 'deepseek-chat',
431+ xai_model: 'grok-3-beta',
432432 custom_model: '',
433433 custom_url: '',
434434 custom_include_body: '',
@@ -527,6 +527,13 @@ function setOpenAIMessages(chat) {
527527 let role = chat[j]['is_user'] ? 'user' : 'assistant';
528528 let content = chat[j]['mes'];
529529
530+ // If this symbol flag is set, completely ignore the message.
531+ // This can be used to hide messages without affecting the number of messages in the chat.
532+ if (chat[j].extra?.[IGNORE_SYMBOL]) {
533+ j++;
534+ continue;
535+ }
536+
530537 // 100% legal way to send a message as system
531538 if (chat[j].extra?.type === system_message_types.NARRATOR) {
532539 role = 'system';
@@ -705,16 +712,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
705712 return result;
706713}
707714
708-function formatWorldInfo(value) {
715+export function formatWorldInfo(value, { wiFormat = null } = {}) {
709716 if (!value) {
710717 return '';
711718 }
712719
713- if (!oai_settings.wi_format.trim()) {
720+ const format = wiFormat ?? oai_settings.wi_format;
721+
722+ if (!format.trim()) {
714723 return value;
715724 }
716725
717726 return stringFormat(oai_settings.wi_formatformat, value);
718727}
719728
720729/**
@@ -733,7 +742,8 @@ async function populationInjectionPrompts(prompts, messages) {
733742 'assistant': extension_prompt_roles.ASSISTANT,
734743 };
735744
736- for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {
745+ const maxDepth = getExtensionPromptMaxDepth();
746+ for (let i = 0; i <= maxDepth; i++) {
737747 // Get prompts for current depth
738748 const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content);
739749
@@ -952,7 +962,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
952962 * @param {number} position - Prompt position in the extensions object.
953963 * @returns {string|false} - The prompt position for prompt collection.
954964 */
955965export function getPromptPosition(position) {
956966 if (position == extension_prompt_types.BEFORE_PROMPT) {
957967 return 'start';
958968 }
@@ -969,7 +979,7 @@ function getPromptPosition(position) {
969979 * @param {number} role Role of the prompt.
970980 * @returns {string} Mapped role.
971981 */
972982export function getPromptRole(role) {
973983 switch (role) {
974984 case extension_prompt_roles.SYSTEM:
975985 return 'system';
@@ -1402,9 +1412,9 @@ export async function prepareOpenAIMessages({
14021412 await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
14031413 } catch (error) {
14041414 if (error instanceof TokenBudgetExceededError) {
14051415 toastr.error(t`An error occurred whileMandatory countingprompts tokens:exceed Tokenthe budgetcontext exceededsize.`);
14061416 chatCompletion.log('TokenMandatory budgetprompts exceededexceed the context size.');
14071417 promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token Limitlimit or disable custom prompts.`;
14081418 } else if (error instanceof InvalidCharacterNameError) {
14091419 toastr.warning(t`An error occurred while counting tokens: Invalid character name`);
14101420 chatCompletion.log('Invalid character name');
@@ -1442,8 +1452,10 @@ export async function prepareOpenAIMessages({
14421452 * Handles errors during streaming requests.
14431453 * @param {Response} response
14441454 * @param {string} decoded - response text or decoded stream data
1455+ * @param {object} [options]
1456+ * @param {boolean?} [options.quiet=false] Suppress toast messages
14451457 */
14461458export function tryParseStreamingError(response, decoded, { quiet = false } = {}) {
14471459 try {
14481460 const data = JSON.parse(decoded);
14491461
@@ -1451,19 +1463,19 @@ function tryParseStreamingError(response, decoded) {
14511463 return;
14521464 }
14531465
14541466 checkQuotaError(data, { quiet });
14551467 checkModerationError(data, { quiet });
14561468
14571469 // these do not throw correctly (equiv to Error("[object Object]"))
14581470 // if trying to fix "[object Object]" displayed to users, start here
14591471
14601472 if (data.error) {
14611473 !quiet && toastr.error(data.error.message || response.statusText, 'Chat Completion API');
14621474 throw new Error(data);
14631475 }
14641476
14651477 if (data.message) {
14661478 !quiet && toastr.error(data.message, 'Chat Completion API');
14671479 throw new Error(data);
14681480 }
14691481 }
@@ -1475,16 +1487,18 @@ function tryParseStreamingError(response, decoded) {
14751487/**
14761488 * Checks if the response contains a quota error and displays a popup if it does.
14771489 * @param data
1490+ * @param {object} [options]
1491+ * @param {boolean?} [options.quiet=false] Suppress toast messages
14781492 * @returns {void}
14791493 * @throws {object} - response JSON
14801494 */
14811495function checkQuotaError(data, { quiet = false } = {}) {
14821496 if (!data) {
14831497 return;
14841498 }
14851499
14861500 if (data.quota_error) {
14871501 !quiet && renderTemplateAsync('quotaError').then((html) => Popup.show.text('Quota Error', html));
14881502
14891503 // this does not throw correctly (equiv to Error("[object Object]"))
14901504 // if trying to fix "[object Object]" displayed to users, start here
@@ -1492,9 +1506,14 @@ function checkQuotaError(data) {
14921506 }
14931507}
14941508
1495-function checkModerationError(data) {
1509+/**
1510+ * @param {any} data
1511+ * @param {object} [options]
1512+ * @param {boolean?} [options.quiet=false] Suppress toast messages
1513+ */
1514+function checkModerationError(data, { quiet = false } = {}) {
14961515 const moderationError = data?.error?.message?.includes('requires moderation');
14971516 if (moderationError && !quiet) {
14981517 const moderationReason = `Reasons: ${data?.error?.metadata?.reasons?.join(', ') ?? '(N/A)'}`;
14991518 const flaggedText = data?.error?.metadata?.flagged_input ?? '(N/A)';
15001519 toastr.info(flaggedText, moderationReason, { timeOut: 10000 });
@@ -1626,12 +1645,12 @@ export function getChatCompletionModel(source = null) {
16261645 return oai_settings.groq_model;
16271646 case chat_completion_sources.ZEROONEAI:
16281647 return oai_settings.zerooneai_model;
1629- case chat_completion_sources.BLOCKENTROPY:
1630- return oai_settings.blockentropy_model;
16311648 case chat_completion_sources.NANOGPT:
16321649 return oai_settings.nanogpt_model;
16331650 case chat_completion_sources.DEEPSEEK:
16341651 return oai_settings.deepseek_model;
1652+ case chat_completion_sources.XAI:
1653+ return oai_settings.xai_model;
16351654 default:
16361655 throw new Error(`Unknown chat completion source: ${activeSource}`);
16371656 }
@@ -1675,6 +1694,11 @@ function calculateOpenRouterCost() {
16751694 }
16761695 }
16771696
1697+ if (oai_settings.enable_web_search) {
1698+ const webSearchCost = (0.02).toFixed(2);
1699+ cost = t`${cost} + $${webSearchCost}`;
1700+ }
1701+
16781702 $('#openrouter_max_prompt_cost').text(cost);
16791703}
16801704
@@ -1746,23 +1770,6 @@ function saveModelList(data) {
17461770 $('#model_01ai_select').val(oai_settings.zerooneai_model).trigger('change');
17471771 }
17481772
1749- if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
1750- $('#model_blockentropy_select').empty();
1751- model_list.forEach((model) => {
1752- $('#model_blockentropy_select').append(
1753- $('<option>', {
1754- value: model.id,
1755- text: model.id,
1756- }));
1757- });
1758-
1759- if (!oai_settings.blockentropy_model && model_list.length > 0) {
1760- oai_settings.blockentropy_model = model_list[0].id;
1761- }
1762-
1763- $('#model_blockentropy_select').val(oai_settings.blockentropy_model).trigger('change');
1764- }
1765-
17661773 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {
17671774 /** @type {HTMLSelectElement} */
17681775 const mistralModelSelect = document.querySelector('#model_mistralai_select');
@@ -1966,13 +1973,14 @@ async function sendOpenAIRequest(type, messages, signal) {
19661973 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
19671974 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
19681975 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
1976+ const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
19691977 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
19701978 const isQuiet = type === 'quiet';
19711979 const isImpersonate = type === 'impersonate';
19721980 const isContinue = type === 'continue';
19731981 const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model));
19741982 const useLogprobs = !!power_user.request_token_probabilities;
19751983 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom || isXAI);
19761984
19771985 // If we're using the window.ai extension, use that instead
19781986 // Doesn't support logit bias yet
@@ -2018,6 +2026,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20182026 'reasoning_effort': String(oai_settings.reasoning_effort),
20192027 'enable_web_search': Boolean(oai_settings.enable_web_search),
20202028 'request_images': Boolean(oai_settings.request_images),
2029+ 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,
20212030 };
20222031
20232032 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
@@ -2030,14 +2039,14 @@ async function sendOpenAIRequest(type, messages, signal) {
20302039 }
20312040
20322041 // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite
20332042 if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
20342043 await validateReverseProxy();
20352044 generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
20362045 generate_data['proxy_password'] = oai_settings.proxy_password;
20372046 }
20382047
20392048 // Add logprobs request (currently OpenAI only, max 5 on their side)
20402049 if (useLogprobs && (isOAI || isCustom || isDeepSeek || isXAI)) {
20412050 generate_data['logprobs'] = 5;
20422051 }
20432052
@@ -2048,7 +2057,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20482057 delete generate_data.stop;
20492058 delete generate_data.logprobs;
20502059 }
20512060 if (isOAI && oai_settings.openai_model.includes('gpt-4.5-preview') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5-preview')) {
20522061 delete generate_data.logprobs;
20532062 }
20542063
@@ -2098,7 +2107,6 @@ async function sendOpenAIRequest(type, messages, signal) {
20982107 generate_data['custom_include_body'] = oai_settings.custom_include_body;
20992108 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;
21002109 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;
2101- generate_data['custom_prompt_post_processing'] = oai_settings.custom_prompt_post_processing;
21022110 }
21032111
21042112 if (isCohere) {
@@ -2157,29 +2165,42 @@ async function sendOpenAIRequest(type, messages, signal) {
21572165 }
21582166 }
21592167
2160- if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) {
2168+ if (isXAI) {
2161- generate_data['seed'] = oai_settings.seed;
2169+ if (generate_data.model.includes('grok-3-mini')) {
2170+ delete generate_data.presence_penalty;
2171+ delete generate_data.frequency_penalty;
2172+ }
2173+ if (generate_data.model.includes('grok-vision')) {
2174+ delete generate_data.tools;
2175+ delete generate_data.tool_choice;
2176+ }
21622177 }
21632178
2164- if (isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) {
2179+ if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano || isXAI) && oai_settings.seed >= 0) {
2165- generate_data.messages.forEach((msg) => {
2180+ generate_data['seed'] = oai_settings.seed;
2166- if (msg.role === 'system') {
2167- msg.role = 'user';
21682181 }
2169- });
2182+
2183+ if (isOAI && /^(o1|o3|o4)/.test(oai_settings.openai_model)) {
21702184 generate_data.max_completion_tokens = generate_data.max_tokens;
21712185 delete generate_data.max_tokens;
21722186 delete generate_data.logprobs;
21732187 delete generate_data.top_logprobs;
21742188 delete generate_data.nstop;
2189+ delete generate_data.logit_bias;
21752190 delete generate_data.temperature;
21762191 delete generate_data.top_p;
21772192 delete generate_data.frequency_penalty;
21782193 delete generate_data.presence_penalty;
2194+ if (oai_settings.openai_model.startsWith('o1')) {
2195+ generate_data.messages.forEach((msg) => {
2196+ if (msg.role === 'system') {
2197+ msg.role = 'user';
2198+ }
2199+ });
2200+ delete generate_data.n;
21792201 delete generate_data.tools;
21802202 delete generate_data.tool_choice;
2181- delete generate_data.stop;
2203+ }
2182- delete generate_data.logit_bias;
21832204 }
21842205
21852206 await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
@@ -2215,7 +2236,8 @@ async function sendOpenAIRequest(type, messages, signal) {
22152236
22162237 if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
22172238 const swipeIndex = parsed.choices[0].index - 1;
2218- swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state);
2239+ // FIXME: state.reasoning should be an array to support multi-swipe
2240+ swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state, { overrideShowThoughts: false });
22192241 } else {
22202242 text += getStreamingReply(parsed, state);
22212243 }
@@ -2253,37 +2275,48 @@ async function sendOpenAIRequest(type, messages, signal) {
22532275 * Extracts the reply from the response data from a chat completions-like source
22542276 * @param {object} data Response data from the chat completions-like source
22552277 * @param {object} state Additional state to keep track of
2278+ * @param {object} [options] Additional options
2279+ * @param {string?} [options.chatCompletionSource] Chat completion source
2280+ * @param {boolean?} [options.overrideShowThoughts] Override show thoughts
22562281 * @returns {string} The reply extracted from the response data
22572282 */
2258-function getStreamingReply(data, state) {
2283+export function getStreamingReply(data, state, { chatCompletionSource = null, overrideShowThoughts = null } = {}) {
2259- if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
2284+ const chat_completion_source = chatCompletionSource ?? oai_settings.chat_completion_source;
2260- if (oai_settings.show_thoughts) {
2285+ const show_thoughts = overrideShowThoughts ?? oai_settings.show_thoughts;
2286+
2287+ if (chat_completion_source === chat_completion_sources.CLAUDE) {
2288+ if (show_thoughts) {
22612289 state.reasoning += data?.delta?.thinking || '';
22622290 }
22632291 return data?.delta?.text || '';
22642292 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
22652293 const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData;
22662294 if (inlineData) {
22672295 state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`;
22682296 }
22692297 if (oai_settings.show_thoughts) {
22702298 state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');
22712299 }
22722300 return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';
22732301 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
22742302 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
22752303 } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
22762304 if (oai_settings.show_thoughts) {
2305+ state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
2306+ }
2307+ return data.choices?.[0]?.delta?.content || '';
2308+ } else if (chat_completion_source === chat_completion_sources.XAI) {
2309+ if (show_thoughts) {
22772310 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
22782311 }
22792312 return data.choices?.[0]?.delta?.content || '';
22802313 } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) {
22812314 if (oai_settings.show_thoughts) {
22822315 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
22832316 }
22842317 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
22852318 } else if (oai_settings.chat_completion_source === chat_completion_sources.CUSTOM) {
22862319 if (oai_settings.show_thoughts) {
22872320 state.reasoning +=
22882321 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
22892322 data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning ??
@@ -2309,6 +2342,7 @@ function parseChatCompletionLogprobs(data) {
23092342 switch (oai_settings.chat_completion_source) {
23102343 case chat_completion_sources.OPENAI:
23112344 case chat_completion_sources.DEEPSEEK:
2345+ case chat_completion_sources.XAI:
23122346 case chat_completion_sources.CUSTOM:
23132347 if (!data.choices?.length) {
23142348 return null;
@@ -3229,8 +3263,8 @@ function loadOpenAISettings(data, settings) {
32293263 oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
32303264 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
32313265 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
3232- oai_settings.blockentropy_model = settings.blockentropy_model ?? default_settings.blockentropy_model;
32333266 oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
3267+ oai_settings.xai_model = settings.xai_model ?? default_settings.xai_model;
32343268 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
32353269 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
32363270 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@@ -3316,7 +3350,8 @@ function loadOpenAISettings(data, settings) {
33163350 $('#model_deepseek_select').val(oai_settings.deepseek_model);
33173351 $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
33183352 $('#model_01ai_select').val(oai_settings.zerooneai_model);
33193353 $('#model_blockentropy_selectmodel_xai_select').val(oai_settings.blockentropy_modelxai_model);
3354+ $(`#model_xai_select option[value="${oai_settings.xai_model}"`).attr('selected', true);
33203355 $('#custom_model_id').val(oai_settings.custom_model);
33213356 $('#custom_api_url_text').val(oai_settings.custom_url);
33223357 $('#openai_max_context').val(oai_settings.openai_max_context);
@@ -3476,7 +3511,7 @@ async function getStatusOpen() {
34763511 let status;
34773512
34783513 if ('ai' in window) {
34793514 status = 't`Valid'`;
34803515 }
34813516 else {
34823517 showWindowExtensionError();
@@ -3513,7 +3548,7 @@ async function getStatusOpen() {
35133548 chat_completion_source: oai_settings.chat_completion_source,
35143549 };
35153550
35163551 if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
35173552 await validateReverseProxy();
35183553 }
35193554
@@ -3525,7 +3560,7 @@ async function getStatusOpen() {
35253560
35263561 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;
35273562 if (canBypass) {
35283563 setOnlineStatus('t`Status check bypassed'`);
35293564 }
35303565
35313566 try {
@@ -3547,7 +3582,7 @@ async function getStatusOpen() {
35473582 saveModelList(responseData.data);
35483583 }
35493584 if (!('error' in responseData)) {
35503585 setOnlineStatus('t`Valid'`);
35513586 }
35523587 } catch (error) {
35533588 console.error(error);
@@ -3596,7 +3631,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
35963631 perplexity_model: settings.perplexity_model,
35973632 groq_model: settings.groq_model,
35983633 zerooneai_model: settings.zerooneai_model,
3599- blockentropy_model: settings.blockentropy_model,
36003634 custom_model: settings.custom_model,
36013635 custom_url: settings.custom_url,
36023636 custom_include_body: settings.custom_include_body,
@@ -4094,9 +4128,15 @@ function getMaxContextOpenAI(value) {
40944128 if (oai_settings.max_context_unlocked) {
40954129 return unlocked_max;
40964130 }
40974131 else if (value.startsWithincludes('o1') || valuegpt-4.startsWith('o31')) {
4132+ return max_1mil;
4133+ }
4134+ else if (value.startsWith('o1')) {
40984135 return max_128k;
40994136 }
4137+ else if (value.startsWith('o4') || value.startsWith('o3')) {
4138+ return max_200k;
4139+ }
41004140 else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
41014141 return max_128k;
41024142 }
@@ -4168,6 +4208,80 @@ function getMaxContextWindowAI(value) {
41684208}
41694209
41704210/**
4211+ * Get the maximum context size for the Mistral model
4212+ * @param {string} model Model identifier
4213+ * @param {boolean} isUnlocked Whether context limits are unlocked
4214+ * @returns {number} Maximum context size in tokens
4215+ */
4216+function getMistralMaxContext(model, isUnlocked) {
4217+ if (isUnlocked) {
4218+ return unlocked_max;
4219+ }
4220+
4221+ if (Array.isArray(model_list) && model_list.length > 0) {
4222+ const contextLength = model_list.find((record) => record.id === model)?.max_context_length;
4223+ if (contextLength) {
4224+ return contextLength;
4225+ }
4226+ }
4227+
4228+ const contextMap = {
4229+ 'codestral-2411-rc5': 262144,
4230+ 'codestral-2412': 262144,
4231+ 'codestral-2501': 262144,
4232+ 'codestral-latest': 262144,
4233+ 'codestral-mamba-2407': 262144,
4234+ 'codestral-mamba-latest': 262144,
4235+ 'open-codestral-mamba': 262144,
4236+ 'ministral-3b-2410': 131072,
4237+ 'ministral-3b-latest': 131072,
4238+ 'ministral-8b-2410': 131072,
4239+ 'ministral-8b-latest': 131072,
4240+ 'mistral-large-2407': 131072,
4241+ 'mistral-large-2411': 131072,
4242+ 'mistral-large-latest': 131072,
4243+ 'mistral-large-pixtral-2411': 131072,
4244+ 'mistral-tiny-2407': 131072,
4245+ 'mistral-tiny-latest': 131072,
4246+ 'open-mistral-nemo': 131072,
4247+ 'open-mistral-nemo-2407': 131072,
4248+ 'pixtral-12b': 131072,
4249+ 'pixtral-12b-2409': 131072,
4250+ 'pixtral-12b-latest': 131072,
4251+ 'pixtral-large-2411': 131072,
4252+ 'pixtral-large-latest': 131072,
4253+ 'open-mixtral-8x22b': 65536,
4254+ 'open-mixtral-8x22b-2404': 65536,
4255+ 'codestral-2405': 32768,
4256+ 'mistral-embed': 32768,
4257+ 'mistral-large-2402': 32768,
4258+ 'mistral-medium': 32768,
4259+ 'mistral-medium-2312': 32768,
4260+ 'mistral-medium-latest': 32768,
4261+ 'mistral-moderation-2411': 32768,
4262+ 'mistral-moderation-latest': 32768,
4263+ 'mistral-ocr-2503': 32768,
4264+ 'mistral-ocr-latest': 32768,
4265+ 'mistral-saba-2502': 32768,
4266+ 'mistral-saba-latest': 32768,
4267+ 'mistral-small': 32768,
4268+ 'mistral-small-2312': 32768,
4269+ 'mistral-small-2402': 32768,
4270+ 'mistral-small-2409': 32768,
4271+ 'mistral-small-2501': 32768,
4272+ 'mistral-small-2503': 32768,
4273+ 'mistral-small-latest': 32768,
4274+ 'mistral-tiny': 32768,
4275+ 'mistral-tiny-2312': 32768,
4276+ 'open-mistral-7b': 32768,
4277+ 'open-mixtral-8x7b': 32768,
4278+ };
4279+
4280+ // Return context size if model found, otherwise default to 32k
4281+ return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || 32768;
4282+}
4283+
4284+/**
41714285 * Get the maximum context size for the Groq model
41724286 * @param {string} model Model identifier
41734287 * @param {boolean} isUnlocked Whether context limits are unlocked
@@ -4195,6 +4309,9 @@ function getGroqMaxContext(model, isUnlocked) {
41954309 'qwen-2.5-32b': max_128k,
41964310 'deepseek-r1-distill-qwen-32b': max_128k,
41974311 'deepseek-r1-distill-llama-70b-specdec': max_128k,
4312+ 'mistral-saba-24b': max_32k,
4313+ 'meta-llama/llama-4-scout-17b-16e-instruct': max_128k,
4314+ 'meta-llama/llama-4-maverick-17b-128e-instruct': max_128k,
41984315 };
41994316
42004317 // Return context size if model found, otherwise default to 128k
@@ -4305,18 +4422,17 @@ async function onModelChange() {
43054422 oai_settings.zerooneai_model = value;
43064423 }
43074424
4308- if (value && $(this).is('#model_blockentropy_select')) {
4309- console.log('Block Entropy model changed to', value);
4310- oai_settings.blockentropy_model = value;
4311- $('#blockentropy_model_id').val(value).trigger('input');
4312- }
4313-
43144425 if (value && $(this).is('#model_custom_select')) {
43154426 console.log('Custom model changed to', value);
43164427 oai_settings.custom_model = value;
43174428 $('#custom_model_id').val(value).trigger('input');
43184429 }
43194430
4431+ if ($(this).is('#model_xai_select')) {
4432+ console.log('XAI model changed to', value);
4433+ oai_settings.xai_model = value;
4434+ }
4435+
43204436 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
43214437 if (oai_settings.max_context_unlocked) {
43224438 $('#openai_max_context').attr('max', unlocked_max);
@@ -4335,7 +4451,7 @@ async function onModelChange() {
43354451 $('#openai_max_context').attr('max', max_32k);
43364452 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) {
43374453 $('#openai_max_context').attr('max', max_2mil);
43384454 } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.5-flash-preview-04-17') || value.includes('gemini-2.5-pro-exp-03-25') || value.includes('gemini-2.5-pro-preview-03-25')) {
43394455 $('#openai_max_context').attr('max', max_1mil);
43404456 } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') {
43414457 $('#openai_max_context').attr('max', max_32k);
@@ -4433,27 +4549,10 @@ async function onModelChange() {
44334549 }
44344550
44354551 if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
4436- if (oai_settings.max_context_unlocked) {
4552+ const maxContext = getMistralMaxContext(oai_settings.mistralai_model, oai_settings.max_context_unlocked);
44374553 $('#openai_max_context').attr('max', unlocked_maxmaxContext);
4438- } else if (oai_settings.mistralai_model.includes('codestral-mamba')) {
4439- $('#openai_max_context').attr('max', max_256k);
4440- } else if (['mistral-large-2407', 'mistral-large-2411', 'mistral-large-latest'].includes(oai_settings.mistralai_model)) {
4441- $('#openai_max_context').attr('max', max_128k);
4442- } else if (oai_settings.mistralai_model.includes('mistral-nemo')) {
4443- $('#openai_max_context').attr('max', max_128k);
4444- } else if (oai_settings.mistralai_model.includes('mixtral-8x22b')) {
4445- $('#openai_max_context').attr('max', max_64k);
4446- } else if (oai_settings.mistralai_model.includes('pixtral')) {
4447- $('#openai_max_context').attr('max', max_128k);
4448- } else if (oai_settings.mistralai_model.includes('ministral')) {
4449- $('#openai_max_context').attr('max', max_32k);
4450- } else {
4451- $('#openai_max_context').attr('max', max_32k);
4452- }
44534554 oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
44544555 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4455-
4456- //mistral also caps temp at 1.0
44574556 oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
44584557 $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
44594558 }
@@ -4560,29 +4659,6 @@ async function onModelChange() {
45604659 oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
45614660 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
45624661 }
4563- if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) {
4564- if (oai_settings.max_context_unlocked) {
4565- $('#openai_max_context').attr('max', unlocked_max);
4566- }
4567- else if (oai_settings.blockentropy_model.includes('llama3.1')) {
4568- $('#openai_max_context').attr('max', max_16k);
4569- }
4570- else if (oai_settings.blockentropy_model.includes('72b')) {
4571- $('#openai_max_context').attr('max', max_16k);
4572- }
4573- else if (oai_settings.blockentropy_model.includes('120b')) {
4574- $('#openai_max_context').attr('max', max_12k);
4575- }
4576- else {
4577- $('#openai_max_context').attr('max', max_8k);
4578- }
4579-
4580- oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
4581- $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4582-
4583- oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
4584- $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4585- }
45864662
45874663 if (oai_settings.chat_completion_source === chat_completion_sources.NANOGPT) {
45884664 if (oai_settings.max_context_unlocked) {
@@ -4612,6 +4688,22 @@ async function onModelChange() {
46124688 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
46134689 }
46144690
4691+ if (oai_settings.chat_completion_source === chat_completion_sources.XAI) {
4692+ if (oai_settings.max_context_unlocked) {
4693+ $('#openai_max_context').attr('max', unlocked_max);
4694+ } else if (oai_settings.xai_model.includes('grok-2-vision')) {
4695+ $('#openai_max_context').attr('max', max_32k);
4696+ } else if (oai_settings.xai_model.includes('grok-vision')) {
4697+ $('#openai_max_context').attr('max', max_8k);
4698+ } else {
4699+ $('#openai_max_context').attr('max', max_128k);
4700+ }
4701+
4702+ oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
4703+ $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4704+ $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4705+ }
4706+
46154707 if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
46164708 oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);
46174709 $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');
@@ -4849,15 +4941,16 @@ async function onConnectButtonClick(e) {
48494941 return;
48504942 }
48514943 }
4852- if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
4853- const api_key_blockentropy = String($('#api_key_blockentropy').val()).trim();
48544944
48554945 if (api_key_blockentropyoai_settings.lengthchat_completion_source === chat_completion_sources.XAI) {
4856- await writeSecret(SECRET_KEYS.BLOCKENTROPY, api_key_blockentropy);
4946+ const api_key_xai = String($('#api_key_xai').val()).trim();
4947+
4948+ if (api_key_xai.length) {
4949+ await writeSecret(SECRET_KEYS.XAI, api_key_xai);
48574950 }
48584951
48594952 if (!secret_state[SECRET_KEYS.BLOCKENTROPYXAI] && !oai_settings.reverse_proxy) {
48604953 console.log('No secret key saved for Block EntropyXAI');
48614954 return;
48624955 }
48634956 }
@@ -4915,12 +5008,12 @@ function toggleChatCompletionForms() {
49155008 else if (oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) {
49165009 $('#model_custom_select').trigger('change');
49175010 }
4918- else if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
4919- $('#model_blockentropy_select').trigger('change');
4920- }
49215011 else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
49225012 $('#model_deepseek_select').trigger('change');
49235013 }
5014+ else if (oai_settings.chat_completion_source == chat_completion_sources.XAI) {
5015+ $('#model_xai_select').trigger('change');
5016+ }
49245017 $('[data-source]').each(function () {
49255018 const validSources = $(this).data('source').split(',');
49265019 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
@@ -5006,8 +5099,11 @@ export function isImageInliningSupported() {
50065099 // gultra just isn't being offered as multimodal, thanks google.
50075100 const visionSupportedModels = [
50085101 'gpt-4-vision',
5102+ 'gemini-2.5-pro-exp-03-25',
5103+ 'gemini-2.5-pro-preview-03-25',
50095104 'gemini-2.0-pro-exp',
50105105 'gemini-2.0-pro-exp-02-05',
5106+ 'gemini-2.5-flash-preview-04-17',
50115107 'gemini-2.0-flash-lite-preview',
50125108 'gemini-2.0-flash-lite-preview-02-05',
50135109 'gemini-2.0-flash',
@@ -5047,7 +5143,9 @@ export function isImageInliningSupported() {
50475143 'o1-2024-12-17',
50485144 'chatgpt-4o-latest',
50495145 'yi-vision',
50505146 'mistral-large-pixtral-latest2411',
5147+ 'mistral-small-2503',
5148+ 'mistral-small-latest',
50515149 'pixtral-12b-latest',
50525150 'pixtral-12b',
50535151 'pixtral-12b-2409',
@@ -5055,11 +5153,18 @@ export function isImageInliningSupported() {
50555153 'pixtral-large-2411',
50565154 'c4ai-aya-vision-8b',
50575155 'c4ai-aya-vision-32b',
5156+ 'grok-2-vision',
5157+ 'grok-vision',
5158+ 'gpt-4.1',
5159+ 'o3',
5160+ 'o3-2025-04-16',
5161+ 'o4-mini',
5162+ 'o4-mini-2025-04-16',
50585163 ];
50595164
50605165 switch (oai_settings.chat_completion_source) {
50615166 case chat_completion_sources.OPENAI:
50625167 return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview') && !oai_settings.openai_model.includes('o3-mini'));
50635168 case chat_completion_sources.MAKERSUITE:
50645169 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
50655170 case chat_completion_sources.CLAUDE:
@@ -5074,6 +5179,8 @@ export function isImageInliningSupported() {
50745179 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));
50755180 case chat_completion_sources.COHERE:
50765181 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
5182+ case chat_completion_sources.XAI:
5183+ return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
50775184 default:
50785185 return false;
50795186 }
@@ -5614,6 +5721,7 @@ export function initOpenAI() {
56145721
56155722 $('#openai_enable_web_search').on('input', function () {
56165723 oai_settings.enable_web_search = !!$(this).prop('checked');
5724+ calculateOpenRouterCost();
56175725 saveSettingsDebounced();
56185726 });
56195727
@@ -5669,8 +5777,8 @@ export function initOpenAI() {
56695777 $('#model_nanogpt_select').on('change', onModelChange);
56705778 $('#model_deepseek_select').on('change', onModelChange);
56715779 $('#model_01ai_select').on('change', onModelChange);
5672- $('#model_blockentropy_select').on('change', onModelChange);
56735780 $('#model_custom_select').on('change', onModelChange);
5781+ $('#model_xai_select').on('change', onModelChange);
56745782 $('#settings_preset_openai').on('change', onSettingsPresetChange);
56755783 $('#new_oai_preset').on('click', onNewPresetClick);
56765784 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/personas.js+9 -14
@@ -111,6 +111,7 @@ export function setUserAvatar(imgfile, { toastPersonaNameChange = true, navigate
111111 reloadUserAvatar();
112112 updatePersonaUIStates({ navigateToCurrent: navigateToCurrent });
113113 selectCurrentPersona({ toastPersonaNameChange: toastPersonaNameChange });
114+ retriggerFirstMessageOnEmptyChat();
114115 saveSettingsDebounced();
115116 $('.zoomed_avatar[forchar]').remove();
116117}
@@ -465,7 +466,7 @@ export function initPersona(avatarId, personaName, personaDescription) {
465466 * @returns {Promise<boolean>} A promise that resolves to true if the character was converted, false otherwise.
466467 */
467468export async function convertCharacterToPersona(characterId = null) {
468469 if (null === characterId) characterId = Number(this_chid);
469470
470471 const avatarUrl = characters[characterId]?.avatar;
471472 if (!avatarUrl) {
@@ -800,7 +801,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
800801 chat_metadata['persona'] = user_avatar;
801802 console.log(`Auto locked persona to ${user_avatar}`);
802803 if (toastPersonaNameChange && power_user.persona_show_notifications) {
803804 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
804805 }
805806 saveMetadataDebounced();
806807 updatePersonaUIStates();
@@ -1243,7 +1244,7 @@ function getPersonaStates(avatarId) {
12431244 /** @type {PersonaConnection[]} */
12441245 const connections = power_user.persona_descriptions[avatarId]?.connections;
12451246 const hasCharLock = !!connections?.some(c =>
12461247 (!selected_group && c.type === 'character' && c.id === characters[Number(this_chid)]?.avatar)
12471248 || (selected_group && c.type === 'group' && c.id === selected_group));
12481249
12491250 return {
@@ -1481,7 +1482,7 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
14811482 * @returns {string[]} - An array of persona keys that are connected to the given character key
14821483 */
14831484export function getConnectedPersonas(characterKey = undefined) {
14841485 characterKey ??= selected_group || characters[Number(this_chid)]?.avatar;
14851486 const connectedPersonas = Object.entries(power_user.persona_descriptions)
14861487 .filter(([_, desc]) => desc.connections?.some(conn => conn.type === 'character' && conn.id === characterKey))
14871488 .map(([key, _]) => key);
@@ -1513,7 +1514,7 @@ export async function showCharConnections() {
15131514 console.log(`Unlocking persona ${personaId} from current character ${name2}`);
15141515 power_user.persona_descriptions[personaId].connections = connections.filter(c => {
15151516 if (menu_type == 'group_edit' && c.type == 'group' && c.id == selected_group) return false;
15161517 else if (c.type == 'character' && c.id == characters[Number(this_chid)]?.avatar) return false;
15171518 return true;
15181519 });
15191520 saveSettingsDebounced();
@@ -1545,8 +1546,8 @@ export async function showCharConnections() {
15451546export function getCurrentConnectionObj() {
15461547 if (selected_group)
15471548 return { type: 'group', id: selected_group };
15481549 if (characters[Number(this_chid)]?.avatar)
15491550 return { type: 'character', id: characters[Number(this_chid)]?.avatar };
15501551 return null;
15511552}
15521553
@@ -1664,7 +1665,7 @@ async function syncUserNameToPersona() {
16641665 * Only works if only the first message is present, and not in group mode.
16651666 */
16661667export function retriggerFirstMessageOnEmptyChat() {
16671668 if (Number(this_chid) >= 0 && !selected_group && chat.length === 1) {
16681669 $('#firstmessage_textarea').trigger('input');
16691670 }
16701671}
@@ -1782,7 +1783,6 @@ function setNameCallback({ mode = 'all' }, name) {
17821783 if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
17831784 if (persona) {
17841785 autoSelectPersona(persona);
1785- retriggerFirstMessageOnEmptyChat();
17861786 return '';
17871787 } else if (mode === 'lookup') {
17881788 toastr.warning(`Persona ${name} not found`);
@@ -1793,7 +1793,6 @@ function setNameCallback({ mode = 'all' }, name) {
17931793 if (['temp', 'all'].includes(mode)) {
17941794 // Otherwise, set just the name
17951795 setUserName(name); //this prevented quickReply usage
1796- retriggerFirstMessageOnEmptyChat();
17971796 }
17981797
17991798 return '';
@@ -1944,9 +1943,6 @@ export async function initPersonas() {
19441943 $(document).on('click', '#user_avatar_block .avatar-container', function () {
19451944 const imgfile = $(this).attr('data-avatar-id');
19461945 setUserAvatar(imgfile);
1947-
1948- // force firstMes {{user}} update on persona switch
1949- retriggerFirstMessageOnEmptyChat();
19501946 });
19511947
19521948 $('#persona_rename_button').on('click', () => renamePersona(user_avatar));
@@ -1979,4 +1975,3 @@ export async function initPersonas() {
19791975 eventSource.on(event_types.CHAT_CHANGED, loadPersonaForCurrentChat);
19801976 switchPersonaGridView();
19811977}
1982-
public/scripts/power-user.js+17 -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,
@@ -70,8 +71,8 @@ export {
7071
7172export const MAX_CONTEXT_DEFAULT = 8192;
7273export const MAX_RESPONSE_DEFAULT = 2048;
7374const MAX_CONTEXT_UNLOCKED = 200512 * 1024;
7475const MAX_RESPONSE_UNLOCKED = 3264 * 1024;
7576const unlockedMaxContextStep = 512;
7677const maxContextMin = 512;
7778const maxContextStep = 64;
@@ -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 }
public/scripts/preset-manager.js+58 -3
@@ -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';
@@ -36,8 +36,9 @@ import {
3636 textgenerationwebui_presets,
3737 textgenerationwebui_settings as textgen_settings,
3838} from './textgen-settings.js';
3939import { download, equalsIgnoreCaseAndAccents, 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])) {
@@ -428,6 +454,9 @@ class PresetManager {
428454
429455 async renamePreset(newName) {
430456 const oldName = this.getSelectedPresetName();
457+ if (equalsIgnoreCaseAndAccents(oldName, newName)) {
458+ throw new Error('New name must be different from old name');
459+ }
431460 try {
432461 await this.savePreset(newName);
433462 await this.deletePreset(oldName);
@@ -478,6 +507,10 @@ class PresetManager {
478507 presets = system_prompts;
479508 preset_names = system_prompts.map(x => x.name);
480509 break;
510+ case 'reasoning':
511+ presets = reasoning_templates;
512+ preset_names = reasoning_templates.map(x => x.name);
513+ break;
481514 default:
482515 console.warn(`Unknown API ID ${api}`);
483516 }
@@ -490,7 +523,7 @@ class PresetManager {
490523 }
491524
492525 isAdvancedFormatting() {
493- return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
526+ return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494527 }
495528
496529 updateList(name, preset) {
@@ -553,6 +586,11 @@ class PresetManager {
553586 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554587 return sysprompt_preset;
555588 }
589+ case 'reasoning': {
590+ const reasoning_preset = structuredClone(power_user.reasoning);
591+ reasoning_preset['name'] = name || power_user.reasoning.preset;
592+ return reasoning_preset;
593+ }
556594 default:
557595 console.warn(`Unknown API ID ${apiId}`);
558596 return {};
@@ -599,6 +637,13 @@ class PresetManager {
599637 'include_reasoning',
600638 'global_banned_tokens',
601639 'send_banned_tokens',
640+
641+ // Reasoning exclusions
642+ 'auto_parse',
643+ 'add_to_prompts',
644+ 'auto_expand',
645+ 'show_hidden',
646+ 'max_additions',
602647 ];
603648 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
604649
@@ -850,9 +895,19 @@ export async function initPresetManager() {
850895 console.debug(!presetManager.isAdvancedFormatting() ? 'Preset rename cancelled' : 'Template rename cancelled');
851896 return;
852897 }
898+ if (equalsIgnoreCaseAndAccents(oldName, newName)) {
899+ toastr.warning(t`Name not accepted, as it is the same as before (ignoring case and accents).`, t`Rename Preset`);
900+ return;
901+ }
853902
854903 await presetManager.renamePreset(newName);
855904
905+ if (apiId === 'openai') {
906+ // This is a horrible mess, but prevents the renamed preset from being corrupted.
907+ $('#update_oai_preset').trigger('click');
908+ return;
909+ }
910+
856911 const successToast = !presetManager.isAdvancedFormatting() ? t`Preset renamed` : t`Template renamed`;
857912 toastr.success(successToast);
858913 });
public/scripts/reasoning.js+199 -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,21 +89,28 @@ 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 ?? '';
112+ case chat_completion_sources.XAI:
113+ return data?.choices?.[0]?.message?.reasoning_content ?? '';
75114 case chat_completion_sources.OPENROUTER:
76115 return data?.choices?.[0]?.message?.reasoning ?? '';
77116 case chat_completion_sources.MAKERSUITE:
@@ -664,57 +703,102 @@ export class PromptReasoning {
664703}
665704
666705function loadReasoningSettings() {
667706 UI.$('#reasoning_add_to_prompts')addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
668707 UI.$('#reasoning_add_to_prompts')addToPrompts.on('change', function () {
669708 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
670709 saveSettingsDebounced();
671710 });
672711
673712 UI.$('#reasoning_prefix')prefix.val(power_user.reasoning.prefix);
674713 UI.$('#reasoning_prefix')prefix.on('input', function () {
675714 power_user.reasoning.prefix = String($(this).val());
676715 saveSettingsDebounced();
677716 });
678717
679718 UI.$('#reasoning_suffix')suffix.val(power_user.reasoning.suffix);
680719 UI.$('#reasoning_suffix')suffix.on('input', function () {
681720 power_user.reasoning.suffix = String($(this).val());
682721 saveSettingsDebounced();
683722 });
684723
685724 UI.$('#reasoning_separator')separator.val(power_user.reasoning.separator);
686725 UI.$('#reasoning_separator')separator.on('input', function () {
687726 power_user.reasoning.separator = String($(this).val());
688727 saveSettingsDebounced();
689728 });
690729
691730 UI.$('#reasoning_max_additions')maxAdditions.val(power_user.reasoning.max_additions);
692731 UI.$('#reasoning_max_additions')maxAdditions.on('input', function () {
693732 power_user.reasoning.max_additions = Number($(this).val());
694733 saveSettingsDebounced();
695734 });
696735
697736 UI.$('#reasoning_auto_parse')autoParse.prop('checked', power_user.reasoning.auto_parse);
698737 UI.$('#reasoning_auto_parse')autoParse.on('change', function () {
699738 power_user.reasoning.auto_parse = !!$(this).prop('checked');
700739 saveSettingsDebounced();
701740 });
702741
703742 UI.$('#reasoning_auto_expand')autoExpand.prop('checked', power_user.reasoning.auto_expand);
704743 UI.$('#reasoning_auto_expand')autoExpand.on('change', function () {
705744 power_user.reasoning.auto_expand = !!$(this).prop('checked');
706745 toggleReasoningAutoExpand();
707746 saveSettingsDebounced();
708747 });
709748 toggleReasoningAutoExpand();
710749
711750 UI.$('#reasoning_show_hidden')showHidden.prop('checked', power_user.reasoning.show_hidden);
712751 UI.$('#reasoning_show_hidden')showHidden.on('change', function () {
713752 power_user.reasoning.show_hidden = !!$(this).prop('checked');
714753 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
715754 saveSettingsDebounced();
716755 });
717756 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
757+
758+ UI.$select.on('change', async function () {
759+ const name = String($(this).val());
760+ const template = reasoning_templates.find(p => p.name === name);
761+ if (!template) {
762+ return;
763+ }
764+
765+ UI.$prefix.val(template.prefix);
766+ UI.$suffix.val(template.suffix);
767+ UI.$separator.val(template.separator);
768+
769+ power_user.reasoning.name = name;
770+ power_user.reasoning.prefix = template.prefix;
771+ power_user.reasoning.suffix = template.suffix;
772+ power_user.reasoning.separator = template.separator;
773+
774+ saveSettingsDebounced();
775+ });
776+}
777+
778+function selectReasoningTemplateCallback(args, name) {
779+ if (!name) {
780+ return power_user.reasoning.name ?? '';
781+ }
782+
783+ const quiet = isTrueBoolean(args?.quiet);
784+ const templateNames = reasoning_templates.map(preset => preset.name);
785+ let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
786+
787+ if (!foundName) {
788+ const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
789+
790+ if (result.length === 0) {
791+ !quiet && toastr.warning(`Reasoning template "${name}" not found`);
792+ return '';
793+ }
794+
795+ foundName = result[0].item;
796+ }
797+
798+ UI.$select.val(foundName).trigger('change');
799+ !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
800+ return foundName;
801+
718802}
719803
720804function registerReasoningSlashCommands() {
@@ -750,6 +834,12 @@ function registerReasoningSlashCommands() {
750834 typeList: ARGUMENT_TYPE.NUMBER,
751835 enumProvider: commonEnumProviders.messages(),
752836 }),
837+ SlashCommandNamedArgument.fromProps({
838+ name: 'collapse',
839+ description: 'Whether to collapse the reasoning block. (If not provided, uses the default expand setting)',
840+ typeList: [ARGUMENT_TYPE.BOOLEAN],
841+ enumList: commonEnumProviders.boolean('trueFalse')(),
842+ }),
753843 ],
754844 unnamedArgumentList: [
755845 SlashCommandArgument.fromProps({
@@ -774,6 +864,9 @@ function registerReasoningSlashCommands() {
774864
775865 closeMessageEditor('reasoning');
776866 updateMessageBlock(messageId, message);
867+
868+ if (isTrueBoolean(String(args.collapse))) $(`#chat [mesid="${messageId}"] .mes_reasoning_details`).removeAttr('open');
869+ if (isFalseBoolean(String(args.collapse))) $(`#chat [mesid="${messageId}"] .mes_reasoning_details`).attr('open', '');
777870 return message.extra.reasoning;
778871 },
779872 }));
@@ -848,6 +941,42 @@ function registerReasoningSlashCommands() {
848941 : parsedReasoning.reasoning;
849942 },
850943 }));
944+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
945+ name: 'reasoning-template',
946+ aliases: ['reasoning-formatting', 'reasoning-preset'],
947+ callback: selectReasoningTemplateCallback,
948+ returns: 'template name',
949+ namedArgumentList: [
950+ SlashCommandNamedArgument.fromProps({
951+ name: 'quiet',
952+ description: 'Suppress the toast message on template change',
953+ typeList: [ARGUMENT_TYPE.BOOLEAN],
954+ defaultValue: 'false',
955+ enumList: commonEnumProviders.boolean('trueFalse')(),
956+ }),
957+ ],
958+ unnamedArgumentList: [
959+ SlashCommandArgument.fromProps({
960+ description: 'reasoning template name',
961+ typeList: [ARGUMENT_TYPE.STRING],
962+ enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
963+ }),
964+ ],
965+ helpString: `
966+ <div>
967+ Selects a reasoning template by name, using fuzzy search to find the closest match.
968+ Gets the current template if no name is provided.
969+ </div>
970+ <div>
971+ <strong>Example:</strong>
972+ <ul>
973+ <li>
974+ <pre><code class="language-stscript">/reasoning-template DeepSeek</code></pre>
975+ </li>
976+ </ul>
977+ </div>
978+ `,
979+ }));
851980}
852981
853982function registerReasoningMacros() {
@@ -1207,6 +1336,53 @@ function registerReasoningAppEvents() {
12071336 }
12081337}
12091338
1339+/**
1340+ * Loads reasoning templates from the settings data.
1341+ * @param {object} data Settings data
1342+ * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
1343+ * @returns {Promise<void>}
1344+ */
1345+export async function loadReasoningTemplates(data) {
1346+ if (data.reasoning !== undefined) {
1347+ reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
1348+ }
1349+
1350+ for (const template of reasoning_templates) {
1351+ $('<option>').val(template.name).text(template.name).appendTo(UI.$select);
1352+ }
1353+
1354+ // No template name, need to migrate
1355+ if (power_user.reasoning.name === undefined) {
1356+ const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
1357+ if (defaultTemplate) {
1358+ // If the reasoning settings were modified - migrate them to a custom template
1359+ if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
1360+ /** @type {ReasoningTemplate} */
1361+ const data = {
1362+ name: '[Migrated] Custom',
1363+ prefix: power_user.reasoning.prefix,
1364+ suffix: power_user.reasoning.suffix,
1365+ separator: power_user.reasoning.separator,
1366+ };
1367+ await getPresetManager('reasoning')?.savePreset(data.name, data);
1368+ power_user.reasoning.name = data.name;
1369+ } else {
1370+ power_user.reasoning.name = defaultTemplate.name;
1371+ }
1372+ } else {
1373+ // Template not found (deleted or content check skipped - leave blank)
1374+ power_user.reasoning.name = '';
1375+ }
1376+
1377+ saveSettingsDebounced();
1378+ }
1379+
1380+ UI.$select.val(power_user.reasoning.name);
1381+}
1382+
1383+/**
1384+ * Initializes reasoning settings and event handlers.
1385+ */
12101386export function initReasoning() {
12111387 loadReasoningSettings();
12121388 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+10 -6
@@ -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',
@@ -33,7 +34,6 @@ export const SECRET_KEYS = {
3334 ZEROONEAI: 'api_key_01ai',
3435 HUGGINGFACE: 'api_key_huggingface',
3536 STABILITY: 'api_key_stability',
36- BLOCKENTROPY: 'api_key_blockentropy',
3737 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
3838 NANOGPT: 'api_key_nanogpt',
3939 TAVILY: 'api_key_tavily',
@@ -42,6 +42,7 @@ export const SECRET_KEYS = {
4242 DEEPSEEK: 'api_key_deepseek',
4343 SERPER: 'api_key_serper',
4444 FALAI: 'api_key_falai',
45+ XAI: 'api_key_xai',
4546};
4647
4748const INPUT_MAP = {
@@ -73,10 +74,10 @@ const INPUT_MAP = {
7374 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
7475 [SECRET_KEYS.ZEROONEAI]: '#api_key_01ai',
7576 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
76- [SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy',
7777 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
7878 [SECRET_KEYS.GENERIC]: '#api_key_generic',
7979 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
80+ [SECRET_KEYS.XAI]: '#api_key_xai',
8081};
8182
8283async function clearSecret() {
@@ -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
@@ -188,14 +189,17 @@ export async function findSecret(key) {
188189}
189190
190191function authorizeOpenRouter() {
191192 const openRouterUrlredirectUrl = `https:new URL('/callback/openrouter', window.ai/auth?callback_url=${encodeURIComponent(location.origin)}`;
193+ const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(redirectUrl.toString())}`;
192194 location.href = openRouterUrl;
193195}
194196
195197async function checkOpenRouterAuth() {
196198 const params = new URLSearchParams(location.search);
197199 ifconst (source = params.hasget('codesource')) {;
198- const code = params.get('code');
200+ if (source === 'openrouter') {
201+ const query = new URLSearchParams(params.get('query'));
202+ const code = query.get('code');
199203 try {
200204 const response = await fetch('https://openrouter.ai/api/v1/auth/keys', {
201205 method: 'POST',
public/scripts/showdown-underscore.js+4 -4
@@ -8,10 +8,10 @@ export const markdownUnderscoreExt = () => {
88
99 return [{
1010 type: 'output',
1111 regex: new RegExp('(<code(?:\\s+[^>]*)?>[\\s\\S]*?<\\/code>|<style(?:\\s+[^>]*)?>[\\s\\S]*?<\\/style>)|\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'ggi'),
1212 replace: function(match, codeContenttagContent, italicContent) {
1313 if (codeContenttagContent) {
1414 // If it's inside <code> or <style> tags, return unchanged
1515 return match;
1616 } else if (italicContent) {
1717 // If it's an italic group, apply the replacement
public/scripts/slash-commands.js+73 -15
@@ -75,6 +75,9 @@ import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakC
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';
80+import { t } from './i18n.js';
7881export {
7982 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
8083};
@@ -224,6 +227,7 @@ export function initDefaultSlashCommands() {
224227 }));
225228 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
226229 name: 'sendas',
230+ rawQuotes: true,
227231 callback: sendMessageAs,
228232 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
229233 namedArgumentList: [
@@ -290,6 +294,7 @@ export function initDefaultSlashCommands() {
290294 }));
291295 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
292296 name: 'sys',
297+ rawQuotes: true,
293298 callback: sendNarratorMessage,
294299 aliases: ['nar'],
295300 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
@@ -354,6 +359,7 @@ export function initDefaultSlashCommands() {
354359 }));
355360 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
356361 name: 'comment',
362+ rawQuotes: true,
357363 callback: sendCommentMessage,
358364 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
359365 namedArgumentList: [
@@ -571,6 +577,7 @@ export function initDefaultSlashCommands() {
571577 }));
572578 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
573579 name: 'send',
580+ rawQuotes: true,
574581 callback: sendUserMessageCallback,
575582 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
576583 namedArgumentList: [
@@ -933,6 +940,7 @@ export function initDefaultSlashCommands() {
933940 }));
934941 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
935942 name: 'echo',
943+ rawQuotes: true,
936944 callback: echoCallback,
937945 returns: 'the text',
938946 namedArgumentList: [
@@ -1554,16 +1562,28 @@ export function initDefaultSlashCommands() {
15541562 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
15551563 name: 'buttons',
15561564 callback: buttonsCallback,
15571565 returns: 'clicked button label (or array of labels if multiple is enabled)',
15581566 namedArgumentList: [
15591567 new SlashCommandNamedArgument.fromProps({
1560- 'labels', 'button labels', [ARGUMENT_TYPE.LIST], true,
1568+ name: 'labels',
1561- ),
1569+ description: 'button labels',
1570+ typeList: [ARGUMENT_TYPE.LIST],
1571+ isRequired: true,
1572+ }),
1573+ SlashCommandNamedArgument.fromProps({
1574+ name: 'multiple',
1575+ description: 'if enabled multiple buttons can be clicked/toggled, and all clicked buttons are returned as an array',
1576+ typeList: [ARGUMENT_TYPE.BOOLEAN],
1577+ enumList: commonEnumProviders.boolean('trueFalse')(),
1578+ defaultValue: 'false',
1579+ }),
15621580 ],
15631581 unnamedArgumentList: [
15641582 new SlashCommandArgument.fromProps({
1565- 'text', [ARGUMENT_TYPE.STRING], true,
1583+ description: 'text',
1566- ),
1584+ typeList: [ARGUMENT_TYPE.STRING],
1585+ isRequired: true,
1586+ }),
15671587 ],
15681588 helpString: `
15691589 <div>
@@ -2375,6 +2395,18 @@ async function trimTokensCallback(arg, value) {
23752395 }
23762396}
23772397
2398+/**
2399+ * Displays a popup with buttons based on provided labels and handles button interactions.
2400+ *
2401+ * @param {object} args - Named arguments for the command
2402+ * @param {string} args.labels - JSON string of an array of button labels
2403+ * @param {string} [args.multiple=false] - Flag indicating if multiple buttons can be toggled
2404+ * @param {string} text - The text content to be displayed within the popup
2405+ *
2406+ * @returns {Promise<string>} - A promise that resolves to a string of the button labels selected
2407+ * If 'multiple' is true, returns a JSON string array of labels.
2408+ * If 'multiple' is false, returns a single label string.
2409+ */
23782410async function buttonsCallback(args, text) {
23792411 try {
23802412 /** @type {string[]} */
@@ -2385,6 +2417,10 @@ async function buttonsCallback(args, text) {
23852417 return '';
23862418 }
23872419
2420+ /** @type {Set<number>} */
2421+ const multipleToggledState = new Set();
2422+ const multiple = isTrueBoolean(args?.multiple);
2423+
23882424 // Map custom buttons to results. Start at 2 because 1 and 0 are reserved for ok and cancel
23892425 const resultToButtonMap = new Map(buttons.map((button, index) => [index + 2, button]));
23902426
@@ -2402,11 +2438,24 @@ async function buttonsCallback(args, text) {
24022438
24032439 for (const [result, button] of resultToButtonMap) {
24042440 const buttonElement = document.createElement('div');
24052441 buttonElement.classList.add('menu_button', 'result-control', 'wide100p');
2406- buttonElement.dataset.result = String(result);
2442+
2443+ if (multiple) {
2444+ buttonElement.classList.add('toggleable');
2445+ buttonElement.dataset.toggleValue = String(result);
24072446 buttonElement.addEventListener('click', async () => {
2408- await popup.complete(result);
2447+ buttonElement.classList.toggle('toggled');
2448+ if (buttonElement.classList.contains('toggled')) {
2449+ multipleToggledState.add(result);
2450+ } else {
2451+ multipleToggledState.delete(result);
2452+ }
24092453 });
2454+ } else {
2455+ buttonElement.classList.add('result-control');
2456+ buttonElement.dataset.result = String(result);
2457+ }
2458+
24102459 buttonElement.innerText = button;
24112460 buttonContainer.appendChild(buttonElement);
24122461 }
@@ -2422,10 +2471,19 @@ async function buttonsCallback(args, text) {
24222471 popupContainer.style.flexDirection = 'column';
24232472 popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup
24242473
24252474 popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: 'multiple ? t`Ok` : t`Cancel'`, allowVerticalScrolling: true });
24262475 popup.show()
24272476 .then((result => resolve(typeof result === 'number' ? resultToButtonMap.getgetResult(result) ?? '' : '')))
24282477 .catch(() => resolve(''));
2478+
2479+ /** @returns {string} @param {string|number|boolean} result */
2480+ function getResult(result) {
2481+ if (multiple) {
2482+ const array = result === POPUP_RESULT.AFFIRMATIVE ? Array.from(multipleToggledState).map(r => resultToButtonMap.get(r) ?? '') : [];
2483+ return JSON.stringify(array);
2484+ }
2485+ return typeof result === 'number' ? resultToButtonMap.get(result) ?? '' : '';
2486+ }
24292487 });
24302488 } catch {
24312489 return '';
@@ -3883,8 +3941,8 @@ function getModelOptions(quiet) {
38833941 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
38843942 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
38853943 { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
3886- { id: 'model_blockentropy_select', api: 'openai', type: chat_completion_sources.BLOCKENTROPY },
38873944 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
3945+ { id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI },
38883946 { id: 'model_novel_select', api: 'novel', type: null },
38893947 { id: 'horde_model', api: 'koboldhorde', type: null },
38903948 ];
@@ -4345,7 +4403,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
43454403 * @prop {boolean} [handleParserErrors] (true) Whether to handle parser errors (show toast on error) or throw.
43464404 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
43474405 * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
43484406 * @prop {{[id:PARSER_FLAG]:boolean}import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
43494407 * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
43504408 * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
43514409 * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
@@ -4355,7 +4413,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
43554413/**
43564414 * @typedef ExecuteSlashCommandsOnChatInputOptions
43574415 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
43584416 * @prop {{[id:PARSER_FLAG]:boolean}import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
43594417 * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
43604418 * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
43614419 */
public/scripts/slash-commands/SlashCommand.js+13 -5
@@ -1,18 +1,15 @@
11import { hljs } from '../../lib.js';
2+import { t } from '../i18n.js';
23import { SlashCommandAbortController } from './SlashCommandAbortController.js';
34import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
45import { SlashCommandClosure } from './SlashCommandClosure.js';
56import { SlashCommandDebugController } from './SlashCommandDebugController.js';
6-import { PARSER_FLAG } from './SlashCommandParser.js';
77import { SlashCommandScope } from './SlashCommandScope.js';
88
9-
10-
11-
129/**
1310 * @typedef {{
1411 * _scope:SlashCommandScope,
15- * _parserFlags:{[id:PARSER_FLAG]:boolean},
12+ * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
1613 * _abortController:SlashCommandAbortController,
1714 * _debugController:SlashCommandDebugController,
1815 * _hasUnnamedArgument:boolean,
@@ -40,6 +37,7 @@ export class SlashCommand {
4037 * @param {string} [props.helpString]
4138 * @param {boolean} [props.splitUnnamedArgument]
4239 * @param {Number} [props.splitUnnamedArgumentCount]
40+ * @param {boolean} [props.rawQuotes] If set to true, does not remove wrapping quotes from the unnamed argument.
4341 * @param {string[]} [props.aliases]
4442 * @param {string} [props.returns]
4543 * @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
@@ -58,6 +56,7 @@ export class SlashCommand {
5856 /**@type {string}*/ helpString;
5957 /**@type {boolean}*/ splitUnnamedArgument = false;
6058 /**@type {Number}*/ splitUnnamedArgumentCount;
59+ /** @type {boolean} */ rawQuotes = false;
6160 /**@type {string[]}*/ aliases = [];
6261 /**@type {string}*/ returns;
6362 /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
@@ -262,6 +261,15 @@ export class SlashCommand {
262261 ].filter(it=>it).join('\n');
263262 head.append(src);
264263 }
264+ if (this.rawQuotes) {
265+ const rawQuotes = document.createElement('div'); {
266+ rawQuotes.classList.add('rawQuotes');
267+ rawQuotes.classList.add('fa-solid');
268+ rawQuotes.classList.add('fa-quote-left');
269+ rawQuotes.title = t`Does not alter quoted literal unnamed arguments`;
270+ head.append(rawQuotes);
271+ }
272+ }
265273 specs.append(head);
266274 }
267275 const body = document.createElement('div'); {
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+8 -9
@@ -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;
@@ -976,7 +975,7 @@ export class SlashCommandParser {
976975 cmd.startUnnamedArgs = this.index - (/\s(\s*)$/s.exec(this.behind)?.[1]?.length ?? 0);
977976 cmd.endUnnamedArgs = this.index;
978977 if (this.testUnnamedArgument()) {
979978 cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument, cmd?.command?.splitUnnamedArgumentCount, cmd?.command?.rawQuotes);
980979 cmd.endUnnamedArgs = this.index;
981980 if (cmd.name == 'let') {
982981 const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key');
@@ -1036,7 +1035,7 @@ export class SlashCommandParser {
10361035 testUnnamedArgumentEnd() {
10371036 return this.testCommandEnd();
10381037 }
10391038 parseUnnamedArgument(split, splitCount = null, rawQuotes = false) {
10401039 const wasSplit = split;
10411040 /**@type {SlashCommandClosure|String}*/
10421041 let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one
@@ -1046,7 +1045,7 @@ export class SlashCommandParser {
10461045 /**@type {SlashCommandUnnamedArgumentAssignment}*/
10471046 let assignment = new SlashCommandUnnamedArgumentAssignment();
10481047 assignment.start = this.index;
10491048 if (!split && !rawQuotes && this.testQuotedValue()) {
10501049 // if the next bit is a quoted value, take the whole value and gather contents as a list
10511050 assignment.value = this.parseQuotedValue();
10521051 assignment.end = this.index;
public/scripts/slash-commands/SlashCommandScope.js+0 -0
public/scripts/slash-commands/SlashCommandUnnamedArgumentAssignment.js+0 -0
public/scripts/st-context.js+11 -1
@@ -49,6 +49,7 @@ import {
4949 clearChat,
5050 unshallowCharacter,
5151 deleteLastMessage,
52+ getCharacterCardFields,
5253} from '../script.js';
5354import {
5455 extension_settings,
@@ -78,9 +79,11 @@ 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, reloadEditor, 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';
86+import { IGNORE_SYMBOL } from './constants.js';
8487
8588export function getContext() {
8689 return {
@@ -188,6 +191,7 @@ export function getContext() {
188191 textCompletionSettings: textgenerationwebui_settings,
189192 powerUserSettings: power_user,
190193 getCharacters,
194+ getCharacterCardFields,
191195 uuidv4,
192196 humanizedDateTime,
193197 updateMessageBlock,
@@ -204,8 +208,10 @@ export function getContext() {
204208 },
205209 loadWorldInfo,
206210 saveWorldInfo,
211+ reloadWorldInfoEditor: reloadEditor,
207212 updateWorldInfoList,
208213 convertCharacterBook,
214+ getWorldInfoPrompt,
209215 CONNECT_API_MAP,
210216 getTextGenServer,
211217 extractMessageFromData,
@@ -215,10 +221,14 @@ export function getContext() {
215221 clearChat,
216222 ChatCompletionService,
217223 TextCompletionService,
224+ ConnectionManagerRequestService,
218225 updateReasoningUI,
219226 parseReasoningFromString,
220227 unshallowCharacter,
221228 unshallowGroupMembers,
229+ symbols: {
230+ ignore: IGNORE_SYMBOL,
231+ },
222232 };
223233}
224234
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/promptManagerExportForCharacter.html+0 -4
@@ -1,4 +0,0 @@
1-<div class="row">
2- <a class="export-promptmanager-prompts-character list-group-item" data-i18n="Export for character">Export for character</a>
3- <span class="tooltip fa-solid fa-info-circle" data-i18n="[title]Export prompts for this character, including their order." title="Export prompts for this character, including their order."></span>
4-</div>
public/scripts/templates/promptManagerExportPopup.html+0 -12
@@ -1,12 +0,0 @@
1-<div id="prompt-manager-export-format-popup" class="list-group">
2- <div class="prompt-manager-export-format-popup-flex">
3- <div class="row">
4- <a class="export-promptmanager-prompts-full list-group-item" data-i18n="Export all">Export all</a>
5- <span class="tooltip fa-solid fa-info-circle" data-i18n="[title]Export all your prompts to a file" title="Export all your prompts to a file"></span>
6- </div>
7- {{#if isGlobalStrategy}}
8- {{else}}
9- {{{exportForCharacter}}}
10- {{/if}}
11- </div>
12-</div>
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/templates/worldInfoKeywordHeaders.html+1 -1
@@ -1,4 +1,4 @@
11<div id="WIEntryHeaderTitlesPC" class="flex-container wide100p spaceBetween justifyCenter textAlignCenter" style="padding:0 47.5em0em;">
22 <small class="flex1" data-i18n="Title/Memo">Title/Memo</small>
33 <small style="width: calc(3.5em + 10px)" data-i18n="Strategy">Strategy</small>
44 <small style="width: calc(3.5em + 20px)" data-i18n="Position">Position</small>
public/scripts/textgen-models.js+9 -0
@@ -58,6 +58,8 @@ const OPENROUTER_PROVIDERS = [
5858 'Minimax',
5959 'Nineteen',
6060 'Liquid',
61+ 'Stealth',
62+ 'NCompass',
6163 'InferenceNet',
6264 'Friendli',
6365 'AionLabs',
@@ -69,6 +71,9 @@ const OPENROUTER_PROVIDERS = [
6971 'Targon',
7072 'Ubicloud',
7173 'Parasail',
74+ 'Phala',
75+ 'Cent-ML',
76+ 'Venice',
7277 '01.AI',
7378 'HuggingFace',
7479 'Mancer',
@@ -923,6 +928,10 @@ export function getCurrentDreamGenModelTokenizer() {
923928 return tokenizers.YI;
924929 } else if (model.id.startsWith('opus-v1-xl')) {
925930 return tokenizers.LLAMA;
931+ } else if (model.id.startsWith('lucid-v1-medium')) {
932+ return tokenizers.NEMO;
933+ } else if (model.id.startsWith('lucid-v1-extra-large')) {
934+ return tokenizers.LLAMA3;
926935 } else {
927936 return tokenizers.MISTRAL;
928937 }
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/tokenizers.js+0 -9
@@ -729,15 +729,6 @@ export function getTokenizerModel() {
729729 return yiTokenizer;
730730 }
731731
732- if (oai_settings.chat_completion_source === chat_completion_sources.BLOCKENTROPY) {
733- if (oai_settings.blockentropy_model.includes('llama3')) {
734- return llama3Tokenizer;
735- }
736- if (oai_settings.blockentropy_model.includes('miqu') || oai_settings.blockentropy_model.includes('mixtral')) {
737- return mistralTokenizer;
738- }
739- }
740-
741732 // Default to Turbo 3.5
742733 return turboTokenizer;
743734}
public/scripts/tool-calling.js+1 -0
@@ -586,6 +586,7 @@ export class ToolManager {
586586 chat_completion_sources.DEEPSEEK,
587587 chat_completion_sources.MAKERSUITE,
588588 chat_completion_sources.AI21,
589+ chat_completion_sources.XAI,
589590 ];
590591 return supportedSources.includes(oai_settings.chat_completion_source);
591592 }
public/scripts/user.js+2 -2
@@ -9,8 +9,8 @@ import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './uti
99export let currentUser = null;
1010export let accountsEnabled = false;
1111
1212// Extend the session every 3010 minutes
1313const SESSION_EXTEND_INTERVAL = 3010 * 60 * 1000;
1414
1515/**
1616 * Enable or disable user account controls in the UI.
public/scripts/utils.js+2 -0
@@ -1437,6 +1437,8 @@ export async function ensureImageFormatSupported(file) {
14371437 'image/tiff',
14381438 'image/gif',
14391439 'image/apng',
1440+ 'image/webp',
1441+ 'image/avif',
14401442 ];
14411443
14421444 if (supportedTypes.includes(file.type) || !file.type.startsWith('image/')) {
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+233 -14
@@ -1,7 +1,7 @@
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, equalsIgnoreCaseAndAccents } from './utils.js';
55import { extension_settings, getContext } from './extensions.js';
66import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
77import { isMobile } from './RossAscends-mods.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
@@ -899,13 +908,12 @@ export function setWorldInfoSettings(settings, data) {
899908 registerWorldInfoSlashCommands();
900909}
901910
902-function registerWorldInfoSlashCommands() {
903911/**
904912 * Reloads the editor with the specified world info file
905913 * @param {string} file - The file to load in the editor
906914 * @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
907915 */
908916 export function reloadEditor(file, loadIfNotSelected = false) {
909917 const currentIndex = Number($('#world_editor_select').val());
910918 const selectedIndex = world_names.indexOf(file);
911919 if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
@@ -913,6 +921,7 @@ function registerWorldInfoSlashCommands() {
913921 }
914922}
915923
924+function registerWorldInfoSlashCommands() {
916925 /**
917926 * Gets a *rough* approximation of the current chat context.
918927 * Normally, it is provided externally by the prompt builder.
@@ -1329,6 +1338,18 @@ function registerWorldInfoSlashCommands() {
13291338 }
13301339 }
13311340
1341+ async function getGlobalBooksCallback() {
1342+ if (!selected_world_info?.length) {
1343+ return JSON.stringify([]);
1344+ }
1345+
1346+ let entries = selected_world_info.slice();
1347+
1348+ console.debug(`[WI] Selected global world info has ${entries.length} entries`, selected_world_info);
1349+
1350+ return JSON.stringify(entries);
1351+ }
1352+
13321353 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
13331354 name: 'world',
13341355 callback: onWorldInfoChange,
@@ -1371,6 +1392,13 @@ function registerWorldInfoSlashCommands() {
13711392 aliases: ['getchatlore', 'getchatwi'],
13721393 }));
13731394 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1395+ name: 'getglobalbooks',
1396+ callback: getGlobalBooksCallback,
1397+ returns: 'list of selected lorebook names',
1398+ helpString: 'Get a list of names of the selected global lorebooks and pass it down the pipe.',
1399+ aliases: ['getgloballore', 'getglobalwi'],
1400+ }));
1401+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
13741402 name: 'getpersonabook',
13751403 callback: getPersonaBookCallback,
13761404 returns: 'lorebook name',
@@ -2199,7 +2227,7 @@ function verifyWorldInfoSearchSortRule() {
21992227 * Use `originalWIDataKeyMap` to find the correct value to be set.
22002228 *
22012229 * @param {object} data - The data object containing the original data entries.
22022230 * @param {stringnumber} uid - The unique identifier of the data entry.
22032231 * @param {string} key - The key of the value to be set.
22042232 * @param {any} value - The value to be set.
22052233 */
@@ -2223,7 +2251,9 @@ export function setWIOriginalDataValue(data, uid, key, value) {
22232251 */
22242252export function deleteWIOriginalDataValue(data, uid) {
22252253 if (data.originalData && Array.isArray(data.originalData.entries)) {
2226- const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);
2254+ // Non-strict equality is used here to allow for both string and number comparisons
2255+ // @eslint-disable-next-line eqeqeq
2256+ const originalIndex = data.originalData.entries.findIndex(x => x.uid == uid);
22272257
22282258 if (originalIndex >= 0) {
22292259 data.originalData.entries.splice(originalIndex, 1);
@@ -2671,8 +2701,10 @@ export async function getWorldEntry(name, data, entry) {
26712701 $(counter).text(numberOfTokens);
26722702 }, debounce_timeout.relaxed);
26732703
2704+ const contentInputId = `world_entry_content_${entry.uid}`;
26742705 const contentInput = template.find('textarea[name="content"]');
26752706 contentInput.data('uid', entry.uid);
2707+ contentInput.attr('id', contentInputId);
26762708 contentInput.on('input', async function (_, { skipCount } = {}) {
26772709 const uid = $(this).data('uid');
26782710 const value = $(this).val();
@@ -2689,7 +2721,9 @@ export async function getWorldEntry(name, data, entry) {
26892721 countTokensDebounced(counter, value);
26902722 });
26912723 contentInput.val(entry.content).trigger('input', { skipCount: true });
2692- //initScrollHeight(contentInput);
2724+
2725+ const contentExpandButton = template.find('.editor_maximize');
2726+ contentExpandButton.attr('data-for', contentInputId);
26932727
26942728 template.find('.inline-drawer-toggle').on('click', function () {
26952729 if (counter.data('first-run')) {
@@ -3130,6 +3164,84 @@ export async function getWorldEntry(name, data, entry) {
31303164 updateEditor(navigation_option.previous);
31313165 });
31323166
3167+ // move button
3168+ const moveButton = template.find('.move_entry_button');
3169+ moveButton.attr('data-uid', entry.uid);
3170+ moveButton.attr('data-current-world', name);
3171+ moveButton.on('click', async function (e) {
3172+ e.stopPropagation();
3173+ const sourceUid = $(this).attr('data-uid');
3174+ const sourceWorld = $(this).attr('data-current-world');
3175+ const sourceWorldInfo = await loadWorldInfo(sourceWorld);
3176+ if (!sourceWorldInfo) {
3177+ return;
3178+ }
3179+ const sourceName = sourceWorldInfo.entries[sourceUid]?.comment;
3180+ if (sourceName === undefined) {
3181+ return;
3182+ }
3183+
3184+ const select = document.createElement('select');
3185+ select.id = 'move_entry_target_select';
3186+ select.classList.add('text_pole', 'wide100p', 'marginTop10');
3187+
3188+ const defaultOption = document.createElement('option');
3189+ defaultOption.value = '';
3190+ defaultOption.textContent = `-- ${t`Select Target Lorebook`} --`;
3191+ select.appendChild(defaultOption);
3192+
3193+ let selectableWorldCount = 0;
3194+ world_names.forEach(worldName => {
3195+ if (worldName !== sourceWorld) { // Exclude current world
3196+ const option = document.createElement('option');
3197+ option.value = world_names.indexOf(worldName).toString();
3198+ option.textContent = worldName;
3199+ select.appendChild(option);
3200+ selectableWorldCount++;
3201+ }
3202+ });
3203+
3204+ if (selectableWorldCount === 0) {
3205+ toastr.warning(t`There are no other lorebooks to move to.`);
3206+ return;
3207+ }
3208+
3209+ // Create wrapper div
3210+ const wrapper = document.createElement('div');
3211+ wrapper.textContent = t`Move "${sourceName}" to:`;
3212+
3213+ // Create container and append elements
3214+ const container = document.createElement('div');
3215+ container.appendChild(wrapper);
3216+ container.appendChild(select);
3217+
3218+ let selectedWorldIndex = -1;
3219+ select.addEventListener('change', function() {
3220+ selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
3221+ });
3222+
3223+ const popupConfirm = await callGenericPopup(container, POPUP_TYPE.CONFIRM, '', {
3224+ okButton: t`Move`,
3225+ cancelButton: t`Cancel`,
3226+ });
3227+ if (!popupConfirm) {
3228+ return;
3229+ }
3230+
3231+ if (selectedWorldIndex === -1) {
3232+ return;
3233+ }
3234+
3235+ const selectedValue = world_names[selectedWorldIndex];
3236+
3237+ if (!selectedValue) {
3238+ toastr.warning(t`Please select a target lorebook.`);
3239+ return;
3240+ }
3241+
3242+ await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);
3243+ });
3244+
31333245 // scan depth
31343246 const scanDepthInput = template.find('input[name="scanDepth"]');
31353247 scanDepthInput.data('uid', entry.uid);
@@ -3493,6 +3605,10 @@ async function renameWorldInfo(name, data) {
34933605 console.debug('World info rename cancelled');
34943606 return;
34953607 }
3608+ if (equalsIgnoreCaseAndAccents(oldName, newName)) {
3609+ toastr.warning(t`Name not accepted, as it is the same as before (ignoring case and accents).`, t`Rename World Info`);
3610+ return;
3611+ }
34963612
34973613 const entryPreviouslySelected = selected_world_info.findIndex((e) => e === oldName);
34983614
@@ -3862,7 +3978,14 @@ function parseDecorators(content) {
38623978 * @param {string[]} chat The chat messages to scan, in reverse order.
38633979 * @param {number} maxContext The maximum context size of the generation.
38643980 * @param {boolean} isDryRun Whether to perform a dry run.
3865- * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
3981+ * @typedef {object} WIActivated
3982+ * @property {string} worldInfoBefore The world info before the chat.
3983+ * @property {string} worldInfoAfter The world info after the chat.
3984+ * @property {any[]} EMEntries The entries for examples.
3985+ * @property {any[]} WIDepthEntries The depth entries.
3986+ * @property {any[]} ANBeforeEntries The entries before Author's Note.
3987+ * @property {any[]} ANAfterEntries The entries after Author's Note.
3988+ * @property {Set<any>} allActivatedEntries All entries.
38663989 * @returns {Promise<WIActivated>} The world info activated.
38673990 */
38683991export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +4029,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39064029 timedEffects.checkTimedEffects();
39074030
39084031 if (sortedEntries.length === 0) {
39094032 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
39104033 }
39114034
39124035 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4478,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
43554478 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
43564479 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
43574480
43584481 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
43594482}
43604483
43614484/**
@@ -5251,3 +5374,99 @@ jQuery(() => {
52515374 });
52525375 });
52535376});
5377+
5378+/**
5379+ * Moves a World Info entry from a source lorebook to a target lorebook.
5380+ *
5381+ * @param {string} sourceName - The name of the source lorebook file.
5382+ * @param {string} targetName - The name of the target lorebook file.
5383+ * @param {string|number} uid - The UID of the entry to move from the source lorebook.
5384+ * @returns {Promise<boolean>} True if the move was successful, false otherwise.
5385+ */
5386+export async function moveWorldInfoEntry(sourceName, targetName, uid) {
5387+ if (sourceName === targetName) {
5388+ return false;
5389+ }
5390+
5391+ if (!world_names.includes(sourceName)) {
5392+ toastr.error(t`Source lorebook '${sourceName}' not found.`);
5393+ console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`);
5394+ return false;
5395+ }
5396+
5397+ if (!world_names.includes(targetName)) {
5398+ toastr.error(t`Target lorebook '${targetName}' not found.`);
5399+ console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`);
5400+ return false;
5401+ }
5402+
5403+ const entryUidString = String(uid);
5404+
5405+ try {
5406+ const sourceData = await loadWorldInfo(sourceName);
5407+ const targetData = await loadWorldInfo(targetName);
5408+
5409+ if (!sourceData || !sourceData.entries) {
5410+ toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`);
5411+ console.error(`[WI Move] Could not load source data for '${sourceName}'.`);
5412+ return false;
5413+ }
5414+ if (!targetData || !targetData.entries) {
5415+ toastr.error(t`Failed to load data for target lorebook '${targetName}'.`);
5416+ console.error(`[WI Move] Could not load target data for '${targetName}'.`);
5417+ return false;
5418+ }
5419+
5420+ if (!sourceData.entries[entryUidString]) {
5421+ toastr.error(t`Entry not found in source lorebook '${sourceName}'.`);
5422+ console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`);
5423+ return false;
5424+ }
5425+
5426+ const entryToMove = structuredClone(sourceData.entries[entryUidString]);
5427+
5428+
5429+ const newUid = getFreeWorldEntryUid(targetData);
5430+ if (newUid === null) {
5431+ console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`);
5432+ return false;
5433+ }
5434+
5435+ entryToMove.uid = newUid;
5436+ // Place the entry at the end of the target lorebook
5437+ const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1);
5438+ entryToMove.displayIndex = maxDisplayIndex + 1;
5439+
5440+ targetData.entries[newUid] = entryToMove;
5441+
5442+ delete sourceData.entries[entryUidString];
5443+ // Remove from originalData if it exists
5444+ deleteWIOriginalDataValue(sourceData, entryUidString);
5445+ // TODO: setWIOriginalDataValue
5446+ console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`);
5447+
5448+
5449+ await saveWorldInfo(targetName, targetData, true);
5450+ console.debug(`[WI Move] Saved target lorebook '${targetName}'.`);
5451+ await saveWorldInfo(sourceName, sourceData, true);
5452+ console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`);
5453+
5454+
5455+ console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`);
5456+
5457+ // Check if the currently viewed book in the editor is the source or target and reload it
5458+ const currentEditorBookIndex = Number($('#world_editor_select').val());
5459+ if (!isNaN(currentEditorBookIndex)) {
5460+ const currentEditorBookName = world_names[currentEditorBookIndex];
5461+ if (currentEditorBookName === sourceName || currentEditorBookName === targetName) {
5462+ reloadEditor(currentEditorBookName);
5463+ }
5464+ }
5465+
5466+ return true;
5467+ } catch (error) {
5468+ toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`);
5469+ console.error('[WI Move] Unexpected error:', error);
5470+ return false;
5471+ }
5472+}
public/style.css+40 -1
@@ -2086,6 +2086,15 @@ body[data-stscript-style] .hljs.language-stscript {
20862086 }
20872087 }
20882088
2089+ >.head>.rawQuotes {
2090+ padding: 0 0.5em;
2091+ cursor: help;
2092+
2093+ &:hover {
2094+ text-decoration: 1px dotted underline;
2095+ }
2096+ }
2097+
20892098 >.head>.source {
20902099 padding: 0 0.5em;
20912100 cursor: help;
@@ -2911,6 +2920,35 @@ select option:not(:checked) {
29112920 pointer-events: none;
29122921}
29132922
2923+.menu_button.toggleable {
2924+ padding-left: 20px;
2925+}
2926+
2927+.menu_button.toggleable.toggled {
2928+ border-color: var(--active);
2929+}
2930+
2931+.menu_button.toggleable:not(.toggled) {
2932+ filter: brightness(80%);
2933+}
2934+
2935+.menu_button.toggleable::before {
2936+ font-family: "Font Awesome 6 Free";
2937+ margin-left: 10px;
2938+ position: absolute;
2939+ left: 0;
2940+}
2941+
2942+.menu_button.toggleable.toggled::before {
2943+ content: "\f00c";
2944+ color: var(--active);
2945+}
2946+
2947+.menu_button.toggleable:not(.toggled)::before {
2948+ content: "\f00d";
2949+ color: var(--fullred);
2950+}
2951+
29142952.fav_on {
29152953 color: var(--golden) !important;
29162954}
@@ -2921,7 +2959,7 @@ select option:not(:checked) {
29212959}
29222960
29232961.menu_button.togglable:not(.toggleEnabled) {
29242962 color: redvar(--fullred);
29252963}
29262964
29272965.displayBlock {
@@ -4957,6 +4995,7 @@ a:hover {
49574995 max-width: 100%;
49584996 max-height: 40vh;
49594997 image-rendering: -webkit-optimize-contrast;
4998+ cursor: pointer;
49604999}
49615000
49625001.mes_img_swipes,
server.js+21 -2
@@ -20,6 +20,7 @@ import bodyParser from 'body-parser';
2020import open from 'open';
2121
2222// local library imports
23+import './src/fetch-patch.js';
2324import { serverEvents, EVENT_NAMES } from './src/server-events.js';
2425import { CommandLineParser } from './src/command-line.js';
2526import { loadPlugins } from './src/plugin-loader.js';
@@ -66,6 +67,7 @@ import { init as statsInit, onExit as statsOnExit } from './src/endpoints/stats.
6667import { checkForNewContent } from './src/endpoints/content-manager.js';
6768import { init as settingsInit } from './src/endpoints/settings.js';
6869import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from './src/server-startup.js';
70+import { diskCache } from './src/endpoints/characters.js';
6971
7072// Unrestrict console logs display limit
7173util.inspect.defaultOptions.maxArrayLength = null;
@@ -149,7 +151,7 @@ if (cliArgs.enableCorsProxy) {
149151
150152app.use(cookieSession({
151153 name: getCookieSessionName(),
152154 sameSite: 'strictlax',
153155 httpOnly: true,
154156 maxAge: getSessionCookieAge(),
155157 secret: getCookieSecret(globalThis.DATA_ROOT),
@@ -212,6 +214,17 @@ app.get('/', getCacheBusterMiddleware(), (request, response) => {
212214 return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') });
213215});
214216
217+// Callback endpoint for OAuth PKCE flows (e.g. OpenRouter)
218+app.get('/callback/:source?', (request, response) => {
219+ const source = request.params.source;
220+ const query = request.url.split('?')[1];
221+ const searchParams = new URLSearchParams();
222+ source && searchParams.set('source', source);
223+ query && searchParams.set('query', query);
224+ const path = `/?${searchParams.toString()}`;
225+ return response.redirect(307, path);
226+});
227+
215228// Host login page
216229app.get('/login', loginPageMiddleware);
217230
@@ -267,7 +280,8 @@ async function preSetupTasks() {
267280
268281 const directories = await getUserDirectoriesList();
269282 await checkForNewContent(directories);
270283 await ensureThumbnailCache(directories);
284+ await diskCache.verify(directories);
271285 cleanUploads();
272286 migrateAccessLog();
273287
@@ -286,6 +300,7 @@ async function preSetupTasks() {
286300 if (typeof cleanupPlugins === 'function') {
287301 await cleanupPlugins();
288302 }
303+ diskCache.dispose();
289304 setWindowTitle(consoleTitle);
290305 process.exit();
291306 };
@@ -315,8 +330,12 @@ async function postSetupTasks(result) {
315330 const autorunUrl = cliArgs.getAutorunUrl(autorunHostname);
316331
317332 if (cliArgs.autorun) {
333+ try {
318334 console.log('Launching in a browser...');
319335 await open(autorunUrl.toString());
336+ } catch (error) {
337+ console.error('Failed to launch the browser. Open the URL manually.');
338+ }
320339 }
321340
322341 setWindowTitle('SillyTavern WebServer');
src/character-card-parser.js+3 -3
@@ -1,7 +1,7 @@
11import fs from 'node:fs';
22import { Buffer } from 'node:buffer';
33
44import encode from './png-chunks-/encode.js';
55import extract from 'png-chunks-extract';
66import PNGtext from 'png-chunk-text';
77
@@ -81,9 +81,9 @@ export const read = (image) => {
8181 * Parses a card image and returns the character metadata.
8282 * @param {string} cardUrl Path to the card image
8383 * @param {string} format File format
8484 * @returns {Promise<string>} Character data
8585 */
8686export const parse = async (cardUrl, format) => {
8787 let fileFormat = format === undefined ? 'png' : format;
8888
8989 switch (fileFormat) {
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
src/endpoints/avatars.js+0 -0
src/endpoints/backends/chat-completions.js+0 -0
src/endpoints/backends/kobold.js+0 -0
src/endpoints/backends/text-completions.js+0 -0
src/endpoints/characters.js+0 -0
src/endpoints/chats.js+0 -0
src/endpoints/content-manager.js+0 -0
src/endpoints/google.js+0 -0
src/endpoints/novelai.js+0 -0
src/endpoints/openai.js+0 -0
src/endpoints/presets.js+0 -0
src/endpoints/secrets.js+0 -0
src/endpoints/settings.js+0 -0
src/endpoints/stable-diffusion.js+0 -0
src/endpoints/thumbnails.js+0 -0
src/endpoints/tokenizers.js+0 -0
src/endpoints/vectors.js+0 -0
src/fetch-patch.js+0 -0
src/jimp.js+0 -0
src/png/encode.js+0 -0
src/prompt-converters.js+0 -0
src/users.js+0 -0
src/util.js+0 -0
Diff truncated