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 = {
3 extends: [3 extends: [
4 'eslint:recommended',4 'eslint:recommended',
5 ],5 ],
6 plugins: [
7 'jsdoc',
8 ],
6 env: {9 env: {
7 es6: true,10 es6: true,
8 },11 },
@@ -78,6 +81,7 @@ module.exports = {
78 'public/scripts/extensions/tts/lib/**',81 'public/scripts/extensions/tts/lib/**',
79 ],82 ],
80 rules: {83 rules: {
84 'jsdoc/no-undefined-types': ['warn', { disableReporting: true, markVariablesAsUsed: true }],
81 'no-unused-vars': ['error', { args: 'none' }],85 'no-unused-vars': ['error', { args: 'none' }],
82 'no-control-regex': 'off',86 'no-control-regex': 'off',
83 'no-constant-condition': ['error', { checkLoops: false }],87 'no-constant-condition': ['error', { checkLoops: false }],
.github/issues-auto-labels.yml+2 -2
@@ -14,7 +14,7 @@
14 - '(🐧 Linux)'14 - '(🐧 Linux)'
1515
16🦊 Firefox:16🦊 Firefox:
17 - '(firefox|mozilla)'17 - '\b(firefox|mozilla)\b'
1818
19📱 Mobile:19📱 Mobile:
20 - '(iphone|ios|android|📱 Termux)'20 - '\b(iphone|ios|android|📱 Termux)\b'
.github/pr-auto-labels-by-branch.yml+6 -0
@@ -34,6 +34,9 @@
34🦊 Firefox:34🦊 Firefox:
35- head-branch: ['\bfirefox\b']35- head-branch: ['\bfirefox\b']
3636
37🧑‍🤝‍🧑 Group Chat:
38- head-branch: ['\bgroups?\b']
39
37🖼️ Image Gen:40🖼️ Image Gen:
38- head-branch: ['\bimage-gen\b']41- head-branch: ['\bimage-gen\b']
3942
@@ -58,6 +61,9 @@
58📜 Prompt:61📜 Prompt:
59- head-branch: ['\bprompt\b']62- head-branch: ['\bprompt\b']
6063
64🧠 Reasoning:
65- head-branch: ['\breasoning\b', '\breason\b', '\bthinking\b']
66
61🚚 Refactor:67🚚 Refactor:
62- head-branch: ['\brefactor(s|ed)?\b']68- head-branch: ['\brefactor(s|ed)?\b']
6369
.github/workflows/issues-auto-manager.yml+7 -7
@@ -32,7 +32,7 @@ jobs:
32 with:32 with:
33 configuration-path: .github/issues-auto-labels.yml33 configuration-path: .github/issues-auto-labels.yml
34 enable-versioned-regex: 034 enable-versioned-regex: 0
35 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}35 repo-token: ${{ secrets.GITHUB_TOKEN }}
3636
37 label-on-labels:37 label-on-labels:
38 name: 🏷️ Label Issues by Labels38 name: 🏷️ Label Issues by Labels
@@ -46,7 +46,7 @@ jobs:
46 uses: actions-cool/issues-helper@v3.6.046 uses: actions-cool/issues-helper@v3.6.0
47 with:47 with:
48 actions: 'add-labels'48 actions: 'add-labels'
49 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}49 token: ${{ secrets.GITHUB_TOKEN }}
50 labels: '👍 Approved'50 labels: '👍 Approved'
5151
52 - name: ❌ Remove progress labels when issue is marked done or stale52 - name: ❌ Remove progress labels when issue is marked done or stale
@@ -56,7 +56,7 @@ jobs:
56 uses: actions-cool/issues-helper@v3.6.056 uses: actions-cool/issues-helper@v3.6.0
57 with:57 with:
58 actions: 'remove-labels'58 actions: 'remove-labels'
59 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}59 token: ${{ secrets.GITHUB_TOKEN }}
60 labels: '🧑‍💻 In Progress,🤔 Unsure,🤔 Under Consideration'60 labels: '🧑‍💻 In Progress,🤔 Unsure,🤔 Under Consideration'
6161
62 - name: ❌ Remove temporary labels when confirmed labels are added62 - name: ❌ Remove temporary labels when confirmed labels are added
@@ -66,7 +66,7 @@ jobs:
66 uses: actions-cool/issues-helper@v3.6.066 uses: actions-cool/issues-helper@v3.6.0
67 with:67 with:
68 actions: 'remove-labels'68 actions: 'remove-labels'
69 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}69 token: ${{ secrets.GITHUB_TOKEN }}
70 labels: '🤔 Unsure,🤔 Under Consideration'70 labels: '🤔 Unsure,🤔 Under Consideration'
7171
72 - name: ❌ Remove no bug labels when "🪲 Confirmed" is added72 - name: ❌ Remove no bug labels when "🪲 Confirmed" is added
@@ -76,7 +76,7 @@ jobs:
76 uses: actions-cool/issues-helper@v3.6.076 uses: actions-cool/issues-helper@v3.6.0
77 with:77 with:
78 actions: 'remove-labels'78 actions: 'remove-labels'
79 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}79 token: ${{ secrets.GITHUB_TOKEN }}
80 labels: '✖️ Not Reproducible,✖️ Not A Bug'80 labels: '✖️ Not Reproducible,✖️ Not A Bug'
8181
82 remove-stale-label:82 remove-stale-label:
@@ -92,7 +92,7 @@ jobs:
92 uses: actions-cool/issues-helper@v3.6.092 uses: actions-cool/issues-helper@v3.6.0
93 with:93 with:
94 actions: 'remove-labels'94 actions: 'remove-labels'
95 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}95 token: ${{ secrets.GITHUB_TOKEN }}
96 issue-number: ${{ github.event.issue.number }}96 issue-number: ${{ github.event.issue.number }}
97 labels: '⚰️ Stale,🕸️ Inactive,🚏 Awaiting User Response,🛑 No Response'97 labels: '⚰️ Stale,🕸️ Inactive,🚏 Awaiting User Response,🛑 No Response'
9898
@@ -113,4 +113,4 @@ jobs:
113 uses: peaceiris/actions-label-commenter@v1.10.0113 uses: peaceiris/actions-label-commenter@v1.10.0
114 with:114 with:
115 config_file: .github/issues-auto-comments.yml115 config_file: .github/issues-auto-comments.yml
116 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}116 github_token: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/issues-updates-on-merge.yml+4 -4
@@ -31,15 +31,15 @@ jobs:
31 - name: Label Linked Issues31 - name: Label Linked Issues
32 id: label_linked_issues32 id: label_linked_issues
33 env:33 env:
34 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}34 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 run: |35 run: |
36 for ISSUE in $(echo $issues | jq -r '.[]'); do36 for ISSUE in $(echo $issues | jq -r '.[]'); do
37 if [ "${{ github.ref }}" == "refs/heads/staging" ]; then37 if [ "${{ github.ref }}" == "refs/heads/staging" ]; then
38 LABEL="✅ Done (staging)"38 LABEL="✅ Done (staging)"
39 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL"39 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL" --remove-label "🧑‍💻 In Progress"
40 elif [ "${{ github.ref }}" == "refs/heads/release" ]; then40 elif [ "${{ github.ref }}" == "refs/heads/release" ]; then
41 LABEL="✅ Done"41 LABEL="✅ Done"
42 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL"42 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "$LABEL" --remove-label "🧑‍💻 In Progress"
43 fi43 fi
44 echo "Added label '$LABEL' to issue #$ISSUE"44 echo "Added label '$LABEL' (and removed '🧑‍💻 In Progress' if present) in issue #$ISSUE"
45 done45 done
.github/workflows/job-close-stale.yml+3 -3
@@ -22,7 +22,7 @@ jobs:
22 # https://github.com/marketplace/actions/close-stale-issues22 # https://github.com/marketplace/actions/close-stale-issues
23 uses: actions/stale@v9.1.023 uses: actions/stale@v9.1.0
24 with:24 with:
25 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}25 repo-token: ${{ secrets.GITHUB_TOKEN }}
26 days-before-stale: 18326 days-before-stale: 183
27 days-before-close: 727 days-before-close: 7
28 operations-per-run: 3028 operations-per-run: 30
@@ -56,7 +56,7 @@ jobs:
56 # https://github.com/marketplace/actions/close-stale-issues56 # https://github.com/marketplace/actions/close-stale-issues
57 uses: actions/stale@v9.1.057 uses: actions/stale@v9.1.0
58 with:58 with:
59 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}59 repo-token: ${{ secrets.GITHUB_TOKEN }}
60 days-before-stale: 760 days-before-stale: 7
61 days-before-close: 761 days-before-close: 7
62 operations-per-run: 3062 operations-per-run: 30
@@ -83,7 +83,7 @@ jobs:
83 # https://github.com/marketplace/actions/close-stale-issues83 # https://github.com/marketplace/actions/close-stale-issues
84 uses: actions/stale@v9.1.084 uses: actions/stale@v9.1.0
85 with:85 with:
86 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}86 repo-token: ${{ secrets.GITHUB_TOKEN }}
87 days-before-stale: 787 days-before-stale: 7
88 days-before-close: 788 days-before-close: 7
89 operations-per-run: 3089 operations-per-run: 30
.github/workflows/on-close-handler.yml+1 -1
@@ -23,6 +23,6 @@ jobs:
23 uses: actions-cool/issues-helper@v3.6.023 uses: actions-cool/issues-helper@v3.6.0
24 with:24 with:
25 actions: remove-labels25 actions: remove-labels
26 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}26 token: ${{ secrets.GITHUB_TOKEN }}
27 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}27 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
28 labels: '🚏 Awaiting User Response,🧑‍💻 In Progress,📌 Keep Open,🚫 Merge Conflicts,🔬 Needs Testing,🔨 Needs Work,⚰️ Stale,⛔ Waiting For External/Upstream'28 labels: '🚏 Awaiting User Response,🧑‍💻 In Progress,📌 Keep Open,🚫 Merge Conflicts,🔬 Needs Testing,🔨 Needs Work,⚰️ Stale,⛔ Waiting For External/Upstream'
.github/workflows/on-open-handler.yml+1 -1
@@ -24,6 +24,6 @@ jobs:
24 uses: actions-cool/issues-helper@v3.6.024 uses: actions-cool/issues-helper@v3.6.0
25 with:25 with:
26 actions: 'add-labels'26 actions: 'add-labels'
27 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}27 token: ${{ secrets.GITHUB_TOKEN }}
28 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}28 issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}
29 labels: '👷 Maintainer'29 labels: '👷 Maintainer'
.github/workflows/pr-auto-manager.yml+84 -26
@@ -1,6 +1,7 @@
1name: 🔀 Pull Request Manager1name: 🔀 Pull Request Manager
22
3on:3on:
4 workflow_dispatch: # Allow to manually call this workflow
4 pull_request_target:5 pull_request_target:
5 types: [opened, synchronize, reopened, edited, labeled, unlabeled, closed]6 types: [opened, synchronize, reopened, edited, labeled, unlabeled, closed]
6 pull_request_review_comment:7 pull_request_review_comment:
@@ -11,9 +12,63 @@ permissions:
11 pull-requests: write12 pull-requests: write
1213
13jobs:14jobs:
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
14 label-by-size:59 label-by-size:
15 name: 🏷️ Label PR by Size60 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]
16 runs-on: ubuntu-latest63 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
18 steps:73 steps:
19 - name: Label PR Size74 - name: Label PR Size
@@ -21,7 +76,7 @@ jobs:
21 # https://github.com/marketplace/actions/pull-request-size-labeler76 # https://github.com/marketplace/actions/pull-request-size-labeler
22 uses: codelytv/pr-size-labeler@v1.10.277 uses: codelytv/pr-size-labeler@v1.10.2
23 with:78 with:
24 GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}79 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 xs_label: '🟩 ⬤○○○○'80 xs_label: '🟩 ⬤○○○○'
26 xs_max_size: '20'81 xs_max_size: '20'
27 s_label: '🟩 ⬤⬤○○○'82 s_label: '🟩 ⬤⬤○○○'
@@ -32,20 +87,15 @@ jobs:
32 l_max_size: '1000'87 l_max_size: '1000'
33 xl_label: '🟥 ⬤⬤⬤⬤⬤'88 xl_label: '🟥 ⬤⬤⬤⬤⬤'
34 fail_if_xl: 'false'89 fail_if_xl: 'false'
35 github_api_url: 'https://api.github.com'
36 files_to_ignore: |90 files_to_ignore: |
37 "package-lock.json"91 "package-lock.json"
38 "public/lib/*"92 "public/lib/*"
3993
40 label-by-branches:94 label-by-branches:
41 name: 🏷️ Label PR by Branches95 name: 🏷️ Label PR by Branches
42 needs: [label-by-size]
43 runs-on: ubuntu-latest96 runs-on: ubuntu-latest
44 # Run, even if the previous jobs were skipped/failed97 # 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 removal98 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
50 steps:100 steps:
51 - name: Checkout Repository101 - name: Checkout Repository
@@ -59,14 +109,13 @@ jobs:
59 uses: actions/labeler@v5.0.0109 uses: actions/labeler@v5.0.0
60 with:110 with:
61 configuration-path: .github/pr-auto-labels-by-branch.yml111 configuration-path: .github/pr-auto-labels-by-branch.yml
62 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}112 repo-token: ${{ secrets.GITHUB_TOKEN }}
63113
64 label-by-files:114 label-by-files:
65 name: 🏷️ Label PR by Files115 name: 🏷️ Label PR by Files
66 needs: [label-by-branches]
67 runs-on: ubuntu-latest116 runs-on: ubuntu-latest
68 # Run, even if the previous jobs were skipped/failed117 # Only needs to run when code is changed
69 if: always()118 if: github.event.action == 'opened' || github.event.action == 'synchronize'
70119
71 steps:120 steps:
72 - name: Checkout Repository121 - name: Checkout Repository
@@ -80,16 +129,19 @@ jobs:
80 uses: actions/labeler@v5.0.0129 uses: actions/labeler@v5.0.0
81 with:130 with:
82 configuration-path: .github/pr-auto-labels-by-files.yml131 configuration-path: .github/pr-auto-labels-by-files.yml
83 repo-token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}132 repo-token: ${{ secrets.GITHUB_TOKEN }}
84133
85 remove-stale-label:134 remove-stale-label:
86 name: 🗑️ Remove Stale Label on Comment135 name: 🗑️ Remove Stale Label on Comment
87 needs: [label-by-files]
88 runs-on: ubuntu-latest136 runs-on: ubuntu-latest
89 # Only runs when this is not done by the github actions bot137 # Only runs on comments 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
94 steps:146 steps:
95 - name: Remove Stale Label147 - name: Remove Stale Label
@@ -98,13 +150,13 @@ jobs:
98 uses: actions-cool/issues-helper@v3.6.0150 uses: actions-cool/issues-helper@v3.6.0
99 with:151 with:
100 actions: 'remove-labels'152 actions: 'remove-labels'
101 token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}153 token: ${{ secrets.GITHUB_TOKEN }}
102 issue-number: ${{ github.event.pull_request.number }}154 issue-number: ${{ github.event.pull_request.number }}
103 labels: '⚰️ Stale'155 labels: '⚰️ Stale'
104156
105 check-merge-blocking-labels:157 check-merge-blocking-labels:
106 name: 🚫 Check Merge Blocking Labels158 name: 🚫 Check Merge Blocking Labels
107 needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]159 needs: [label-by-branches, label-by-files]
108 runs-on: ubuntu-latest160 runs-on: ubuntu-latest
109 # Run, even if the previous jobs were skipped/failed161 # Run, even if the previous jobs were skipped/failed
110 if: always()162 if: always()
@@ -154,7 +206,7 @@ jobs:
154206
155 write-auto-comments:207 write-auto-comments:
156 name: 💬 Post PR Comments Based on Labels208 name: 💬 Post PR Comments Based on Labels
157 needs: [label-by-size, label-by-branches, label-by-files, remove-stale-label]209 needs: [label-by-branches, label-by-files]
158 runs-on: ubuntu-latest210 runs-on: ubuntu-latest
159 # Run, even if the previous jobs were skipped/failed211 # Run, even if the previous jobs were skipped/failed
160 if: always()212 if: always()
@@ -171,7 +223,7 @@ jobs:
171 uses: peaceiris/actions-label-commenter@v1.10.0223 uses: peaceiris/actions-label-commenter@v1.10.0
172 with:224 with:
173 config_file: .github/pr-auto-comments.yml225 config_file: .github/pr-auto-comments.yml
174 github_token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}226 github_token: ${{ secrets.GITHUB_TOKEN }}
175227
176 # 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.228 # 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.
177 update-linked-issues:229 update-linked-issues:
@@ -179,6 +231,12 @@ jobs:
179 runs-on: ubuntu-latest231 runs-on: ubuntu-latest
180 if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'232 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
182 steps:240 steps:
183 - name: Extract Linked Issues From PR Description241 - name: Extract Linked Issues From PR Description
184 id: extract_issues242 id: extract_issues
@@ -192,7 +250,7 @@ jobs:
192 PR_NUMBER=${{ github.event.pull_request.number }}250 PR_NUMBER=${{ github.event.pull_request.number }}
193 REPO=${{ github.repository }}251 REPO=${{ github.repository }}
194 API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"252 API_URL="https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/issues"
195 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]')253 ISSUES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "$API_URL" | jq -r '.[].number' | jq -R -s -c 'split("\n")[:-1]')
196 echo "linked_issues=$ISSUES" >> $GITHUB_ENV254 echo "linked_issues=$ISSUES" >> $GITHUB_ENV
197255
198 - name: Merge Issue Lists256 - name: Merge Issue Lists
@@ -204,9 +262,9 @@ jobs:
204 - name: Label Linked Issues262 - name: Label Linked Issues
205 id: label_linked_issues263 id: label_linked_issues
206 env:264 env:
207 GH_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}265 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
208 run: |266 run: |
209 for ISSUE in $(echo $final_issues | jq -r '.[]'); do267 for ISSUE in $(echo $final_issues | jq -r '.[]'); do
210 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "✅ Done (staging)"268 gh issue edit $ISSUE -R ${{ github.repository }} --add-label "✅ Done (staging)" --remove-label "🧑‍💻 In Progress"
211 echo "Added label '✅ Done (staging)' to issue #$ISSUE"269 echo "Added label '✅ Done (staging)' (and removed '🧑‍💻 In Progress' if present) in issue #$ISSUE"
212 done270 done
.github/workflows/pr-check-merge-conflicts.yaml+1 -1
@@ -23,6 +23,6 @@ jobs:
23 uses: eps1lon/actions-label-merge-conflict@v3.0.323 uses: eps1lon/actions-label-merge-conflict@v3.0.3
24 with:24 with:
25 dirtyLabel: '🚫 Merge Conflicts'25 dirtyLabel: '🚫 Merge Conflicts'
26 repoToken: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}26 repoToken: ${{ secrets.GITHUB_TOKEN }}
27 commentOnDirty: >27 commentOnDirty: >
28 ⚠️ This PR has conflicts that need to be resolved before it can be merged.28 ⚠️ This PR has conflicts that need to be resolved before it can be merged.
default/config.yaml+5 -0
@@ -114,6 +114,8 @@ backups:
114 chat:114 chat:
115 # Enable automatic chat backups115 # Enable automatic chat backups
116 enabled: true116 enabled: true
117 # Verify integrity of chat files before saving
118 checkIntegrity: true
117 # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.119 # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups.
118 maxTotalBackups: -1120 maxTotalBackups: -1
119 # Interval in milliseconds to throttle chat backups per user121 # Interval in milliseconds to throttle chat backups per user
@@ -140,6 +142,8 @@ performance:
140 lazyLoadCharacters: false142 lazyLoadCharacters: false
141 # The maximum amount of memory that parsed character cards can use. Set to 0 to disable memory caching.143 # The maximum amount of memory that parsed character cards can use. Set to 0 to disable memory caching.
142 memoryCacheCapacity: '100mb'144 memoryCacheCapacity: '100mb'
145 # Enables disk caching for character cards. Improves performances with large card libraries.
146 useDiskCache: true
143147
144# Allow secret keys exposure via API148# Allow secret keys exposure via API
145allowKeysExposure: false149allowKeysExposure: false
@@ -151,6 +155,7 @@ whitelistImportDomains:
151 - cdn.discordapp.com155 - cdn.discordapp.com
152 - files.catbox.moe156 - files.catbox.moe
153 - raw.githubusercontent.com157 - raw.githubusercontent.com
158 - char-archive.evulid.cc
154# API request overrides (for KoboldAI and Text Completion APIs)159# API request overrides (for KoboldAI and Text Completion APIs)
155## Note: host includes the port number if it's not the default (80 or 443)160## Note: host includes the port number if it's not the default (80 or 443)
156## Format is an array of objects:161## Format is an array of objects:
default/content/index.json+16 -0
@@ -564,6 +564,10 @@
564 "type": "context"564 "type": "context"
565 },565 },
566 {566 {
567 "filename": "presets/context/Llama 4 Instruct.json",
568 "type": "context"
569 },
570 {
567 "filename": "presets/context/Phi.json",571 "filename": "presets/context/Phi.json",
568 "type": "context"572 "type": "context"
569 },573 },
@@ -664,6 +668,10 @@
664 "type": "instruct"668 "type": "instruct"
665 },669 },
666 {670 {
671 "filename": "presets/instruct/Llama 4 Instruct.json",
672 "type": "instruct"
673 },
674 {
667 "filename": "presets/instruct/Phi.json",675 "filename": "presets/instruct/Phi.json",
668 "type": "instruct"676 "type": "instruct"
669 },677 },
@@ -786,5 +794,13 @@
786 {794 {
787 "filename": "presets/context/DeepSeek-V2.5.json",795 "filename": "presets/context/DeepSeek-V2.5.json",
788 "type": "context"796 "type": "context"
797 },
798 {
799 "filename": "presets/reasoning/DeepSeek.json",
800 "type": "reasoning"
801 },
802 {
803 "filename": "presets/reasoning/Blank.json",
804 "type": "reasoning"
789 }805 }
790]806]
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 @@
16 "input_suffix": "<|eot_id|>",16 "input_suffix": "<|eot_id|>",
17 "system_suffix": "<|eot_id|>",17 "system_suffix": "<|eot_id|>",
18 "user_alignment_message": "",18 "user_alignment_message": "",
19 "system_same_as_user": true,19 "system_same_as_user": false,
20 "last_system_sequence": "",20 "last_system_sequence": "",
21 "name": "Llama 3 Instruct"21 "name": "Llama 3 Instruct"
22}22}
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 @@
2 "compilerOptions": {2 "compilerOptions": {
3 "module": "ESNext",3 "module": "ESNext",
4 "target": "ES2023",4 "target": "ES2023",
5 "moduleResolution": "Node",5 "moduleResolution": "Bundler",
6 "strictNullChecks": true,6 "strictNullChecks": true,
7 "strictFunctionTypes": true,7 "strictFunctionTypes": true,
8 "checkJs": true,8 "checkJs": true,
package-lock.json+1081 -333
@@ -1,12 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.12.13",3 "version": "1.12.14",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.12.13",9 "version": "1.12.14",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
@@ -14,6 +14,26 @@
14 "@agnai/sentencepiece-js": "^1.1.1",14 "@agnai/sentencepiece-js": "^1.1.1",
15 "@agnai/web-tokenizers": "^0.1.3",15 "@agnai/web-tokenizers": "^0.1.3",
16 "@iconfu/svg-inject": "^1.2.3",16 "@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",
17 "@mozilla/readability": "^0.6.0",37 "@mozilla/readability": "^0.6.0",
18 "@popperjs/core": "^2.11.8",38 "@popperjs/core": "^2.11.8",
19 "@zeldafan0225/ai_horde": "^5.2.0",39 "@zeldafan0225/ai_horde": "^5.2.0",
@@ -28,6 +48,7 @@
28 "cookie-parser": "^1.4.6",48 "cookie-parser": "^1.4.6",
29 "cookie-session": "^2.1.0",49 "cookie-session": "^2.1.0",
30 "cors": "^2.8.5",50 "cors": "^2.8.5",
51 "crc": "^4.3.2",
31 "csrf-sync": "^4.0.3",52 "csrf-sync": "^4.0.3",
32 "diff-match-patch": "^1.0.5",53 "diff-match-patch": "^1.0.5",
33 "dompurify": "^3.2.4",54 "dompurify": "^3.2.4",
@@ -46,7 +67,6 @@
46 "ip-regex": "^5.0.0",67 "ip-regex": "^5.0.0",
47 "ipaddr.js": "^2.2.0",68 "ipaddr.js": "^2.2.0",
48 "is-docker": "^3.0.0",69 "is-docker": "^3.0.0",
49 "jimp": "^0.22.10",
50 "localforage": "^1.10.0",70 "localforage": "^1.10.0",
51 "lodash": "^4.17.21",71 "lodash": "^4.17.21",
52 "mime-types": "^2.1.35",72 "mime-types": "^2.1.35",
@@ -57,7 +77,6 @@
57 "node-persist": "^4.0.4",77 "node-persist": "^4.0.4",
58 "open": "^8.4.2",78 "open": "^8.4.2",
59 "png-chunk-text": "^1.0.0",79 "png-chunk-text": "^1.0.0",
60 "png-chunks-encode": "^1.0.0",
61 "png-chunks-extract": "^1.0.0",80 "png-chunks-extract": "^1.0.0",
62 "proxy-agent": "^6.5.0",81 "proxy-agent": "^6.5.0",
63 "rate-limiter-flexible": "^5.0.5",82 "rate-limiter-flexible": "^5.0.5",
@@ -102,7 +121,6 @@
102 "@types/node": "^18.19.80",121 "@types/node": "^18.19.80",
103 "@types/node-persist": "^3.1.8",122 "@types/node-persist": "^3.1.8",
104 "@types/png-chunk-text": "^1.0.3",123 "@types/png-chunk-text": "^1.0.3",
105 "@types/png-chunks-encode": "^1.0.2",
106 "@types/png-chunks-extract": "^1.0.2",124 "@types/png-chunks-extract": "^1.0.2",
107 "@types/response-time": "^2.3.8",125 "@types/response-time": "^2.3.8",
108 "@types/select2": "^4.0.63",126 "@types/select2": "^4.0.63",
@@ -110,7 +128,8 @@
110 "@types/write-file-atomic": "^4.0.3",128 "@types/write-file-atomic": "^4.0.3",
111 "@types/yargs": "^17.0.33",129 "@types/yargs": "^17.0.33",
112 "@types/yauzl": "^2.10.3",130 "@types/yauzl": "^2.10.3",
113 "eslint": "^8.57.1"131 "eslint": "^8.57.1",
132 "eslint-plugin-jsdoc": "^48.10.0"
114 },133 },
115 "engines": {134 "engines": {
116 "node": ">= 18"135 "node": ">= 18"
@@ -147,6 +166,21 @@
147 "integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA==",166 "integrity": "sha512-KlmTftToTtmb6aLVdne4NluS+POWputPF5J8v25UN/EQS+K9vahWEIe1NPRSFqBQclObkqHaj7JOnFrmnSm5MA==",
148 "license": "Apache-2.0"167 "license": "Apache-2.0"
149 },168 },
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 },
150 "node_modules/@eslint-community/eslint-utils": {184 "node_modules/@eslint-community/eslint-utils": {
151 "version": "4.4.0",185 "version": "4.4.0",
152 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",186 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -412,7 +446,55 @@
412 "@jimp/custom": ">=0.3.5"446 "@jimp/custom": ">=0.3.5"
413 }447 }
414 },448 },
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 },
415 "node_modules/@jimp/core": {458 "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": {
416 "version": "0.22.12",498 "version": "0.22.12",
417 "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz",499 "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz",
418 "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==",500 "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==",
@@ -428,13 +510,67 @@
428 "tinycolor2": "^1.6.0"510 "tinycolor2": "^1.6.0"
429 }511 }
430 },512 },
431 "node_modules/@jimp/custom": {513 "node_modules/@jimp/custom/node_modules/@jimp/utils": {
432 "version": "0.22.12",514 "version": "0.22.12",
433 "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",515 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
434 "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",516 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
435 "license": "MIT",517 "license": "MIT",
436 "dependencies": {518 "dependencies": {
437 "@jimp/core": "^0.22.12"519 "regenerator-runtime": "^0.13.3"
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"
438 }574 }
439 },575 },
440 "node_modules/@jimp/gif": {576 "node_modules/@jimp/gif": {
@@ -451,6 +587,15 @@
451 "@jimp/custom": ">=0.3.5"587 "@jimp/custom": ">=0.3.5"
452 }588 }
453 },589 },
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 },
454 "node_modules/@jimp/jpeg": {599 "node_modules/@jimp/jpeg": {
455 "version": "0.22.12",600 "version": "0.22.12",
456 "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz",601 "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz",
@@ -464,10 +609,237 @@
464 "@jimp/custom": ">=0.3.5"609 "@jimp/custom": ">=0.3.5"
465 }610 }
466 },611 },
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 },
467 "node_modules/@jimp/plugin-blit": {693 "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": {
468 "version": "0.22.12",840 "version": "0.22.12",
469 "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",841 "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz",
470 "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",842 "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==",
471 "license": "MIT",843 "license": "MIT",
472 "dependencies": {844 "dependencies": {
473 "@jimp/utils": "^0.22.12"845 "@jimp/utils": "^0.22.12"
@@ -476,10 +848,40 @@
476 "@jimp/custom": ">=0.3.5"848 "@jimp/custom": ">=0.3.5"
477 }849 }
478 },850 },
479 "node_modules/@jimp/plugin-blur": {851 "node_modules/@jimp/plugin-gaussian/node_modules/@jimp/utils": {
480 "version": "0.22.12",852 "version": "0.22.12",
481 "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz",853 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
482 "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==",854 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
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==",
483 "license": "MIT",885 "license": "MIT",
484 "dependencies": {886 "dependencies": {
485 "@jimp/utils": "^0.22.12"887 "@jimp/utils": "^0.22.12"
@@ -488,10 +890,32 @@
488 "@jimp/custom": ">=0.3.5"890 "@jimp/custom": ">=0.3.5"
489 }891 }
490 },892 },
491 "node_modules/@jimp/plugin-circle": {893 "node_modules/@jimp/plugin-invert/node_modules/@jimp/utils": {
492 "version": "0.22.12",894 "version": "0.22.12",
493 "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz",895 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
494 "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==",896 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/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==",
495 "license": "MIT",919 "license": "MIT",
496 "dependencies": {920 "dependencies": {
497 "@jimp/utils": "^0.22.12"921 "@jimp/utils": "^0.22.12"
@@ -500,65 +924,158 @@
500 "@jimp/custom": ">=0.3.5"924 "@jimp/custom": ">=0.3.5"
501 }925 }
502 },926 },
503 "node_modules/@jimp/plugin-color": {927 "node_modules/@jimp/plugin-normalize/node_modules/@jimp/utils": {
504 "version": "0.22.12",928 "version": "0.22.12",
505 "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",929 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
506 "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",930 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
507 "license": "MIT",931 "license": "MIT",
508 "dependencies": {932 "dependencies": {
509 "@jimp/utils": "^0.22.12",933 "regenerator-runtime": "^0.13.3"
510 "tinycolor2": "^1.6.0"934 }
511 },935 },
512 "peerDependencies": {936 "node_modules/@jimp/plugin-quantize": {
513 "@jimp/custom": ">=0.3.5"937 "version": "1.6.0",
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"
514 }947 }
515 },948 },
516 "node_modules/@jimp/plugin-contain": {949 "node_modules/@jimp/plugin-resize": {
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": {
517 "version": "0.22.12",981 "version": "0.22.12",
518 "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz",982 "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",
519 "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==",983 "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
520 "license": "MIT",984 "license": "MIT",
521 "dependencies": {985 "dependencies": {
522 "@jimp/utils": "^0.22.12"986 "@jimp/utils": "^0.22.12"
523 },987 },
524 "peerDependencies": {988 "peerDependencies": {
525 "@jimp/custom": ">=0.3.5",989 "@jimp/custom": ">=0.3.5",
526 "@jimp/plugin-blit": ">=0.3.5",990 "@jimp/plugin-resize": ">=0.3.5"
527 "@jimp/plugin-resize": ">=0.3.5",
528 "@jimp/plugin-scale": ">=0.3.5"
529 }991 }
530 },992 },
531 "node_modules/@jimp/plugin-cover": {993 "node_modules/@jimp/plugin-scale/node_modules/@jimp/utils": {
532 "version": "0.22.12",994 "version": "0.22.12",
533 "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz",995 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
534 "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==",996 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
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==",
535 "license": "MIT",1006 "license": "MIT",
536 "dependencies": {1007 "dependencies": {
537 "@jimp/utils": "^0.22.12"1008 "@jimp/utils": "^0.22.12"
538 },1009 },
539 "peerDependencies": {1010 "peerDependencies": {
540 "@jimp/custom": ">=0.3.5",1011 "@jimp/custom": ">=0.3.5",
541 "@jimp/plugin-crop": ">=0.3.5",1012 "@jimp/plugin-blur": ">=0.3.5",
542 "@jimp/plugin-resize": ">=0.3.5",1013 "@jimp/plugin-resize": ">=0.3.5"
543 "@jimp/plugin-scale": ">=0.3.5"
544 }1014 }
545 },1015 },
546 "node_modules/@jimp/plugin-crop": {1016 "node_modules/@jimp/plugin-shadow/node_modules/@jimp/utils": {
547 "version": "0.22.12",1017 "version": "0.22.12",
548 "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",1018 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
549 "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",1019 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
550 "license": "MIT",1020 "license": "MIT",
551 "dependencies": {1021 "dependencies": {
552 "@jimp/utils": "^0.22.12"1022 "regenerator-runtime": "^0.13.3"
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"
553 },1070 },
554 "peerDependencies": {1071 "peerDependencies": {
555 "@jimp/custom": ">=0.3.5"1072 "@jimp/custom": ">=0.3.5"
556 }1073 }
557 },1074 },
558 "node_modules/@jimp/plugin-displace": {1075 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-blit": {
559 "version": "0.22.12",1076 "version": "0.22.12",
560 "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz",1077 "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",
561 "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==",1078 "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
562 "license": "MIT",1079 "license": "MIT",
563 "dependencies": {1080 "dependencies": {
564 "@jimp/utils": "^0.22.12"1081 "@jimp/utils": "^0.22.12"
@@ -567,10 +1084,10 @@
567 "@jimp/custom": ">=0.3.5"1084 "@jimp/custom": ">=0.3.5"
568 }1085 }
569 },1086 },
570 "node_modules/@jimp/plugin-dither": {1087 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-blur": {
571 "version": "0.22.12",1088 "version": "0.22.12",
572 "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz",1089 "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz",
573 "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==",1090 "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==",
574 "license": "MIT",1091 "license": "MIT",
575 "dependencies": {1092 "dependencies": {
576 "@jimp/utils": "^0.22.12"1093 "@jimp/utils": "^0.22.12"
@@ -579,10 +1096,10 @@
579 "@jimp/custom": ">=0.3.5"1096 "@jimp/custom": ">=0.3.5"
580 }1097 }
581 },1098 },
582 "node_modules/@jimp/plugin-fisheye": {1099 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-circle": {
583 "version": "0.22.12",1100 "version": "0.22.12",
584 "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz",1101 "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz",
585 "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==",1102 "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==",
586 "license": "MIT",1103 "license": "MIT",
587 "dependencies": {1104 "dependencies": {
588 "@jimp/utils": "^0.22.12"1105 "@jimp/utils": "^0.22.12"
@@ -591,23 +1108,53 @@
591 "@jimp/custom": ">=0.3.5"1108 "@jimp/custom": ">=0.3.5"
592 }1109 }
593 },1110 },
594 "node_modules/@jimp/plugin-flip": {1111 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-color": {
595 "version": "0.22.12",1112 "version": "0.22.12",
596 "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz",1113 "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",
597 "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==",1114 "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+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==",
598 "license": "MIT",1128 "license": "MIT",
599 "dependencies": {1129 "dependencies": {
600 "@jimp/utils": "^0.22.12"1130 "@jimp/utils": "^0.22.12"
601 },1131 },
602 "peerDependencies": {1132 "peerDependencies": {
603 "@jimp/custom": ">=0.3.5",1133 "@jimp/custom": ">=0.3.5",
604 "@jimp/plugin-rotate": ">=0.3.5"1134 "@jimp/plugin-blit": ">=0.3.5",
1135 "@jimp/plugin-resize": ">=0.3.5",
1136 "@jimp/plugin-scale": ">=0.3.5"
605 }1137 }
606 },1138 },
607 "node_modules/@jimp/plugin-gaussian": {1139 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-cover": {
608 "version": "0.22.12",1140 "version": "0.22.12",
609 "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz",1141 "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz",
610 "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==",1142 "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+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==",
611 "license": "MIT",1158 "license": "MIT",
612 "dependencies": {1159 "dependencies": {
613 "@jimp/utils": "^0.22.12"1160 "@jimp/utils": "^0.22.12"
@@ -616,10 +1163,10 @@
616 "@jimp/custom": ">=0.3.5"1163 "@jimp/custom": ">=0.3.5"
617 }1164 }
618 },1165 },
619 "node_modules/@jimp/plugin-invert": {1166 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-displace": {
620 "version": "0.22.12",1167 "version": "0.22.12",
621 "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz",1168 "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz",
622 "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==",1169 "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==",
623 "license": "MIT",1170 "license": "MIT",
624 "dependencies": {1171 "dependencies": {
625 "@jimp/utils": "^0.22.12"1172 "@jimp/utils": "^0.22.12"
@@ -628,10 +1175,10 @@
628 "@jimp/custom": ">=0.3.5"1175 "@jimp/custom": ">=0.3.5"
629 }1176 }
630 },1177 },
631 "node_modules/@jimp/plugin-mask": {1178 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-dither": {
632 "version": "0.22.12",1179 "version": "0.22.12",
633 "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz",1180 "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz",
634 "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==",1181 "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==",
635 "license": "MIT",1182 "license": "MIT",
636 "dependencies": {1183 "dependencies": {
637 "@jimp/utils": "^0.22.12"1184 "@jimp/utils": "^0.22.12"
@@ -640,10 +1187,10 @@
640 "@jimp/custom": ">=0.3.5"1187 "@jimp/custom": ">=0.3.5"
641 }1188 }
642 },1189 },
643 "node_modules/@jimp/plugin-normalize": {1190 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-fisheye": {
644 "version": "0.22.12",1191 "version": "0.22.12",
645 "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz",1192 "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz",
646 "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==",1193 "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==",
647 "license": "MIT",1194 "license": "MIT",
648 "dependencies": {1195 "dependencies": {
649 "@jimp/utils": "^0.22.12"1196 "@jimp/utils": "^0.22.12"
@@ -652,24 +1199,23 @@
652 "@jimp/custom": ">=0.3.5"1199 "@jimp/custom": ">=0.3.5"
653 }1200 }
654 },1201 },
655 "node_modules/@jimp/plugin-print": {1202 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-flip": {
656 "version": "0.22.12",1203 "version": "0.22.12",
657 "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz",1204 "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz",
658 "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==",1205 "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==",
659 "license": "MIT",1206 "license": "MIT",
660 "dependencies": {1207 "dependencies": {
661 "@jimp/utils": "^0.22.12",1208 "@jimp/utils": "^0.22.12"
662 "load-bmfont": "^1.4.1"
663 },1209 },
664 "peerDependencies": {1210 "peerDependencies": {
665 "@jimp/custom": ">=0.3.5",1211 "@jimp/custom": ">=0.3.5",
666 "@jimp/plugin-blit": ">=0.3.5"1212 "@jimp/plugin-rotate": ">=0.3.5"
667 }1213 }
668 },1214 },
669 "node_modules/@jimp/plugin-resize": {1215 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-mask": {
670 "version": "0.22.12",1216 "version": "0.22.12",
671 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",1217 "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz",
672 "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",1218 "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==",
673 "license": "MIT",1219 "license": "MIT",
674 "dependencies": {1220 "dependencies": {
675 "@jimp/utils": "^0.22.12"1221 "@jimp/utils": "^0.22.12"
@@ -678,49 +1224,48 @@
678 "@jimp/custom": ">=0.3.5"1224 "@jimp/custom": ">=0.3.5"
679 }1225 }
680 },1226 },
681 "node_modules/@jimp/plugin-rotate": {1227 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-print": {
682 "version": "0.22.12",1228 "version": "0.22.12",
683 "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",1229 "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz",
684 "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",1230 "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==",
685 "license": "MIT",1231 "license": "MIT",
686 "dependencies": {1232 "dependencies": {
687 "@jimp/utils": "^0.22.12"1233 "@jimp/utils": "^0.22.12",
1234 "load-bmfont": "^1.4.1"
688 },1235 },
689 "peerDependencies": {1236 "peerDependencies": {
690 "@jimp/custom": ">=0.3.5",1237 "@jimp/custom": ">=0.3.5",
691 "@jimp/plugin-blit": ">=0.3.5",1238 "@jimp/plugin-blit": ">=0.3.5"
692 "@jimp/plugin-crop": ">=0.3.5",
693 "@jimp/plugin-resize": ">=0.3.5"
694 }1239 }
695 },1240 },
696 "node_modules/@jimp/plugin-scale": {1241 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-resize": {
697 "version": "0.22.12",1242 "version": "0.22.12",
698 "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",1243 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",
699 "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",1244 "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
700 "license": "MIT",1245 "license": "MIT",
701 "dependencies": {1246 "dependencies": {
702 "@jimp/utils": "^0.22.12"1247 "@jimp/utils": "^0.22.12"
703 },1248 },
704 "peerDependencies": {1249 "peerDependencies": {
705 "@jimp/custom": ">=0.3.5",1250 "@jimp/custom": ">=0.3.5"
706 "@jimp/plugin-resize": ">=0.3.5"
707 }1251 }
708 },1252 },
709 "node_modules/@jimp/plugin-shadow": {1253 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-rotate": {
710 "version": "0.22.12",1254 "version": "0.22.12",
711 "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz",1255 "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",
712 "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==",1256 "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
713 "license": "MIT",1257 "license": "MIT",
714 "dependencies": {1258 "dependencies": {
715 "@jimp/utils": "^0.22.12"1259 "@jimp/utils": "^0.22.12"
716 },1260 },
717 "peerDependencies": {1261 "peerDependencies": {
718 "@jimp/custom": ">=0.3.5",1262 "@jimp/custom": ">=0.3.5",
719 "@jimp/plugin-blur": ">=0.3.5",1263 "@jimp/plugin-blit": ">=0.3.5",
1264 "@jimp/plugin-crop": ">=0.3.5",
720 "@jimp/plugin-resize": ">=0.3.5"1265 "@jimp/plugin-resize": ">=0.3.5"
721 }1266 }
722 },1267 },
723 "node_modules/@jimp/plugin-threshold": {1268 "node_modules/@jimp/plugins/node_modules/@jimp/plugin-threshold": {
724 "version": "0.22.12",1269 "version": "0.22.12",
725 "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz",1270 "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz",
726 "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==",1271 "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==",
@@ -734,37 +1279,13 @@
734 "@jimp/plugin-resize": ">=0.8.0"1279 "@jimp/plugin-resize": ">=0.8.0"
735 }1280 }
736 },1281 },
737 "node_modules/@jimp/plugins": {1282 "node_modules/@jimp/plugins/node_modules/@jimp/utils": {
738 "version": "0.22.12",1283 "version": "0.22.12",
739 "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz",1284 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",
740 "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==",1285 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",
741 "license": "MIT",1286 "license": "MIT",
742 "dependencies": {1287 "dependencies": {
743 "@jimp/plugin-blit": "^0.22.12",1288 "regenerator-runtime": "^0.13.3"
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"
768 }1289 }
769 },1290 },
770 "node_modules/@jimp/png": {1291 "node_modules/@jimp/png": {
@@ -780,6 +1301,24 @@
780 "@jimp/custom": ">=0.3.5"1301 "@jimp/custom": ">=0.3.5"
781 }1302 }
782 },1303 },
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 },
783 "node_modules/@jimp/tiff": {1322 "node_modules/@jimp/tiff": {
784 "version": "0.22.12",1323 "version": "0.22.12",
785 "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz",1324 "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz",
@@ -793,29 +1332,81 @@
793 }1332 }
794 },1333 },
795 "node_modules/@jimp/types": {1334 "node_modules/@jimp/types": {
796 "version": "0.22.12",1335 "version": "1.6.0",
797 "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz",1336 "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz",
798 "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==",1337 "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==",
799 "license": "MIT",1338 "license": "MIT",
800 "dependencies": {1339 "dependencies": {
801 "@jimp/bmp": "^0.22.12",1340 "zod": "^3.23.8"
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"
807 },1341 },
808 "peerDependencies": {1342 "engines": {
809 "@jimp/custom": ">=0.3.5"1343 "node": ">=18"
810 }1344 }
811 },1345 },
812 "node_modules/@jimp/utils": {1346 "node_modules/@jimp/utils": {
813 "version": "0.22.12",1347 "version": "1.6.0",
814 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz",1348 "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz",
815 "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==",1349 "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==",
816 "license": "MIT",1350 "license": "MIT",
817 "dependencies": {1351 "dependencies": {
818 "regenerator-runtime": "^0.13.3"1352 "@jimp/types": "1.6.0",
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"
819 }1410 }
820 },1411 },
821 "node_modules/@jridgewell/gen-mapping": {1412 "node_modules/@jridgewell/gen-mapping": {
@@ -876,6 +1467,45 @@
876 "@jridgewell/sourcemap-codec": "^1.4.14"1467 "@jridgewell/sourcemap-codec": "^1.4.14"
877 }1468 }
878 },1469 },
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 },
879 "node_modules/@kwsites/file-exists": {1509 "node_modules/@kwsites/file-exists": {
880 "version": "1.1.1",1510 "version": "1.1.1",
881 "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",1511 "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@@ -970,6 +1600,19 @@
970 "node": ">=14"1600 "node": ">=14"
971 }1601 }
972 },1602 },
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 },
973 "node_modules/@popperjs/core": {1616 "node_modules/@popperjs/core": {
974 "version": "2.11.8",1617 "version": "2.11.8",
975 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",1618 "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1389,13 +2032,6 @@
1389 "dev": true,2032 "dev": true,
1390 "license": "MIT"2033 "license": "MIT"
1391 },2034 },
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 },
1399 "node_modules/@types/png-chunks-extract": {2035 "node_modules/@types/png-chunks-extract": {
1400 "version": "1.0.2",2036 "version": "1.0.2",
1401 "resolved": "https://registry.npmjs.org/@types/png-chunks-extract/-/png-chunks-extract-1.0.2.tgz",2037 "resolved": "https://registry.npmjs.org/@types/png-chunks-extract/-/png-chunks-extract-1.0.2.tgz",
@@ -1926,29 +2562,6 @@
1926 "balanced-match": "^1.0.0"2562 "balanced-match": "^1.0.0"
1927 }2563 }
1928 },2564 },
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 },
1952 "node_modules/archiver-utils/node_modules/glob": {2565 "node_modules/archiver-utils/node_modules/glob": {
1953 "version": "10.3.12",2566 "version": "10.3.12",
1954 "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",2567 "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
@@ -2026,29 +2639,6 @@
2026 "safe-buffer": "~5.2.0"2639 "safe-buffer": "~5.2.0"
2027 }2640 }
2028 },2641 },
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 },
2052 "node_modules/archiver/node_modules/buffer-crc32": {2642 "node_modules/archiver/node_modules/buffer-crc32": {
2053 "version": "1.0.0",2643 "version": "1.0.0",
2054 "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",2644 "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
@@ -2099,6 +2689,16 @@
2099 "safe-buffer": "~5.2.0"2689 "safe-buffer": "~5.2.0"
2100 }2690 }
2101 },2691 },
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 },
2102 "node_modules/argparse": {2702 "node_modules/argparse": {
2103 "version": "2.0.1",2703 "version": "2.0.1",
2104 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",2704 "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2135,6 +2735,15 @@
2135 "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",2735 "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
2136 "license": "MIT"2736 "license": "MIT"
2137 },2737 },
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 },
2138 "node_modules/axios": {2747 "node_modules/axios": {
2139 "version": "1.8.3",2748 "version": "1.8.3",
2140 "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",2749 "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
@@ -2212,6 +2821,12 @@
2212 "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",2821 "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
2213 "license": "MIT"2822 "license": "MIT"
2214 },2823 },
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 },
2215 "node_modules/body-parser": {2830 "node_modules/body-parser": {
2216 "version": "1.20.3",2831 "version": "1.20.3",
2217 "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",2832 "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
@@ -2304,9 +2919,9 @@
2304 }2919 }
2305 },2920 },
2306 "node_modules/buffer": {2921 "node_modules/buffer": {
2307 "version": "5.7.1",2922 "version": "6.0.3",
2308 "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",2923 "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
2309 "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",2924 "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
2310 "funding": [2925 "funding": [
2311 {2926 {
2312 "type": "github",2927 "type": "github",
@@ -2324,7 +2939,7 @@
2324 "license": "MIT",2939 "license": "MIT",
2325 "dependencies": {2940 "dependencies": {
2326 "base64-js": "^1.3.1",2941 "base64-js": "^1.3.1",
2327 "ieee754": "^1.1.13"2942 "ieee754": "^1.2.1"
2328 }2943 }
2329 },2944 },
2330 "node_modules/buffer-crc32": {2945 "node_modules/buffer-crc32": {
@@ -2614,6 +3229,16 @@
2614 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",3229 "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
2615 "license": "MIT"3230 "license": "MIT"
2616 },3231 },
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 },
2617 "node_modules/compress-commons": {3242 "node_modules/compress-commons": {
2618 "version": "6.0.2",3243 "version": "6.0.2",
2619 "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",3244 "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
@@ -2629,29 +3254,6 @@
2629 "node": ">= 14"3254 "node": ">= 14"
2630 }3255 }
2631 },3256 },
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 },
2655 "node_modules/compress-commons/node_modules/crc-32": {3257 "node_modules/compress-commons/node_modules/crc-32": {
2656 "version": "1.2.2",3258 "version": "1.2.2",
2657 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",3259 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -2928,8 +3530,25 @@
2928 "object-assign": "^4",3530 "object-assign": "^4",
2929 "vary": "^1"3531 "vary": "^1"
2930 },3532 },
2931 "engines": {3533 "engines": {
2932 "node": ">= 0.10"3534 "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 }
2933 }3552 }
2934 },3553 },
2935 "node_modules/crc-32": {3554 "node_modules/crc-32": {
@@ -2953,29 +3572,6 @@
2953 "node": ">= 14"3572 "node": ">= 14"
2954 }3573 }
2955 },3574 },
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 },
2979 "node_modules/crc32-stream/node_modules/crc-32": {3575 "node_modules/crc32-stream/node_modules/crc-32": {
2980 "version": "1.2.2",3576 "version": "1.2.2",
2981 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",3577 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -3560,6 +4156,69 @@
3560 "url": "https://opencollective.com/eslint"4156 "url": "https://opencollective.com/eslint"
3561 }4157 }
3562 },4158 },
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 },
3563 "node_modules/eslint-scope": {4222 "node_modules/eslint-scope": {
3564 "version": "7.2.2",4223 "version": "7.2.2",
3565 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",4224 "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -3690,9 +4349,9 @@
3690 }4349 }
3691 },4350 },
3692 "node_modules/esquery": {4351 "node_modules/esquery": {
3693 "version": "1.5.0",4352 "version": "1.6.0",
3694 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",4353 "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
3695 "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",4354 "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
3696 "dev": true,4355 "dev": true,
3697 "license": "BSD-3-Clause",4356 "license": "BSD-3-Clause",
3698 "dependencies": {4357 "dependencies": {
@@ -4978,18 +5637,6 @@
4978 "url": "https://github.com/chalk/supports-color?sponsor=1"5637 "url": "https://github.com/chalk/supports-color?sponsor=1"
4979 }5638 }
4980 },5639 },
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 },
4993 "node_modules/jpeg-js": {5640 "node_modules/jpeg-js": {
4994 "version": "0.4.4",5641 "version": "0.4.4",
4995 "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",5642 "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
@@ -5015,6 +5662,16 @@
5015 "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",5662 "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
5016 "license": "MIT"5663 "license": "MIT"
5017 },5664 },
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 },
5018 "node_modules/json-buffer": {5675 "node_modules/json-buffer": {
5019 "version": "3.0.1",5676 "version": "3.0.1",
5020 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",5677 "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -5886,11 +6543,25 @@
5886 }6543 }
5887 },6544 },
5888 "node_modules/parse-headers": {6545 "node_modules/parse-headers": {
5889 "version": "2.0.5",6546 "version": "2.0.6",
5890 "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",6547 "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz",
5891 "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",6548 "integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==",
5892 "license": "MIT"6549 "license": "MIT"
5893 },6550 },
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 },
5894 "node_modules/parse5": {6565 "node_modules/parse5": {
5895 "version": "7.1.2",6566 "version": "7.1.2",
5896 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",6567 "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@@ -6012,27 +6683,6 @@
6012 "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",6683 "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
6013 "license": "ISC"6684 "license": "ISC"
6014 },6685 },
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 },
6036 "node_modules/platform": {6686 "node_modules/platform": {
6037 "version": "1.3.6",6687 "version": "1.3.6",
6038 "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",6688 "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
@@ -6045,16 +6695,6 @@
6045 "integrity": "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==",6695 "integrity": "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==",
6046 "license": "MIT"6696 "license": "MIT"
6047 },6697 },
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 },
6058 "node_modules/png-chunks-extract": {6698 "node_modules/png-chunks-extract": {
6059 "version": "1.0.0",6699 "version": "1.0.0",
6060 "resolved": "https://registry.npmjs.org/png-chunks-extract/-/png-chunks-extract-1.0.0.tgz",6700 "resolved": "https://registry.npmjs.org/png-chunks-extract/-/png-chunks-extract-1.0.0.tgz",
@@ -6065,12 +6705,12 @@
6065 }6705 }
6066 },6706 },
6067 "node_modules/pngjs": {6707 "node_modules/pngjs": {
6068 "version": "6.0.0",6708 "version": "7.0.0",
6069 "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",6709 "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
6070 "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",6710 "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
6071 "license": "MIT",6711 "license": "MIT",
6072 "engines": {6712 "engines": {
6073 "node": ">=12.13.0"6713 "node": ">=14.19.0"
6074 }6714 }
6075 },6715 },
6076 "node_modules/prelude-ls": {6716 "node_modules/prelude-ls": {
@@ -6342,12 +6982,12 @@
6342 }6982 }
6343 },6983 },
6344 "node_modules/readable-web-to-node-stream": {6984 "node_modules/readable-web-to-node-stream": {
6345 "version": "3.0.2",6985 "version": "3.0.4",
6346 "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",6986 "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz",
6347 "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",6987 "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==",
6348 "license": "MIT",6988 "license": "MIT",
6349 "dependencies": {6989 "dependencies": {
6350 "readable-stream": "^3.6.0"6990 "readable-stream": "^4.7.0"
6351 },6991 },
6352 "engines": {6992 "engines": {
6353 "node": ">=8"6993 "node": ">=8"
@@ -6358,17 +6998,48 @@
6358 }6998 }
6359 },6999 },
6360 "node_modules/readable-web-to-node-stream/node_modules/readable-stream": {7000 "node_modules/readable-web-to-node-stream/node_modules/readable-stream": {
6361 "version": "3.6.2",7001 "version": "4.7.0",
6362 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",7002 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
6363 "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",7003 "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
6364 "license": "MIT",7004 "license": "MIT",
6365 "dependencies": {7005 "dependencies": {
6366 "inherits": "^2.0.3",7006 "abort-controller": "^3.0.0",
6367 "string_decoder": "^1.1.1",7007 "buffer": "^6.0.3",
6368 "util-deprecate": "^1.0.1"7008 "events": "^3.3.0",
7009 "process": "^0.11.10",
7010 "string_decoder": "^1.3.0"
6369 },7011 },
6370 "engines": {7012 "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"
6372 }7043 }
6373 },7044 },
6374 "node_modules/readdir-glob": {7045 "node_modules/readdir-glob": {
@@ -6600,6 +7271,19 @@
6600 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",7271 "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
6601 "license": "MIT"7272 "license": "MIT"
6602 },7273 },
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 },
6603 "node_modules/send": {7287 "node_modules/send": {
6604 "version": "0.19.0",7288 "version": "0.19.0",
6605 "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",7289 "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
@@ -6773,6 +7457,35 @@
6773 "onnxruntime-web": "1.14.0"7457 "onnxruntime-web": "1.14.0"
6774 }7458 }
6775 },7459 },
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 },
6776 "node_modules/simple-git": {7489 "node_modules/simple-git": {
6777 "version": "3.27.0",7490 "version": "3.27.0",
6778 "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",7491 "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz",
@@ -6811,11 +7524,12 @@
6811 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",7524 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
6812 "license": "MIT"7525 "license": "MIT"
6813 },7526 },
6814 "node_modules/sliced": {7527 "node_modules/slashes": {
6815 "version": "1.0.1",7528 "version": "3.0.12",
6816 "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",7529 "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz",
6817 "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==",7530 "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==",
6818 "license": "MIT"7531 "dev": true,
7532 "license": "ISC"
6819 },7533 },
6820 "node_modules/slidetoggle": {7534 "node_modules/slidetoggle": {
6821 "version": "4.0.0",7535 "version": "4.0.0",
@@ -6903,6 +7617,31 @@
6903 "source-map": "^0.6.0"7617 "source-map": "^0.6.0"
6904 }7618 }
6905 },7619 },
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 },
6906 "node_modules/sprintf-js": {7645 "node_modules/sprintf-js": {
6907 "version": "1.1.3",7646 "version": "1.1.3",
6908 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",7647 "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
@@ -7042,6 +7781,23 @@
7042 "node": ">=8"7781 "node": ">=8"
7043 }7782 }
7044 },7783 },
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 },
7045 "node_modules/tapable": {7801 "node_modules/tapable": {
7046 "version": "2.2.1",7802 "version": "2.2.1",
7047 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",7803 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -7391,6 +8147,12 @@
7391 "vectra": "bin/vectra.js"8147 "vectra": "bin/vectra.js"
7392 }8148 }
7393 },8149 },
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 },
7394 "node_modules/watchpack": {8156 "node_modules/watchpack": {
7395 "version": "2.4.2",8157 "version": "2.4.2",
7396 "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",8158 "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
@@ -7757,29 +8519,6 @@
7757 "node": ">= 14"8519 "node": ">= 14"
7758 }8520 }
7759 },8521 },
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 },
7783 "node_modules/zip-stream/node_modules/readable-stream": {8522 "node_modules/zip-stream/node_modules/readable-stream": {
7784 "version": "4.5.2",8523 "version": "4.5.2",
7785 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",8524 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
@@ -7821,6 +8560,15 @@
7821 "dependencies": {8560 "dependencies": {
7822 "safe-buffer": "~5.2.0"8561 "safe-buffer": "~5.2.0"
7823 }8562 }
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 }
7824 }8572 }
7825 }8573 }
7826}8574}
package.json+24 -5
@@ -4,6 +4,26 @@
4 "@agnai/sentencepiece-js": "^1.1.1",4 "@agnai/sentencepiece-js": "^1.1.1",
5 "@agnai/web-tokenizers": "^0.1.3",5 "@agnai/web-tokenizers": "^0.1.3",
6 "@iconfu/svg-inject": "^1.2.3",6 "@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",
7 "@mozilla/readability": "^0.6.0",27 "@mozilla/readability": "^0.6.0",
8 "@popperjs/core": "^2.11.8",28 "@popperjs/core": "^2.11.8",
9 "@zeldafan0225/ai_horde": "^5.2.0",29 "@zeldafan0225/ai_horde": "^5.2.0",
@@ -18,6 +38,7 @@
18 "cookie-parser": "^1.4.6",38 "cookie-parser": "^1.4.6",
19 "cookie-session": "^2.1.0",39 "cookie-session": "^2.1.0",
20 "cors": "^2.8.5",40 "cors": "^2.8.5",
41 "crc": "^4.3.2",
21 "csrf-sync": "^4.0.3",42 "csrf-sync": "^4.0.3",
22 "diff-match-patch": "^1.0.5",43 "diff-match-patch": "^1.0.5",
23 "dompurify": "^3.2.4",44 "dompurify": "^3.2.4",
@@ -36,7 +57,6 @@
36 "ip-regex": "^5.0.0",57 "ip-regex": "^5.0.0",
37 "ipaddr.js": "^2.2.0",58 "ipaddr.js": "^2.2.0",
38 "is-docker": "^3.0.0",59 "is-docker": "^3.0.0",
39 "jimp": "^0.22.10",
40 "localforage": "^1.10.0",60 "localforage": "^1.10.0",
41 "lodash": "^4.17.21",61 "lodash": "^4.17.21",
42 "mime-types": "^2.1.35",62 "mime-types": "^2.1.35",
@@ -47,7 +67,6 @@
47 "node-persist": "^4.0.4",67 "node-persist": "^4.0.4",
48 "open": "^8.4.2",68 "open": "^8.4.2",
49 "png-chunk-text": "^1.0.0",69 "png-chunk-text": "^1.0.0",
50 "png-chunks-encode": "^1.0.0",
51 "png-chunks-extract": "^1.0.0",70 "png-chunks-extract": "^1.0.0",
52 "proxy-agent": "^6.5.0",71 "proxy-agent": "^6.5.0",
53 "rate-limiter-flexible": "^5.0.5",72 "rate-limiter-flexible": "^5.0.5",
@@ -90,7 +109,7 @@
90 "type": "git",109 "type": "git",
91 "url": "https://github.com/SillyTavern/SillyTavern.git"110 "url": "https://github.com/SillyTavern/SillyTavern.git"
92 },111 },
93 "version": "1.12.13",112 "version": "1.12.14",
94 "scripts": {113 "scripts": {
95 "start": "node server.js",114 "start": "node server.js",
96 "debug": "node --inspect server.js",115 "debug": "node --inspect server.js",
@@ -132,7 +151,6 @@
132 "@types/node": "^18.19.80",151 "@types/node": "^18.19.80",
133 "@types/node-persist": "^3.1.8",152 "@types/node-persist": "^3.1.8",
134 "@types/png-chunk-text": "^1.0.3",153 "@types/png-chunk-text": "^1.0.3",
135 "@types/png-chunks-encode": "^1.0.2",
136 "@types/png-chunks-extract": "^1.0.2",154 "@types/png-chunks-extract": "^1.0.2",
137 "@types/response-time": "^2.3.8",155 "@types/response-time": "^2.3.8",
138 "@types/select2": "^4.0.63",156 "@types/select2": "^4.0.63",
@@ -140,6 +158,7 @@
140 "@types/write-file-atomic": "^4.0.3",158 "@types/write-file-atomic": "^4.0.3",
141 "@types/yargs": "^17.0.33",159 "@types/yargs": "^17.0.33",
142 "@types/yauzl": "^2.10.3",160 "@types/yauzl": "^2.10.3",
143 "eslint": "^8.57.1"161 "eslint": "^8.57.1",
162 "eslint-plugin-jsdoc": "^48.10.0"
144 }163 }
145}164}
post-install.js+0 -3
@@ -101,15 +101,12 @@ const keyMigrationMap = [
101 newKey: 'performance.memoryCacheCapacity',101 newKey: 'performance.memoryCacheCapacity',
102 migrate: (value) => `${value}mb`,102 migrate: (value) => `${value}mb`,
103 },103 },
104 // uncomment one release after 1.12.13
105 /*
106 {104 {
107 oldKey: 'cookieSecret',105 oldKey: 'cookieSecret',
108 newKey: 'cookieSecret',106 newKey: 'cookieSecret',
109 migrate: () => void 0,107 migrate: () => void 0,
110 remove: true,108 remove: true,
111 },109 },
112 */
113];110];
114111
115/**112/**
public/css/extensions-panel.css+12 -0
@@ -146,3 +146,15 @@ input.extension_missing[type="checkbox"] {
146.extensions_info .extension_actions {146.extensions_info .extension_actions {
147 flex-wrap: nowrap;147 flex-wrap: nowrap;
148}148}
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 @@
13 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));13 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)*2));
14 color: var(--SmartThemeBodyColor);14 color: var(--SmartThemeBodyColor);
15 z-index: 40000;15 z-index: 40000;
16 user-select: none;
16}17}
1718
18.select2-container .select2-selection .select2-selection__clear {19.select2-container .select2-selection .select2-selection__clear {
public/global.d.ts+4 -0
@@ -1,7 +1,11 @@
1import libs from './lib';1import libs from './lib';
2import getContext from './scripts/st-context';2import getContext from './scripts/st-context';
3import { power_user } from './scripts/power-user';
34
4declare global {5declare global {
6 // Custom types
7 declare type InstructSettings = typeof power_user.instruct;
8
5 // Global namespace modules9 // Global namespace modules
6 interface Window {10 interface Window {
7 ai: any;11 ai: any;
public/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 @@
197 <div id="update_oai_preset" class="menu_button menu_button_icon" title="Update current preset" data-i18n="[title]Update current preset">197 <div id="update_oai_preset" class="menu_button menu_button_icon" title="Update current preset" data-i18n="[title]Update current preset">
198 <i class="fa-fw fa-solid fa-save"></i>198 <i class="fa-fw fa-solid fa-save"></i>
199 </div>199 </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>
200 <div id="new_oai_preset" class="menu_button menu_button_icon" title="Save preset as" data-i18n="[title]Save preset as">203 <div id="new_oai_preset" class="menu_button menu_button_icon" title="Save preset as" data-i18n="[title]Save preset as">
201 <i class="fa-fw fa-solid fa-file-circle-plus"></i>204 <i class="fa-fw fa-solid fa-file-circle-plus"></i>
202 </div>205 </div>
@@ -643,7 +646,7 @@
643 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">646 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
644 </div>647 </div>
645 </div>648 </div>
646 <div class="range-block" data-source="openai,custom">649 <div class="range-block" data-source="openai,custom,xai">
647 <div class="range-block-title" data-i18n="Multiple swipes per generation">650 <div class="range-block-title" data-i18n="Multiple swipes per generation">
648 Multiple swipes per generation651 Multiple swipes per generation
649 </div>652 </div>
@@ -682,7 +685,7 @@
682 </span>685 </span>
683 </div>686 </div>
684 </div>687 </div>
685 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">688 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
686 <div class="range-block-title" data-i18n="Temperature">689 <div class="range-block-title" data-i18n="Temperature">
687 Temperature690 Temperature
688 </div>691 </div>
@@ -695,7 +698,7 @@
695 </div>698 </div>
696 </div>699 </div>
697 </div>700 </div>
698 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">701 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
699 <div class="range-block-title" data-i18n="Frequency Penalty">702 <div class="range-block-title" data-i18n="Frequency Penalty">
700 Frequency Penalty703 Frequency Penalty
701 </div>704 </div>
@@ -708,7 +711,7 @@
708 </div>711 </div>
709 </div>712 </div>
710 </div>713 </div>
711 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek">714 <div class="range-block" data-source="openai,openrouter,custom,cohere,perplexity,groq,mistralai,nanogpt,deepseek,xai">
712 <div class="range-block-title" data-i18n="Presence Penalty">715 <div class="range-block-title" data-i18n="Presence Penalty">
713 Presence Penalty716 Presence Penalty
714 </div>717 </div>
@@ -734,7 +737,7 @@
734 </div>737 </div>
735 </div>738 </div>
736 </div>739 </div>
737 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek">740 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
738 <div class="range-block-title" data-i18n="Top P">741 <div class="range-block-title" data-i18n="Top P">
739 Top P742 Top P
740 </div>743 </div>
@@ -971,7 +974,7 @@
971 </div>974 </div>
972 </div>975 </div>
973 </div>976 </div>
974 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt">977 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai">
975 <div class="range-block-title justifyLeft" data-i18n="Seed">978 <div class="range-block-title justifyLeft" data-i18n="Seed">
976 Seed979 Seed
977 </div>980 </div>
@@ -1416,7 +1419,7 @@
1416 </div>1419 </div>
1417 </div>1420 </div>
14181421
1419 <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p">1422 <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp, dreamgen" id="dryBlock" class="wide100p">
1420 <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">1423 <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">
1421 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>1424 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
1422 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">1425 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -1571,7 +1574,7 @@
1571 <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>1574 <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>
1572 </label>1575 </label>
1573 </label>1576 </label>
1574 <label data-tg-type="ooba, llamacpp, tabby, koboldcpp" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui">1577 <label data-tg-type="ooba, llamacpp, tabby, koboldcpp, dreamgen" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui">
1575 <input type="checkbox" id="ban_eos_token_textgenerationwebui" />1578 <input type="checkbox" id="ban_eos_token_textgenerationwebui" />
1576 <label>1579 <label>
1577 <small data-i18n="Ban EOS Token">Ban EOS Token</small>1580 <small data-i18n="Ban EOS Token">Ban EOS Token</small>
@@ -1957,12 +1960,15 @@
1957 <span data-i18n="Enable web search">Enable web search</span>1960 <span data-i18n="Enable web search">Enable web search</span>
1958 </label>1961 </label>
1959 <div class="flexBasis100p toggle-description justifyLeft">1962 <div class="flexBasis100p toggle-description justifyLeft">
1960 <span>1963 <span data-i18n="Use search capabilities provided by the backend.">
1961 Use search capabilities provided by the backend.1964 Use search capabilities provided by the backend.
1962 </span>1965 </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>
1963 </div>1969 </div>
1964 </div>1970 </div>
1965 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21">1971 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai">
1966 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1972 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1967 <input id="openai_function_calling" type="checkbox" />1973 <input id="openai_function_calling" type="checkbox" />
1968 <span data-i18n="Enable function calling">Enable function calling</span>1974 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1972,7 +1978,7 @@
1972 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>1978 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
1973 </div>1979 </div>
1974 </div>1980 </div>
1975 <div class="range-block" data-source="openai,openrouter,makersuite,claude,custom,01ai">1981 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai">
1976 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">1982 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
1977 <input id="openai_image_inlining" type="checkbox" />1983 <input id="openai_image_inlining" type="checkbox" />
1978 <span data-i18n="Send inline images">Send inline images</span>1984 <span data-i18n="Send inline images">Send inline images</span>
@@ -1984,7 +1990,7 @@
1984 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>1990 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
1985 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>1991 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
1986 </div>1992 </div>
1987 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom">1993 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai">
1988 <div class="flex-container oneline-dropdown">1994 <div class="flex-container oneline-dropdown">
1989 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">1995 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
1990 Inline Image Quality1996 Inline Image Quality
@@ -2028,7 +2034,7 @@
2028 </span>2034 </span>
2029 </div>2035 </div>
2030 </div>2036 </div>
2031 <div class="range-block" data-source="deepseek,openrouter,custom,claude">2037 <div class="range-block" data-source="deepseek,openrouter,custom,claude,xai">
2032 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2038 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2033 <input id="openai_show_thoughts" type="checkbox" />2039 <input id="openai_show_thoughts" type="checkbox" />
2034 <span>2040 <span>
@@ -2042,7 +2048,7 @@
2042 </span>2048 </span>
2043 </div>2049 </div>
2044 </div>2050 </div>
2045 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude">2051 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai">
2046 <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.">2052 <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.">
2047 <label for="openai_reasoning_effort">2053 <label for="openai_reasoning_effort">
2048 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2054 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2188,7 +2194,7 @@
2188 <input id="horde_trusted_workers_only" type="checkbox" />2194 <input id="horde_trusted_workers_only" type="checkbox" />
2189 <span data-i18n="Trusted workers only">Trusted workers only</span>2195 <span data-i18n="Trusted workers only">Trusted workers only</span>
2190 </label>2196 </label>
2191 <small id="adjustedHordeParams">Context: --, Response: --</small>2197 <small id="adjustedHordeParams"><span data-i18n="Context">Context</span>: --, <span data-i18n="Response">Response</span>: --</small>
2192 <h4 data-i18n="API key">API key</h4>2198 <h4 data-i18n="API key">API key</h4>
2193 <small>2199 <small>
2194 <span data-i18n="Get it here:">Get it here: </span> <a target="_blank" href="https://aihorde.net/register" data-i18n="Register">Register</a> (<a id="horde_kudos" href="javascript:void(0);" data-i18n="View my Kudos">View my Kudos</a>)<br>2200 <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 @@
2429 </div>2435 </div>
2430 <div class="flex1">2436 <div class="flex1">
2431 <h4 data-i18n="Server url">Server URL</h4>2437 <h4 data-i18n="Server url">Server URL</h4>
2432 <small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>2438 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
2433 <input id="generic_api_url_text" name="generic_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="generic">2439 <input id="generic_api_url_text" name="generic_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="generic">
2434 </div>2440 </div>
2435 <datalist id="generic_model_fill"></datalist>2441 <datalist id="generic_model_fill"></datalist>
@@ -2458,7 +2464,7 @@
2458 </div>2464 </div>
2459 <div class="flex1">2465 <div class="flex1">
2460 <h4 data-i18n="Server url">Server URL</h4>2466 <h4 data-i18n="Server url">Server URL</h4>
2461 <small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>2467 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
2462 <input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ooba_blocking">2468 <input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ooba_blocking">
2463 </div>2469 </div>
2464 <input id="custom_model_textgenerationwebui" class="text_pole wide100p" placeholder="Custom model (optional)" data-i18n="[placeholder]Custom model (optional)" type="text">2470 <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 @@
2531 </div>2537 </div>
2532 <div class="flex1">2538 <div class="flex1">
2533 <h4 data-i18n="API url">API URL</h4>2539 <h4 data-i18n="API url">API URL</h4>
2534 <small data-i18n="Example: 127.0.0.1:8000">Example: http://127.0.0.1:8000</small>2540 <small data-i18n="Example: http://127.0.0.1:8000">Example: http://127.0.0.1:8000</small>
2535 <input id="vllm_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="vllm">2541 <input id="vllm_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="vllm">
2536 </div>2542 </div>
2537 <div>2543 <div>
@@ -2577,7 +2583,7 @@
2577 </div>2583 </div>
2578 <div class="flex1">2584 <div class="flex1">
2579 <h4 data-i18n="API url">API URL</h4>2585 <h4 data-i18n="API url">API URL</h4>
2580 <small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>2586 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
2581 <input id="aphrodite_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="aphrodite">2587 <input id="aphrodite_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="aphrodite">
2582 </div>2588 </div>
2583 <div>2589 <div>
@@ -2606,7 +2612,7 @@
2606 </div>2612 </div>
2607 <div class="flex1">2613 <div class="flex1">
2608 <h4 data-i18n="API url">API URL</h4>2614 <h4 data-i18n="API url">API URL</h4>
2609 <small data-i18n="Example: 127.0.0.1:8080">Example: http://127.0.0.1:8080</small>2615 <small data-i18n="Example: http://127.0.0.1:8080">Example: http://127.0.0.1:8080</small>
2610 <input id="llamacpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="llamacpp">2616 <input id="llamacpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="llamacpp">
2611 </div>2617 </div>
2612 </div>2618 </div>
@@ -2618,7 +2624,7 @@
2618 </div>2624 </div>
2619 <div class="flex1">2625 <div class="flex1">
2620 <h4 data-i18n="API url">API URL</h4>2626 <h4 data-i18n="API url">API URL</h4>
2621 <small data-i18n="Example: 127.0.0.1:11434">Example: http://127.0.0.1:11434</small>2627 <small data-i18n="Example: http://127.0.0.1:11434">Example: http://127.0.0.1:11434</small>
2622 <input id="ollama_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ollama">2628 <input id="ollama_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="ollama">
2623 </div>2629 </div>
2624 <div class="flex1">2630 <div class="flex1">
@@ -2653,7 +2659,7 @@
2653 </div>2659 </div>
2654 <div class="flex1">2660 <div class="flex1">
2655 <h4 data-i18n="API url">API URL</h4>2661 <h4 data-i18n="API url">API URL</h4>
2656 <small data-i18n="Example: 127.0.0.1:5000">Example: http://127.0.0.1:5000</small>2662 <small data-i18n="Example: http://127.0.0.1:5000">Example: http://127.0.0.1:5000</small>
2657 <input id="tabby_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="tabby">2663 <input id="tabby_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="tabby">
2658 </div>2664 </div>
2659 <div class="flex1">2665 <div class="flex1">
@@ -2705,7 +2711,7 @@
2705 </div>2711 </div>
2706 <div class="flex1">2712 <div class="flex1">
2707 <h4 data-i18n="API url">API URL</h4>2713 <h4 data-i18n="API url">API URL</h4>
2708 <small data-i18n="Example: 127.0.0.1:5001">Example: http://127.0.0.1:5001</small>2714 <small data-i18n="Example: http://127.0.0.1:5001">Example: http://127.0.0.1:5001</small>
2709 <input id="koboldcpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="koboldcpp">2715 <input id="koboldcpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="koboldcpp">
2710 </div>2716 </div>
2711 </div>2717 </div>
@@ -2742,7 +2748,6 @@
2742 <optgroup>2748 <optgroup>
2743 <option value="01ai">01.AI (Yi)</option>2749 <option value="01ai">01.AI (Yi)</option>
2744 <option value="ai21">AI21</option>2750 <option value="ai21">AI21</option>
2745 <option value="blockentropy">Block Entropy</option>
2746 <option value="claude">Claude</option>2751 <option value="claude">Claude</option>
2747 <option value="cohere">Cohere</option>2752 <option value="cohere">Cohere</option>
2748 <option value="deepseek">DeepSeek</option>2753 <option value="deepseek">DeepSeek</option>
@@ -2754,9 +2759,10 @@
2754 <option value="perplexity">Perplexity</option>2759 <option value="perplexity">Perplexity</option>
2755 <option value="scale">Scale</option>2760 <option value="scale">Scale</option>
2756 <option value="windowai">Window AI</option>2761 <option value="windowai">Window AI</option>
2762 <option value="xai">xAI (Grok)</option>
2757 </optgroup>2763 </optgroup>
2758 </select>2764 </select>
2759 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek">2765 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek,xai">
2760 <div class="inline-drawer-toggle inline-drawer-header">2766 <div class="inline-drawer-toggle inline-drawer-header">
2761 <b data-i18n="Reverse Proxy">Reverse Proxy</b>2767 <b data-i18n="Reverse Proxy">Reverse Proxy</b>
2762 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>2768 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -2819,7 +2825,7 @@
2819 </div>2825 </div>
2820 </div>2826 </div>
2821 </div>2827 </div>
2822 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek">2828 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek,xai">
2823 <div class="reverse_proxy_warning">2829 <div class="reverse_proxy_warning">
2824 <b>2830 <b>
2825 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">2831 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">
@@ -2883,7 +2889,15 @@
2883 <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>2889 <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
2884 <option value="chatgpt-4o-latest">chatgpt-4o-latest</option>2890 <option value="chatgpt-4o-latest">chatgpt-4o-latest</option>
2885 </optgroup>2891 </optgroup>
2886 <optgroup label="o1 and o1-mini">2892 <optgroup label="GPT-4.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">
2887 <option value="o1">o1</option>2901 <option value="o1">o1</option>
2888 <option value="o1-2024-12-17">o1-2024-12-17</option>2902 <option value="o1-2024-12-17">o1-2024-12-17</option>
2889 <option value="o1-mini">o1-mini</option>2903 <option value="o1-mini">o1-mini</option>
@@ -2892,9 +2906,15 @@
2892 <option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option>2906 <option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option>
2893 </optgroup>2907 </optgroup>
2894 <optgroup label="o3">2908 <optgroup label="o3">
2909 <option value="o3">o3</option>
2910 <option value="o3-2025-04-16">o3-2025-04-16</option>
2895 <option value="o3-mini">o3-mini</option>2911 <option value="o3-mini">o3-mini</option>
2896 <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option>2912 <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option>
2897 </optgroup>2913 </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>
2898 <optgroup label="GPT-4.5">2918 <optgroup label="GPT-4.5">
2899 <option value="gpt-4.5-preview">gpt-4.5-preview</option>2919 <option value="gpt-4.5-preview">gpt-4.5-preview</option>
2900 <option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>2920 <option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
@@ -3138,8 +3158,11 @@
3138 <option value="gemma-3-27b-it">Gemma 3 27B</option>3158 <option value="gemma-3-27b-it">Gemma 3 27B</option>
3139 </optgroup>3159 </optgroup>
3140 <optgroup label="Subversions">3160 <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>
3141 <option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option>3163 <option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option>
3142 <option value="gemini-2.0-pro-exp-02-05">Gemini 2.0 Pro Experimental 2025-02-05</option>3164 <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>
3143 <option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option>3166 <option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option>
3144 <option value="gemini-2.0-flash-lite-preview-02-05">Gemini 2.0 Flash-Lite Preview 2025-02-05</option>3167 <option value="gemini-2.0-flash-lite-preview-02-05">Gemini 2.0 Flash-Lite Preview 2025-02-05</option>
3145 <option value="gemini-2.0-flash-001">Gemini 2.0 Flash [001]</option>3168 <option value="gemini-2.0-flash-001">Gemini 2.0 Flash [001]</option>
@@ -3193,6 +3216,7 @@
3193 <option value="mistral-small-latest">mistral-small-latest</option>3216 <option value="mistral-small-latest">mistral-small-latest</option>
3194 <option value="mistral-medium-latest">mistral-medium-latest</option>3217 <option value="mistral-medium-latest">mistral-medium-latest</option>
3195 <option value="mistral-large-latest">mistral-large-latest</option>3218 <option value="mistral-large-latest">mistral-large-latest</option>
3219 <option value="mistral-saba-latest">mistral-saba-latest</option>
3196 <option value="codestral-latest">codestral-latest</option>3220 <option value="codestral-latest">codestral-latest</option>
3197 <option value="codestral-mamba-latest">codestral-mamba-latest</option>3221 <option value="codestral-mamba-latest">codestral-mamba-latest</option>
3198 <option value="pixtral-12b-latest">pixtral-12b-latest</option>3222 <option value="pixtral-12b-latest">pixtral-12b-latest</option>
@@ -3208,13 +3232,20 @@
3208 <option value="mistral-small-2312">mistral-small-2312</option>3232 <option value="mistral-small-2312">mistral-small-2312</option>
3209 <option value="mistral-small-2402">mistral-small-2402</option>3233 <option value="mistral-small-2402">mistral-small-2402</option>
3210 <option value="mistral-small-2409">mistral-small-2409</option>3234 <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>
3211 <option value="mistral-medium-2312">mistral-medium-2312</option>3237 <option value="mistral-medium-2312">mistral-medium-2312</option>
3212 <option value="mistral-large-2402">mistral-large-2402</option>3238 <option value="mistral-large-2402">mistral-large-2402</option>
3213 <option value="mistral-large-2407">mistral-large-2407</option>3239 <option value="mistral-large-2407">mistral-large-2407</option>
3214 <option value="mistral-large-2411">mistral-large-2411</option>3240 <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>
3215 <option value="codestral-2405">codestral-2405</option>3243 <option value="codestral-2405">codestral-2405</option>
3216 <option value="codestral-2405-blue">codestral-2405-blue</option>3244 <option value="codestral-2405-blue">codestral-2405-blue</option>
3217 <option value="codestral-mamba-2407">codestral-mamba-2407</option>3245 <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>
3218 <option value="pixtral-12b-2409">pixtral-12b-2409</option>3249 <option value="pixtral-12b-2409">pixtral-12b-2409</option>
3219 <option value="pixtral-large-2411">pixtral-large-2411</option>3250 <option value="pixtral-large-2411">pixtral-large-2411</option>
3220 </optgroup>3251 </optgroup>
@@ -3237,16 +3268,16 @@
3237 <option value="qwen-2.5-32b">qwen-2.5-32b</option>3268 <option value="qwen-2.5-32b">qwen-2.5-32b</option>
3238 <option value="qwen-2.5-coder-32b">qwen-2.5-coder-32b</option>3269 <option value="qwen-2.5-coder-32b">qwen-2.5-coder-32b</option>
3239 </optgroup>3270 </optgroup>
3240 <optgroup label="DeepSeek / Alibaba Cloud">3271 <optgroup label="DeepSeek">
3241 <option value="deepseek-r1-distill-qwen-32b">deepseek-r1-distill-qwen-32b</option>3272 <option value="deepseek-r1-distill-qwen-32b">deepseek-r1-distill-qwen-32b</option>
3242 </optgroup>
3243 <optgroup label="DeepSeek / Meta">
3244 <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option>3273 <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option>
3245 </optgroup>3274 </optgroup>
3246 <optgroup label="Google">3275 <optgroup label="Google">
3247 <option value="gemma2-9b-it">gemma2-9b-it</option>3276 <option value="gemma2-9b-it">gemma2-9b-it</option>
3248 </optgroup>3277 </optgroup>
3249 <optgroup label="Meta">3278 <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>
3250 <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>3281 <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>
3251 <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview </option>3282 <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview </option>
3252 <option value="llama-3.2-1b-preview">llama-3.2-1b-preview </option>3283 <option value="llama-3.2-1b-preview">llama-3.2-1b-preview </option>
@@ -3259,6 +3290,7 @@
3259 <option value="llama3-8b-8192">llama3-8b-8192</option>3290 <option value="llama3-8b-8192">llama3-8b-8192</option>
3260 </optgroup>3291 </optgroup>
3261 <optgroup label="Mistral AI">3292 <optgroup label="Mistral AI">
3293 <option value="mistral-saba-24b">mistral-saba-24b</option>
3262 <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>3294 <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>
3263 </optgroup>3295 </optgroup>
3264 </select>3296 </select>
@@ -3363,20 +3395,6 @@
3363 </select>3395 </select>
3364 </div>3396 </div>
3365 </form>3397 </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>
3380 <form id="custom_form" data-source="custom">3398 <form id="custom_form" data-source="custom">
3381 <h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>3399 <h4 data-i18n="Custom Endpoint (Base URL)">Custom Endpoint (Base URL)</h4>
3382 <div class="flex-container">3400 <div class="flex-container">
@@ -3407,17 +3425,10 @@
3407 <div class="flex-container">3425 <div class="flex-container">
3408 <select id="model_custom_select" class="text_pole model_custom_select"></select>3426 <select id="model_custom_select" class="text_pole model_custom_select"></select>
3409 </div>3427 </div>
3410 <h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
3411 <select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">
3412 <option data-i18n="prompt_post_processing_none" value="">None</option>
3413 <option data-i18n="prompt_post_processing_merge" value="merge">Merge consecutive roles</option>
3414 <option data-i18n="prompt_post_processing_semi" value="semi">Semi-strict (alternating roles)</option>
3415 <option data-i18n="prompt_post_processing_strict" value="strict">Strict (user first, alternating roles)</option>
3416 </select>
3417 </form>3428 </form>
3418 <div id="01ai_form" data-source="01ai">3429 <div id="01ai_form" data-source="01ai">
3419 <h4>3430 <h4>
3420 <a data-i18n="01.AI API Key" href="https://platform.01.ai/" target="_blank" rel="noopener noreferrer">3431 <a data-i18n="01.AI API Key" href="https://platform.lingyiwanwu.com/" target="_blank" rel="noopener noreferrer">
3421 01.AI API Key3432 01.AI API Key
3422 </a>3433 </a>
3423 </h4>3434 </h4>
@@ -3432,6 +3443,40 @@
3432 <select id="model_01ai_select">3443 <select id="model_01ai_select">
3433 </select>3444 </select>
3434 </div>3445 </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>
3435 <div class="flex-container flex">3480 <div class="flex-container flex">
3436 <div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>3481 <div id="api_button_openai" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect">Connect</div>
3437 <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>3482 <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>
@@ -3917,6 +3962,19 @@
3917 <summary data-i18n="Reasoning Formatting">3962 <summary data-i18n="Reasoning Formatting">
3918 Reasoning Formatting3963 Reasoning Formatting
3919 </summary>3964 </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>
3920 <div class="flex-container">3978 <div class="flex-container">
3921 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">3979 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
3922 <small data-i18n="Prefix">Prefix</small>3980 <small data-i18n="Prefix">Prefix</small>
@@ -5099,7 +5157,7 @@
5099 <div id="persona_depth_position_settings" class="flex-container">5157 <div id="persona_depth_position_settings" class="flex-container">
5100 <div class="flex1">5158 <div class="flex1">
5101 <label for="persona_depth_value" data-i18n="Depth:">Depth:</label>5159 <label for="persona_depth_value" data-i18n="Depth:">Depth:</label>
5102 <input id="persona_depth_value" class="text_pole" type="number" min="0" max="999" step="1">5160 <input id="persona_depth_value" class="text_pole" type="number" min="0" max="9999" step="1">
5103 </div>5161 </div>
5104 <div class="flex1">5162 <div class="flex1">
5105 <label for="persona_depth_role" data-i18n="Role:">Role:</label>5163 <label for="persona_depth_role" data-i18n="Role:">Role:</label>
@@ -5752,7 +5810,7 @@
5752 @ Depth5810 @ Depth
5753 </span>5811 </span>
5754 </h4>5812 </h4>
5755 <input id="depth_prompt_depth" name="depth_prompt_depth" class="text_pole textarea_compact m-t-0" type="number" min="0" max="999" value="4" form="form_create" />5813 <input id="depth_prompt_depth" name="depth_prompt_depth" class="text_pole textarea_compact m-t-0" type="number" min="0" max="9999" value="4" form="form_create" />
5756 <h4>5814 <h4>
5757 <span data-i18n="Role">5815 <span data-i18n="Role">
5758 Role5816 Role
@@ -5973,11 +6031,11 @@
5973 </div>6031 </div>
5974 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">6032 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
5975 <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>6033 <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
5976 <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="999" />6034 <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" />
5977 </div>6035 </div>
5978 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">6036 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
5979 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>6037 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
5980 <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="999" />6038 <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" />
5981 </div>6039 </div>
5982 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">6040 <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
5983 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>6041 <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
@@ -5986,6 +6044,7 @@
5986 </div>6044 </div>
5987 </div>6045 </div>
5988 </div>6046 </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>
5989 <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>6048 <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>
5990 <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>6049 <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>
5991 </div>6050 </div>
@@ -6074,9 +6133,12 @@
6074 <label for="content ">6133 <label for="content ">
6075 <small>6134 <small>
6076 <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">6135 <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
6077 <span data-i18n="Content" class="alignitemscenter flex-container flexNoGap mdhotkey_location">6136 <span class="alignitemscenter flex-container">
6137 <span data-i18n="Content" class="mdhotkey_location">
6078 Content6138 Content
6079 </span>6139 </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>
6080 <span>6142 <span>
6081 (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;6143 (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
6082 <span class="world_entry_form_uid_value" data-first-run="true"></span>6144 <span class="world_entry_form_uid_value" data-first-run="true"></span>
@@ -6335,7 +6397,7 @@
6335 <span data-i18n="prompt_manager_depth">Depth</span>6397 <span data-i18n="prompt_manager_depth">Depth</span>
6336 </label>6398 </label>
6337 <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>6399 <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>
6338 <input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="999" value="4" />6400 <input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="9999" value="4" />
6339 </div>6401 </div>
6340 </div>6402 </div>
6341 <div class="completion_prompt_manager_popup_entry_form_control">6403 <div class="completion_prompt_manager_popup_entry_form_control">
@@ -6434,7 +6496,7 @@
6434 <div class="mes_text"></div>6496 <div class="mes_text"></div>
6435 <div class="mes_img_container">6497 <div class="mes_img_container">
6436 <div class="mes_img_controls">6498 <div class="mes_img_controls">
6437 <div title="Enlarge" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge" data-i18n="[title]Enlarge"></div>6499 <div title="Expand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_img_enlarge" data-i18n="[title]Expand and zoom"></div>
6438 <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>6500 <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
6439 <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>6501 <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
6440 </div>6502 </div>
@@ -6563,7 +6625,7 @@
6563 <div class="ch_name"></div>6625 <div class="ch_name"></div>
6564 <small class="ch_additional_info group_select_counter"></small>6626 <small class="ch_additional_info group_select_counter"></small>
6565 </div>6627 </div>
6566 <small class="character_name_block_sub_line">in this group</small>6628 <small class="character_name_block_sub_line" data-i18n="in this group">in this group</small>
6567 <i class='group_fav_icon fa-solid fa-star'></i>6629 <i class='group_fav_icon fa-solid fa-star'></i>
6568 <input class="ch_fav" value="" hidden />6630 <input class="ch_fav" value="" hidden />
6569 <div class="group_select_block_list ch_description"></div>6631 <div class="group_select_block_list ch_description"></div>
@@ -6695,7 +6757,7 @@
6695 <label class="checkbox_label alignItemsCenter" for="extension_floating_position_depth">6757 <label class="checkbox_label alignItemsCenter" for="extension_floating_position_depth">
6696 <input type="radio" id="extension_floating_position_depth" name="extension_floating_position" value="1" />6758 <input type="radio" id="extension_floating_position_depth" name="extension_floating_position" value="1" />
6697 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>6759 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
6698 <input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="999" />6760 <input id="extension_floating_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999" />
6699 <span data-i18n="as">as</span>6761 <span data-i18n="as">as</span>
6700 <select id="extension_floating_role" class="text_pole widthNatural">6762 <select id="extension_floating_role" class="text_pole widthNatural">
6701 <option data-i18n="System" value="0">System</option>6763 <option data-i18n="System" value="0">System</option>
@@ -6710,7 +6772,7 @@
6710 <span data-i18n="Insertion Frequency">Insertion Frequency</span>6772 <span data-i18n="Insertion Frequency">Insertion Frequency</span>
6711 <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>6773 <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>
6712 </label>6774 </label>
6713 <input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="999" />6775 <input id="extension_floating_interval" class="text_pole widthUnset" type="number" min="0" max="9999" />
6714 </div>6776 </div>
6715 <br>6777 <br>
6716 <span><span data-i18n="User inputs until next insertion:">User inputs until next insertion:</span> <span id="extension_floating_counter">(disabled)</span></span>6778 <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 @@
6780 <label class="checkbox_label alignItemsCenter" for="extension_default_position_depth">6842 <label class="checkbox_label alignItemsCenter" for="extension_default_position_depth">
6781 <input type="radio" id="extension_default_position_depth" name="extension_default_position" value="1" />6843 <input type="radio" id="extension_default_position_depth" name="extension_default_position" value="1" />
6782 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>6844 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
6783 <input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="999" />6845 <input id="extension_default_depth" class="text_pole textarea_compact widthNatural" type="number" min="0" max="9999" />
6784 <span data-i18n="as">as</span>6846 <span data-i18n="as">as</span>
6785 <select id="extension_default_role" class="text_pole widthNatural">6847 <select id="extension_default_role" class="text_pole widthNatural">
6786 <option data-i18n="System" value="0">System</option>6848 <option data-i18n="System" value="0">System</option>
@@ -6794,7 +6856,7 @@
6794 <span data-i18n="Insertion Frequency">Insertion Frequency</span>6856 <span data-i18n="Insertion Frequency">Insertion Frequency</span>
6795 <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>6857 <small data-i18n="(0 = Disable, 1 = Always)">(0 = Disable, 1 = Always)</small>
6796 </label>6858 </label>
6797 <input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="999" />6859 <input id="extension_default_interval" class="text_pole widthUnset" type="number" min="0" max="9999" />
6798 </div>6860 </div>
6799 </div>6861 </div>
6800 </div>6862 </div>
public/locales/ar-sa.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "وضع علامة",318 "flag": "وضع علامة",
319 "API key (optional)": "مفتاح API (اختياري)",319 "API key (optional)": "مفتاح API (اختياري)",
320 "Server url": "رابط الخادم",320 "Server url": "رابط الخادم",
321 "Example: 127.0.0.1:5000": "مثال: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "مثال: http://127.0.0.1:5000",
322 "Custom model (optional)": "نموذج مخصص (اختياري)",322 "Custom model (optional)": "نموذج مخصص (اختياري)",
323 "vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",323 "vllm-project/vllm": "vllm-project/vllm (وضع غلاف OpenAI API)",
324 "vLLM API key": "مفتاح واجهة برمجة التطبيقات vLLM",324 "vLLM API key": "مفتاح واجهة برمجة التطبيقات vLLM",
325 "Example: 127.0.0.1:8000": "مثال: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "مثال: http://127.0.0.1:8000",
326 "vLLM Model": "نموذج vLLM",326 "vLLM Model": "نموذج vLLM",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (وضع التغليف لواجهة برمجة التطبيقات OpenAI)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (وضع التغليف لواجهة برمجة التطبيقات OpenAI)",
328 "Aphrodite API key": "مفتاح واجهة برمجة التطبيقات Aphrodite",328 "Aphrodite API key": "مفتاح واجهة برمجة التطبيقات Aphrodite",
329 "Aphrodite Model": "نموذج أفروديت",329 "Aphrodite Model": "نموذج أفروديت",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (خادم إخراج)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (خادم إخراج)",
331 "Example: 127.0.0.1:8080": "مثال: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "مثال: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "مثال: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "مثال: http://127.0.0.1:11434",
333 "Ollama Model": "نموذج Ollama",333 "Ollama Model": "نموذج Ollama",
334 "Download": "تحميل",334 "Download": "تحميل",
335 "Tabby API key": "مفتاح API لـ Tabby",335 "Tabby API key": "مفتاح API لـ Tabby",
336 "koboldcpp API key (optional)": "مفتاح koboldcpp API (اختياري)",336 "koboldcpp API key (optional)": "مفتاح koboldcpp API (اختياري)",
337 "Example: 127.0.0.1:5001": "مثال: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "مثال: http://127.0.0.1:5001",
338 "Authorize": "تفويض",338 "Authorize": "تفويض",
339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "احصل على رمز واجهة برمجة التطبيقات الخاص بك لموزع الاتصالات باستخدام تدفق OAuth. سيتم توجيهك إلى openrouter.ai",339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "احصل على رمز واجهة برمجة التطبيقات الخاص بك لموزع الاتصالات باستخدام تدفق OAuth. سيتم توجيهك إلى openrouter.ai",
340 "Bypass status check": "تجاوز فحص الحالة",340 "Bypass status check": "تجاوز فحص الحالة",
public/locales/de-de.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "Flagge",318 "flag": "Flagge",
319 "API key (optional)": "API-Schlüssel (optional)",319 "API key (optional)": "API-Schlüssel (optional)",
320 "Server url": "Server-URL",320 "Server url": "Server-URL",
321 "Example: 127.0.0.1:5000": "Beispiel: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Beispiel: http://127.0.0.1:5000",
322 "Custom model (optional)": "Benutzerdefiniertes Modell (optional)",322 "Custom model (optional)": "Benutzerdefiniertes Modell (optional)",
323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-Wrappermodus)",
324 "vLLM API key": "vLLM-API-Schlüssel",324 "vLLM API key": "vLLM-API-Schlüssel",
325 "Example: 127.0.0.1:8000": "Beispiel: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Beispiel: http://127.0.0.1:8000",
326 "vLLM Model": "vLLM-Modell",326 "vLLM Model": "vLLM-Modell",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus für OpenAI API)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus für OpenAI API)",
328 "Aphrodite API key": "Aphrodite API-Schlüssel",328 "Aphrodite API key": "Aphrodite API-Schlüssel",
329 "Aphrodite Model": "Aphrodite-Modell",329 "Aphrodite Model": "Aphrodite-Modell",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-Server)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-Server)",
331 "Example: 127.0.0.1:8080": "Beispiel: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Beispiel: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Beispiel: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Beispiel: http://127.0.0.1:11434",
333 "Ollama Model": "Ollama-Modell",333 "Ollama Model": "Ollama-Modell",
334 "Download": "Herunterladen",334 "Download": "Herunterladen",
335 "Tabby API key": "Tabby API-Schlüssel",335 "Tabby API key": "Tabby API-Schlüssel",
336 "koboldcpp API key (optional)": "koboldcpp API-Schlüssel (optional)",336 "koboldcpp API key (optional)": "koboldcpp API-Schlüssel (optional)",
337 "Example: 127.0.0.1:5001": "Beispiel: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Beispiel: http://127.0.0.1:5001",
338 "Authorize": "Autorisieren",338 "Authorize": "Autorisieren",
339 "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",339 "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",
340 "Bypass status check": "Umgehe Statusüberprüfung",340 "Bypass status check": "Umgehe Statusüberprüfung",
public/locales/es-es.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "bandera",318 "flag": "bandera",
319 "API key (optional)": "Clave API (opcional)",319 "API key (optional)": "Clave API (opcional)",
320 "Server url": "URL del servidor",320 "Server url": "URL del servidor",
321 "Example: 127.0.0.1:5000": "Ejemplo: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Ejemplo: http://127.0.0.1:5000",
322 "Custom model (optional)": "Modelo personalizado (opcional)",322 "Custom model (optional)": "Modelo personalizado (opcional)",
323 "vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",323 "vllm-project/vllm": "vllm-project/vllm (modo contenedor de API OpenAI)",
324 "vLLM API key": "Clave API vLLM",324 "vLLM API key": "Clave API vLLM",
325 "Example: 127.0.0.1:8000": "Ejemplo: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Ejemplo: http://127.0.0.1:8000",
326 "vLLM Model": "Modelo vLLM",326 "vLLM Model": "Modelo vLLM",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo envolvente para API de OpenAI)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo envolvente para API de OpenAI)",
328 "Aphrodite API key": "Clave de API de Aphrodite",328 "Aphrodite API key": "Clave de API de Aphrodite",
329 "Aphrodite Model": "Modelo Afrodita",329 "Aphrodite Model": "Modelo Afrodita",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de salida)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de salida)",
331 "Example: 127.0.0.1:8080": "Ejemplo: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Ejemplo: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Ejemplo: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Ejemplo: http://127.0.0.1:11434",
333 "Ollama Model": "Modelo Ollama",333 "Ollama Model": "Modelo Ollama",
334 "Download": "Descargar",334 "Download": "Descargar",
335 "Tabby API key": "Clave API de Tabby",335 "Tabby API key": "Clave API de Tabby",
336 "koboldcpp API key (optional)": "Clave API de koboldcpp (opcional)",336 "koboldcpp API key (optional)": "Clave API de koboldcpp (opcional)",
337 "Example: 127.0.0.1:5001": "Ejemplo: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Ejemplo: http://127.0.0.1:5001",
338 "Authorize": "Autorizar",338 "Authorize": "Autorizar",
339 "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",339 "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",
340 "Bypass status check": "Saltar la verificación del estado",340 "Bypass status check": "Saltar la verificación del estado",
public/locales/fr-fr.json+5 -5
@@ -301,23 +301,23 @@
301 "flag": "fanion",301 "flag": "fanion",
302 "API key (optional)": "Clé API (optionnelle)",302 "API key (optional)": "Clé API (optionnelle)",
303 "Server url": "URL du serveur",303 "Server url": "URL du serveur",
304 "Example: 127.0.0.1:5000": "Exemple : 127.0.0.1:5000",304 "Example: http://127.0.0.1:5000": "Exemple : http://127.0.0.1:5000",
305 "Custom model (optional)": "Modèle personnalisé (optionnel)",305 "Custom model (optional)": "Modèle personnalisé (optionnel)",
306 "vllm-project/vllm": "vllm-project/vllm (mode wrapper de l'API OpenAI)",306 "vllm-project/vllm": "vllm-project/vllm (mode wrapper de l'API OpenAI)",
307 "vLLM API key": "Clé API vLLM",307 "vLLM API key": "Clé API vLLM",
308 "Example: 127.0.0.1:8000": "Exemple : http://127.0.0.1:8000",308 "Example: http://127.0.0.1:8000": "Exemple : http://127.0.0.1:8000",
309 "vLLM Model": "Modèle vLLM",309 "vLLM Model": "Modèle vLLM",
310 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (mode wrapper pour l'API OpenAI)",310 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (mode wrapper pour l'API OpenAI)",
311 "Aphrodite API key": "Clé API Aphrodite",311 "Aphrodite API key": "Clé API Aphrodite",
312 "Aphrodite Model": "Modèle Aphrodite",312 "Aphrodite Model": "Modèle Aphrodite",
313 "ggerganov/llama.cpp": "ggerganov/llama.cpp",313 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
314 "Example: 127.0.0.1:8080": "Exemple : 127.0.0.1:8080",314 "Example: http://127.0.0.1:8080": "Exemple : http://127.0.0.1:8080",
315 "Example: 127.0.0.1:11434": "Exemple : 127.0.0.1:11434",315 "Example: http://127.0.0.1:11434": "Exemple : http://127.0.0.1:11434",
316 "Ollama Model": "Modèle Ollama",316 "Ollama Model": "Modèle Ollama",
317 "Download": "Télécharger",317 "Download": "Télécharger",
318 "Tabby API key": "Clé API de Tabby",318 "Tabby API key": "Clé API de Tabby",
319 "koboldcpp API key (optional)": "Clé API koboldcpp (facultatif)",319 "koboldcpp API key (optional)": "Clé API koboldcpp (facultatif)",
320 "Example: 127.0.0.1:5001": "Exemple : 127.0.0.1:5001",320 "Example: http://127.0.0.1:5001": "Exemple : http://127.0.0.1:5001",
321 "Authorize": "Autoriser",321 "Authorize": "Autoriser",
322 "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",322 "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",
323 "Bypass status check": "Contourner la vérification de l'état",323 "Bypass status check": "Contourner la vérification de l'état",
public/locales/is-is.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "merki",318 "flag": "merki",
319 "API key (optional)": "API lykill (valkvæmt)",319 "API key (optional)": "API lykill (valkvæmt)",
320 "Server url": "URL þjóns",320 "Server url": "URL þjóns",
321 "Example: 127.0.0.1:5000": "Dæmi: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Dæmi: http://127.0.0.1:5000",
322 "Custom model (optional)": "Sérsniðið módel (valkvæmt)",322 "Custom model (optional)": "Sérsniðið módel (valkvæmt)",
323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API umbúðastilling)",323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API umbúðastilling)",
324 "vLLM API key": "vLLM API lykill",324 "vLLM API key": "vLLM API lykill",
325 "Example: 127.0.0.1:8000": "Dæmi: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Dæmi: http://127.0.0.1:8000",
326 "vLLM Model": "vLLM líkan",326 "vLLM Model": "vLLM líkan",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI forritunargrensl)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI forritunargrensl)",
328 "Aphrodite API key": "Aphrodite API lykill",328 "Aphrodite API key": "Aphrodite API lykill",
329 "Aphrodite Model": "Afródíta fyrirmynd",329 "Aphrodite Model": "Afródíta fyrirmynd",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (úttak þjónn)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (úttak þjónn)",
331 "Example: 127.0.0.1:8080": "Dæmi: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Dæmi: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Dæmi: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Dæmi: http://127.0.0.1:11434",
333 "Ollama Model": "Ollama módel",333 "Ollama Model": "Ollama módel",
334 "Download": "Niðurhal",334 "Download": "Niðurhal",
335 "Tabby API key": "Tabby API lykill",335 "Tabby API key": "Tabby API lykill",
336 "koboldcpp API key (optional)": "koboldcpp API lykill (valfrjálst)",336 "koboldcpp API key (optional)": "koboldcpp API lykill (valfrjálst)",
337 "Example: 127.0.0.1:5001": "Dæmi: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Dæmi: http://127.0.0.1:5001",
338 "Authorize": "Heimild",338 "Authorize": "Heimild",
339 "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",339 "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",
340 "Bypass status check": "Hlaupa framhjá stöðutík",340 "Bypass status check": "Hlaupa framhjá stöðutík",
public/locales/it-it.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "bandiera",318 "flag": "bandiera",
319 "API key (optional)": "Chiave API (opzionale)",319 "API key (optional)": "Chiave API (opzionale)",
320 "Server url": "URL del server",320 "Server url": "URL del server",
321 "Example: 127.0.0.1:5000": "Esempio: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Esempio: http://127.0.0.1:5000",
322 "Custom model (optional)": "Modello personalizzato (opzionale)",322 "Custom model (optional)": "Modello personalizzato (opzionale)",
323 "vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",323 "vllm-project/vllm": "vllm-project/vllm (modalità wrapper API OpenAI)",
324 "vLLM API key": "Chiave API vLLM",324 "vLLM API key": "Chiave API vLLM",
325 "Example: 127.0.0.1:8000": "Esempio: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Esempio: http://127.0.0.1:8000",
326 "vLLM Model": "Modello vLLM",326 "vLLM Model": "Modello vLLM",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modalità wrapper per l'API OpenAI)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modalità wrapper per l'API OpenAI)",
328 "Aphrodite API key": "Chiave API di Aphrodite",328 "Aphrodite API key": "Chiave API di Aphrodite",
329 "Aphrodite Model": "Modello di Afrodite",329 "Aphrodite Model": "Modello di Afrodite",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Server di output)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Server di output)",
331 "Example: 127.0.0.1:8080": "Esempio: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Esempio: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Esempio: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Esempio: http://127.0.0.1:11434",
333 "Ollama Model": "Modello Ollama",333 "Ollama Model": "Modello Ollama",
334 "Download": "Scarica",334 "Download": "Scarica",
335 "Tabby API key": "Chiave API di Tabby",335 "Tabby API key": "Chiave API di Tabby",
336 "koboldcpp API key (optional)": "Chiave API koboldcpp (opzionale)",336 "koboldcpp API key (optional)": "Chiave API koboldcpp (opzionale)",
337 "Example: 127.0.0.1:5001": "Esempio: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Esempio: http://127.0.0.1:5001",
338 "Authorize": "Autorizzare",338 "Authorize": "Autorizzare",
339 "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",339 "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",
340 "Bypass status check": "Ignora controllo stato",340 "Bypass status check": "Ignora controllo stato",
public/locales/ja-jp.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "フラグ",318 "flag": "フラグ",
319 "API key (optional)": "APIキー(オプション)",319 "API key (optional)": "APIキー(オプション)",
320 "Server url": "サーバーURL",320 "Server url": "サーバーURL",
321 "Example: 127.0.0.1:5000": "例: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "例: http://127.0.0.1:5000",
322 "Custom model (optional)": "カスタムモデル(オプション)",322 "Custom model (optional)": "カスタムモデル(オプション)",
323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API ラッパーモード)",
324 "vLLM API key": "vLLM API キー",324 "vLLM API key": "vLLM API キー",
325 "Example: 127.0.0.1:8000": "例: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "例: http://127.0.0.1:8000",
326 "vLLM Model": "vLLM モデル",326 "vLLM Model": "vLLM モデル",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(OpenAI APIエンドポイントのパッケージングモード)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine(OpenAI APIエンドポイントのパッケージングモード)",
328 "Aphrodite API key": "アフロディーテAPIキー",328 "Aphrodite API key": "アフロディーテAPIキー",
329 "Aphrodite Model": "アフロディーテモデル",329 "Aphrodite Model": "アフロディーテモデル",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp(出力サーバー)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp(出力サーバー)",
331 "Example: 127.0.0.1:8080": "例: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "例: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "例: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "例: http://127.0.0.1:11434",
333 "Ollama Model": "Ollamaモデル",333 "Ollama Model": "Ollamaモデル",
334 "Download": "ダウンロード",334 "Download": "ダウンロード",
335 "Tabby API key": "TabbyのAPIキー",335 "Tabby API key": "TabbyのAPIキー",
336 "koboldcpp API key (optional)": "koboldcpp API キー (オプション)",336 "koboldcpp API key (optional)": "koboldcpp API キー (オプション)",
337 "Example: 127.0.0.1:5001": "例: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "例: http://127.0.0.1:5001",
338 "Authorize": "承認",338 "Authorize": "承認",
339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuthフローを使用してOpenRouter APIトークンを取得します。 openrouter.aiにリダイレクトされます",339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuthフローを使用してOpenRouter APIトークンを取得します。 openrouter.aiにリダイレクトされます",
340 "Bypass status check": "ステータスのチェックをバイパスする",340 "Bypass status check": "ステータスのチェックをバイパスする",
public/locales/ko-kr.json+5 -5
@@ -320,23 +320,23 @@
320 "flag": "깃발",320 "flag": "깃발",
321 "API key (optional)": "API 키 (선택 사항)",321 "API key (optional)": "API 키 (선택 사항)",
322 "Server url": "서버 URL",322 "Server url": "서버 URL",
323 "Example: 127.0.0.1:5000": "예시: 127.0.0.1:5000",323 "Example: http://127.0.0.1:5000": "예시: http://127.0.0.1:5000",
324 "Custom model (optional)": "사용자 정의 모델 (선택 사항)",324 "Custom model (optional)": "사용자 정의 모델 (선택 사항)",
325 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",325 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 래퍼 모드)",
326 "vLLM API key": "vLLM API 키",326 "vLLM API key": "vLLM API 키",
327 "Example: 127.0.0.1:8000": "예: http://127.0.0.1:8000",327 "Example: http://127.0.0.1:8000": "예: http://127.0.0.1:8000",
328 "vLLM Model": "vLLM 모델",328 "vLLM Model": "vLLM 모델",
329 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI API의 래퍼 모드)",329 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (OpenAI API의 래퍼 모드)",
330 "Aphrodite API key": "Aphrodite API 키",330 "Aphrodite API key": "Aphrodite API 키",
331 "Aphrodite Model": "Aphrodite 모델",331 "Aphrodite Model": "Aphrodite 모델",
332 "ggerganov/llama.cpp": "ggerganov/llama.cpp (출력 서버)",332 "ggerganov/llama.cpp": "ggerganov/llama.cpp (출력 서버)",
333 "Example: 127.0.0.1:8080": "예: 127.0.0.1:8080",333 "Example: http://127.0.0.1:8080": "예: http://127.0.0.1:8080",
334 "Example: 127.0.0.1:11434": "예: 127.0.0.1:11434",334 "Example: http://127.0.0.1:11434": "예: http://127.0.0.1:11434",
335 "Ollama Model": "Ollama 모델",335 "Ollama Model": "Ollama 모델",
336 "Download": "다운로드",336 "Download": "다운로드",
337 "Tabby API key": "Tabby API 키",337 "Tabby API key": "Tabby API 키",
338 "koboldcpp API key (optional)": "koboldcpp API 키(선택사항)",338 "koboldcpp API key (optional)": "koboldcpp API 키(선택사항)",
339 "Example: 127.0.0.1:5001": "예: 127.0.0.1:5001",339 "Example: http://127.0.0.1:5001": "예: http://127.0.0.1:5001",
340 "Authorize": "승인하기",340 "Authorize": "승인하기",
341 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuth 플로우를 사용하여 OpenRouter API 토큰을 가져옵니다. openrouter.ai로 리디렉션됩니다.",341 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "OAuth 플로우를 사용하여 OpenRouter API 토큰을 가져옵니다. openrouter.ai로 리디렉션됩니다.",
342 "Legacy API (pre-OAI, no streaming)": "레거시 API (OAI 이전, 스트리밍 없음)",342 "Legacy API (pre-OAI, no streaming)": "레거시 API (OAI 이전, 스트리밍 없음)",
public/locales/nl-nl.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "vlag",318 "flag": "vlag",
319 "API key (optional)": "API-sleutel (optioneel)",319 "API key (optional)": "API-sleutel (optioneel)",
320 "Server url": "Server-URL",320 "Server url": "Server-URL",
321 "Example: 127.0.0.1:5000": "Voorbeeld: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Voorbeeld: http://127.0.0.1:5000",
322 "Custom model (optional)": "Aangepast model (optioneel)",322 "Custom model (optional)": "Aangepast model (optioneel)",
323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-wrappermodus)",323 "vllm-project/vllm": "vllm-project/vllm (OpenAI API-wrappermodus)",
324 "vLLM API key": "vLLM API-sleutel",324 "vLLM API key": "vLLM API-sleutel",
325 "Example: 127.0.0.1:8000": "Voorbeeld: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Voorbeeld: http://127.0.0.1:8000",
326 "vLLM Model": "vLLM-model",326 "vLLM Model": "vLLM-model",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus voor OpenAI API)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Wrappermodus voor OpenAI API)",
328 "Aphrodite API key": "Aphrodite API-sleutel",328 "Aphrodite API key": "Aphrodite API-sleutel",
329 "Aphrodite Model": "Aphrodite-model",329 "Aphrodite Model": "Aphrodite-model",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-server)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Output-server)",
331 "Example: 127.0.0.1:8080": "Voorbeeld: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Voorbeeld: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Voorbeeld: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Voorbeeld: http://127.0.0.1:11434",
333 "Ollama Model": "Ollama-model",333 "Ollama Model": "Ollama-model",
334 "Download": "Downloaden",334 "Download": "Downloaden",
335 "Tabby API key": "Tabby API-sleutel",335 "Tabby API key": "Tabby API-sleutel",
336 "koboldcpp API key (optional)": "koboldcpp API-sleutel (optioneel)",336 "koboldcpp API key (optional)": "koboldcpp API-sleutel (optioneel)",
337 "Example: 127.0.0.1:5001": "Voorbeeld: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Voorbeeld: http://127.0.0.1:5001",
338 "Authorize": "Toestemming geven",338 "Authorize": "Toestemming geven",
339 "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",339 "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",
340 "Bypass status check": "Omzeil statuscontrole",340 "Bypass status check": "Omzeil statuscontrole",
public/locales/pt-pt.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "bandeira",318 "flag": "bandeira",
319 "API key (optional)": "Chave da API (opcional)",319 "API key (optional)": "Chave da API (opcional)",
320 "Server url": "URL do servidor",320 "Server url": "URL do servidor",
321 "Example: 127.0.0.1:5000": "Exemplo: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Exemplo: http://127.0.0.1:5000",
322 "Custom model (optional)": "Modelo personalizado (opcional)",322 "Custom model (optional)": "Modelo personalizado (opcional)",
323 "vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",323 "vllm-project/vllm": "vllm-project/vllm (modo wrapper da API OpenAI)",
324 "vLLM API key": "Chave de API vLLM",324 "vLLM API key": "Chave de API vLLM",
325 "Example: 127.0.0.1:8000": "Exemplo: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Exemplo: http://127.0.0.1:8000",
326 "vLLM Model": "Modelo vLLM",326 "vLLM Model": "Modelo vLLM",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo Wrapper para API OpenAI)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo Wrapper para API OpenAI)",
328 "Aphrodite API key": "Chave da API Aphrodite",328 "Aphrodite API key": "Chave da API Aphrodite",
329 "Aphrodite Model": "Modelo Afrodite",329 "Aphrodite Model": "Modelo Afrodite",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de Saída)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (Servidor de Saída)",
331 "Example: 127.0.0.1:8080": "Exemplo: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Exemplo: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Exemplo: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Exemplo: http://127.0.0.1:11434",
333 "Ollama Model": "Modelo Ollama",333 "Ollama Model": "Modelo Ollama",
334 "Download": "Baixar",334 "Download": "Baixar",
335 "Tabby API key": "Chave da API do Tabby",335 "Tabby API key": "Chave da API do Tabby",
336 "koboldcpp API key (optional)": "Chave API koboldcpp (opcional)",336 "koboldcpp API key (optional)": "Chave API koboldcpp (opcional)",
337 "Example: 127.0.0.1:5001": "Exemplo: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Exemplo: http://127.0.0.1:5001",
338 "Authorize": "Autorizar",338 "Authorize": "Autorizar",
339 "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",339 "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",
340 "Bypass status check": "Ignorar verificação de status",340 "Bypass status check": "Ignorar verificação de status",
public/locales/ru-ru.json+162 -27
@@ -23,9 +23,8 @@
23 "Mirostat Mode": "Режим",23 "Mirostat Mode": "Режим",
24 "Mirostat Tau": "Tau",24 "Mirostat Tau": "Tau",
25 "Mirostat Eta": "Eta",25 "Mirostat Eta": "Eta",
26 "Variability parameter for Mirostat outputs": "Параметр изменчивости для выходных данных Mirostat.",26 "Variability parameter for Mirostat outputs": "Вариативность для выходных данных Mirostat.",
27 "Learning rate of Mirostat": "Скорость обучения Mirostat.",27 "Learning rate of Mirostat": "Скорость обучения Mirostat.",
28 "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Сила условия регуляризации контрастивного поиска. Установите значение 0, чтобы отключить CS.",
29 "Temperature Last": "Температура последней",28 "Temperature Last": "Температура последней",
30 "LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",29 "LLaMA / Mistral / Yi models only": "Только для моделей LLaMA / Mistral / Yi. Перед этим обязательно выберите подходящий токенизатор.\nПоследовательности, которых не должно быть на выходе.\nОдна на строку. Текст или [идентификаторы токенов].\nМногие токены имеют пробел впереди. Используйте счетчик токенов, если не уверены.",
31 "Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",30 "Example: some text [42, 69, 1337]": "Пример:\nкакой-то текст\n[42, 69, 1337]",
@@ -60,13 +59,11 @@
60 "Add BOS Token": "Добавлять BOS-токен",59 "Add BOS Token": "Добавлять BOS-токен",
61 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",60 "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Добавлять BOS-токен в начале промпта. Если выключить, ответы могут стать более креативными.",
62 "Ban EOS Token": "Запретить EOS-токен",61 "Ban EOS Token": "Запретить EOS-токен",
63 "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию преждевременно",62 "Ban the eos_token. This forces the model to never end the generation prematurely": "Запрет EOS-токена не позволит модели завершить генерацию самостоятельно (только при достижении лимита токенов)",
64 "Skip Special Tokens": "Пропускать спец. токены",63 "Skip Special Tokens": "Пропускать спец. токены",
65 "Beam search": "Поиск Beam",64 "Beam search": "Beam Search",
66 "Number of Beams": "Количество Beam",
67 "Length Penalty": "Штраф за длину",65 "Length Penalty": "Штраф за длину",
68 "Early Stopping": "Преждевременная остановка",66 "Early Stopping": "Прекращать сразу",
69 "Contrastive search": "Контрастный поиск",
70 "Penalty Alpha": "Penalty Alpha",67 "Penalty Alpha": "Penalty Alpha",
71 "Seed": "Зерно",68 "Seed": "Зерно",
72 "Epsilon Cutoff": "Epsilon Cutoff",69 "Epsilon Cutoff": "Epsilon Cutoff",
@@ -89,7 +86,7 @@
89 "Text Completion presets": "Пресеты для Text Completion",86 "Text Completion presets": "Пресеты для Text Completion",
90 "Documentation on sampling parameters": "Документация по параметрам сэмплеров",87 "Documentation on sampling parameters": "Документация по параметрам сэмплеров",
91 "Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",88 "Set all samplers to their neutral/disabled state.": "Установить все сэмплеры в нейтральное/отключенное состояние.",
92 "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы знаете, что делаете.",89 "Only enable this if your model supports context sizes greater than 8192 tokens": "Включайте эту опцию, только если ваша модель поддерживает размер контекста более 8192 токенов.\nУвеличивайте только если вы понимаете, что делаете.",
93 "Wrap in Quotes": "Заключать в кавычки",90 "Wrap in Quotes": "Заключать в кавычки",
94 "Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",91 "Wrap entire user message in quotes before sending.": "Перед отправкой заключать всё сообщение пользователя в кавычки.",
95 "Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",92 "Leave off if you use quotes manually for speech.": "Оставьте выключенным, если вручную выставляете кавычки для прямой речи.",
@@ -109,13 +106,13 @@
109 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",106 "Adjust response length to worker capabilities": "Подстраивать длину ответа под возможности рабочих машин",
110 "API key": "API-ключ",107 "API key": "API-ключ",
111 "Tabby API key": "Tabby API-ключ",108 "Tabby API key": "Tabby API-ключ",
112 "Get it here:": "Получить здесь:",109 "Get it here:": "Получите здесь:",
113 "Register": "Зарегистрироваться",110 "Register": "Зарегистрироваться",
114 "TogetherAI Model": "Модель TogetherAI",111 "TogetherAI Model": "Модель TogetherAI",
115 "Example: 127.0.0.1:5001": "Пример: http://127.0.0.1:5001",112 "Example: http://127.0.0.1:5001": "Пример: http://127.0.0.1:5001",
116 "ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер вывода)",113 "ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер вывода)",
117 "Example: 127.0.0.1:8080": "Пример: http://127.0.0.1:8080",114 "Example: http://127.0.0.1:8080": "Пример: http://127.0.0.1:8080",
118 "Example: 127.0.0.1:11434": "Пример: http://127.0.0.1:11434",115 "Example: http://127.0.0.1:11434": "Пример: http://127.0.0.1:11434",
119 "Ollama Model": "Модель Ollama",116 "Ollama Model": "Модель Ollama",
120 "Download": "Скачать",117 "Download": "Скачать",
121 "TogetherAI API Key": "TogetherAI API-ключ",118 "TogetherAI API Key": "TogetherAI API-ключ",
@@ -136,7 +133,7 @@
136 "Server url": "URL-адрес сервера",133 "Server url": "URL-адрес сервера",
137 "Custom model (optional)": "Пользовательская модель (необязательно)",134 "Custom model (optional)": "Пользовательская модель (необязательно)",
138 "Bypass API status check": "Обход проверки статуса API",135 "Bypass API status check": "Обход проверки статуса API",
139 "Example: 127.0.0.1:5000": "Пример: http://127.0.0.1:5000",136 "Example: http://127.0.0.1:5000": "Пример: http://127.0.0.1:5000",
140 "Bypass status check": "Обход проверки статуса",137 "Bypass status check": "Обход проверки статуса",
141 "Mancer API key": "Ключ от Mancer API",138 "Mancer API key": "Ключ от Mancer API",
142 "to get your OpenAI API key.": "для получения ключа от API OpenAI",139 "to get your OpenAI API key.": "для получения ключа от API OpenAI",
@@ -289,10 +286,10 @@
289 "Author's Note": "Заметки автора",286 "Author's Note": "Заметки автора",
290 "Replace empty message": "Заменять пустые сообщения",287 "Replace empty message": "Заменять пустые сообщения",
291 "Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",288 "Send this text instead of nothing when the text box is empty.": "Этот текст будет отправлен в случае отсутствия текста на отправку.",
292 "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно знаете, что делаете",289 "Unrestricted maximum value for the context slider": "Убрать потолок для ползунка контекста. Включайте только если точно понимаете, что делаете",
293 "Chat Completion Source": "Источник для Chat Completion",290 "Chat Completion Source": "Источник для Chat Completion",
294 "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",291 "Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde.",
295 "Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",292 "Review the Privacy statement": "Ознакомьтесь с заявлением о конфиденциальности",
296 "Trusted workers only": "Только доверенные рабочие машины",293 "Trusted workers only": "Только доверенные рабочие машины",
297 "For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",294 "For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
298 "-- Horde models not loaded --": "--Модель Horde не загружена--",295 "-- Horde models not loaded --": "--Модель Horde не загружена--",
@@ -699,7 +696,7 @@
699 "Aggressive": "Агрессивный",696 "Aggressive": "Агрессивный",
700 "Very aggressive": "Очень агрессивный",697 "Very aggressive": "Очень агрессивный",
701 "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",698 "Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
702 "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как внести свой вклад в свои свободные GPU-циклы в орду",699 "Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
703 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",700 "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
704 "Load koboldcpp order": "Загрузить порядок из koboldcpp",701 "Load koboldcpp order": "Загрузить порядок из koboldcpp",
705 "Use Google Tokenizer": "Использовать токенизатор Google",702 "Use Google Tokenizer": "Использовать токенизатор Google",
@@ -744,7 +741,7 @@
744 "Last Assistant Prefix": "Последний префикс ассистента",741 "Last Assistant Prefix": "Последний префикс ассистента",
745 "System Instruction Prefix": "Префикс системной инструкции",742 "System Instruction Prefix": "Префикс системной инструкции",
746 "User Filler Message": "Принудительное сообщение пользователя",743 "User Filler Message": "Принудительное сообщение пользователя",
747 "Permanent": "перманентных",744 "Permanent": "постоянных",
748 "Alt. Greetings": "Др. варианты",745 "Alt. Greetings": "Др. варианты",
749 "Smooth Streaming": "Плавный стриминг",746 "Smooth Streaming": "Плавный стриминг",
750 "Save checkpoint": "Сохранить чекпоинт",747 "Save checkpoint": "Сохранить чекпоинт",
@@ -1227,7 +1224,6 @@
1227 "JSON-serialized array of strings.": "Список строк в формате JSON.",1224 "JSON-serialized array of strings.": "Список строк в формате JSON.",
1228 "Mirostat_desc": "Mirostat - своего рода термометр, измеряющий перплексию для выводимого текста.\nMirostat подгоняет перплексию генерируемого текста к перплексии входного текста, что позволяет избежать повторов.\n(когда по мере генерации текста авторегрессионным инференсом, перплексия всё больше приближается к нулю)\n а также ловушки перплексии (когда перплексия начинает уходить в сторону)\nБолее подробное описание в статье Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nРежим выбирает версию Mirostat. 0=отключить, 1=Mirostat 1.0 (только llama.cpp), 2=Mirostat 2.0.",1225 "Mirostat_desc": "Mirostat - своего рода термометр, измеряющий перплексию для выводимого текста.\nMirostat подгоняет перплексию генерируемого текста к перплексии входного текста, что позволяет избежать повторов.\n(когда по мере генерации текста авторегрессионным инференсом, перплексия всё больше приближается к нулю)\n а также ловушки перплексии (когда перплексия начинает уходить в сторону)\nБолее подробное описание в статье Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).\nРежим выбирает версию Mirostat. 0=отключить, 1=Mirostat 1.0 (только llama.cpp), 2=Mirostat 2.0.",
1229 "Helpful tip coming soon.": "Подсказку скоро добавим.",1226 "Helpful tip coming soon.": "Подсказку скоро добавим.",
1230 "Temperature_Last_desc": "Использовать Temperature сэмплер в последнюю очередь. Это почти всегда разумно.\nПри включении: сначала выборка набора правдоподобных токенов, затем применение Temperature для корректировки их относительных вероятностей (технически, логитов).\nПри отключении: сначала применение Temperature для корректировки относительных вероятностей ВСЕХ токенов, затем выборка правдоподобных токенов из этого.\nОтключение Temperature Last увеличивает вероятности в хвосте распределения, что увеличивает шансы получить несогласованный ответ.",
1231 "Speculative Ngram": "Speculative Ngram",1227 "Speculative Ngram": "Speculative Ngram",
1232 "Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",1228 "Use a different speculative decoding method without a draft model": "Use a different speculative decoding method without a draft model.\rUsing a draft model is preferred. Speculative ngram is not as effective.",
1233 "Spaces Between Special Tokens": "Spaces Between Special Tokens",1229 "Spaces Between Special Tokens": "Spaces Between Special Tokens",
@@ -1256,7 +1252,6 @@
1256 "DreamGen Model": "Модель DreamGen",1252 "DreamGen Model": "Модель DreamGen",
1257 "vllm-project/vllm": "vllm-project/vllm (режим враппера OpenAI API)",1253 "vllm-project/vllm": "vllm-project/vllm (режим враппера OpenAI API)",
1258 "vLLM API key": "Ключ от API vLLM",1254 "vLLM API key": "Ключ от API vLLM",
1259 "Example: 127.0.0.1:8000": "Example: http://127.0.0.1:8000",
1260 "vLLM Model": "Модель vLLM",1255 "vLLM Model": "Модель vLLM",
1261 "Aphrodite Model": "Модель Aphrodite",1256 "Aphrodite Model": "Модель Aphrodite",
1262 "Peek a password": "Посмотреть пароль",1257 "Peek a password": "Посмотреть пароль",
@@ -1734,7 +1729,7 @@
1734 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",1729 "markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
1735 "Save and Update": "Сохранить и обновить",1730 "Save and Update": "Сохранить и обновить",
1736 "Profile name:": "Название профиля:",1731 "Profile name:": "Название профиля:",
1737 "API returned an error": "API вернуло ошибку",1732 "API returned an error": "API ответило ошибкой",
1738 "Failed to save preset": "Не удалось сохранить пресет",1733 "Failed to save preset": "Не удалось сохранить пресет",
1739 "Preset name should be unique.": "Название пресета должно быть уникальным.",1734 "Preset name should be unique.": "Название пресета должно быть уникальным.",
1740 "Invalid file": "Невалидный файл",1735 "Invalid file": "Невалидный файл",
@@ -1756,8 +1751,7 @@
1756 "dot quota_error": "имеется достаточно кредитов.",1751 "dot quota_error": "имеется достаточно кредитов.",
1757 "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",1752 "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.",
1758 "Proxy preset '${0}' not found": "Пресет '${0}' не найден",1753 "Proxy preset '${0}' not found": "Пресет '${0}' не найден",
1759 "Window.ai returned an error": "Window.ai вернул ошибку",1754 "Window.ai returned an error": "Window.ai ответил ошибкой",
1760 "Get it here:": "Загрузите здесь:",
1761 "Extension is not installed": "Расширение не установлено",1755 "Extension is not installed": "Расширение не установлено",
1762 "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",1756 "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.",
1763 "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",1757 "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.",
@@ -1866,7 +1860,7 @@
1866 "Group Chat could not be saved": "Не удалось сохранить групповой чат",1860 "Group Chat could not be saved": "Не удалось сохранить групповой чат",
1867 "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",1861 "Deleted group member swiped. To get a reply, add them back to the group.": "Вы пытаетесь свайпнуть удалённого члена группы. Чтобы получить ответ, добавьте этого персонажа обратно в группу.",
1868 "Currently no group selected.": "В данный момент не выбрано ни одной группы.",1862 "Currently no group selected.": "В данный момент не выбрано ни одной группы.",
1869 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонаж закончит печатать.",1863 "Not so fast! Wait for the characters to stop typing before deleting the group.": "Чуть помедленнее! Перед удалением группы дождитесь, пока персонажи закончат печатать.",
1870 "Delete the group?": "Удалить группу?",1864 "Delete the group?": "Удалить группу?",
1871 "This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",1865 "This will also delete all your chats with that group. If you want to delete a single conversation, select a \"View past chats\" option in the lower left menu.": "Вместе с ней будут удалены и все её чаты. Если требуется удалить только один чат, воспользуйтесь кнопкой \"Все чаты\" в меню в левом нижнем углу.",
1872 "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",1866 "Can't peek a character while group reply is being generated": "Невозможно открыть карточку персонажа во время генерации ответа",
@@ -1997,7 +1991,7 @@
1997 "Default persona deleted": "Удалена персона по умолчанию",1991 "Default persona deleted": "Удалена персона по умолчанию",
1998 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",1992 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
1999 "Persona deleted": "Персона удалена",1993 "Persona deleted": "Персона удалена",
2000 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задать имя.",1994 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо присвоить имя.",
2001 "Persona name not set": "У персоны отсутствует имя",1995 "Persona name not set": "У персоны отсутствует имя",
2002 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",1996 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
2003 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",1997 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
@@ -2038,7 +2032,7 @@
2038 "[Currently loaded]": "[Загруженная сейчас]",2032 "[Currently loaded]": "[Загруженная сейчас]",
2039 "Search providers...": "Искать по провайдерам...",2033 "Search providers...": "Искать по провайдерам...",
2040 "Automatically chooses an alternative provider if chosen providers can't serve your request.": "Автоматически переключаться на другого провайдера, если текущий не может обслужить запрос.",2034 "Automatically chooses an alternative provider if chosen providers can't serve your request.": "Автоматически переключаться на другого провайдера, если текущий не может обслужить запрос.",
2041 "Example: 127.0.0.1:8000": "Пример: 127.0.0.1:8000",2035 "Example: http://127.0.0.1:8000": "Пример: http://127.0.0.1:8000",
2042 "Edit a connection profile": "Редактировать профиль соединения",2036 "Edit a connection profile": "Редактировать профиль соединения",
2043 "System Prompt Name": "Название системного промпта",2037 "System Prompt Name": "Название системного промпта",
2044 "Use System Prompt": "Использовать системный промпт",2038 "Use System Prompt": "Использовать системный промпт",
@@ -2203,5 +2197,146 @@
2203 "Input:": "Входные данные:",2197 "Input:": "Входные данные:",
2204 "Tokenized text:": "Токенизированный текст:",2198 "Tokenized text:": "Токенизированный текст:",
2205 "Token IDs:": "Идентификаторы токенов:",2199 "Token IDs:": "Идентификаторы токенов:",
2206 "Tokens:": "Токенов:"2200 "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": "Работает"
2207}2342}
public/locales/uk-ua.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "прапорцем",318 "flag": "прапорцем",
319 "API key (optional)": "Ключ API (необов'язково)",319 "API key (optional)": "Ключ API (необов'язково)",
320 "Server url": "URL-адреса сервера",320 "Server url": "URL-адреса сервера",
321 "Example: 127.0.0.1:5000": "Приклад: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Приклад: http://127.0.0.1:5000",
322 "Custom model (optional)": "Власна модель (необов'язково)",322 "Custom model (optional)": "Власна модель (необов'язково)",
323 "vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",323 "vllm-project/vllm": "vllm-project/vllm (режим оболонки OpenAI API)",
324 "vLLM API key": "Ключ API vLLM",324 "vLLM API key": "Ключ API vLLM",
325 "Example: 127.0.0.1:8000": "Приклад: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Приклад: http://127.0.0.1:8000",
326 "vLLM Model": "Модель vLLM",326 "vLLM Model": "Модель vLLM",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (режим OpenAI API)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (режим OpenAI API)",
328 "Aphrodite API key": "Ключ API для Aphrodite",328 "Aphrodite API key": "Ключ API для Aphrodite",
329 "Aphrodite Model": "Модель Афродіта",329 "Aphrodite Model": "Модель Афродіта",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер виведення)",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp (сервер виведення)",
331 "Example: 127.0.0.1:8080": "Приклад: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Приклад: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Приклад: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Приклад: http://127.0.0.1:11434",
333 "Ollama Model": "Модель Ollama",333 "Ollama Model": "Модель Ollama",
334 "Download": "Завантажити",334 "Download": "Завантажити",
335 "Tabby API key": "Ключ API для Tabby",335 "Tabby API key": "Ключ API для Tabby",
336 "koboldcpp API key (optional)": "API-ключ koboldcpp (необов’язково)",336 "koboldcpp API key (optional)": "API-ключ koboldcpp (необов’язково)",
337 "Example: 127.0.0.1:5001": "Приклад: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Приклад: http://127.0.0.1:5001",
338 "Authorize": "Авторизувати",338 "Authorize": "Авторизувати",
339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Отримайте свій токен API OpenRouter за допомогою OAuth. Вас буде перенаправлено на openrouter.ai",339 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Отримайте свій токен API OpenRouter за допомогою OAuth. Вас буде перенаправлено на openrouter.ai",
340 "Bypass status check": "Обійти перевірку статусу",340 "Bypass status check": "Обійти перевірку статусу",
public/locales/vi-vn.json+5 -5
@@ -318,23 +318,23 @@
318 "flag": "cờ",318 "flag": "cờ",
319 "API key (optional)": "Key API (tùy chọn)",319 "API key (optional)": "Key API (tùy chọn)",
320 "Server url": "URL máy chủ",320 "Server url": "URL máy chủ",
321 "Example: 127.0.0.1:5000": "Ví dụ: 127.0.0.1:5000",321 "Example: http://127.0.0.1:5000": "Ví dụ: http://127.0.0.1:5000",
322 "Custom model (optional)": "Model tùy chỉnh (tùy chọn)",322 "Custom model (optional)": "Model tùy chỉnh (tùy chọn)",
323 "vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",323 "vllm-project/vllm": "vllm-project/vllm (Chế độ trình bao bọc API OpenAI)",
324 "vLLM API key": "Key API vLLM",324 "vLLM API key": "Key API vLLM",
325 "Example: 127.0.0.1:8000": "Ví dụ: http://127.0.0.1:8000",325 "Example: http://127.0.0.1:8000": "Ví dụ: http://127.0.0.1:8000",
326 "vLLM Model": "Model vLLM",326 "vLLM Model": "Model vLLM",
327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Chế độ đóng gói cho Giao diện lập trình ứng dụng OpenAI)",327 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Chế độ đóng gói cho Giao diện lập trình ứng dụng OpenAI)",
328 "Aphrodite API key": "Key API Aphrodite",328 "Aphrodite API key": "Key API Aphrodite",
329 "Aphrodite Model": "Moddel cho Aphrodite",329 "Aphrodite Model": "Moddel cho Aphrodite",
330 "ggerganov/llama.cpp": "ggerganov/llama.cpp",330 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
331 "Example: 127.0.0.1:8080": "Ví dụ: 127.0.0.1:8080",331 "Example: http://127.0.0.1:8080": "Ví dụ: http://127.0.0.1:8080",
332 "Example: 127.0.0.1:11434": "Ví dụ: 127.0.0.1:11434",332 "Example: http://127.0.0.1:11434": "Ví dụ: http://127.0.0.1:11434",
333 "Ollama Model": "Model Ollama",333 "Ollama Model": "Model Ollama",
334 "Download": "Tải xuống",334 "Download": "Tải xuống",
335 "Tabby API key": "Key API Tabby",335 "Tabby API key": "Key API Tabby",
336 "koboldcpp API key (optional)": "Key API koboldcpp (tùy chọn)",336 "koboldcpp API key (optional)": "Key API koboldcpp (tùy chọn)",
337 "Example: 127.0.0.1:5001": "Ví dụ: 127.0.0.1:5001",337 "Example: http://127.0.0.1:5001": "Ví dụ: http://127.0.0.1:5001",
338 "Cho phép": "Ủy quyền",338 "Cho phép": "Ủy quyền",
339 "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",339 "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",
340 "Bypass status check": "Bỏ qua check trạng thái",340 "Bypass status check": "Bỏ qua check trạng thái",
public/locales/zh-cn.json+5 -5
@@ -347,7 +347,7 @@
347 "Mancer Model": "Mancer 模型",347 "Mancer Model": "Mancer 模型",
348 "API key (optional)": "API密钥(可选)",348 "API key (optional)": "API密钥(可选)",
349 "Server url": "服务器URL",349 "Server url": "服务器URL",
350 "Example: 127.0.0.1:5000": "示例:127.0.0.1:5000",350 "Example: http://127.0.0.1:5000": "示例:http://127.0.0.1:5000",
351 "Model ID (optional)": "模型 ID(可选)",351 "Model ID (optional)": "模型 ID(可选)",
352 "Make sure you run it with": "确保您在运行时加上",352 "Make sure you run it with": "确保您在运行时加上",
353 "flag": "标志",353 "flag": "标志",
@@ -364,7 +364,7 @@
364 "No model description": "[无描述]",364 "No model description": "[无描述]",
365 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 包装器模式)",365 "vllm-project/vllm": "vllm-project/vllm(OpenAI API 包装器模式)",
366 "vLLM API key": "vLLM API 密钥",366 "vLLM API key": "vLLM API 密钥",
367 "Example: 127.0.0.1:8000": "示例:http://127.0.0.1:8000",367 "Example: http://127.0.0.1:8000": "示例:http://127.0.0.1:8000",
368 "vLLM Model": "vLLM 模型",368 "vLLM Model": "vLLM 模型",
369 "HuggingFace Token": "HuggingFace 代币",369 "HuggingFace Token": "HuggingFace 代币",
370 "Endpoint URL": "端点 URL",370 "Endpoint URL": "端点 URL",
@@ -373,8 +373,8 @@
373 "Aphrodite API key": "Aphrodite API 密钥",373 "Aphrodite API key": "Aphrodite API 密钥",
374 "Aphrodite Model": "Aphrodite 模型",374 "Aphrodite Model": "Aphrodite 模型",
375 "ggerganov/llama.cpp": "ggerganov/llama.cpp",375 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
376 "Example: 127.0.0.1:8080": "示例:127.0.0.1:8080",376 "Example: http://127.0.0.1:8080": "示例:http://127.0.0.1:8080",
377 "Example: 127.0.0.1:11434": "示例:127.0.0.1:11434",377 "Example: http://127.0.0.1:11434": "示例:http://127.0.0.1:11434",
378 "Ollama Model": "Ollama 模型",378 "Ollama Model": "Ollama 模型",
379 "Download": "下载",379 "Download": "下载",
380 "Tabby API key": "Tabby API 密钥",380 "Tabby API key": "Tabby API 密钥",
@@ -382,7 +382,7 @@
382 "must be set in Tabby's config.yml to switch models.": "必须在Tabby的config.yml内设置以切换模型",382 "must be set in Tabby's config.yml to switch models.": "必须在Tabby的config.yml内设置以切换模型",
383 "Use an admin API key.": "使用管理员API密钥。",383 "Use an admin API key.": "使用管理员API密钥。",
384 "koboldcpp API key (optional)": "koboldcpp API 密钥(可选)",384 "koboldcpp API key (optional)": "koboldcpp API 密钥(可选)",
385 "Example: 127.0.0.1:5001": "示例:127.0.0.1:5001",385 "Example: http://127.0.0.1:5001": "示例:http://127.0.0.1:5001",
386 "Bypass status check": "跳过状态检查",386 "Bypass status check": "跳过状态检查",
387 "Derive context size from backend": "从后端获取上下文长度",387 "Derive context size from backend": "从后端获取上下文长度",
388 "Authorize": "授权",388 "Authorize": "授权",
public/locales/zh-tw.json+5 -5
@@ -319,23 +319,23 @@
319 "flag": "旗標",319 "flag": "旗標",
320 "API key (optional)": "API 金鑰(可選)",320 "API key (optional)": "API 金鑰(可選)",
321 "Server url": "伺服器 URL",321 "Server url": "伺服器 URL",
322 "Example: 127.0.0.1:5000": "範例:127.0.0.1:5000",322 "Example: http://127.0.0.1:5000": "範例:http://127.0.0.1:5000",
323 "Custom model (optional)": "自訂模型(選填)",323 "Custom model (optional)": "自訂模型(選填)",
324 "vllm-project/vllm": "vllm-project/vllm",324 "vllm-project/vllm": "vllm-project/vllm",
325 "vLLM API key": "vLLM API 金鑰",325 "vLLM API key": "vLLM API 金鑰",
326 "Example: 127.0.0.1:8000": "範例:127.0.0.1:8000",326 "Example: http://127.0.0.1:8000": "範例:http://127.0.0.1:8000",
327 "vLLM Model": "vLLM 模型",327 "vLLM Model": "vLLM 模型",
328 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite 引擎",328 "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite 引擎",
329 "Aphrodite API key": "Aphrodite API 金鑰",329 "Aphrodite API key": "Aphrodite API 金鑰",
330 "Aphrodite Model": "Aphrodite 模型",330 "Aphrodite Model": "Aphrodite 模型",
331 "ggerganov/llama.cpp": "ggerganov/llama.cpp",331 "ggerganov/llama.cpp": "ggerganov/llama.cpp",
332 "Example: 127.0.0.1:8080": "範例:127.0.0.1:8080",332 "Example: http://127.0.0.1:8080": "範例:http://127.0.0.1:8080",
333 "Example: 127.0.0.1:11434": "範例:127.0.0.1:11434",333 "Example: http://127.0.0.1:11434": "範例:http://127.0.0.1:11434",
334 "Ollama Model": "Ollama 模型",334 "Ollama Model": "Ollama 模型",
335 "Download": "下載",335 "Download": "下載",
336 "Tabby API key": "Tabby API 金鑰",336 "Tabby API key": "Tabby API 金鑰",
337 "koboldcpp API key (optional)": "KoboldCpp API 金鑰(可選)",337 "koboldcpp API key (optional)": "KoboldCpp API 金鑰(可選)",
338 "Example: 127.0.0.1:5001": "範例:127.0.0.1:5001",338 "Example: http://127.0.0.1:5001": "範例:http://127.0.0.1:5001",
339 "Authorize": "授權",339 "Authorize": "授權",
340 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "使用 OAuth 流程取得您的 OpenRouter API 符元。您將被重新導向到 openrouter.ai",340 "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "使用 OAuth 流程取得您的 OpenRouter API 符元。您將被重新導向到 openrouter.ai",
341 "Bypass status check": "繞過狀態檢查",341 "Bypass status check": "繞過狀態檢查",
public/script.js+306 -96
@@ -172,8 +172,10 @@ import {
172 copyText,172 copyText,
173 escapeHtml,173 escapeHtml,
174 saveBase64AsFile,174 saveBase64AsFile,
175 uuidv4,
176 equalsIgnoreCaseAndAccents,
175} from './scripts/utils.js';177} from './scripts/utils.js';
176import { debounce_timeout } from './scripts/constants.js';178import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js';
177179
178import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';180import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
179import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';181import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';
@@ -494,6 +496,8 @@ export const event_types = {
494 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',496 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
495 GENERATE_AFTER_DATA: 'generate_after_data',497 GENERATE_AFTER_DATA: 'generate_after_data',
496 GROUP_MEMBER_DRAFTED: 'group_member_drafted',498 GROUP_MEMBER_DRAFTED: 'group_member_drafted',
499 GROUP_WRAPPER_STARTED: 'group_wrapper_started',
500 GROUP_WRAPPER_FINISHED: 'group_wrapper_finished',
497 WORLD_INFO_ACTIVATED: 'world_info_activated',501 WORLD_INFO_ACTIVATED: 'world_info_activated',
498 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',502 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
499 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',503 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',
@@ -514,6 +518,9 @@ export const event_types = {
514 ONLINE_STATUS_CHANGED: 'online_status_changed',518 ONLINE_STATUS_CHANGED: 'online_status_changed',
515 IMAGE_SWIPED: 'image_swiped',519 IMAGE_SWIPED: 'image_swiped',
516 CONNECTION_PROFILE_LOADED: 'connection_profile_loaded',520 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',
517 TOOL_CALLS_PERFORMED: 'tool_calls_performed',524 TOOL_CALLS_PERFORMED: 'tool_calls_performed',
518 TOOL_CALLS_RENDERED: 'tool_calls_rendered',525 TOOL_CALLS_RENDERED: 'tool_calls_rendered',
519};526};
@@ -589,7 +596,7 @@ let is_delete_mode = false;
589let fav_ch_checked = false;596let fav_ch_checked = false;
590let scrollLock = false;597let scrollLock = false;
591export let abortStatusCheck = new AbortController();598export let abortStatusCheck = new AbortController();
592let charDragDropHandler = null;599export let charDragDropHandler = null;
593600
594/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */601/** @type {debounce_timeout} The debounce timeout used for chat/settings save. debounce_timeout.long: 1.000 ms */
595export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;602export const DEFAULT_SAVE_EDIT_TIMEOUT = debounce_timeout.relaxed;
@@ -644,7 +651,7 @@ export const extension_prompt_roles = {
644 ASSISTANT: 2,651 ASSISTANT: 2,
645};652};
646653
647export const MAX_INJECTION_DEPTH = 1000;654export const MAX_INJECTION_DEPTH = 10000;
648655
649const SAFETY_CHAT = [656const SAFETY_CHAT = [
650 {657 {
@@ -1140,7 +1147,7 @@ export async function clearItemizedPrompts() {
1140async function getStatusHorde() {1147async function getStatusHorde() {
1141 try {1148 try {
1142 const hordeStatus = await checkHordeStatus();1149 const hordeStatus = await checkHordeStatus();
1143 setOnlineStatus(hordeStatus ? 'Connected' : 'no_connection');1150 setOnlineStatus(hordeStatus ? t`Connected` : 'no_connection');
1144 }1151 }
1145 catch {1152 catch {
1146 setOnlineStatus('no_connection');1153 setOnlineStatus('no_connection');
@@ -1207,7 +1214,7 @@ async function getStatusTextgen() {
1207 }1214 }
12081215
1209 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {1216 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) {
1210 setOnlineStatus('Status check bypassed');1217 setOnlineStatus(t`Status check bypassed`);
1211 return resultCheckStatus();1218 return resultCheckStatus();
1212 }1219 }
12131220
@@ -1232,7 +1239,7 @@ async function getStatusTextgen() {
1232 setOnlineStatus(textgen_settings.togetherai_model);1239 setOnlineStatus(textgen_settings.togetherai_model);
1233 } else if (textgen_settings.type === textgen_types.OLLAMA) {1240 } else if (textgen_settings.type === textgen_types.OLLAMA) {
1234 loadOllamaModels(data?.data);1241 loadOllamaModels(data?.data);
1235 setOnlineStatus(textgen_settings.ollama_model || 'Connected');1242 setOnlineStatus(textgen_settings.ollama_model || t`Connected`);
1236 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {1243 } else if (textgen_settings.type === textgen_types.INFERMATICAI) {
1237 loadInfermaticAIModels(data?.data);1244 loadInfermaticAIModels(data?.data);
1238 setOnlineStatus(textgen_settings.infermaticai_model);1245 setOnlineStatus(textgen_settings.infermaticai_model);
@@ -1256,7 +1263,7 @@ async function getStatusTextgen() {
1256 setOnlineStatus(textgen_settings.tabby_model || data?.result);1263 setOnlineStatus(textgen_settings.tabby_model || data?.result);
1257 } else if (textgen_settings.type === textgen_types.GENERIC) {1264 } else if (textgen_settings.type === textgen_types.GENERIC) {
1258 loadGenericModels(data?.data);1265 loadGenericModels(data?.data);
1259 setOnlineStatus(textgen_settings.generic_model || data?.result || 'Connected');1266 setOnlineStatus(textgen_settings.generic_model || data?.result || t`Connected`);
1260 } else {1267 } else {
1261 setOnlineStatus(data?.result);1268 setOnlineStatus(data?.result);
1262 }1269 }
@@ -1370,8 +1377,11 @@ export function resultCheckStatus() {
1370 * If the character ID doesn't exist, if the chat is being saved, or if a group is being generated, this function does nothing.1377 * If the character ID doesn't exist, if the chat is being saved, or if a group is being generated, this function does nothing.
1371 * If the character is different from the currently selected one, it will clear the chat and reset any selected character or group.1378 * If the character is different from the currently selected one, it will clear the chat and reset any selected character or group.
1372 * @param {number} id The ID of the character to switch to.1379 * @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.
1373 */1383 */
1374export async function selectCharacterById(id) {1384export async function selectCharacterById(id, { switchMenu = true } = {}) {
1375 if (characters[id] === undefined) {1385 if (characters[id] === undefined) {
1376 return;1386 return;
1377 }1387 }
@@ -1400,9 +1410,9 @@ export async function selectCharacterById(id) {
1400 }1410 }
1401 } else {1411 } else {
1402 //if clicked on character that was already selected1412 //if clicked on character that was already selected
1403 selected_button = 'character_edit';1413 switchMenu && (selected_button = 'character_edit');
1404 await unshallowCharacter(this_chid);1414 await unshallowCharacter(this_chid);
1405 select_selected_character(this_chid);1415 select_selected_character(this_chid, { switchMenu });
1406 }1416 }
1407}1417}
14081418
@@ -1787,6 +1797,7 @@ export async function getCharacters() {
1787 body: JSON.stringify({}),1797 body: JSON.stringify({}),
1788 });1798 });
1789 if (response.ok === true) {1799 if (response.ok === true) {
1800 const previousAvatar = this_chid !== undefined ? characters[this_chid]?.avatar : null;
1790 characters.splice(0, characters.length);1801 characters.splice(0, characters.length);
1791 const getData = await response.json();1802 const getData = await response.json();
1792 for (let i = 0; i < getData.length; i++) {1803 for (let i = 0; i < getData.length; i++) {
@@ -1800,8 +1811,16 @@ export async function getCharacters() {
18001811
1801 characters[i]['chat'] = String(characters[i]['chat']);1812 characters[i]['chat'] = String(characters[i]['chat']);
1802 }1813 }
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 }
1805 }1824 }
18061825
1807 await getGroups();1826 await getGroups();
@@ -2051,8 +2070,9 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
2051 }2070 }
20522071
2053 // Prompt bias replacement should be applied on the raw message2072 // 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);
2056 }2076 }
20572077
2058 if (!isSystem) {2078 if (!isSystem) {
@@ -2114,7 +2134,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
2114 }2134 }
21152135
2116 mes = mes.replace(2136 mes = mes.replace(
2117 /```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`|(".*?")|(\u201C.*?\u201D)|(\u00AB.*?\u00BB)|(\u300C.*?\u300D)|(\u300E.*?\u300F)|(\uFF02.*?\uFF02)/gm,2137 /<style>[\s\S]*?<\/style>|```[\s\S]*?```|~~~[\s\S]*?~~~|``[\s\S]*?``|`[\s\S]*?`|(".*?")|(\u201C.*?\u201D)|(\u00AB.*?\u00BB)|(\u300C.*?\u300D)|(\u300E.*?\u300F)|(\uFF02.*?\uFF02)/gim,
2118 function (match, p1, p2, p3, p4, p5, p6) {2138 function (match, p1, p2, p3, p4, p5, p6) {
2119 if (p1) {2139 if (p1) {
2120 // English double quotes2140 // English double quotes
@@ -2730,6 +2750,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2730 environment.mesExamplesRaw = fields.mesExamples || '';2750 environment.mesExamplesRaw = fields.mesExamples || '';
2731 environment.charVersion = fields.version || '';2751 environment.charVersion = fields.version || '';
2732 environment.char_version = fields.version || '';2752 environment.char_version = fields.version || '';
2753 environment.charDepthPrompt = fields.charDepthPrompt || '';
2733 }2754 }
27342755
2735 // Must be substituted last so that they're replaced inside {{description}}2756 // Must be substituted last so that they're replaced inside {{description}}
@@ -3026,6 +3047,20 @@ export async function getExtensionPromptByName(moduleName) {
3026}3047}
30273048
3028/**3049/**
3050 * Gets the maximum depth of extension prompts.
3051 * @returns {number} Maximum depth of extension prompts
3052 */
3053export 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/**
3029 * Returns the extension prompt for the given position, depth, and role.3064 * Returns the extension prompt for the given position, depth, and role.
3030 * If multiple prompts are found, they are joined with a separator.3065 * If multiple prompts are found, they are joined with a separator.
3031 * @param {number} [position] Position of the prompt3066 * @param {number} [position] Position of the prompt
@@ -3081,13 +3116,38 @@ export function baseChatReplace(value, name1, name2) {
30813116
3082/**3117/**
3083 * Returns the character card fields for the current character.3118 * 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
3085 */3133 */
3086export function getCharacterCardFields() {3134export 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 };
3088 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);3148 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
30893149
3090 const character = characters[this_chid];3150 const character = characters[currentChid];
30913151
3092 if (!character) {3152 if (!character) {
3093 return result;3153 return result;
@@ -3101,9 +3161,10 @@ export function getCharacterCardFields() {
3101 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?.trim(), name1, name2) : '';3161 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?.trim(), name1, name2) : '';
3102 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';3162 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
3103 result.version = character.data?.character_version ?? '';3163 result.version = character.data?.character_version ?? '';
3164 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
31043165
3105 if (selected_group) {3166 if (selected_group) {
3106 const groupCards = getGroupCharacterCards(selected_group, Number(this_chid));3167 const groupCards = getGroupCharacterCards(selected_group, Number(currentChid));
31073168
3108 if (groupCards) {3169 if (groupCards) {
3109 result.description = groupCards.description;3170 result.description = groupCards.description;
@@ -3269,13 +3330,25 @@ class StreamingProcessor {
32693330
3270 if (!isImpersonate && !isContinue && Array.isArray(this.swipes) && this.swipes.length > 0) {3331 if (!isImpersonate && !isContinue && Array.isArray(this.swipes) && this.swipes.length > 0) {
3271 for (let i = 0; i < this.swipes.length; i++) {3332 for (let i = 0; i < this.swipes.length; i++) {
3272 this.swipes[i] = cleanUpMessage(this.swipes[i], false, false, true, this.stoppingStrings);3333 this.swipes[i] = cleanUpMessage({
3334 getMessage: this.swipes[i],
3335 isImpersonate: false,
3336 isContinue: false,
3337 displayIncompleteSentences: true,
3338 stoppingStrings: this.stoppingStrings,
3339 });
3273 }3340 }
3274 }3341 }
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
3278 const charsToBalance = ['*', '"', '```'];3351 const charsToBalance = ['*', '"', '```', '~~~'];
3279 for (const char of charsToBalance) {3352 for (const char of charsToBalance) {
3280 if (!isFinal && isOdd(countOccurrences(processedText, char))) {3353 if (!isFinal && isOdd(countOccurrences(processedText, char))) {
3281 const separator = char.length > 1 ? '\n' : '';3354 const separator = char.length > 1 ? '\n' : '';
@@ -3505,9 +3578,10 @@ class StreamingProcessor {
3505 * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode3578 * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode
3506 * @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI.3579 * @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI.
3507 * @param {number} [responseLength] Maximum response length. If unset, the global default value is used.3580 * @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.
3508 * @returns {Promise<string>} Generated message3582 * @returns {Promise<string>} Generated message
3509 */3583 */
3510export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt, responseLength) {3584export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt, responseLength, trimNames = true) {
3511 if (!api) {3585 if (!api) {
3512 api = main_api;3586 api = main_api;
3513 }3587 }
@@ -3596,7 +3670,16 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
3596 throw new Error(data.response);3670 throw new Error(data.response);
3597 }3671 }
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
3601 if (!message) {3684 if (!message) {
3602 throw new Error('No message generated');3685 throw new Error('No message generated');
@@ -3905,6 +3988,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3905 mesExamples,3988 mesExamples,
3906 system,3989 system,
3907 jailbreak,3990 jailbreak,
3991 charDepthPrompt,
3908 } = getCharacterCardFields();3992 } = getCharacterCardFields();
39093993
3910 if (main_api !== 'openai') {3994 if (main_api !== 'openai') {
@@ -3927,7 +4011,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3927 setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);4011 setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);
3928 });4012 });
3929 } else {4013 } else {
3930 const depthPromptText = baseChatReplace(characters[this_chid]?.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2) || '';4014 const depthPromptText = charDepthPrompt || '';
3931 const depthPromptDepth = characters[this_chid]?.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;4015 const depthPromptDepth = characters[this_chid]?.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;
3932 const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid]?.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);4016 const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid]?.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);
3933 setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);4017 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
4797 hideSwipeButtons();4881 hideSwipeButtons();
4798 let getMessage = await streamingProcessor.generate();4882 let getMessage = await streamingProcessor.generate();
4799 let messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);4883 let messageChunk = cleanUpMessage({
4884 getMessage: getMessage,
4885 isImpersonate: isImpersonate,
4886 isContinue: isContinue,
4887 displayIncompleteSentences: false,
4888 });
48004889
4801 if (isContinue) {4890 if (isContinue) {
4802 getMessage = continue_mag + getMessage;4891 getMessage = continue_mag + getMessage;
@@ -4880,7 +4969,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
48804969
4881 const swipes = extractMultiSwipes(data, type);4970 const swipes = extractMultiSwipes(data, type);
48824971
4883 messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);4972 messageChunk = cleanUpMessage({
4973 getMessage: getMessage,
4974 isImpersonate: isImpersonate,
4975 isContinue: isContinue,
4976 displayIncompleteSentences: false,
4977 });
4978
4979
4884 reasoning = getRegexedString(reasoning, regex_placement.REASONING);4980 reasoning = getRegexedString(reasoning, regex_placement.REASONING);
48854981
4886 if (power_user.trim_spaces) {4982 if (power_user.trim_spaces) {
@@ -4894,7 +4990,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
48944990
4895 //Formating4991 //Formating
4896 const displayIncomplete = type === 'quiet' && !quietToLoud;4992 const displayIncomplete = type === 'quiet' && !quietToLoud;
4897 getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete);4993 getMessage = cleanUpMessage({
4994 getMessage: getMessage,
4995 isImpersonate: isImpersonate,
4996 isContinue: isContinue,
4997 displayIncompleteSentences: displayIncomplete,
4998 });
48984999
4899 if (isImpersonate) {5000 if (isImpersonate) {
4900 $('#send_textarea').val(getMessage)[0].dispatchEvent(new Event('input', { bubbles: true }));5001 $('#send_textarea').val(getMessage)[0].dispatchEvent(new Event('input', { bubbles: true }));
@@ -5012,7 +5113,8 @@ async function doChatInject(messages, isContinue) {
5012 let totalInsertedMessages = 0;5113 let totalInsertedMessages = 0;
5013 messages.reverse();5114 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++) {
5016 // Order of priority (most important go lower)5118 // Order of priority (most important go lower)
5017 const roles = [extension_prompt_roles.SYSTEM, extension_prompt_roles.USER, extension_prompt_roles.ASSISTANT];5119 const roles = [extension_prompt_roles.SYSTEM, extension_prompt_roles.USER, extension_prompt_roles.ASSISTANT];
5018 const names = {5120 const names = {
@@ -5215,6 +5317,12 @@ function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
5215 const itemName = chatItem.is_user ? chatItem['name'] : characterName;5317 const itemName = chatItem.is_user ? chatItem['name'] : characterName;
5216 const shouldPrependName = !isNarratorType;5318 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
5218 // Don't include a name if it's empty5326 // Don't include a name if it's empty
5219 let textResult = chatItem?.name && shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`;5327 let textResult = chatItem?.name && shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`;
52205328
@@ -5860,7 +5968,13 @@ function extractMultiSwipes(data, type) {
58605968
5861 for (let i = 1; i < data.choices.length; i++) {5969 for (let i = 1; i < data.choices.length; i++) {
5862 const text = data?.choices[i]?.message?.content ?? data?.choices[i]?.text ?? '';5970 const text = data?.choices[i]?.message?.content ?? data?.choices[i]?.text ?? '';
5863 const cleanedText = cleanUpMessage(text, false, false, false);5971 const cleanedText = cleanUpMessage({
5972 getMessage: text,
5973 isImpersonate: false,
5974 isContinue: false,
5975 displayIncompleteSentences: false,
5976 });
5977
5864 swipes.push(cleanedText);5978 swipes.push(cleanedText);
5865 }5979 }
5866 }5980 }
@@ -5868,13 +5982,33 @@ function extractMultiSwipes(data, type) {
5868 return swipes;5982 return swipes;
5869}5983}
58705984
5871export 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 */
5999export 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
5872 if (!getMessage) {6005 if (!getMessage) {
5873 return '';6006 return '';
5874 }6007 }
58756008
5876 // Add the prompt bias before anything else6009 // Add the prompt bias before anything else
5877 if (6010 if (
6011 includeUserPromptBias &&
5878 power_user.user_prompt_bias &&6012 power_user.user_prompt_bias &&
5879 !isImpersonate &&6013 !isImpersonate &&
5880 !isContinue &&6014 !isContinue &&
@@ -5912,21 +6046,32 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
5912 // "trailing whitespace on newlines\nevery line of the string\nsample text"6046 // "trailing whitespace on newlines\nevery line of the string\nsample text"
5913 getMessage = getMessage.replace(/[^\S\r\n]+$/gm, '');6047 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.`);
5922 }6065 }
59236066
5924 if (nameToTrim && getMessage.indexOf(`${nameToTrim}:`) == 0) {6067 // If there is trailing text starting with the wrong name, trim it off.
5925 getMessage = getMessage.substring(0, getMessage.indexOf(`${nameToTrim}:`));6068 startIndex = getMessage.indexOf(`\n${wrongName}:`);
6069 if (startIndex >= 0) {
6070 getMessage = getMessage.substring(0, startIndex);
5926 }6071 }
5927 if (nameToTrim && getMessage.indexOf(`\n${nameToTrim}:`) >= 0) {
5928 getMessage = getMessage.substring(0, getMessage.indexOf(`\n${nameToTrim}:`));
5929 }6072 }
6073 }
6074
5930 if (getMessage.indexOf('<|endoftext|>') != -1) {6075 if (getMessage.indexOf('<|endoftext|>') != -1) {
5931 getMessage = getMessage.substring(0, getMessage.indexOf('<|endoftext|>'));6076 getMessage = getMessage.substring(0, getMessage.indexOf('<|endoftext|>'));
5932 }6077 }
@@ -5986,14 +6131,19 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
5986 getMessage = fixMarkdown(getMessage, false);6131 getMessage = fixMarkdown(getMessage, false);
5987 }6132 }
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.
5989 const nameToTrim2 = isImpersonate6138 const nameToTrim2 = isImpersonate
5990 ? (!power_user.allow_name1_display ? name1 : '')6139 ? (!power_user.allow_name1_display ? name1 : '') // user
5991 : (!power_user.allow_name2_display ? name2 : '');6140 : (!power_user.allow_name2_display ? name2 : ''); // char
59926141
5993 if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) {6142 if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) {
5994 getMessage = getMessage.replace(nameToTrim2 + ':', '');6143 getMessage = getMessage.replace(nameToTrim2 + ':', '');
5995 getMessage = getMessage.trimStart();6144 getMessage = getMessage.trimStart();
5996 }6145 }
6146 }
59976147
5998 if (isImpersonate) {6148 if (isImpersonate) {
5999 getMessage = getMessage.trim();6149 getMessage = getMessage.trim();
@@ -6588,6 +6738,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65886738
6589 await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar);6739 await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar);
65906740
6741 // Unload current character
6742 setCharacterId(undefined);
6591 // Reload characters list6743 // Reload characters list
6592 await getCharacters();6744 await getCharacters();
65936745
@@ -6596,7 +6748,6 @@ export async function renameCharacter(name = null, { silent = false, renameChats
65966748
6597 if (newChId !== -1) {6749 if (newChId !== -1) {
6598 // Select the character after the renaming6750 // Select the character after the renaming
6599 setCharacterId(undefined);
6600 await selectCharacterById(newChId);6751 await selectCharacterById(newChId);
66016752
6602 // Async delay to update UI6753 // Async delay to update UI
@@ -6729,7 +6880,22 @@ export function saveChatDebounced() {
6729 }, DEFAULT_SAVE_EDIT_TIMEOUT);6880 }, DEFAULT_SAVE_EDIT_TIMEOUT);
6730}6881}
67316882
6732export 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 */
6893export 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
6733 const metadata = { ...chat_metadata, ...(withMetadata || {}) };6899 const metadata = { ...chat_metadata, ...(withMetadata || {}) };
6734 const fileName = chatName ?? characters[this_chid]?.chat;6900 const fileName = chatName ?? characters[this_chid]?.chat;
67356901
@@ -6749,53 +6915,59 @@ export async function saveChat(chatName, withMetadata, mesId) {
6749 toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);6915 toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`);
6750 throw new Error('Group chat saved from saveChat');6916 throw new Error('Group chat saved from saveChat');
6751 }6917 }
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 */
6762 });6918 });
67636919
6764 const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)6920 const trimmedChat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
6765 ? chat.slice(0, parseInt(mesId) + 1)6921 ? chat.slice(0, Number(mesId) + 1)
6766 : chat;6922 : chat.slice();
67676923
6768 var save_chat = [6924 const chatToSave = [
6769 {6925 {
6770 user_name: name1,6926 user_name: name1,
6771 character_name: name2,6927 character_name: name2,
6772 create_date: chat_create_date,6928 create_date: chat_create_date,
6773 chat_metadata: metadata,6929 chat_metadata: metadata,
6774 },6930 },
6775 ...trimmed_chat,6931 ...trimmedChat,
6776 ];6932 ];
6777 return jQuery.ajax({6933
6778 type: 'POST',6934 try {
6779 url: '/api/chats/save',6935 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({
6781 ch_name: characters[this_chid].name,6940 ch_name: characters[this_chid].name,
6782 file_name: fileName,6941 file_name: fileName,
6783 chat: save_chat,6942 chat: chatToSave,
6784 avatar_url: characters[this_chid].avatar,6943 avatar_url: characters[this_chid].avatar,
6944 force: force,
6785 }),6945 }),
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);
6794 toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`);6969 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 });
6799}6971}
68006972
6801async function read_avatar_load(input) {6973async function read_avatar_load(input) {
@@ -6972,6 +7144,9 @@ export async function getChat() {
6972 } else {7144 } else {
6973 chat_create_date = humanizedDateTime();7145 chat_create_date = humanizedDateTime();
6974 }7146 }
7147 if (!chat_metadata['integrity']) {
7148 chat_metadata['integrity'] = uuidv4();
7149 }
6975 await getChatResult();7150 await getChatResult();
6976 eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });7151 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) {
7943 }8118 }
7944}8119}
79458120
7946export 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 */
8127export function select_selected_character(chid, { switchMenu = true } = {}) {
7947 //character select8128 //character select
7948 //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');8129 //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')');
7949 select_rm_create();8130 select_rm_create({ switchMenu });
7950 setMenuType('character_edit');8131 switchMenu && setMenuType('character_edit');
7951 $('#delete_button').css('display', 'flex');8132 $('#delete_button').css('display', 'flex');
7952 $('#export_button').css('display', 'flex');8133 $('#export_button').css('display', 'flex');
7953 var display_name = characters[chid].name;
79548134
7955 //create text poles8135 //create text poles
7956 $('#rm_button_back').css('display', 'none');8136 $('#rm_button_back').css('display', 'none');
@@ -7965,7 +8145,7 @@ export function select_selected_character(chid) {
79658145
7966 // Don't update the navbar name if we're peeking the group member defs8146 // Don't update the navbar name if we're peeking the group member defs
7967 if (!selected_group) {8147 if (!selected_group) {
7968 $('#rm_button_selected_ch').children('h2').text(display_name);8148 $('#rm_button_selected_ch').children('h2').text(characters[chid].name);
7969 }8149 }
79708150
7971 $('#add_avatar_button').val('');8151 $('#add_avatar_button').val('');
@@ -7996,22 +8176,20 @@ export function select_selected_character(chid) {
7996 $('#chat_import_avatar_url').val(characters[chid].avatar);8176 $('#chat_import_avatar_url').val(characters[chid].avatar);
7997 $('#chat_import_character_name').val(characters[chid].name);8177 $('#chat_import_character_name').val(characters[chid].name);
7998 $('#character_json_data').val(characters[chid].json_data);8178 $('#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
8004 updateFavButtonState(characters[chid].fav || characters[chid].fav == 'true');8180 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;
8007 $('#name_div').removeClass('displayBlock');8183 $('#avatar_load_preview').attr('src', avatarUrl);
8008 $('#name_div').addClass('displayNone');
8009 $('#renameCharButton').css('display', '');
8010 $('.open_alternate_greetings').data('chid', chid);8184 $('.open_alternate_greetings').data('chid', chid);
8011 $('#set_character_world').data('chid', chid);8185 $('#set_character_world').data('chid', chid);
8012 setWorldInfoButtonClass(chid);8186 setWorldInfoButtonClass(chid);
8013 checkEmbeddedWorld(chid);8187 checkEmbeddedWorld(chid);
80148188
8189 $('#name_div').removeClass('displayBlock');
8190 $('#name_div').addClass('displayNone');
8191 $('#renameCharButton').css('display', '');
8192
8015 $('#form_create').attr('actiontype', 'editcharacter');8193 $('#form_create').attr('actiontype', 'editcharacter');
8016 $('.form_create_bottom_buttons_block .chat_lorebook_button').show();8194 $('.form_create_bottom_buttons_block .chat_lorebook_button').show();
80178195
@@ -8023,8 +8201,13 @@ export function select_selected_character(chid) {
8023 saveSettingsDebounced();8201 saveSettingsDebounced();
8024}8202}
80258203
8026function 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 */
8209function select_rm_create({ switchMenu = true } = {}) {
8210 switchMenu && setMenuType('create');
80288211
8029 //console.log('select_rm_Create() -- selected button: '+selected_button);8212 //console.log('select_rm_Create() -- selected button: '+selected_button);
8030 if (selected_button == 'create') {8213 if (selected_button == 'create') {
@@ -8034,7 +8217,7 @@ function select_rm_create() {
8034 }8217 }
8035 }8218 }
80368219
8037 selectRightMenuWithAnimation('rm_ch_create_block');8220 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
80388221
8039 $('#set_chat_scenario').hide();8222 $('#set_chat_scenario').hide();
8040 $('#delete_button_div').css('display', 'none');8223 $('#delete_button_div').css('display', 'none');
@@ -9256,6 +9439,17 @@ function swipe_right(_event, { source, repeated } = {}) {
9256 }9439 }
9257}9440}
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 */
9259export const CONNECT_API_MAP = {9453export const CONNECT_API_MAP = {
9260 // Default APIs not contined inside text gen / chat gen9454 // Default APIs not contined inside text gen / chat gen
9261 'kobold': {9455 'kobold': {
@@ -9700,6 +9894,15 @@ export async function renameChat(oldFileName, newName) {
9700 renamed_file: `${newName.trim()}.jsonl`,9894 renamed_file: `${newName.trim()}.jsonl`,
9701 };9895 };
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
9703 try {9906 try {
9704 showLoader();9907 showLoader();
9705 const response = await fetch('/api/chats/rename', {9908 const response = await fetch('/api/chats/rename', {
@@ -10477,7 +10680,7 @@ jQuery(async function () {
10477 e.stopPropagation();10680 e.stopPropagation();
10478 chat_file_for_del = $(this).attr('file_name');10681 chat_file_for_del = $(this).attr('file_name');
10479 console.debug('detected cross click for' + chat_file_for_del);10682 console.debug('detected cross click for' + chat_file_for_del);
10480 callPopup('<h3>Delete the Chat File?</h3>', 'del_chat');10683 callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat');
10481 });10684 });
1048210685
10483 $('#advanced_div').click(function () {10686 $('#advanced_div').click(function () {
@@ -11644,8 +11847,8 @@ jQuery(async function () {
11644 return;11847 return;
11645 }11848 }
11646 const drawer = $(this).closest('.inline-drawer');11849 const drawer = $(this).closest('.inline-drawer');
11647 const icon = drawer.find('.inline-drawer-icon');11850 const icon = drawer.find('>.inline-drawer-header .inline-drawer-icon');
11648 const drawerContent = drawer.find('.inline-drawer-content');11851 const drawerContent = drawer.find('>.inline-drawer-content');
11649 icon.toggleClass('down up');11852 icon.toggleClass('down up');
11650 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');11853 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
11651 drawerContent.stop().slideToggle({11854 drawerContent.stop().slideToggle({
@@ -12031,4 +12234,11 @@ jQuery(async function () {
12031 });12234 });
1203212235
12033 initCustomSelectedSamplers();12236 initCustomSelectedSamplers();
12237
12238 window.addEventListener('beforeunload', (e) => {
12239 if (isChatSaving) {
12240 e.preventDefault();
12241 e.returnValue = true;
12242 }
12243 });
12034});12244});
public/scripts/PromptManager.js+3 -31
@@ -1,10 +1,10 @@
1'use strict';1'use strict';
22
3import { DOMPurify, Popper } from '../lib.js';3import { DOMPurify } from '../lib.js';
44
5import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';5import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js';
6import { is_group_generating } from './group-chats.js';6import { is_group_generating } from './group-chats.js';
7import { Message, TokenHandler } from './openai.js';7import { Message, MessageCollection, TokenHandler } from './openai.js';
8import { power_user } from './power-user.js';8import { power_user } from './power-user.js';
9import { debounce, waitUntilCondition, escapeHtml } from './utils.js';9import { debounce, waitUntilCondition, escapeHtml } from './utils.js';
10import { debounce_timeout } from './constants.js';10import { debounce_timeout } from './constants.js';
@@ -1440,36 +1440,8 @@ class PromptManager {
1440 footerDiv.querySelector('select').selectedIndex = selectedPromptIndex;1440 footerDiv.querySelector('select').selectedIndex = selectedPromptIndex;
14411441
1442 // Add prompt export dialogue and options1442 // 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
1469 footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport);1443 footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport);
1470 footerDiv.querySelector('#prompt-manager-export').addEventListener('click', showExportSelection);1444 footerDiv.querySelector('#prompt-manager-export').addEventListener('click', this.handleFullExport);
1471 rangeBlockDiv.querySelector('.export-promptmanager-prompts-full').addEventListener('click', this.handleFullExport);
1472 rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
1473 }1445 }
1474 }1446 }
14751447
public/scripts/RossAscends-mods.js+1 -1
@@ -407,9 +407,9 @@ function RA_autoconnect(PrevApi) {
407 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)407 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
408 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)408 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
409 || (secret_state[SECRET_KEYS.ZEROONEAI] && oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI)409 || (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)
411 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)410 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
412 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)411 || (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)
413 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)413 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
414 ) {414 ) {
415 $('#api_button_openai').trigger('click');415 $('#api_button_openai').trigger('click');
public/scripts/authors-note.js+5 -0
@@ -17,6 +17,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
17import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';17import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
18export { MODULE_NAME as NOTE_MODULE_NAME };18export { MODULE_NAME as NOTE_MODULE_NAME };
19import { t } from './i18n.js';19import { t } from './i18n.js';
20import { MacrosParser } from './macros.js';
2021
21const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory22const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
2223
@@ -576,4 +577,8 @@ export function initAuthorsNote() {
576 `,577 `,
577 }));578 }));
578 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);579 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
580
581 MacrosParser.registerMacro('authorsNote', () => chat_metadata[metadata_keys.prompt] ?? '', t`The contents of the Author's Note`);
582 MacrosParser.registerMacro('charAuthorsNote', () => this_chid !== undefined ? (extension_settings.note.chara.find((e) => e.name === getCharaFilename())?.prompt ?? '') : '', t`The contents of the Character Author's Note`);
583 MacrosParser.registerMacro('defaultAuthorsNote', () => extension_settings.note.default ?? '', t`The contents of the Default Author's Note`);
579}584}
public/scripts/autocomplete/AutoComplete.js+0 -2
@@ -3,10 +3,8 @@ import { debounce, escapeRegex } from '../utils.js';
3import { AutoCompleteOption } from './AutoCompleteOption.js';3import { AutoCompleteOption } from './AutoCompleteOption.js';
4import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';4import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
5import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';5import { BlankAutoCompleteOption } from './BlankAutoCompleteOption.js';
6// eslint-disable-next-line no-unused-vars
7import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';6import { AutoCompleteNameResult } from './AutoCompleteNameResult.js';
8import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';7import { AutoCompleteSecondaryNameResult } from './AutoCompleteSecondaryNameResult.js';
9import { Popup, getTopmostModalLayer } from '../popup.js';
108
11/**@readonly*/9/**@readonly*/
12/**@enum {Number}*/10/**@enum {Number}*/
public/scripts/autocomplete/AutoCompleteNameResultBase.js+0 -1
@@ -1,4 +1,3 @@
1import { SlashCommandNamedArgumentAutoCompleteOption } from '../slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js';
2import { AutoCompleteOption } from './AutoCompleteOption.js';1import { AutoCompleteOption } from './AutoCompleteOption.js';
32
43
public/scripts/autocomplete/AutoCompleteOption.js+0 -1
@@ -1,4 +1,3 @@
1import { SlashCommand } from '../slash-commands/SlashCommand.js';
2import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';1import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
32
43
public/scripts/backgrounds.js+2 -1
@@ -5,6 +5,7 @@ import { saveMetadataDebounced } from './extensions.js';
5import { SlashCommand } from './slash-commands/SlashCommand.js';5import { SlashCommand } from './slash-commands/SlashCommand.js';
6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';6import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
7import { flashHighlight, stringFormat } from './utils.js';7import { flashHighlight, stringFormat } from './utils.js';
8import { t } from './i18n.js';
89
9const BG_METADATA_KEY = 'custom_background';10const BG_METADATA_KEY = 'custom_background';
10const LIST_METADATA_KEY = 'chat_backgrounds';11const LIST_METADATA_KEY = 'chat_backgrounds';
@@ -243,7 +244,7 @@ async function getNewBackgroundName(referenceElement) {
243 const fileExtension = oldBg.split('.').pop();244 const fileExtension = oldBg.split('.').pop();
244 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;245 const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg;
245 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');246 const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, '');
246 const newBgExtensionless = await callPopup('<h3>Enter new background name:</h3>', 'input', oldBgExtensionless);247 const newBgExtensionless = await callPopup('<h3>' + t`Enter new background name:` + '</h3>', 'input', oldBgExtensionless);
247248
248 if (!newBgExtensionless) {249 if (!newBgExtensionless) {
249 console.debug('no new_bg_extensionless');250 console.debug('no new_bg_extensionless');
public/scripts/bookmarks.js+4 -5
@@ -1,7 +1,6 @@
1import {1import {
2 characters,2 characters,
3 saveChat,3 saveChat,
4 system_messages,
5 system_message_types,4 system_message_types,
6 this_chid,5 this_chid,
7 openCharacterChat,6 openCharacterChat,
@@ -13,7 +12,7 @@ import {
13 saveChatConditional,12 saveChatConditional,
14 saveItemizedPrompts,13 saveItemizedPrompts,
15} from '../script.js';14} from '../script.js';
16import { humanizedDateTime, getMessageTimeStamp } from './RossAscends-mods.js';15import { humanizedDateTime } from './RossAscends-mods.js';
17import {16import {
18 getGroupPastChats,17 getGroupPastChats,
19 group_activation_strategy,18 group_activation_strategy,
@@ -156,7 +155,7 @@ export async function createBranch(mesId) {
156 if (selected_group) {155 if (selected_group) {
157 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);156 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
158 } else {157 } else {
159 await saveChat(name, newMetadata, mesId);158 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
160 }159 }
161 // append to branches list if it exists160 // append to branches list if it exists
162 // otherwise create it161 // otherwise create it
@@ -212,7 +211,7 @@ export async function createNewBookmark(mesId, { forceName = null } = {}) {
212 if (selected_group) {211 if (selected_group) {
213 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);212 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
214 } else {213 } else {
215 await saveChat(name, newMetadata, mesId);214 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
216 }215 }
217216
218 lastMes.extra['bookmark_link'] = name;217 lastMes.extra['bookmark_link'] = name;
@@ -358,7 +357,7 @@ export async function convertSoloToGroupChat() {
358 // Click on the freshly selected group to open it357 // Click on the freshly selected group to open it
359 await openGroupById(group.id);358 await openGroupById(group.id);
360359
361 toastr.success('The chat has been successfully converted!');360 toastr.success(t`The chat has been successfully converted!`);
362}361}
363362
364/**363/**
public/scripts/chats.js+11 -5
@@ -459,7 +459,7 @@ export async function appendFileContent(message, messageText) {
459 * @copyright https://github.com/kwaroran/risuAI459 * @copyright https://github.com/kwaroran/risuAI
460 */460 */
461export function encodeStyleTags(text) {461export function encodeStyleTags(text) {
462 const styleRegex = /<style>(.+?)<\/style>/gms;462 const styleRegex = /<style>(.+?)<\/style>/gims;
463 return text.replaceAll(styleRegex, (_, match) => {463 return text.replaceAll(styleRegex, (_, match) => {
464 return `<custom-style>${escape(match)}</custom-style>`;464 return `<custom-style>${escape(match)}</custom-style>`;
465 });465 });
@@ -575,8 +575,8 @@ export function isExternalMediaAllowed() {
575 return !power_user.forbid_external_media;575 return !power_user.forbid_external_media;
576}576}
577577
578async function enlargeMessageImage() {578function expandMessageImage(event) {
579 const mesBlock = $(this).closest('.mes');579 const mesBlock = $(event.currentTarget).closest('.mes');
580 const mesId = mesBlock.attr('mesid');580 const mesId = mesBlock.attr('mesid');
581 const message = chat[mesId];581 const message = chat[mesId];
582 const imgSrc = message?.extra?.image;582 const imgSrc = message?.extra?.image;
@@ -620,7 +620,12 @@ async function enlargeMessageImage() {
620 popup.completeCancelled();620 popup.completeCancelled();
621 });621 });
622622
623 await popup.show();623 popup.show();
624 return img;
625}
626
627function expandAndZoomMessageImage(event) {
628 expandMessageImage(event).click();
624}629}
625630
626async function deleteMessageImage() {631async function deleteMessageImage() {
@@ -1603,7 +1608,8 @@ jQuery(function () {
1603 reloadCurrentChat();1608 reloadCurrentChat();
1604 });1609 });
16051610
1606 $(document).on('click', '.mes_img_enlarge', enlargeMessageImage);1611 $(document).on('click', '.mes_img', expandMessageImage);
1612 $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage);
1607 $(document).on('click', '.mes_img_delete', deleteMessageImage);1613 $(document).on('click', '.mes_img_delete', deleteMessageImage);
16081614
1609 $('#file_form_input').on('change', async () => {1615 $('#file_form_input').on('change', async () => {
public/scripts/constants.js+8 -0
@@ -14,3 +14,11 @@ export const debounce_timeout = {
14 /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */14 /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */
15 extended: 5000,15 extended: 5000,
16};16};
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 */
24export const IGNORE_SYMBOL = Symbol.for('ignore');
public/scripts/custom-request.js+415 -46
@@ -1,22 +1,26 @@
1import { getPresetManager } from './preset-manager.js';1import { getPresetManager } from './preset-manager.js';
2import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';2import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';
3import { getTextGenServer } from './textgen-settings.js';3import { getTextGenServer } from './textgen-settings.js';
4import { extractReasoningFromData } from './reasoning.js';
5import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js';
6import { getStreamingReply, tryParseStreamingError } from './openai.js';
7import EventSourceStream from './sse-stream.js';
48
5// #region Type Definitions9// #region Type Definitions
6/**10/**
7 * @typedef {Object} TextCompletionRequestBase11 * @typedef {Object} TextCompletionRequestBase
8 * @property {string} prompt - The text prompt for completion12 * @property {boolean?} [stream=false] - Whether to stream the response
9 * @property {number} max_tokens - Maximum number of tokens to generate13 * @property {number} max_tokens - Maximum number of tokens to generate
10 * @property {string} [model] - Optional model name14 * @property {string} [model] - Optional model name
11 * @property {string} api_type - Type of API to use15 * @property {string} api_type - Type of API to use
12 * @property {string} [api_server] - Optional API server URL16 * @property {string} [api_server] - Optional API server URL
13 * @property {number} [temperature] - Optional temperature parameter17 * @property {number} [temperature] - Optional temperature parameter
18 * @property {number} [min_p] - Optional min_p parameter
14 */19 */
1520
16/** @typedef {Record<string, any> & TextCompletionRequestBase} TextCompletionRequest */
17
18/**21/**
19 * @typedef {Object} TextCompletionPayloadBase22 * @typedef {Object} TextCompletionPayloadBase
23 * @property {boolean?} [stream=false] - Whether to stream the response
20 * @property {string} prompt - The text prompt for completion24 * @property {string} prompt - The text prompt for completion
21 * @property {number} max_tokens - Maximum number of tokens to generate25 * @property {number} max_tokens - Maximum number of tokens to generate
22 * @property {number} max_new_tokens - Alias for max_tokens26 * @property {number} max_new_tokens - Alias for max_tokens
@@ -36,29 +40,49 @@ import { getTextGenServer } from './textgen-settings.js';
3640
37/**41/**
38 * @typedef {Object} ChatCompletionPayloadBase42 * @typedef {Object} ChatCompletionPayloadBase
43 * @property {boolean?} [stream=false] - Whether to stream the response
39 * @property {ChatCompletionMessage[]} messages - Array of chat messages44 * @property {ChatCompletionMessage[]} messages - Array of chat messages
40 * @property {string} [model] - Optional model name to use for completion45 * @property {string} [model] - Optional model name to use for completion
41 * @property {string} chat_completion_source - Source provider for chat completion46 * @property {string} chat_completion_source - Source provider
42 * @property {number} max_tokens - Maximum number of tokens to generate47 * @property {number} max_tokens - Maximum number of tokens to generate
43 * @property {number} [temperature] - Optional temperature parameter for response randomness48 * @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
44 */52 */
4553
46/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */54/** @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
47// #endregion71// #endregion
4872
49/**73/**
50 * Creates & sends a text completion request. Streaming is not supported.74 * Creates & sends a text completion request.
51 */75 */
52export class TextCompletionService {76export class TextCompletionService {
53 static TYPE = 'textgenerationwebui';77 static TYPE = 'textgenerationwebui';
5478
55 /**79 /**
56 * @param {TextCompletionRequest} custom80 * @param {Record<string, any> & TextCompletionRequestBase & {prompt: string}} custom
57 * @returns {TextCompletionPayload}81 * @returns {TextCompletionPayload}
58 */82 */
59 static createRequestData({ prompt, max_tokens, model, api_type, api_server, temperature, ...props }) {83 static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) {
60 return {84 const payload = {
61 ...props,85 stream,
62 prompt,86 prompt,
63 max_tokens,87 max_tokens,
64 max_new_tokens: max_tokens,88 max_new_tokens: max_tokens,
@@ -66,24 +90,36 @@ export class TextCompletionService {
66 api_type,90 api_type,
67 api_server: api_server ?? getTextGenServer(api_type),91 api_server: api_server ?? getTextGenServer(api_type),
68 temperature,92 temperature,
69 stream: false,93 min_p,
94 ...props,
70 };95 };
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;
71 }105 }
72106
73 /**107 /**
74 * Sends a text completion request to the specified server108 * Sends a text completion request to the specified server
75 * @param {TextCompletionPayload} data Request data109 * @param {TextCompletionPayload} data Request data
76 * @param {boolean?} extractData Extract message from the response. Default true110 * @param {boolean?} extractData Extract message from the response. Default true
77 * @returns {Promise<string | any>} Extracted data or the raw response111 * @param {AbortSignal?} signal
112 * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
78 * @throws {Error}113 * @throws {Error}
79 */114 */
80 static async sendRequest(data, extractData = true) {115 static async sendRequest(data, extractData = true, signal = null) {
116 if (!data.stream) {
81 const response = await fetch(getGenerateUrl(this.TYPE), {117 const response = await fetch(getGenerateUrl(this.TYPE), {
82 method: 'POST',118 method: 'POST',
83 headers: getRequestHeaders(),119 headers: getRequestHeaders(),
84 cache: 'no-cache',120 cache: 'no-cache',
85 body: JSON.stringify(data),121 body: JSON.stringify(data),
86 signal: new AbortController().signal,122 signal: signal ?? new AbortController().signal,
87 });123 });
88124
89 const json = await response.json();125 const json = await response.json();
@@ -91,35 +127,260 @@ export class TextCompletionService {
91 throw json;127 throw json;
92 }128 }
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 };
95 }187 }
96188
97 /**189 /**
98 * @param {string} presetName190 * Process and send a text completion request with optional preset & instruct
99 * @param {TextCompletionRequest} custom191 * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom
100 * @param {boolean?} extractData Extract message from the response. Default true192 * @param {Object} options - Configuration options
101 * @returns {Promise<string | any>} Extracted data or the raw response193 * @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
102 * @throws {Error}199 * @throws {Error}
103 */200 */
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) {
105 const presetManager = getPresetManager(this.TYPE);213 const presetManager = getPresetManager(this.TYPE);
106 if (!presetManager) {214 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 }
108 }226 }
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);
113 }275 }
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;
118 }379 }
119}380}
120381
121/**382/**
122 * Creates & sends a chat completion request. Streaming is not supported.383 * Creates & sends a chat completion request.
123 */384 */
124export class ChatCompletionService {385export class ChatCompletionService {
125 static TYPE = 'openai';386 static TYPE = 'openai';
@@ -128,62 +389,170 @@ export class ChatCompletionService {
128 * @param {ChatCompletionPayload} custom389 * @param {ChatCompletionPayload} custom
129 * @returns {ChatCompletionPayload}390 * @returns {ChatCompletionPayload}
130 */391 */
131 static createRequestData({ messages, model, chat_completion_source, max_tokens, temperature, ...props }) {392 static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, ...props }) {
132 return {393 const payload = {
133 ...props,394 stream,
134 messages,395 messages,
135 model,396 model,
136 chat_completion_source,397 chat_completion_source,
137 max_tokens,398 max_tokens,
138 temperature,399 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,
140 };406 };
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;
141 }416 }
142417
143 /**418 /**
144 * Sends a chat completion request419 * Sends a chat completion request
145 * @param {ChatCompletionPayload} data Request data420 * @param {ChatCompletionPayload} data Request data
146 * @param {boolean?} extractData Extract message from the response. Default true421 * @param {boolean?} extractData Extract message from the response. Default true
147 * @returns {Promise<string | any>} Extracted data or the raw response422 * @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
148 * @throws {Error}424 * @throws {Error}
149 */425 */
150 static async sendRequest(data, extractData = true) {426 static async sendRequest(data, extractData = true, signal = null) {
151 const response = await fetch('/api/backends/chat-completions/generate', {427 const response = await fetch('/api/backends/chat-completions/generate', {
152 method: 'POST',428 method: 'POST',
153 headers: getRequestHeaders(),429 headers: getRequestHeaders(),
154 cache: 'no-cache',430 cache: 'no-cache',
155 body: JSON.stringify(data),431 body: JSON.stringify(data),
156 signal: new AbortController().signal,432 signal: signal ?? new AbortController().signal,
157 });433 });
158434
435 if (!data.stream) {
159 const json = await response.json();436 const json = await response.json();
160 if (!response.ok || json.error) {437 if (!response.ok || json.error) {
161 throw json;438 throw json;
162 }439 }
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 };
165 }491 }
166492
167 /**493 /**
168 * @param {string} presetName494 * Process and send a chat completion request with optional preset
169 * @param {ChatCompletionPayload} custom495 * @param {ChatCompletionPayload} custom
170 * @param {boolean} extractData Extract message from the response. Default true496 * @param {Object} options - Configuration options
171 * @returns {Promise<string | any>} Extracted data or the raw response497 * @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
172 * @throws {Error}501 * @throws {Error}
173 */502 */
174 static async sendRequestWithPreset(presetName, custom, extractData = true) {503 static async processRequest(custom, options, extractData = true, signal = null) {
504 const { presetName } = options;
505 let requestData = { ...custom };
506
507 // Apply generation preset if specified
508 if (presetName) {
175 const presetManager = getPresetManager(this.TYPE);509 const presetManager = getPresetManager(this.TYPE);
176 if (!presetManager) {510 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 }
178 }522 }
179523
180 const preset = presetManager.getCompletionPresetByName(presetName);524 const data = this.createRequestData(requestData);
181 if (!preset) {525
182 throw new Error('Preset not found');526 return await this.sendRequest(data, extractData, signal);
183 }527 }
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;
188 }557 }
189}558}
public/scripts/extensions.js+42 -16
@@ -783,25 +783,41 @@ async function showExtensionsDetails() {
783 .append(htmlExternal)783 .append(htmlExternal)
784 .append(getModuleInformation());784 .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 () => {
790 requiresReload = true;788 requiresReload = true;
791 await autoUpdateExtensions(true);789 await autoUpdateExtensions(force);
792 await popup.complete(POPUP_RESULT.AFFIRMATIVE);790 await popup.complete(POPUP_RESULT.AFFIRMATIVE);
793 },
794 };791 };
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 () => {
800 abortController.abort();813 abortController.abort();
801 accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');814 accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
802 await showExtensionsDetails();815 await showExtensionsDetails();
803 },816 });
804 };817
818 toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton);
819 html.prepend(toolbar);
820 }
805821
806 let waitingForSave = false;822 let waitingForSave = false;
807823
@@ -809,7 +825,7 @@ async function showExtensionsDetails() {
809 okButton: t`Close`,825 okButton: t`Close`,
810 wide: true,826 wide: true,
811 large: true,827 large: true,
812 customButtons: [sortOrderButton, updateAllButton],828 customButtons: [],
813 allowVerticalScrolling: true,829 allowVerticalScrolling: true,
814 onClosing: async () => {830 onClosing: async () => {
815 if (waitingForSave) {831 if (waitingForSave) {
@@ -1196,7 +1212,7 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
1196}1212}
11971213
1198/**1214/**
1199 * Checks if there are updates available for 3rd-party extensions.1215 * Checks if there are updates available for enabled 3rd-party extensions.
1200 * @param {boolean} force Skip nag check1216 * @param {boolean} force Skip nag check
1201 * @returns {Promise<any>}1217 * @returns {Promise<any>}
1202 */1218 */
@@ -1218,6 +1234,11 @@ async function checkForExtensionUpdates(force) {
1218 const promises = [];1234 const promises = [];
12191235
1220 for (const [id, manifest] of Object.entries(manifests)) {1236 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 }
1221 const isGlobal = getExtensionType(id) === 'global';1242 const isGlobal = getExtensionType(id) === 'global';
1222 if (isGlobal && !isCurrentUserAdmin) {1243 if (isGlobal && !isCurrentUserAdmin) {
1223 console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);1244 console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);
@@ -1247,8 +1268,8 @@ async function checkForExtensionUpdates(force) {
1247}1268}
12481269
1249/**1270/**
1250 * Updates all 3rd-party extensions that have auto-update enabled.1271 * Updates all enabled 3rd-party extensions that have auto-update enabled.
1251 * @param {boolean} forceAll Force update all even if not auto-updating1272 * @param {boolean} forceAll Include disabled and not auto-updating
1252 * @returns {Promise<void>}1273 * @returns {Promise<void>}
1253 */1274 */
1254async function autoUpdateExtensions(forceAll) {1275async function autoUpdateExtensions(forceAll) {
@@ -1260,6 +1281,11 @@ async function autoUpdateExtensions(forceAll) {
1260 const isCurrentUserAdmin = isAdmin();1281 const isCurrentUserAdmin = isAdmin();
1261 const promises = [];1282 const promises = [];
1262 for (const [id, manifest] of Object.entries(manifests)) {1283 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 }
1263 const isGlobal = getExtensionType(id) === 'global';1289 const isGlobal = getExtensionType(id) === 'global';
1264 if (isGlobal && !isCurrentUserAdmin) {1290 if (isGlobal && !isCurrentUserAdmin) {
1265 console.debug(`Skipping global extension: ${manifest.display_name} (${id}) for non-admin user`);1291 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 () => {
424 installHintButton.on('click', async function () {424 installHintButton.on('click', async function () {
425 const installButton = $('#third_party_extension_button');425 const installButton = $('#third_party_extension_button');
426 flashHighlight(installButton, 5000);426 flashHighlight(installButton, 5000);
427 toastr.info('Click the flashing button to install extensions.', 'How to install extensions?');427 toastr.info(t`Click the flashing button to install extensions.`, t`How to install extensions?`);
428 });428 });
429429
430 const connectButton = windowHtml.find('#assets-connect-button');430 const connectButton = windowHtml.find('#assets-connect-button');
public/scripts/extensions/assets/window.html+1 -1
@@ -39,7 +39,7 @@ To install a single 3rd party extension, use the &quot;Install Extensions&quot;
39 <span data-i18n="Characters">Characters</span>39 <span data-i18n="Characters">Characters</span>
40 </div>40 </div>
41 </div>41 </div>
42 <div class="inline-drawer-content" id="assets_menu">42 <div id="assets_menu">
43 </div>43 </div>
44 </div>44 </div>
45 </div>45 </div>
public/scripts/extensions/attachments/attach-button.html+1 -1
@@ -1,4 +1,4 @@
1<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">1<div id="attachFile" class="list-group-item flex-container flexGap5" data-i18n="[title]Attach a file or image to a current chat." title="Attach a file or image to a current chat.">
2 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>2 <div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
3 <span data-i18n="Attach a File">Attach a File</span>3 <span data-i18n="Attach a File">Attach a File</span>
4</div>4</div>
public/scripts/extensions/caption/index.js+1 -0
@@ -428,6 +428,7 @@ jQuery(async function () {
428 'zerooneai': SECRET_KEYS.ZEROONEAI,428 'zerooneai': SECRET_KEYS.ZEROONEAI,
429 'groq': SECRET_KEYS.GROQ,429 'groq': SECRET_KEYS.GROQ,
430 'cohere': SECRET_KEYS.COHERE,430 'cohere': SECRET_KEYS.COHERE,
431 'xai': SECRET_KEYS.XAI,
431 };432 };
432433
433 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {434 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+19 -0
@@ -31,6 +31,7 @@
31 <option value="openrouter">OpenRouter</option>31 <option value="openrouter">OpenRouter</option>
32 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>32 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
33 <option value="vllm">vLLM</option>33 <option value="vllm">vLLM</option>
34 <option value="xai">xAI (Grok)</option>
34 </select>35 </select>
35 </div>36 </div>
36 <div class="flex1 flex-container flexFlowColumn flexNoGap">37 <div class="flex1 flex-container flexFlowColumn flexNoGap">
@@ -42,7 +43,16 @@
42 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>43 <option data-type="mistral" value="pixtral-12b-2409">pixtral-12b-2409</option>
43 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>44 <option data-type="mistral" value="pixtral-large-latest">pixtral-large-latest</option>
44 <option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option>45 <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>
45 <option data-type="zerooneai" value="yi-vision">yi-vision</option>49 <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>
46 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>56 <option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
47 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>57 <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
48 <option data-type="openai" value="gpt-4o">gpt-4o</option>58 <option data-type="openai" value="gpt-4o">gpt-4o</option>
@@ -50,6 +60,10 @@
50 <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>60 <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
51 <option data-type="openai" value="o1">o1</option>61 <option data-type="openai" value="o1">o1</option>
52 <option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option>62 <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>
53 <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>67 <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>
54 <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>68 <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
55 <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>69 <option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>
@@ -62,8 +76,11 @@
62 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>76 <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
63 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>77 <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
64 <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>78 <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>
65 <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option>81 <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option>
66 <option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option>82 <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>
67 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>84 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
68 <option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option>85 <option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option>
69 <option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>86 <option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>
@@ -129,6 +146,8 @@
129 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>146 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
130 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>147 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
131 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>148 <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>
132 </select>151 </select>
133 </div>152 </div>
134 <div data-type="ollama">153 <div data-type="ollama">
public/scripts/extensions/connection-manager/index.js+32 -6
@@ -1,4 +1,4 @@
1import { Fuse } from '../../../lib.js';1import { DOMPurify, Fuse } from '../../../lib.js';
22
3import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';3import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
39 'proxy',39 'proxy',
40 'stop-strings',40 'stop-strings',
41 'start-reply-with',41 'start-reply-with',
42 'reasoning-template',
42];43];
4344
44const TC_COMMANDS = [45const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
54 'tokenizer',55 'tokenizer',
55 'stop-strings',56 'stop-strings',
56 'start-reply-with',57 'start-reply-with',
58 'reasoning-template',
57];59];
5860
59const FANCY_NAMES = {61const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
70 'tokenizer': 'Tokenizer',72 'tokenizer': 'Tokenizer',
71 'stop-strings': 'Custom Stopping Strings',73 'stop-strings': 'Custom Stopping Strings',
72 'start-reply-with': 'Start Reply With',74 'start-reply-with': 'Start Reply With',
75 'reasoning-template': 'Reasoning Template',
73};76};
7477
75/**78/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
154 * @property {string} [tokenizer] Tokenizer157 * @property {string} [tokenizer] Tokenizer
155 * @property {string} [stop-strings] Custom Stopping Strings158 * @property {string} [stop-strings] Custom Stopping Strings
156 * @property {string} [start-reply-with] Start Reply With159 * @property {string} [start-reply-with] Start Reply With
160 * @property {string} [reasoning-template] Reasoning Template
157 * @property {string[]} [exclude] Commands to exclude161 * @property {string[]} [exclude] Commands to exclude
158 */162 */
159163
@@ -267,9 +271,14 @@ async function createConnectionProfile(forceName = null) {
267 });271 });
268 const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);272 const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
269 const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);273 const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
270 const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });274 let name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
271275 // If it's cancelled, it will be false
276 if (!name) {
277 return null;
278 }
279 name = DOMPurify.sanitize(String(name));
272 if (!name) {280 if (!name) {
281 toastr.error('Name cannot be empty.');
273 return null;282 return null;
274 }283 }
275284
@@ -303,7 +312,8 @@ async function deleteConnectionProfile() {
303 return;312 return;
304 }313 }
305314
306 const name = extension_settings.connectionManager.profiles[index].name;315 const profile = extension_settings.connectionManager.profiles[index];
316 const name = profile.name;
307 const confirm = await Popup.show.confirm(t`Are you sure you want to delete the selected profile?`, name);317 const confirm = await Popup.show.confirm(t`Are you sure you want to delete the selected profile?`, name);
308318
309 if (!confirm) {319 if (!confirm) {
@@ -313,6 +323,8 @@ async function deleteConnectionProfile() {
313 extension_settings.connectionManager.profiles.splice(index, 1);323 extension_settings.connectionManager.profiles.splice(index, 1);
314 extension_settings.connectionManager.selectedProfile = null;324 extension_settings.connectionManager.selectedProfile = null;
315 saveSettingsDebounced();325 saveSettingsDebounced();
326
327 await eventSource.emit(event_types.CONNECTION_PROFILE_DELETED, profile);
316}328}
317329
318/**330/**
@@ -512,6 +524,7 @@ async function renderDetailsContent(detailsContent) {
512 saveSettingsDebounced();524 saveSettingsDebounced();
513 renderConnectionProfiles(profiles);525 renderConnectionProfiles(profiles);
514 await renderDetailsContent(detailsContent);526 await renderDetailsContent(detailsContent);
527 await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
515 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);528 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
516 });529 });
517530
@@ -523,9 +536,11 @@ async function renderDetailsContent(detailsContent) {
523 console.log('No profile selected');536 console.log('No profile selected');
524 return;537 return;
525 }538 }
539 const oldProfile = structuredClone(profile);
526 await updateConnectionProfile(profile);540 await updateConnectionProfile(profile);
527 await renderDetailsContent(detailsContent);541 await renderDetailsContent(detailsContent);
528 saveSettingsDebounced();542 saveSettingsDebounced();
543 await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
529 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);544 await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, profile.name);
530 toastr.success('Connection profile updated', '', { timeOut: 1500 });545 toastr.success('Connection profile updated', '', { timeOut: 1500 });
531 });546 });
@@ -559,7 +574,7 @@ async function renderDetailsContent(detailsContent) {
559 return acc;574 return acc;
560 }, {});575 }, {});
561 const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));576 const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));
562 const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {577 let newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
563 rows: 2,578 rows: 2,
564 customButtons: [{579 customButtons: [{
565 text: t`Save and Update`,580 text: t`Save and Update`,
@@ -571,7 +586,13 @@ async function renderDetailsContent(detailsContent) {
571 }],586 }],
572 });587 });
573588
589 // If it's cancelled, it will be false
590 if (!newName) {
591 return;
592 }
593 newName = DOMPurify.sanitize(String(newName));
574 if (!newName) {594 if (!newName) {
595 toastr.error('Name cannot be empty.');
575 return;596 return;
576 }597 }
577598
@@ -584,6 +605,7 @@ async function renderDetailsContent(detailsContent) {
584 return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];605 return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];
585 }).get();606 }).get();
586607
608 const oldProfile = structuredClone(profile);
587 if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {609 if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {
588 profile.exclude = newExcludeList;610 profile.exclude = newExcludeList;
589 for (const command of newExcludeList) {611 for (const command of newExcludeList) {
@@ -598,10 +620,11 @@ async function renderDetailsContent(detailsContent) {
598620
599 if (profile.name !== newName) {621 if (profile.name !== newName) {
600 toastr.success('Connection profile renamed.');622 toastr.success('Connection profile renamed.');
601 profile.name = String(newName);623 profile.name = newName;
602 }624 }
603625
604 saveSettingsDebounced();626 saveSettingsDebounced();
627 await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
605 renderConnectionProfiles(profiles);628 renderConnectionProfiles(profiles);
606 await renderDetailsContent(detailsContent);629 await renderDetailsContent(detailsContent);
607 });630 });
@@ -704,6 +727,7 @@ async function renderDetailsContent(detailsContent) {
704 saveSettingsDebounced();727 saveSettingsDebounced();
705 renderConnectionProfiles(profiles);728 renderConnectionProfiles(profiles);
706 await renderDetailsContent(detailsContent);729 await renderDetailsContent(detailsContent);
730 await eventSource.emit(event_types.CONNECTION_PROFILE_CREATED, profile);
707 return profile.name;731 return profile.name;
708 },732 },
709 }));733 }));
@@ -718,9 +742,11 @@ async function renderDetailsContent(detailsContent) {
718 toastr.warning('No profile selected.');742 toastr.warning('No profile selected.');
719 return '';743 return '';
720 }744 }
745 const oldProfile = structuredClone(profile);
721 await updateConnectionProfile(profile);746 await updateConnectionProfile(profile);
722 await renderDetailsContent(detailsContent);747 await renderDetailsContent(detailsContent);
723 saveSettingsDebounced();748 saveSettingsDebounced();
749 await eventSource.emit(event_types.CONNECTION_PROFILE_UPDATED, oldProfile, profile);
724 return profile.name;750 return profile.name;
725 },751 },
726 }));752 }));
public/scripts/extensions/expressions/index.js+77 -20
@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateRaw, getRequestHeaders, m
4import { dragElement, isMobile } from '../../RossAscends-mods.js';4import { dragElement, isMobile } from '../../RossAscends-mods.js';
5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js';7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js';
8import { hideMutedSprites, selected_group } from '../../group-chats.js';8import { hideMutedSprites, selected_group } from '../../group-chats.js';
9import { isJsonSchemaSupported } from '../../textgen-settings.js';9import { isJsonSchemaSupported } from '../../textgen-settings.js';
10import { debounce_timeout } from '../../constants.js';10import { debounce_timeout } from '../../constants.js';
@@ -17,6 +17,7 @@ import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandRetur
17import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';17import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
18import { Popup, POPUP_RESULT } from '../../popup.js';18import { Popup, POPUP_RESULT } from '../../popup.js';
19import { t } from '../../i18n.js';19import { t } from '../../i18n.js';
20import { removeReasoningFromString } from '../../reasoning.js';
20export { MODULE_NAME };21export { MODULE_NAME };
2122
22/**23/**
@@ -82,6 +83,7 @@ const EXPRESSION_API = {
82 extras: 1,83 extras: 1,
83 llm: 2,84 llm: 2,
84 webllm: 3,85 webllm: 3,
86 none: 99,
85};87};
8688
87let expressionsList = null;89let expressionsList = null;
@@ -273,7 +275,7 @@ async function getLastMessageSprite(avatar) {
273 return null;275 return null;
274}276}
275277
276async function visualNovelUpdateLayers(container) {278export async function visualNovelUpdateLayers(container) {
277 const context = getContext();279 const context = getContext();
278 const group = context.groups.find(x => x.id == context.groupId);280 const group = context.groups.find(x => x.id == context.groupId);
279 const recentMessages = context.chat.map(x => x.original_avatar).filter(x => x).reverse().filter(onlyUnique);281 const recentMessages = context.chat.map(x => x.original_avatar).filter(x => x).reverse().filter(onlyUnique);
@@ -678,7 +680,7 @@ async function setSpriteFolderCommand(_, folder) {
678 return '';680 return '';
679}681}
680682
681async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ { api = null, prompt = null }, text) {683async function classifyCallback(/** @type {{api: string?, filter: string?, prompt: string?}} */ { api = null, filter = null, prompt = null }, text) {
682 if (!text) {684 if (!text) {
683 toastr.error('No text provided');685 toastr.error('No text provided');
684 return '';686 return '';
@@ -689,13 +691,19 @@ async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ {
689 }691 }
690692
691 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;693 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
693 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {701 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {
694 toastr.warning('Text classification is disabled or not available');702 toastr.warning('Text classification is disabled or not available');
695 return '';703 return '';
696 }704 }
697705
698 const label = await getExpressionLabel(text, expressionApi, { customPrompt: prompt });706 const label = await getExpressionLabel(text, expressionApi, { filterAvailable: filterAvailable, customPrompt: prompt });
699 console.debug(`Classification result for "${text}": ${label}`);707 console.debug(`Classification result for "${text}": ${label}`);
700 return label;708 return label;
701}709}
@@ -928,6 +936,9 @@ function parseLlmResponse(emotionResponse, labels) {
928936
929 return response;937 return response;
930 } catch {938 } catch {
939 // Clean possible reasoning from response
940 emotionResponse = removeReasoningFromString(emotionResponse);
941
931 const fuse = new Fuse(labels, { includeScore: true });942 const fuse = new Fuse(labels, { includeScore: true });
932 console.debug('Using fuzzy search in labels:', labels);943 console.debug('Using fuzzy search in labels:', labels);
933 const result = fuse.search(emotionResponse);944 const result = fuse.search(emotionResponse);
@@ -988,10 +999,11 @@ function onTextGenSettingsReady(args) {
988 * @param {string} text - The text to classify and retrieve the expression label for.999 * @param {string} text - The text to classify and retrieve the expression label for.
989 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.1000 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
990 * @param {object} [options={}] - Optional arguments.1001 * @param {object} [options={}] - Optional arguments.
1002 * @param {boolean?} [options.filterAvailable=null] - Whether to filter available expressions. If not specified, uses the extension setting.
991 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.1003 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
992 * @returns {Promise<string?>} - The label of the expression.1004 * @returns {Promise<string?>} - The label of the expression.
993 */1005 */
994export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) {1006export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { filterAvailable = null, customPrompt = null } = {}) {
995 // Return if text is undefined, saving a costly fetch request1007 // Return if text is undefined, saving a costly fetch request
996 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {1008 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
997 return extension_settings.expressions.fallback_expression;1009 return extension_settings.expressions.fallback_expression;
@@ -1003,6 +1015,11 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10031015
1004 text = sampleClassifyText(text);1016 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
1006 try {1023 try {
1007 switch (expressionsApi) {1024 switch (expressionsApi) {
1008 // Local BERT pipeline1025 // Local BERT pipeline
@@ -1027,7 +1044,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1027 return extension_settings.expressions.fallback_expression;1044 return extension_settings.expressions.fallback_expression;
1028 }1045 }
10291046
1030 const expressionsList = await getExpressionsList();1047 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1031 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1048 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1032 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);1049 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
1033 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);1050 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
@@ -1040,7 +1057,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1040 return extension_settings.expressions.fallback_expression;1057 return extension_settings.expressions.fallback_expression;
1041 }1058 }
10421059
1043 const expressionsList = await getExpressionsList();1060 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1044 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1061 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1045 const messages = [1062 const messages = [
1046 { role: 'user', content: text + '\n\n' + prompt },1063 { role: 'user', content: text + '\n\n' + prompt },
@@ -1050,7 +1067,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1050 return parseLlmResponse(emotionResponse, expressionsList);1067 return parseLlmResponse(emotionResponse, expressionsList);
1051 }1068 }
1052 // Extras1069 // Extras
1053 default: {1070 case EXPRESSION_API.extras: {
1054 const url = new URL(getApiUrl());1071 const url = new URL(getApiUrl());
1055 url.pathname = '/api/classify';1072 url.pathname = '/api/classify';
10561073
@@ -1068,6 +1085,15 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1068 return data.classification[0].label;1085 return data.classification[0].label;
1069 }1086 }
1070 } break;1087 } 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 }
1071 }1097 }
1072 } catch (error) {1098 } catch (error) {
1073 toastr.error('Could not classify expression. Check the console or your backend for more information.');1099 toastr.error('Could not classify expression. Check the console or your backend for more information.');
@@ -1320,12 +1346,28 @@ function getCachedExpressions() {
1320 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);1346 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
1321}1347}
13221348
1323export async function getExpressionsList() {1349export async function getExpressionsList({ filterAvailable = false } = {}) {
1324 // Return cached list if available1350 // If there is no cached list, load and cache it
1325 if (Array.isArray(expressionsList)) {1351 if (!Array.isArray(expressionsList)) {
1326 return getCachedExpressions();1352 expressionsList = await resolveExpressionsList();
1327 }1353 }
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
1329 /**1371 /**
1330 * Returns the list of expressions from the API or fallback in offline mode.1372 * Returns the list of expressions from the API or fallback in offline mode.
1331 * @returns {Promise<string[]>}1373 * @returns {Promise<string[]>}
@@ -1372,9 +1414,6 @@ export async function getExpressionsList() {
1372 expressionsList = DEFAULT_EXPRESSIONS.slice();1414 expressionsList = DEFAULT_EXPRESSIONS.slice();
1373 return expressionsList;1415 return expressionsList;
1374 }1416 }
1375
1376 const result = await resolveExpressionsList();
1377 return [...result, ...extension_settings.expressions.custom].filter(onlyUnique);
1378}1417}
13791418
1380/**1419/**
@@ -2036,7 +2075,7 @@ async function fetchImagesNoCache() {
20362075
2037function migrateSettings() {2076function migrateSettings() {
2038 if (extension_settings.expressions.api === undefined) {2077 if (extension_settings.expressions.api === undefined) {
2039 extension_settings.expressions.api = EXPRESSION_API.extras;2078 extension_settings.expressions.api = EXPRESSION_API.none;
2040 saveSettingsDebounced();2079 saveSettingsDebounced();
2041 }2080 }
20422081
@@ -2102,6 +2141,10 @@ function migrateSettings() {
2102 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');2141 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');
2103 saveSettingsDebounced();2142 saveSettingsDebounced();
2104 });2143 });
2144 $('#expressions_filter_available').prop('checked', extension_settings.expressions.filterAvailable).on('input', function () {
2145 extension_settings.expressions.filterAvailable = !!$(this).prop('checked');
2146 saveSettingsDebounced();
2147 });
2105 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);2148 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
2106 $(document).on('dragstart', '.expression', (e) => {2149 $(document).on('dragstart', '.expression', (e) => {
2107 e.preventDefault();2150 e.preventDefault();
@@ -2114,7 +2157,7 @@ function migrateSettings() {
2114 $('#open_chat_expressions').hide();2157 $('#open_chat_expressions').hide();
21152158
2116 await renderAdditionalExpressionSettings();2159 await renderAdditionalExpressionSettings();
2117 $('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.extras);2160 $('#expression_api').val(extension_settings.expressions.api ?? EXPRESSION_API.none);
2118 $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));2161 $('.expression_llm_prompt_block').toggle([EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api));
2119 $('#expression_llm_prompt').val(extension_settings.expressions.llmPrompt ?? '');2162 $('#expression_llm_prompt').val(extension_settings.expressions.llmPrompt ?? '');
2120 $('#expression_llm_prompt').on('input', function () {2163 $('#expression_llm_prompt').on('input', function () {
@@ -2154,7 +2197,7 @@ function migrateSettings() {
2154 imgElement.src = '';2197 imgElement.src = '';
2155 }2198 }
21562199
2157 setExpressionOverrideHtml();2200 setExpressionOverrideHtml(true); // force-clear, as the character might not have an override defined
21582201
2159 if (isVisualNovelMode()) {2202 if (isVisualNovelMode()) {
2160 $('#visual-novel-wrapper').empty();2203 $('#visual-novel-wrapper').empty();
@@ -2279,13 +2322,13 @@ function migrateSettings() {
2279 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2322 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2280 name: 'expression-list',2323 name: 'expression-list',
2281 aliases: ['expressions'],2324 aliases: ['expressions'],
2282 /** @type {(args: {return: string}) => Promise<string>} */2325 /** @type {(args: {return: string, filter: string}) => Promise<string>} */
2283 callback: async (args) => {2326 callback: async (args) => {
2284 let returnType =2327 let returnType =
2285 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */2328 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
2286 (args.return);2329 (args.return);
22872330
2288 const list = await getExpressionsList();2331 const list = await getExpressionsList({ filterAvailable: !isFalseBoolean(args.filter) });
22892332
2290 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });2333 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
2291 },2334 },
@@ -2298,6 +2341,13 @@ function migrateSettings() {
2298 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),2341 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
2299 forceEnum: true,2342 forceEnum: true,
2300 }),2343 }),
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 }),
2301 ],2351 ],
2302 returns: 'The comma-separated list of available expressions, including custom expressions.',2352 returns: 'The comma-separated list of available expressions, including custom expressions.',
2303 helpString: 'Returns a list of available expressions, including custom expressions.',2353 helpString: 'Returns a list of available expressions, including custom expressions.',
@@ -2314,6 +2364,13 @@ function migrateSettings() {
2314 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),2364 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),
2315 }),2365 }),
2316 SlashCommandNamedArgument.fromProps({2366 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({
2317 name: 'prompt',2374 name: 'prompt',
2318 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',2375 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',
2319 typeList: [ARGUMENT_TYPE.STRING],2376 typeList: [ARGUMENT_TYPE.STRING],
public/scripts/extensions/expressions/settings.html+6 -1
@@ -22,6 +22,7 @@
22 <label for="expression_api" data-i18n="Classifier API">Classifier API</label>22 <label for="expression_api" data-i18n="Classifier API">Classifier API</label>
23 <small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small>23 <small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small>
24 <select id="expression_api" class="flex1 margin0">24 <select id="expression_api" class="flex1 margin0">
25 <option value="99" data-i18n="[ None ]">[ None ]</option>
25 <option value="0" data-i18n="Local">Local</option>26 <option value="0" data-i18n="Local">Local</option>
26 <option value="1" data-i18n="Extras">Extras (deprecated)</option>27 <option value="1" data-i18n="Extras">Extras (deprecated)</option>
27 <option value="2" data-i18n="Main API">Main API</option>28 <option value="2" data-i18n="Main API">Main API</option>
@@ -29,7 +30,11 @@
29 </select>30 </select>
30 </div>31 </div>
31 <div class="expression_llm_prompt_block m-b-1 m-t-1">32 <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">
33 <span data-i18n="LLM Prompt">LLM Prompt</span>38 <span data-i18n="LLM Prompt">LLM Prompt</span>
34 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">39 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
35 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>40 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>
public/scripts/extensions/memory/settings.html+1 -1
@@ -132,7 +132,7 @@
132 </label>132 </label>
133 <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.">133 <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.">
134 <input type="radio" name="memory_position" value="1" />134 <input type="radio" name="memory_position" value="1" />
135 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="999" />135 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="9999" />
136 <span data-i18n="as">as</span>136 <span data-i18n="as">as</span>
137 <select id="memory_role" class="text_pole widthNatural">137 <select id="memory_role" class="text_pole widthNatural">
138 <option value="0" data-i18n="System">System</option>138 <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
2import { QuickReply } from '../src/QuickReply.js';1import { QuickReply } from '../src/QuickReply.js';
3import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';2import { QuickReplyContextLink } from '../src/QuickReplyContextLink.js';
4import { QuickReplySet } from '../src/QuickReplySet.js';3import { QuickReplySet } from '../src/QuickReplySet.js';
5// eslint-disable-next-line no-unused-vars
6import { QuickReplySettings } from '../src/QuickReplySettings.js';4import { QuickReplySettings } from '../src/QuickReplySettings.js';
7// eslint-disable-next-line no-unused-vars
8import { SettingsUi } from '../src/ui/SettingsUi.js';5import { SettingsUi } from '../src/ui/SettingsUi.js';
9import { onlyUnique } from '../../../utils.js';6import { onlyUnique } from '../../../utils.js';
107
public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js+0 -2
@@ -1,7 +1,5 @@
1import { warn } from '../index.js';1import { warn } from '../index.js';
2// eslint-disable-next-line no-unused-vars
3import { QuickReply } from './QuickReply.js';2import { QuickReply } from './QuickReply.js';
4// eslint-disable-next-line no-unused-vars
5import { QuickReplySettings } from './QuickReplySettings.js';3import { QuickReplySettings } from './QuickReplySettings.js';
64
7export class AutoExecuteHandler {5export class AutoExecuteHandler {
public/scripts/extensions/quick-reply/src/QuickReply.js+0 -2
@@ -635,7 +635,6 @@ export class QuickReply {
635 }, { passive:true });635 }, { passive:true });
636 const getLineStart = ()=>{636 const getLineStart = ()=>{
637 const start = message.selectionStart;637 const start = message.selectionStart;
638 const end = message.selectionEnd;
639 let lineStart;638 let lineStart;
640 if (start == 0 || message.value[start - 1] == '\n') {639 if (start == 0 || message.value[start - 1] == '\n') {
641 // cursor is already at beginning of line640 // cursor is already at beginning of line
@@ -701,7 +700,6 @@ export class QuickReply {
701 } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {700 } else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {
702 // new line, keep indent701 // new line, keep indent
703 const start = message.selectionStart;702 const start = message.selectionStart;
704 const end = message.selectionEnd;
705 let lineStart = getLineStart();703 let lineStart = getLineStart();
706 const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? '';704 const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? '';
707 if (indent.length) {705 if (indent.length) {
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+0 -1
@@ -8,7 +8,6 @@ import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashC
8import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';8import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
9import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';9import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
10import { isTrueBoolean } from '../../../utils.js';10import { isTrueBoolean } from '../../../utils.js';
11// eslint-disable-next-line no-unused-vars
12import { QuickReplyApi } from '../api/QuickReplyApi.js';11import { QuickReplyApi } from '../api/QuickReplyApi.js';
13import { QuickReply } from './QuickReply.js';12import { QuickReply } from './QuickReply.js';
14import { QuickReplySet } from './QuickReplySet.js';13import { QuickReplySet } from './QuickReplySet.js';
public/scripts/extensions/quick-reply/src/ui/ButtonUi.js+0 -1
@@ -1,7 +1,6 @@
1import { animation_duration } from '../../../../../script.js';1import { animation_duration } from '../../../../../script.js';
2import { dragElement } from '../../../../RossAscends-mods.js';2import { dragElement } from '../../../../RossAscends-mods.js';
3import { loadMovingUIState } from '../../../../power-user.js';3import { loadMovingUIState } from '../../../../power-user.js';
4// eslint-disable-next-line no-unused-vars
5import { QuickReplySettings } from '../QuickReplySettings.js';4import { QuickReplySettings } from '../QuickReplySettings.js';
65
7export class ButtonUi {6export class ButtonUi {
public/scripts/extensions/quick-reply/src/ui/SettingsUi.js+0 -1
@@ -3,7 +3,6 @@ import { getSortableDelay } from '../../../../utils.js';
3import { log, warn } from '../../index.js';3import { log, warn } from '../../index.js';
4import { QuickReply } from '../QuickReply.js';4import { QuickReply } from '../QuickReply.js';
5import { QuickReplySet } from '../QuickReplySet.js';5import { QuickReplySet } from '../QuickReplySet.js';
6// eslint-disable-next-line no-unused-vars
7import { QuickReplySettings } from '../QuickReplySettings.js';6import { QuickReplySettings } from '../QuickReplySettings.js';
87
9export class SettingsUi {8export class SettingsUi {
public/scripts/extensions/quick-reply/src/ui/ctx/ContextMenu.js+0 -1
@@ -1,5 +1,4 @@
1import { QuickReply } from '../../QuickReply.js';1import { QuickReply } from '../../QuickReply.js';
2// eslint-disable-next-line no-unused-vars
3import { QuickReplySet } from '../../QuickReplySet.js';2import { QuickReplySet } from '../../QuickReplySet.js';
4import { MenuHeader } from './MenuHeader.js';3import { MenuHeader } from './MenuHeader.js';
5import { MenuItem } from './MenuItem.js';4import { MenuItem } from './MenuItem.js';
public/scripts/extensions/regex/editor.html+5 -6
@@ -18,9 +18,8 @@
1818
19 <div id="regex_info_block_wrapper">19 <div id="regex_info_block_wrapper">
20 <div id="regex_info_block" class="info-block"></div>20 <div id="regex_info_block" class="info-block"></div>
21 <!-- TODO replace 3rd-party link with our own docs when it's done -->21 <a id="regex_info_block_flags_hint" href="https://docs.sillytavern.app/extensions/regex/#flags" target="_blank" rel="noopener noreferrer">
22 <a id="regex_info_block_flags_hint" href="http://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#advanced_searching_with_flags" target="_blank" rel="noopener noreferrer">22 <i class="fa-solid fa-circle-info" data-i18n="[title]ext_regex_flags_help" title="Click here to learn more about regex flags."></i>
23 <i class="fa-solid fa-circle-info" title="Click here to learn more about regex flags."></i>
24 </a>23 </a>
25 </div>24 </div>
2625
@@ -114,14 +113,14 @@
114 <span data-i18n="Min Depth">Min Depth</span>113 <span data-i18n="Min Depth">Min Depth</span>
115 <span class="fa-solid fa-circle-question note-link-span"></span>114 <span class="fa-solid fa-circle-question note-link-span"></span>
116 </small>115 </small>
117 <input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />116 <input name="min_depth" class="text_pole textarea_compact" type="number" min="-1" max="9999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
118 </div>117 </div>
119 <div class="flex1 flex-container flexNoGap">118 <div class="flex1 flex-container flexNoGap">
120 <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.">119 <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.">
121 <span data-i18n="Max Depth">Max Depth</span>120 <span data-i18n="Max Depth">Max Depth</span>
122 <span class="fa-solid fa-circle-question note-link-span"></span>121 <span class="fa-solid fa-circle-question note-link-span"></span>
123 </small>122 </small>
124 <input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />123 <input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="9999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
125 </div>124 </div>
126 </div>125 </div>
127 </div>126 </div>
@@ -148,7 +147,7 @@
148 </label>147 </label>
149 <span>148 <span>
150 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>149 <small data-i18n="ext_regex_other_options" data-i18n="Ephemerality">Ephemerality</small>
151 <span class="fa-solid fa-circle-question note-link-span" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>150 <span class="fa-solid fa-circle-question note-link-span" data-i18n="[title]ext_regex_other_options_desc" title="By default, regex scripts alter the chat file directly and irreversibly.&#13;Enabling either (or both) of the options below will prevent chat file alteration, while still altering the specified item(s)."></span>
152 </span>151 </span>
153 <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">152 <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_visual_desc" title="Chat history file contents won't change, but regex will be applied to the messages displayed in the Chat UI.">
154 <input type="checkbox" name="only_format_display" />153 <input type="checkbox" name="only_format_display" />
public/scripts/extensions/regex/index.js+1 -1
@@ -398,7 +398,7 @@ function runRegexCallback(args, value) {
398 for (const script of scripts) {398 for (const script of scripts) {
399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {399 if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) {
400 if (script.disabled) {400 if (script.disabled) {
401 toastr.warning(`Regex script "${scriptName}" is disabled.`);401 toastr.warning(t`Regex script "${scriptName}" is disabled.`);
402 return value;402 return value;
403 }403 }
404404
public/scripts/extensions/shared.js+333 -2
@@ -1,6 +1,7 @@
1import { getRequestHeaders } from '../../script.js';1import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';
2import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';2import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
3import { oai_settings } from '../openai.js';3import { t } from '../i18n.js';
4import { oai_settings, proxies } from '../openai.js';
4import { SECRET_KEYS, secret_state } from '../secrets.js';5import { SECRET_KEYS, secret_state } from '../secrets.js';
5import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';6import { textgen_types, textgenerationwebui_settings } from '../textgen-settings.js';
6import { getTokenCountAsync } from '../tokenizers.js';7import { getTokenCountAsync } from '../tokenizers.js';
@@ -152,6 +153,10 @@ function throwIfInvalidModel(useReverseProxy) {
152 throw new Error('Cohere API key is not set.');153 throw new Error('Cohere API key is not set.');
153 }154 }
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
155 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {160 if (extension_settings.caption.multimodal_api === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) {
156 throw new Error('Ollama server URL is not set.');161 throw new Error('Ollama server URL is not set.');
157 }162 }
@@ -273,3 +278,329 @@ export async function getWebLlmContextSize() {
273 const model = await engine.getCurrentModelInfo();278 const model = await engine.getCurrentModelInfo();
274 return model?.context_size;279 return model?.context_size;
275}280}
281
282/**
283 * It uses the profiles to send a generate request to the API.
284 */
285export 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 = {
77 drawthings: 'drawthings',77 drawthings: 'drawthings',
78 pollinations: 'pollinations',78 pollinations: 'pollinations',
79 stability: 'stability',79 stability: 'stability',
80 blockentropy: 'blockentropy',
81 huggingface: 'huggingface',80 huggingface: 'huggingface',
82 nanogpt: 'nanogpt',81 nanogpt: 'nanogpt',
83 bfl: 'bfl',82 bfl: 'bfl',
84 falai: 'falai',83 falai: 'falai',
84 xai: 'xai',
85};85};
8686
87const initiators = {87const initiators = {
@@ -1300,11 +1300,11 @@ async function onModelChange() {
1300 sources.togetherai,1300 sources.togetherai,
1301 sources.pollinations,1301 sources.pollinations,
1302 sources.stability,1302 sources.stability,
1303 sources.blockentropy,
1304 sources.huggingface,1303 sources.huggingface,
1305 sources.nanogpt,1304 sources.nanogpt,
1306 sources.bfl,1305 sources.bfl,
1307 sources.falai,1306 sources.falai,
1307 sources.xai,
1308 ];1308 ];
13091309
1310 if (cloudSources.includes(extension_settings.sd.source)) {1310 if (cloudSources.includes(extension_settings.sd.source)) {
@@ -1511,9 +1511,6 @@ async function loadSamplers() {
1511 case sources.stability:1511 case sources.stability:
1512 samplers = ['N/A'];1512 samplers = ['N/A'];
1513 break;1513 break;
1514 case sources.blockentropy:
1515 samplers = ['N/A'];
1516 break;
1517 case sources.huggingface:1514 case sources.huggingface:
1518 samplers = ['N/A'];1515 samplers = ['N/A'];
1519 break;1516 break;
@@ -1523,6 +1520,9 @@ async function loadSamplers() {
1523 case sources.bfl:1520 case sources.bfl:
1524 samplers = ['N/A'];1521 samplers = ['N/A'];
1525 break;1522 break;
1523 case sources.xai:
1524 samplers = ['N/A'];
1525 break;
1526 }1526 }
15271527
1528 for (const sampler of samplers) {1528 for (const sampler of samplers) {
@@ -1701,9 +1701,6 @@ async function loadModels() {
1701 case sources.stability:1701 case sources.stability:
1702 models = await loadStabilityModels();1702 models = await loadStabilityModels();
1703 break;1703 break;
1704 case sources.blockentropy:
1705 models = await loadBlockEntropyModels();
1706 break;
1707 case sources.huggingface:1704 case sources.huggingface:
1708 models = [{ value: '', text: '<Enter Model ID above>' }];1705 models = [{ value: '', text: '<Enter Model ID above>' }];
1709 break;1706 break;
@@ -1716,6 +1713,9 @@ async function loadModels() {
1716 case sources.falai:1713 case sources.falai:
1717 models = await loadFalaiModels();1714 models = await loadFalaiModels();
1718 break;1715 break;
1716 case sources.xai:
1717 models = await loadXAIModels();
1718 break;
1719 }1719 }
17201720
1721 for (const model of models) {1721 for (const model of models) {
@@ -1768,6 +1768,12 @@ async function loadFalaiModels() {
1768 return [];1768 return [];
1769}1769}
17701770
1771async function loadXAIModels() {
1772 return [
1773 { value: 'grok-2-image-1212', text: 'grok-2-image-1212' },
1774 ];
1775}
1776
1771async function loadPollinationsModels() {1777async function loadPollinationsModels() {
1772 const result = await fetch('/api/sd/pollinations/models', {1778 const result = await fetch('/api/sd/pollinations/models', {
1773 method: 'POST',1779 method: 'POST',
@@ -1799,26 +1805,6 @@ async function loadTogetherAIModels() {
1799 return [];1805 return [];
1800}1806}
18011807
1802async 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
1822async function loadNanoGPTModels() {1808async function loadNanoGPTModels() {
1823 if (!secret_state[SECRET_KEYS.NANOGPT]) {1809 if (!secret_state[SECRET_KEYS.NANOGPT]) {
1824 console.debug('NanoGPT API key is not set.');1810 console.debug('NanoGPT API key is not set.');
@@ -2097,9 +2083,6 @@ async function loadSchedulers() {
2097 case sources.stability:2083 case sources.stability:
2098 schedulers = ['N/A'];2084 schedulers = ['N/A'];
2099 break;2085 break;
2100 case sources.blockentropy:
2101 schedulers = ['N/A'];
2102 break;
2103 case sources.huggingface:2086 case sources.huggingface:
2104 schedulers = ['N/A'];2087 schedulers = ['N/A'];
2105 break;2088 break;
@@ -2112,6 +2095,9 @@ async function loadSchedulers() {
2112 case sources.falai:2095 case sources.falai:
2113 schedulers = ['N/A'];2096 schedulers = ['N/A'];
2114 break;2097 break;
2098 case sources.xai:
2099 schedulers = ['N/A'];
2100 break;
2115 }2101 }
21162102
2117 for (const scheduler of schedulers) {2103 for (const scheduler of schedulers) {
@@ -2188,9 +2174,6 @@ async function loadVaes() {
2188 case sources.stability:2174 case sources.stability:
2189 vaes = ['N/A'];2175 vaes = ['N/A'];
2190 break;2176 break;
2191 case sources.blockentropy:
2192 vaes = ['N/A'];
2193 break;
2194 case sources.huggingface:2177 case sources.huggingface:
2195 vaes = ['N/A'];2178 vaes = ['N/A'];
2196 break;2179 break;
@@ -2200,6 +2183,12 @@ async function loadVaes() {
2200 case sources.bfl:2183 case sources.bfl:
2201 vaes = ['N/A'];2184 vaes = ['N/A'];
2202 break;2185 break;
2186 case sources.falai:
2187 vaes = ['N/A'];
2188 break;
2189 case sources.xai:
2190 vaes = ['N/A'];
2191 break;
2203 }2192 }
22042193
2205 for (const vae of vaes) {2194 for (const vae of vaes) {
@@ -2757,9 +2746,6 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2757 case sources.stability:2746 case sources.stability:
2758 result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);2747 result = await generateStabilityImage(prefixedPrompt, negativePrompt, signal);
2759 break;2748 break;
2760 case sources.blockentropy:
2761 result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);
2762 break;
2763 case sources.huggingface:2749 case sources.huggingface:
2764 result = await generateHuggingFaceImage(prefixedPrompt, signal);2750 result = await generateHuggingFaceImage(prefixedPrompt, signal);
2765 break;2751 break;
@@ -2772,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2772 case sources.falai:2758 case sources.falai:
2773 result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);2759 result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal);
2774 break;2760 break;
2761 case sources.xai:
2762 result = await generateXAIImage(prefixedPrompt, negativePrompt, signal);
2763 break;
2775 }2764 }
27762765
2777 if (!result.data) {2766 if (!result.data) {
@@ -2828,40 +2817,6 @@ async function generateTogetherAIImage(prompt, negativePrompt, signal) {
2828 }2817 }
2829}2818}
28302819
2831async 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
2865/**2820/**
2866 * Generates an image using the Pollinations API.2821 * Generates an image using the Pollinations API.
2867 * @param {string} prompt - The main instruction used to guide the image generation.2822 * @param {string} prompt - The main instruction used to guide the image generation.
@@ -3535,6 +3490,33 @@ async function generateBflImage(prompt, signal) {
3535}3490}
35363491
3537/**3492/**
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 */
3499async 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/**
3538 * Generates an image using the FAL.AI API.3520 * Generates an image using the FAL.AI API.
3539 * @param {string} prompt - The main instruction used to guide the image generation.3521 * @param {string} prompt - The main instruction used to guide the image generation.
3540 * @param {string} negativePrompt - The negative prompt used to guide the image generation.3522 * @param {string} negativePrompt - The negative prompt used to guide the image generation.
@@ -3772,7 +3754,6 @@ async function addSDGenButtons() {
3772 $('#sd_wand_container').append(buttonHtml);3754 $('#sd_wand_container').append(buttonHtml);
3773 $(document.body).append(dropdownHtml);3755 $(document.body).append(dropdownHtml);
37743756
3775 const messageButton = $('.sd_message_gen');
3776 const button = $('#sd_gen');3757 const button = $('#sd_gen');
3777 const dropdown = $('#sd_dropdown');3758 const dropdown = $('#sd_dropdown');
3778 dropdown.hide();3759 dropdown.hide();
@@ -3846,8 +3827,6 @@ function isValidState() {
3846 return true;3827 return true;
3847 case sources.stability:3828 case sources.stability:
3848 return secret_state[SECRET_KEYS.STABILITY];3829 return secret_state[SECRET_KEYS.STABILITY];
3849 case sources.blockentropy:
3850 return secret_state[SECRET_KEYS.BLOCKENTROPY];
3851 case sources.huggingface:3830 case sources.huggingface:
3852 return secret_state[SECRET_KEYS.HUGGINGFACE];3831 return secret_state[SECRET_KEYS.HUGGINGFACE];
3853 case sources.nanogpt:3832 case sources.nanogpt:
@@ -3856,6 +3835,8 @@ function isValidState() {
3856 return secret_state[SECRET_KEYS.BFL];3835 return secret_state[SECRET_KEYS.BFL];
3857 case sources.falai:3836 case sources.falai:
3858 return secret_state[SECRET_KEYS.FALAI];3837 return secret_state[SECRET_KEYS.FALAI];
3838 case sources.xai:
3839 return secret_state[SECRET_KEYS.XAI];
3859 }3840 }
3860}3841}
38613842
public/scripts/extensions/stable-diffusion/settings.html+2 -2
@@ -38,7 +38,6 @@
38 <label for="sd_source" data-i18n="Source">Source</label>38 <label for="sd_source" data-i18n="Source">Source</label>
39 <select id="sd_source">39 <select id="sd_source">
40 <option value="bfl">BFL (Black Forest Labs)</option>40 <option value="bfl">BFL (Black Forest Labs)</option>
41 <option value="blockentropy">Block Entropy</option>
42 <option value="comfy">ComfyUI</option>41 <option value="comfy">ComfyUI</option>
43 <option value="drawthings">DrawThings HTTP API</option>42 <option value="drawthings">DrawThings HTTP API</option>
44 <option value="extras">Extras API (deprecated)</option>43 <option value="extras">Extras API (deprecated)</option>
@@ -53,6 +52,7 @@
53 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>52 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
54 <option value="horde">Stable Horde</option>53 <option value="horde">Stable Horde</option>
55 <option value="togetherai">TogetherAI</option>54 <option value="togetherai">TogetherAI</option>
55 <option value="xai">xAI (Grok)</option>
56 </select>56 </select>
57 <div data-sd-source="auto">57 <div data-sd-source="auto">
58 <label for="sd_auto_url">SD Web UI URL</label>58 <label for="sd_auto_url">SD Web UI URL</label>
@@ -422,7 +422,7 @@
422 </label>422 </label>
423 </div>423 </div>
424424
425 <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,blockentropy,bfl" class="marginTop5">425 <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,bfl" class="marginTop5">
426 <label for="sd_seed">426 <label for="sd_seed">
427 <span data-i18n="Seed">Seed</span>427 <span data-i18n="Seed">Seed</span>
428 <small data-i18n="(-1 for random)">(-1 for random)</small>428 <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
184 let prompt_text = replaceSpeaker(voiceId);184 let prompt_text = replaceSpeaker(voiceId);
185185
186 const streaming = this.settings.streaming;
187
188 const params = {186 const params = {
189 text: inputText,187 text: inputText,
190 prompt_text: prompt_text,188 prompt_text: prompt_text,
public/scripts/extensions/tts/index.js+2 -1
@@ -1,5 +1,5 @@
1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
2import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules, renderExtensionTemplateAsync } from '../../extensions.js';2import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';
4import { EdgeTtsProvider } from './edge.js';4import { EdgeTtsProvider } from './edge.js';
5import { ElevenLabsTtsProvider } from './elevenlabs.js';5import { ElevenLabsTtsProvider } from './elevenlabs.js';
@@ -471,6 +471,7 @@ async function processTtsQueue() {
471 if (extension_settings.tts.skip_codeblocks) {471 if (extension_settings.tts.skip_codeblocks) {
472 text = text.replace(/^\s{4}.*$/gm, '').trim();472 text = text.replace(/^\s{4}.*$/gm, '').trim();
473 text = text.replace(/```.*?```/gs, '').trim();473 text = text.replace(/```.*?```/gs, '').trim();
474 text = text.replace(/~~~.*?~~~/gs, '').trim();
474 }475 }
475476
476 if (extension_settings.tts.skip_tags) {477 if (extension_settings.tts.skip_tags) {
public/scripts/extensions/tts/settings.html+1 -1
@@ -76,7 +76,7 @@
76 <div id="tts_voicemap_block">76 <div id="tts_voicemap_block">
77 </div>77 </div>
78 <hr>78 <hr>
79 <form id="tts_provider_settings" class="inline-drawer-content">79 <form id="tts_provider_settings">
80 </form>80 </form>
81 <div class="tts_buttons">81 <div class="tts_buttons">
82 <input id="tts_voices" class="menu_button" type="submit" value="Available voices" />82 <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 {
79 // Config //79 // Config //
80 //########//80 //########//
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
82 settings;86 settings;
83 ready = false;87 ready = false;
84 voices = [];88 voices = [];
@@ -168,51 +172,97 @@ class SystemTtsProvider {
168 //#################//172 //#################//
169 fetchTtsVoiceObjects() {173 fetchTtsVoiceObjects() {
170 if (!('speechSynthesis' in window)) {174 if (!('speechSynthesis' in window)) {
171 return [];175 return Promise.resolve([]);
172 }176 }
173177
174 return new Promise((resolve) => {178 return new Promise((resolve) => {
175 setTimeout(() => {179 setTimeout(() => {
176 const voices = speechSynthesis180 let 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
178 .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))194 .sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
179 .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));195 .map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
180196 resolve(mappedVoices);
181 resolve(voices);197 }
182 }, 1);198 }, 50);
183 });199 });
184 }200 }
185201
186 previewTtsVoice(voiceId) {202 previewTtsVoice(voiceId) {
187 if (!('speechSynthesis' in window)) {203 if (!('speechSynthesis' in window)) {
188 throw 'Speech synthesis API is not supported';204 throw new Error('Speech synthesis API is not supported');
189 }205 }
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
193 if (!voice) {212 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.');
195 }219 }
196220
197 speechSynthesis.cancel();221 speechSynthesis.cancel();
198 const text = getPreviewString(voice.lang);222 const langForPreview = voice ? voice.lang : (navigator.language || 'en-US');
223 const text = getPreviewString(langForPreview);
199 const utterance = new SpeechSynthesisUtterance(text);224 const utterance = new SpeechSynthesisUtterance(text);
225
226 if (voice) {
200 utterance.voice = voice;227 utterance.voice = voice;
228 }
229
201 utterance.rate = this.settings.rate || 1;230 utterance.rate = this.settings.rate || 1;
202 utterance.pitch = this.settings.pitch || 1;231 utterance.pitch = this.settings.pitch || 1;
232
233 utterance.onerror = (event) => {
234 console.error(`SystemTTS Preview Error: ${event.error}`, event);
235 };
236
203 speechSynthesis.speak(utterance);237 speechSynthesis.speak(utterance);
204 }238 }
205239
206 async getVoice(voiceName) {240 async getVoice(voiceName) {
207 if (!('speechSynthesis' in window)) {241 if (!('speechSynthesis' in window)) {
208 return { voice_id: null };242 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 };
209 }250 }
210251
211 const voices = speechSynthesis.getVoices();252 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
212 const match = voices.find(x => x.name == voiceName);262 const match = voices.find(x => x.name == voiceName);
213263
214 if (!match) {264 if (!match) {
215 throw `TTS Voice name ${voiceName} not found`;265 throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`);
216 }266 }
217267
218 return { voice_id: match.voiceURI, name: match.name };268 return { voice_id: match.voiceURI, name: match.name };
@@ -237,7 +287,6 @@ class SystemTtsProvider {
237 speechUtteranceChunker(utterance, {287 speechUtteranceChunker(utterance, {
238 chunkLength: 200,288 chunkLength: 200,
239 }, function () {289 }, function () {
240 //some code to execute when done
241 resolve(silence);290 resolve(silence);
242 console.log('System TTS done');291 console.log('System TTS done');
243 });292 });
public/scripts/extensions/vectors/index.js+86 -4
@@ -55,6 +55,8 @@ const getBatchSize = () => ['transformers', 'palm', 'ollama'].includes(settings.
55const settings = {55const settings = {
56 // For both56 // For both
57 source: 'transformers',57 source: 'transformers',
58 alt_endpoint_url: '',
59 use_alt_endpoint: false,
58 include_wi: false,60 include_wi: false,
59 togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval',61 togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval',
60 openai_model: 'text-embedding-ada-002',62 openai_model: 'text-embedding-ada-002',
@@ -63,6 +65,7 @@ const settings = {
63 ollama_keep: false,65 ollama_keep: false,
64 vllm_model: '',66 vllm_model: '',
65 webllm_model: '',67 webllm_model: '',
68 google_model: 'text-embedding-004',
66 summarize: false,69 summarize: false,
67 summarize_sent: false,70 summarize_sent: false,
68 summary_source: 'main',71 summary_source: 'main',
@@ -108,6 +111,7 @@ const settings = {
108const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);111const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
109const webllmProvider = new WebLlmVectorProvider();112const webllmProvider = new WebLlmVectorProvider();
110const cachedSummaries = new Map();113const cachedSummaries = new Map();
114const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];
111115
112/**116/**
113 * Gets the Collection ID for a file embedded in the chat.117 * Gets the Collection ID for a file embedded in the chat.
@@ -565,6 +569,8 @@ async function retrieveFileChunks(queryText, collectionId) {
565 * @returns {Promise<boolean>} True if successful, false if not569 * @returns {Promise<boolean>} True if successful, false if not
566 */570 */
567async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overlapPercent) {571async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overlapPercent) {
572 let toast = jQuery();
573
568 try {574 try {
569 if (settings.translate_files && typeof globalThis.translate === 'function') {575 if (settings.translate_files && typeof globalThis.translate === 'function') {
570 console.log(`Vectors: Translating file ${fileName} to English...`);576 console.log(`Vectors: Translating file ${fileName} to English...`);
@@ -574,7 +580,7 @@ async function vectorizeFile(fileText, fileName, collectionId, chunkSize, overla
574580
575 const batchSize = getBatchSize();581 const batchSize = getBatchSize();
576 const toastBody = $('<span>').text('This may take a while. Please wait...');582 const toastBody = $('<span>').text('This may take a while. Please wait...');
577 const toast = toastr.info(toastBody, `Ingesting file ${escapeHtml(fileName)}`, { closeButton: false, escapeHtml: false, timeOut: 0, extendedTimeOut: 0 });583 toast = toastr.info(toastBody, `Ingesting file ${escapeHtml(fileName)}`, { closeButton: false, escapeHtml: false, timeOut: 0, extendedTimeOut: 0 });
578 const overlapSize = Math.round(chunkSize * overlapPercent / 100);584 const overlapSize = Math.round(chunkSize * overlapPercent / 100);
579 const delimiters = getChunkDelimiters();585 const delimiters = getChunkDelimiters();
580 // Overlap should not be included in chunk size. It will be later compensated by overlapChunks586 // 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
596 console.log(`Vectors: Inserted ${chunks.length} vector items for file ${fileName} into ${collectionId}`);602 console.log(`Vectors: Inserted ${chunks.length} vector items for file ${fileName} into ${collectionId}`);
597 return true;603 return true;
598 } catch (error) {604 } catch (error) {
605 toastr.clear(toast);
599 toastr.error(String(error), 'Failed to vectorize file', { preventDuplicates: true });606 toastr.error(String(error), 'Failed to vectorize file', { preventDuplicates: true });
600 console.error('Vectors: Failed to vectorize file', error);607 console.error('Vectors: Failed to vectorize file', error);
601 return false;608 return false;
@@ -773,19 +780,22 @@ function getVectorsRequestBody(args = {}) {
773 break;780 break;
774 case 'ollama':781 case 'ollama':
775 body.model = extension_settings.vectors.ollama_model;782 body.model = extension_settings.vectors.ollama_model;
776 body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];783 body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
777 body.keep = !!extension_settings.vectors.ollama_keep;784 body.keep = !!extension_settings.vectors.ollama_keep;
778 break;785 break;
779 case 'llamacpp':786 case 'llamacpp':
780 body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];787 body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
781 break;788 break;
782 case 'vllm':789 case 'vllm':
783 body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.VLLM];790 body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.VLLM];
784 body.model = extension_settings.vectors.vllm_model;791 body.model = extension_settings.vectors.vllm_model;
785 break;792 break;
786 case 'webllm':793 case 'webllm':
787 body.model = extension_settings.vectors.webllm_model;794 body.model = extension_settings.vectors.webllm_model;
788 break;795 break;
796 case 'palm':
797 body.model = extension_settings.vectors.google_model;
798 break;
789 default:799 default:
790 break;800 break;
791 }801 }
@@ -803,6 +813,12 @@ async function getAdditionalArgs(items) {
803 case 'webllm':813 case 'webllm':
804 args.embeddings = await createWebLlmEmbeddings(items);814 args.embeddings = await createWebLlmEmbeddings(items);
805 break;815 break;
816 case 'koboldcpp': {
817 const { embeddings, model } = await createKoboldCppEmbeddings(items);
818 args.embeddings = embeddings;
819 args.model = model;
820 break;
821 }
806 }822 }
807 return args;823 return args;
808}824}
@@ -870,11 +886,19 @@ function throwIfSourceInvalid() {
870 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });886 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
871 }887 }
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 {
873 if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||895 if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||
874 settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||896 settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||
897 settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] ||
875 settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {898 settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {
876 throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });899 throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
877 }900 }
901 }
878902
879 if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) {903 if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) {
880 throw new Error('Vectors: API model missing', { cause: 'api_model_missing' });904 throw new Error('Vectors: API model missing', { cause: 'api_model_missing' });
@@ -1071,6 +1095,9 @@ function toggleSettings() {
1071 $('#vllm_vectorsModel').toggle(settings.source === 'vllm');1095 $('#vllm_vectorsModel').toggle(settings.source === 'vllm');
1072 $('#nomicai_apiKey').toggle(settings.source === 'nomicai');1096 $('#nomicai_apiKey').toggle(settings.source === 'nomicai');
1073 $('#webllm_vectorsModel').toggle(settings.source === 'webllm');1097 $('#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));
1074 if (settings.source === 'webllm') {1101 if (settings.source === 'webllm') {
1075 loadWebLlmModels();1102 loadWebLlmModels();
1076 }1103 }
@@ -1138,6 +1165,45 @@ async function createWebLlmEmbeddings(items) {
1138 });1165 });
1139}1166}
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 */
1173async 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
1141async function onPurgeClick() {1207async function onPurgeClick() {
1142 const chatId = getCurrentChatId();1208 const chatId = getCurrentChatId();
1143 if (!chatId) {1209 if (!chatId) {
@@ -1412,6 +1478,16 @@ jQuery(async () => {
1412 saveSettingsDebounced();1478 saveSettingsDebounced();
1413 toggleSettings();1479 toggleSettings();
1414 });1480 });
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 });
1415 $('#api_key_nomicai').on('click', async () => {1491 $('#api_key_nomicai').on('click', async () => {
1416 const popupText = 'NomicAI API Key:';1492 const popupText = 'NomicAI API Key:';
1417 const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {1493 const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {
@@ -1688,6 +1764,12 @@ jQuery(async () => {
1688 toastr.success('WebLLM model loaded');1764 toastr.success('WebLLM model loaded');
1689 });1765 });
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
1691 $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]);1773 $('#api_key_nomicai').toggleClass('success', !!secret_state[SECRET_KEYS.NOMICAI]);
16921774
1693 toggleSettings();1775 toggleSettings();
public/scripts/extensions/vectors/settings.html+31 -3
@@ -13,6 +13,7 @@
13 <option value="cohere">Cohere</option>13 <option value="cohere">Cohere</option>
14 <option value="extras">Extras (deprecated)</option>14 <option value="extras">Extras (deprecated)</option>
15 <option value="palm">Google AI Studio</option>15 <option value="palm">Google AI Studio</option>
16 <option value="koboldcpp">KoboldCpp</option>
16 <option value="llamacpp">llama.cpp</option>17 <option value="llamacpp">llama.cpp</option>
17 <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>18 <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
18 <option value="mistral">MistralAI</option>19 <option value="mistral">MistralAI</option>
@@ -24,6 +25,16 @@
24 <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>25 <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
25 </select>26 </select>
26 </div>27 </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>
27 <div class="flex-container flexFlowColumn" id="webllm_vectorsModel">38 <div class="flex-container flexFlowColumn" id="webllm_vectorsModel">
28 <label for="vectors_webllm_model" data-i18n="Vectorization Model">39 <label for="vectors_webllm_model" data-i18n="Vectorization Model">
29 Vectorization Model40 Vectorization Model
@@ -55,6 +66,14 @@
55 Hint: Set the URL in the API connection settings.66 Hint: Set the URL in the API connection settings.
56 </i>67 </i>
57 </div>68 </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>
58 <div class="flex-container flexFlowColumn" id="llamacpp_vectorsModel">77 <div class="flex-container flexFlowColumn" id="llamacpp_vectorsModel">
59 <span data-i18n="The server MUST be started with the --embedding flag to use this feature!">78 <span data-i18n="The server MUST be started with the --embedding flag to use this feature!">
60 The server MUST be started with the <code>--embedding</code> flag to use this feature!79 The server MUST be started with the <code>--embedding</code> flag to use this feature!
@@ -111,7 +130,16 @@
111 Hint: Set the URL in the API connection settings.130 Hint: Set the URL in the API connection settings.
112 </i>131 </i>
113 </div>132 </div>
114133 <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>
115 <div class="flex-container alignItemsCenter" id="nomicai_apiKey">143 <div class="flex-container alignItemsCenter" id="nomicai_apiKey">
116 <label for="api_key_nomicai" class="flex1">144 <label for="api_key_nomicai" class="flex1">
117 <span data-i18n="NomicAI API Key">NomicAI API Key</span>145 <span data-i18n="NomicAI API Key">NomicAI API Key</span>
@@ -292,7 +320,7 @@
292 <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.">320 <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.">
293 <input type="radio" name="vectors_file_position_db" value="1" />321 <input type="radio" name="vectors_file_position_db" value="1" />
294 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>322 <span data-i18n="In-chat @ Depth">In-chat @ Depth</span>
295 <input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="999" />323 <input id="vectors_file_depth_db" class="text_pole widthUnset" type="number" min="0" max="9999" />
296 <span>as</span>324 <span>as</span>
297 <select id="vectors_file_depth_role_db" class="text_pole widthNatural">325 <select id="vectors_file_depth_role_db" class="text_pole widthNatural">
298 <option value="0" data-i18n="System">System</option>326 <option value="0" data-i18n="System">System</option>
@@ -344,7 +372,7 @@
344 <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.">372 <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.">
345 <input type="radio" name="vectors_position" value="1" />373 <input type="radio" name="vectors_position" value="1" />
346 <span data-i18n="In-chat @ Depth">In-chat @ Depth </span>374 <span data-i18n="In-chat @ Depth">In-chat @ Depth </span>
347 <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="999" />375 <input id="vectors_depth" class="text_pole widthUnset" type="number" min="0" max="9999" />
348 </label>376 </label>
349 </div>377 </div>
350 <div class="flex-container">378 <div class="flex-container">
public/scripts/group-chats.js+15 -12
@@ -46,7 +46,6 @@ import {
46 hideSwipeButtons,46 hideSwipeButtons,
47 chat_metadata,47 chat_metadata,
48 updateChatMetadata,48 updateChatMetadata,
49 isStreamingEnabled,
50 getThumbnailUrl,49 getThumbnailUrl,
51 getRequestHeaders,50 getRequestHeaders,
52 setMenuType,51 setMenuType,
@@ -435,16 +434,18 @@ export function getGroupCharacterCards(groupId, characterId) {
435 * @param {string} value Value to replace434 * @param {string} value Value to replace
436 * @param {string} fieldName Name of the field435 * @param {string} fieldName Name of the field
437 * @param {string} characterName Name of the character436 * @param {string} characterName Name of the character
437 * @param {boolean} trim Whether to trim the value
438 * @returns {string} Replaced text438 * @returns {string} Replaced text
439 * */439 * */
440 function customBaseChatReplace(value, fieldName, characterName) {440 function customBaseChatReplace(value, fieldName, characterName, trim) {
441 if (!value) {441 if (!value) {
442 return '';442 return '';
443 }443 }
444444
445 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names445 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names
446 value = value.replace(/<FIELDNAME>/gi, fieldName);446 value = value.replace(/<FIELDNAME>/gi, fieldName);
447 return baseChatReplace(value.trim(), name1, characterName);447 value = trim ? value.trim() : value;
448 return baseChatReplace(value, name1, characterName);
448 }449 }
449450
450 /**451 /**
@@ -467,13 +468,12 @@ export function getGroupCharacterCards(groupId, characterId) {
467 }468 }
468469
469 // Prepare and replace prefixes470 // Prepare and replace prefixes
470 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName);471 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName, false);
471 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName);472 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName, false);
472 const separator = power_user.instruct.wrap ? '\n' : '';
473 // Also run the macro replacement on the actual content473 // Also run the macro replacement on the actual content
474 value = customBaseChatReplace(value, fieldName, characterName);474 value = customBaseChatReplace(value, fieldName, characterName, true);
475475
476 return `${prefix ? prefix + separator : ''}${value}${suffix ? separator + suffix : ''}`;476 return `${prefix}${value}${suffix}`;
477 }477 }
478478
479 const scenarioOverride = chat_metadata['scenario'];479 const scenarioOverride = chat_metadata['scenario'];
@@ -696,7 +696,7 @@ export function getGroupBlock(group) {
696 template.find('.group_fav_icon').css('display', 'none');696 template.find('.group_fav_icon').css('display', 'none');
697 template.addClass(group.fav ? 'is_fav' : '');697 template.addClass(group.fav ? 'is_fav' : '');
698 template.find('.ch_fav').val(group.fav);698 template.find('.ch_fav').val(group.fav);
699 template.find('.group_select_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);699 template.find('.group_select_counter').text(count + ' ' + (count != 1 ? t`characters` : t`character`));
700 template.find('.group_select_block_list').text(namesList.join(', '));700 template.find('.group_select_block_list').text(namesList.join(', '));
701701
702 // Display inline tags702 // Display inline tags
@@ -882,7 +882,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
882 activatedMembers = activateListOrder(enabledMembers);882 activatedMembers = activateListOrder(enabledMembers);
883 }883 }
884 else if (activationStrategy === group_activation_strategy.POOLED) {884 else if (activationStrategy === group_activation_strategy.POOLED) {
885 activatedMembers = activatePooledOrder(enabledMembers, lastMessage);885 activatedMembers = activatePooledOrder(enabledMembers, lastMessage, isUserInput);
886 }886 }
887 else if (activationStrategy === group_activation_strategy.MANUAL && !isUserInput) {887 else if (activationStrategy === group_activation_strategy.MANUAL && !isUserInput) {
888 activatedMembers = shuffle(enabledMembers).slice(0, 1).map(x => characters.findIndex(y => y.avatar === x)).filter(x => x !== -1);888 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 = {}) {
904 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);904 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);
905 }905 }
906 }906 }
907 await eventSource.emit(event_types.GROUP_WRAPPER_STARTED, { selected_group, type });
907 // now the real generation begins: cycle through every activated character908 // now the real generation begins: cycle through every activated character
908 for (const chId of activatedMembers) {909 for (const chId of activatedMembers) {
909 throwIfAborted();910 throwIfAborted();
@@ -942,6 +943,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
942 setCharacterName('');943 setCharacterName('');
943 activateSendButtons();944 activateSendButtons();
944 showSwipeButtons();945 showSwipeButtons();
946 await eventSource.emit(event_types.GROUP_WRAPPER_FINISHED, { selected_group, type });
945 }947 }
946948
947 return Promise.resolve(textResult);949 return Promise.resolve(textResult);
@@ -1028,16 +1030,17 @@ function activateListOrder(members) {
1028 * Activate group members based on the last message.1030 * Activate group members based on the last message.
1029 * @param {string[]} members List of member avatars1031 * @param {string[]} members List of member avatars
1030 * @param {Object} lastMessage Last message1032 * @param {Object} lastMessage Last message
1033 * @param {boolean} isUserInput Whether the user has input text
1031 * @returns {number[]} List of character ids1034 * @returns {number[]} List of character ids
1032 */1035 */
1033function activatePooledOrder(members, lastMessage) {1036function activatePooledOrder(members, lastMessage, isUserInput) {
1034 /** @type {string} */1037 /** @type {string} */
1035 let activatedMember = null;1038 let activatedMember = null;
1036 /** @type {string[]} */1039 /** @type {string[]} */
1037 const spokenSinceUser = [];1040 const spokenSinceUser = [];
10381041
1039 for (const message of chat.slice().reverse()) {1042 for (const message of chat.slice().reverse()) {
1040 if (message.is_user) {1043 if (message.is_user || isUserInput) {
1041 break;1044 break;
1042 }1045 }
10431046
public/scripts/horde.js+4 -3
@@ -10,6 +10,7 @@ import { SECRET_KEYS, writeSecret } from './secrets.js';
10import { delay } from './utils.js';10import { delay } from './utils.js';
11import { isMobile } from './RossAscends-mods.js';11import { isMobile } from './RossAscends-mods.js';
12import { autoSelectInstructPreset } from './instruct-mode.js';12import { autoSelectInstructPreset } from './instruct-mode.js';
13import { t } from './i18n.js';
1314
14export {15export {
15 horde_settings,16 horde_settings,
@@ -169,7 +170,7 @@ async function adjustHordeGenerationParams(max_context_length, max_length) {
169 }170 }
170 }171 }
171 console.log(maxContextLength, maxLength);172 console.log(maxContextLength, maxLength);
172 $('#adjustedHordeParams').text(`Context: ${maxContextLength}, Response: ${maxLength}`);173 $('#adjustedHordeParams').text(t`Context` + `: ${maxContextLength}, ` + t`Response` + `: ${maxLength}`);
173 return { maxContextLength, maxLength };174 return { maxContextLength, maxLength };
174}175}
175176
@@ -177,7 +178,7 @@ function setContextSizePreview() {
177 if (horde_settings.models.length) {178 if (horde_settings.models.length) {
178 adjustHordeGenerationParams(max_context, amount_gen);179 adjustHordeGenerationParams(max_context, amount_gen);
179 } else {180 } else {
180 $('#adjustedHordeParams').text('Context: --, Response: --');181 $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
181 }182 }
182}183}
183184
@@ -404,7 +405,7 @@ jQuery(function () {
404 if (horde_settings.models.length) {405 if (horde_settings.models.length) {
405 adjustHordeGenerationParams(max_context, amount_gen);406 adjustHordeGenerationParams(max_context, amount_gen);
406 } else {407 } else {
407 $('#adjustedHordeParams').text('Context: --, Response: --');408 $('#adjustedHordeParams').text(t`Context` + ': --, ' + t`Response` + ': --');
408 }409 }
409410
410 saveSettingsDebounced();411 saveSettingsDebounced();
public/scripts/instruct-mode.js+62 -49
@@ -243,9 +243,14 @@ export function autoSelectInstructPreset(modelId) {
243243
244/**244/**
245 * Converts instruct mode sequences to an array of stopping strings.245 * 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"
246 * @returns {string[]} Array of instruct mode stopping strings.249 * @returns {string[]} Array of instruct mode stopping strings.
247 */250 */
248export function getInstructStoppingSequences() {251export function getInstructStoppingSequences({ customInstruct = null, useStopStrings = null } = {}) {
252 const instruct = structuredClone(customInstruct ?? power_user.instruct);
253
249 /**254 /**
250 * Adds instruct mode sequence to the result array.255 * Adds instruct mode sequence to the result array.
251 * @param {string} sequence Sequence string.256 * @param {string} sequence Sequence string.
@@ -254,7 +259,7 @@ export function getInstructStoppingSequences() {
254 function addInstructSequence(sequence) {259 function addInstructSequence(sequence) {
255 // Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string260 // Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
256 // But it's a problem for Metharme which doesn't use newlines to separate them.261 // But it's a problem for Metharme which doesn't use newlines to separate them.
257 const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;262 const wrap = (s) => instruct.wrap ? '\n' + s : s;
258 // Sequence must be a non-empty string263 // Sequence must be a non-empty string
259 if (typeof sequence === 'string' && sequence.length > 0) {264 if (typeof sequence === 'string' && sequence.length > 0) {
260 // If sequence is just a whitespace or newline - we don't want to make it a stopping string265 // 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() {
262 if (sequence.trim().length > 0) {267 if (sequence.trim().length > 0) {
263 const wrappedSequence = wrap(sequence);268 const wrappedSequence = wrap(sequence);
264 // Need to respect "insert macro" setting269 // Need to respect "insert macro" setting
265 const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;270 const stopString = instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
266 result.push(stopString);271 result.push(stopString);
267 }272 }
268 }273 }
@@ -270,14 +275,15 @@ export function getInstructStoppingSequences() {
270275
271 const result = [];276 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) {
275 const input_sequence = power_user.instruct.input_sequence?.replace(/{{name}}/gi, name1) || '';280 const stop_sequence = instruct.stop_sequence || '';
276 const output_sequence = power_user.instruct.output_sequence?.replace(/{{name}}/gi, name2) || '';281 const input_sequence = instruct.input_sequence?.replace(/{{name}}/gi, name1) || '';
277 const first_output_sequence = power_user.instruct.first_output_sequence?.replace(/{{name}}/gi, name2) || '';282 const output_sequence = instruct.output_sequence?.replace(/{{name}}/gi, name2) || '';
278 const last_output_sequence = power_user.instruct.last_output_sequence?.replace(/{{name}}/gi, name2) || '';283 const first_output_sequence = instruct.first_output_sequence?.replace(/{{name}}/gi, name2) || '';
279 const system_sequence = power_user.instruct.system_sequence?.replace(/{{name}}/gi, 'System') || '';284 const last_output_sequence = instruct.last_output_sequence?.replace(/{{name}}/gi, name2) || '';
280 const last_system_sequence = power_user.instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';285 const system_sequence = instruct.system_sequence?.replace(/{{name}}/gi, 'System') || '';
286 const last_system_sequence = instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';
281287
282 const combined_sequence = [288 const combined_sequence = [
283 stop_sequence,289 stop_sequence,
@@ -292,7 +298,7 @@ export function getInstructStoppingSequences() {
292 combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);298 combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
293 }299 }
294300
295 if (power_user.context.use_stop_strings) {301 if (useStopStrings ?? power_user.context.use_stop_strings) {
296 if (power_user.context.chat_start) {302 if (power_user.context.chat_start) {
297 result.push(`\n${substituteParams(power_user.context.chat_start)}`);303 result.push(`\n${substituteParams(power_user.context.chat_start)}`);
298 }304 }
@@ -320,59 +326,61 @@ export const force_output_sequence = {
320 * @param {string} name1 User name.326 * @param {string} name1 User name.
321 * @param {string} name2 Character name.327 * @param {string} name2 Character name.
322 * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).328 * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured).
329 * @param {InstructSettings} customInstruct Custom instruct mode settings.
323 * @returns {string} Formatted instruct mode chat message.330 * @returns {string} Formatted instruct mode chat message.
324 */331 */
325export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) {332export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence, customInstruct = null) {
326 let includeNames = isNarrator ? false : power_user.instruct.names_behavior === names_behavior_types.ALWAYS;333 const instruct = structuredClone(customInstruct ?? power_user.instruct);
334 let includeNames = isNarrator ? false : instruct.names_behavior === names_behavior_types.ALWAYS;
327335
328 if (!isNarrator && power_user.instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {336 if (!isNarrator && instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) {
329 includeNames = true;337 includeNames = true;
330 }338 }
331339
332 function getPrefix() {340 function getPrefix() {
333 if (isNarrator) {341 if (isNarrator) {
334 return power_user.instruct.system_same_as_user ? power_user.instruct.input_sequence : power_user.instruct.system_sequence;342 return instruct.system_same_as_user ? instruct.input_sequence : instruct.system_sequence;
335 }343 }
336344
337 if (isUser) {345 if (isUser) {
338 if (forceOutputSequence === force_output_sequence.FIRST) {346 if (forceOutputSequence === force_output_sequence.FIRST) {
339 return power_user.instruct.first_input_sequence || power_user.instruct.input_sequence;347 return instruct.first_input_sequence || instruct.input_sequence;
340 }348 }
341349
342 if (forceOutputSequence === force_output_sequence.LAST) {350 if (forceOutputSequence === force_output_sequence.LAST) {
343 return power_user.instruct.last_input_sequence || power_user.instruct.input_sequence;351 return instruct.last_input_sequence || instruct.input_sequence;
344 }352 }
345353
346 return power_user.instruct.input_sequence;354 return instruct.input_sequence;
347 }355 }
348356
349 if (forceOutputSequence === force_output_sequence.FIRST) {357 if (forceOutputSequence === force_output_sequence.FIRST) {
350 return power_user.instruct.first_output_sequence || power_user.instruct.output_sequence;358 return instruct.first_output_sequence || instruct.output_sequence;
351 }359 }
352360
353 if (forceOutputSequence === force_output_sequence.LAST) {361 if (forceOutputSequence === force_output_sequence.LAST) {
354 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;362 return instruct.last_output_sequence || instruct.output_sequence;
355 }363 }
356364
357 return power_user.instruct.output_sequence;365 return instruct.output_sequence;
358 }366 }
359367
360 function getSuffix() {368 function getSuffix() {
361 if (isNarrator) {369 if (isNarrator) {
362 return power_user.instruct.system_same_as_user ? power_user.instruct.input_suffix : power_user.instruct.system_suffix;370 return instruct.system_same_as_user ? instruct.input_suffix : instruct.system_suffix;
363 }371 }
364372
365 if (isUser) {373 if (isUser) {
366 return power_user.instruct.input_suffix;374 return instruct.input_suffix;
367 }375 }
368376
369 return power_user.instruct.output_suffix;377 return instruct.output_suffix;
370 }378 }
371379
372 let prefix = getPrefix() || '';380 let prefix = getPrefix() || '';
373 let suffix = getSuffix() || '';381 let suffix = getSuffix() || '';
374382
375 if (power_user.instruct.macro) {383 if (instruct.macro) {
376 prefix = substituteParams(prefix, name1, name2);384 prefix = substituteParams(prefix, name1, name2);
377 prefix = prefix.replace(/{{name}}/gi, name || 'System');385 prefix = prefix.replace(/{{name}}/gi, name || 'System');
378386
@@ -380,11 +388,11 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
380 suffix = suffix.replace(/{{name}}/gi, name || 'System');388 suffix = suffix.replace(/{{name}}/gi, name || 'System');
381 }389 }
382390
383 if (!suffix && power_user.instruct.wrap) {391 if (!suffix && instruct.wrap) {
384 suffix = '\n';392 suffix = '\n';
385 }393 }
386394
387 const separator = power_user.instruct.wrap ? '\n' : '';395 const separator = instruct.wrap ? '\n' : '';
388396
389 // Don't include the name if it's empty397 // Don't include the name if it's empty
390 const textArray = includeNames && name ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];398 const textArray = includeNames && name ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];
@@ -396,23 +404,26 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
396/**404/**
397 * Formats instruct mode system prompt.405 * Formats instruct mode system prompt.
398 * @param {string} systemPrompt System prompt string.406 * @param {string} systemPrompt System prompt string.
407 * @param {InstructSettings} customInstruct Custom instruct mode settings.
399 * @returns {string} Formatted instruct mode system prompt.408 * @returns {string} Formatted instruct mode system prompt.
400 */409 */
401export function formatInstructModeSystemPrompt(systemPrompt) {410export function formatInstructModeSystemPrompt(systemPrompt, customInstruct = null) {
402 if (!systemPrompt) {411 if (!systemPrompt) {
403 return '';412 return '';
404 }413 }
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
408 if (power_user.instruct.system_sequence_prefix) {419 if (instruct.system_sequence_prefix) {
409 // TODO: Replace with a proper 'System' prompt entity name input420 // TODO: Replace with a proper 'System' prompt entity name input
410 const prefix = power_user.instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');421 const prefix = instruct.system_sequence_prefix.replace(/{{name}}/gi, 'System');
411 systemPrompt = prefix + separator + systemPrompt;422 systemPrompt = prefix + separator + systemPrompt;
412 }423 }
413424
414 if (power_user.instruct.system_sequence_suffix) {425 if (instruct.system_sequence_suffix) {
415 systemPrompt = systemPrompt + separator + power_user.instruct.system_sequence_suffix;426 systemPrompt = systemPrompt + separator + instruct.system_sequence_suffix;
416 }427 }
417428
418 return systemPrompt;429 return systemPrompt;
@@ -504,30 +515,32 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
504 * @param {string} name2 Character name.515 * @param {string} name2 Character name.
505 * @param {boolean} isQuiet Is quiet mode generation.516 * @param {boolean} isQuiet Is quiet mode generation.
506 * @param {boolean} isQuietToLoud Is quiet to loud generation.517 * @param {boolean} isQuietToLoud Is quiet to loud generation.
518 * @param {InstructSettings} customInstruct Custom instruct settings.
507 * @returns {string} Formatted instruct mode last prompt line.519 * @returns {string} Formatted instruct mode last prompt line.
508 */520 */
509export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) {521export 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
512 function getSequence() {525 function getSequence() {
513 // User impersonation prompt526 // User impersonation prompt
514 if (isImpersonate) {527 if (isImpersonate) {
515 return power_user.instruct.input_sequence;528 return instruct.input_sequence;
516 }529 }
517530
518 // Neutral / system / quiet prompt531 // Neutral / system / quiet prompt
519 // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise532 // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise
520 if (isQuiet && !isQuietToLoud) {533 if (isQuiet && !isQuietToLoud) {
521 return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence;534 return instruct.last_system_sequence || instruct.output_sequence;
522 }535 }
523536
524 // Quiet in-character prompt537 // Quiet in-character prompt
525 if (isQuiet && isQuietToLoud) {538 if (isQuiet && isQuietToLoud) {
526 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;539 return instruct.last_output_sequence || instruct.output_sequence;
527 }540 }
528541
529 // Default AI response542 // Default AI response
530 return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;543 return instruct.last_output_sequence || instruct.output_sequence;
531 }544 }
532545
533 let sequence = getSequence() || '';546 let sequence = getSequence() || '';
@@ -536,21 +549,21 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
536 // A hack for Mistral's formatting that has a normal output sequence ending with a space549 // A hack for Mistral's formatting that has a normal output sequence ending with a space
537 if (550 if (
538 includeNames &&551 includeNames &&
539 power_user.instruct.last_output_sequence &&552 instruct.last_output_sequence &&
540 power_user.instruct.output_sequence &&553 instruct.output_sequence &&
541 sequence === power_user.instruct.last_output_sequence &&554 sequence === instruct.last_output_sequence &&
542 /\s$/.test(power_user.instruct.output_sequence) &&555 /\s$/.test(instruct.output_sequence) &&
543 !/\s$/.test(power_user.instruct.last_output_sequence)556 !/\s$/.test(instruct.last_output_sequence)
544 ) {557 ) {
545 nameFiller = power_user.instruct.output_sequence.slice(-1);558 nameFiller = instruct.output_sequence.slice(-1);
546 }559 }
547560
548 if (power_user.instruct.macro) {561 if (instruct.macro) {
549 sequence = substituteParams(sequence, name1, name2);562 sequence = substituteParams(sequence, name1, name2);
550 sequence = sequence.replace(/{{name}}/gi, name || 'System');563 sequence = sequence.replace(/{{name}}/gi, name || 'System');
551 }564 }
552565
553 const separator = power_user.instruct.wrap ? '\n' : '';566 const separator = instruct.wrap ? '\n' : '';
554 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);567 let text = includeNames ? (separator + sequence + separator + nameFiller + `${name}:`) : (separator + sequence);
555568
556 // Quiet prompt already has a newline at the end569 // Quiet prompt already has a newline at the end
@@ -562,7 +575,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
562 text += (includeNames ? promptBias : (separator + promptBias.trimStart()));575 text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
563 }576 }
564577
565 return (power_user.instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);578 return (instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);
566}579}
567580
568/**581/**
public/scripts/login.js+10 -5
@@ -180,13 +180,18 @@ function displayError(message) {
180 * Preserves the query string.180 * Preserves the query string.
181 */181 */
182function redirectToHome() {182function redirectToHome() {
183 // After a login theres no need to preserve the183 // Create a URL object based on the current location
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();
190}195}
191196
192/**197/**
public/scripts/logprobs.js+6 -1
@@ -368,7 +368,12 @@ function onToggleLogprobsPanel() {
368function createSwipe(messageId, prompt) {368function createSwipe(messageId, prompt) {
369 // need to call `cleanUpMessage` on our new prompt, because we were working369 // need to call `cleanUpMessage` on our new prompt, because we were working
370 // with raw model output and our new prompt is missing trimming/macro replacements370 // with raw model output and our new prompt is missing trimming/macro replacements
371 const cleanedPrompt = cleanUpMessage(prompt, false, false, true);371 const cleanedPrompt = cleanUpMessage({
372 getMessage: prompt,
373 isImpersonate: false,
374 isContinue: false,
375 displayIncompleteSentences: true,
376 });
372377
373 const msg = chat[messageId];378 const msg = chat[messageId];
374 const newSwipeInfo = {379 const newSwipeInfo = {
public/scripts/nai-settings.js+1 -0
@@ -795,6 +795,7 @@ export function parseNovelAILogprobs(data) {
795795
796 // Add the chosen token to `merged` if it's not already there. This can796 // Add the chosen token to `merged` if it's not already there. This can
797 // happen if the chosen token was not among the top 10 most likely ones.797 // happen if the chosen token was not among the top 10 most likely ones.
798 // eslint-disable-next-line no-unused-vars
798 const [[chosenId], [_, chosenAfter]] = data.chosen[0];799 const [[chosenId], [_, chosenAfter]] = data.chosen[0];
799 if (!merged.some(([id]) => id === chosenId)) {800 if (!merged.some(([id]) => id === chosenId)) {
800 merged.push([chosenId, chosenAfter]);801 merged.push([chosenId, chosenAfter]);
public/scripts/openai.js+248 -140
@@ -15,12 +15,12 @@ import {
15 extension_prompt_types,15 extension_prompt_types,
16 Generate,16 Generate,
17 getExtensionPrompt,17 getExtensionPrompt,
18 getExtensionPromptMaxDepth,
18 getNextMessageId,19 getNextMessageId,
19 getRequestHeaders,20 getRequestHeaders,
20 getStoppingStrings,21 getStoppingStrings,
21 is_send_press,22 is_send_press,
22 main_api,23 main_api,
23 MAX_INJECTION_DEPTH,
24 name1,24 name1,
25 name2,25 name2,
26 replaceItemizedPromptText,26 replaceItemizedPromptText,
@@ -75,6 +75,7 @@ import { Popup, POPUP_RESULT } from './popup.js';
75import { t } from './i18n.js';75import { t } from './i18n.js';
76import { ToolManager } from './tool-calling.js';76import { ToolManager } from './tool-calling.js';
77import { accountStorage } from './util/AccountStorage.js';77import { accountStorage } from './util/AccountStorage.js';
78import { IGNORE_SYMBOL } from './constants.js';
7879
79export {80export {
80 openai_messages_count,81 openai_messages_count,
@@ -119,7 +120,6 @@ const default_bias_presets = {
119const max_2k = 2047;120const max_2k = 2047;
120const max_4k = 4095;121const max_4k = 4095;
121const max_8k = 8191;122const max_8k = 8191;
122const max_12k = 12287;
123const max_16k = 16383;123const max_16k = 16383;
124const max_32k = 32767;124const max_32k = 32767;
125const max_64k = 65535;125const max_64k = 65535;
@@ -182,9 +182,9 @@ export const chat_completion_sources = {
182 PERPLEXITY: 'perplexity',182 PERPLEXITY: 'perplexity',
183 GROQ: 'groq',183 GROQ: 'groq',
184 ZEROONEAI: '01ai',184 ZEROONEAI: '01ai',
185 BLOCKENTROPY: 'blockentropy',
186 NANOGPT: 'nanogpt',185 NANOGPT: 'nanogpt',
187 DEEPSEEK: 'deepseek',186 DEEPSEEK: 'deepseek',
187 XAI: 'xai',
188};188};
189189
190const character_names_behavior = {190const character_names_behavior = {
@@ -258,7 +258,7 @@ export const settingsToUpdate = {
258 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],258 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false],
259 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],259 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
260 zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],260 zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
261 blockentropy_model: ['#model_blockentropy_select', 'blockentropy_model', false],261 xai_model: ['#model_xai_select', 'xai_model', false],
262 custom_model: ['#custom_model_id', 'custom_model', false],262 custom_model: ['#custom_model_id', 'custom_model', false],
263 custom_url: ['#custom_api_url_text', 'custom_url', false],263 custom_url: ['#custom_api_url_text', 'custom_url', false],
264 custom_include_body: ['#custom_include_body', 'custom_include_body', false],264 custom_include_body: ['#custom_include_body', 'custom_include_body', false],
@@ -346,8 +346,8 @@ const default_settings = {
346 groq_model: 'llama-3.3-70b-versatile',346 groq_model: 'llama-3.3-70b-versatile',
347 nanogpt_model: 'gpt-4o-mini',347 nanogpt_model: 'gpt-4o-mini',
348 zerooneai_model: 'yi-large',348 zerooneai_model: 'yi-large',
349 blockentropy_model: 'be-70b-base-llama3.1',
350 deepseek_model: 'deepseek-chat',349 deepseek_model: 'deepseek-chat',
350 xai_model: 'grok-3-beta',
351 custom_model: '',351 custom_model: '',
352 custom_url: '',352 custom_url: '',
353 custom_include_body: '',353 custom_include_body: '',
@@ -427,8 +427,8 @@ const oai_settings = {
427 groq_model: 'llama-3.1-70b-versatile',427 groq_model: 'llama-3.1-70b-versatile',
428 nanogpt_model: 'gpt-4o-mini',428 nanogpt_model: 'gpt-4o-mini',
429 zerooneai_model: 'yi-large',429 zerooneai_model: 'yi-large',
430 blockentropy_model: 'be-70b-base-llama3.1',
431 deepseek_model: 'deepseek-chat',430 deepseek_model: 'deepseek-chat',
431 xai_model: 'grok-3-beta',
432 custom_model: '',432 custom_model: '',
433 custom_url: '',433 custom_url: '',
434 custom_include_body: '',434 custom_include_body: '',
@@ -527,6 +527,13 @@ function setOpenAIMessages(chat) {
527 let role = chat[j]['is_user'] ? 'user' : 'assistant';527 let role = chat[j]['is_user'] ? 'user' : 'assistant';
528 let content = chat[j]['mes'];528 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
530 // 100% legal way to send a message as system537 // 100% legal way to send a message as system
531 if (chat[j].extra?.type === system_message_types.NARRATOR) {538 if (chat[j].extra?.type === system_message_types.NARRATOR) {
532 role = 'system';539 role = 'system';
@@ -705,16 +712,18 @@ export function parseExampleIntoIndividual(messageExampleString, appendNamesForG
705 return result;712 return result;
706}713}
707714
708function formatWorldInfo(value) {715export function formatWorldInfo(value, { wiFormat = null } = {}) {
709 if (!value) {716 if (!value) {
710 return '';717 return '';
711 }718 }
712719
713 if (!oai_settings.wi_format.trim()) {720 const format = wiFormat ?? oai_settings.wi_format;
721
722 if (!format.trim()) {
714 return value;723 return value;
715 }724 }
716725
717 return stringFormat(oai_settings.wi_format, value);726 return stringFormat(format, value);
718}727}
719728
720/**729/**
@@ -733,7 +742,8 @@ async function populationInjectionPrompts(prompts, messages) {
733 'assistant': extension_prompt_roles.ASSISTANT,742 'assistant': extension_prompt_roles.ASSISTANT,
734 };743 };
735744
736 for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {745 const maxDepth = getExtensionPromptMaxDepth();
746 for (let i = 0; i <= maxDepth; i++) {
737 // Get prompts for current depth747 // Get prompts for current depth
738 const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content);748 const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content);
739749
@@ -952,7 +962,7 @@ async function populateDialogueExamples(prompts, chatCompletion, messageExamples
952 * @param {number} position - Prompt position in the extensions object.962 * @param {number} position - Prompt position in the extensions object.
953 * @returns {string|false} - The prompt position for prompt collection.963 * @returns {string|false} - The prompt position for prompt collection.
954 */964 */
955function getPromptPosition(position) {965export function getPromptPosition(position) {
956 if (position == extension_prompt_types.BEFORE_PROMPT) {966 if (position == extension_prompt_types.BEFORE_PROMPT) {
957 return 'start';967 return 'start';
958 }968 }
@@ -969,7 +979,7 @@ function getPromptPosition(position) {
969 * @param {number} role Role of the prompt.979 * @param {number} role Role of the prompt.
970 * @returns {string} Mapped role.980 * @returns {string} Mapped role.
971 */981 */
972function getPromptRole(role) {982export function getPromptRole(role) {
973 switch (role) {983 switch (role) {
974 case extension_prompt_roles.SYSTEM:984 case extension_prompt_roles.SYSTEM:
975 return 'system';985 return 'system';
@@ -1402,9 +1412,9 @@ export async function prepareOpenAIMessages({
1402 await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });1412 await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
1403 } catch (error) {1413 } catch (error) {
1404 if (error instanceof TokenBudgetExceededError) {1414 if (error instanceof TokenBudgetExceededError) {
1405 toastr.error(t`An error occurred while counting tokens: Token budget exceeded.`);1415 toastr.error(t`Mandatory prompts exceed the context size.`);
1406 chatCompletion.log('Token budget exceeded.');1416 chatCompletion.log('Mandatory prompts exceed the context size.');
1407 promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.`;1417 promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token limit or disable custom prompts.`;
1408 } else if (error instanceof InvalidCharacterNameError) {1418 } else if (error instanceof InvalidCharacterNameError) {
1409 toastr.warning(t`An error occurred while counting tokens: Invalid character name`);1419 toastr.warning(t`An error occurred while counting tokens: Invalid character name`);
1410 chatCompletion.log('Invalid character name');1420 chatCompletion.log('Invalid character name');
@@ -1442,8 +1452,10 @@ export async function prepareOpenAIMessages({
1442 * Handles errors during streaming requests.1452 * Handles errors during streaming requests.
1443 * @param {Response} response1453 * @param {Response} response
1444 * @param {string} decoded - response text or decoded stream data1454 * @param {string} decoded - response text or decoded stream data
1455 * @param {object} [options]
1456 * @param {boolean?} [options.quiet=false] Suppress toast messages
1445 */1457 */
1446function tryParseStreamingError(response, decoded) {1458export function tryParseStreamingError(response, decoded, { quiet = false } = {}) {
1447 try {1459 try {
1448 const data = JSON.parse(decoded);1460 const data = JSON.parse(decoded);
14491461
@@ -1451,19 +1463,19 @@ function tryParseStreamingError(response, decoded) {
1451 return;1463 return;
1452 }1464 }
14531465
1454 checkQuotaError(data);1466 checkQuotaError(data, { quiet });
1455 checkModerationError(data);1467 checkModerationError(data, { quiet });
14561468
1457 // these do not throw correctly (equiv to Error("[object Object]"))1469 // these do not throw correctly (equiv to Error("[object Object]"))
1458 // if trying to fix "[object Object]" displayed to users, start here1470 // if trying to fix "[object Object]" displayed to users, start here
14591471
1460 if (data.error) {1472 if (data.error) {
1461 toastr.error(data.error.message || response.statusText, 'Chat Completion API');1473 !quiet && toastr.error(data.error.message || response.statusText, 'Chat Completion API');
1462 throw new Error(data);1474 throw new Error(data);
1463 }1475 }
14641476
1465 if (data.message) {1477 if (data.message) {
1466 toastr.error(data.message, 'Chat Completion API');1478 !quiet && toastr.error(data.message, 'Chat Completion API');
1467 throw new Error(data);1479 throw new Error(data);
1468 }1480 }
1469 }1481 }
@@ -1475,16 +1487,18 @@ function tryParseStreamingError(response, decoded) {
1475/**1487/**
1476 * Checks if the response contains a quota error and displays a popup if it does.1488 * Checks if the response contains a quota error and displays a popup if it does.
1477 * @param data1489 * @param data
1490 * @param {object} [options]
1491 * @param {boolean?} [options.quiet=false] Suppress toast messages
1478 * @returns {void}1492 * @returns {void}
1479 * @throws {object} - response JSON1493 * @throws {object} - response JSON
1480 */1494 */
1481function checkQuotaError(data) {1495function checkQuotaError(data, { quiet = false } = {}) {
1482 if (!data) {1496 if (!data) {
1483 return;1497 return;
1484 }1498 }
14851499
1486 if (data.quota_error) {1500 if (data.quota_error) {
1487 renderTemplateAsync('quotaError').then((html) => Popup.show.text('Quota Error', html));1501 !quiet && renderTemplateAsync('quotaError').then((html) => Popup.show.text('Quota Error', html));
14881502
1489 // this does not throw correctly (equiv to Error("[object Object]"))1503 // this does not throw correctly (equiv to Error("[object Object]"))
1490 // if trying to fix "[object Object]" displayed to users, start here1504 // if trying to fix "[object Object]" displayed to users, start here
@@ -1492,9 +1506,14 @@ function checkQuotaError(data) {
1492 }1506 }
1493}1507}
14941508
1495function checkModerationError(data) {1509/**
1510 * @param {any} data
1511 * @param {object} [options]
1512 * @param {boolean?} [options.quiet=false] Suppress toast messages
1513 */
1514function checkModerationError(data, { quiet = false } = {}) {
1496 const moderationError = data?.error?.message?.includes('requires moderation');1515 const moderationError = data?.error?.message?.includes('requires moderation');
1497 if (moderationError) {1516 if (moderationError && !quiet) {
1498 const moderationReason = `Reasons: ${data?.error?.metadata?.reasons?.join(', ') ?? '(N/A)'}`;1517 const moderationReason = `Reasons: ${data?.error?.metadata?.reasons?.join(', ') ?? '(N/A)'}`;
1499 const flaggedText = data?.error?.metadata?.flagged_input ?? '(N/A)';1518 const flaggedText = data?.error?.metadata?.flagged_input ?? '(N/A)';
1500 toastr.info(flaggedText, moderationReason, { timeOut: 10000 });1519 toastr.info(flaggedText, moderationReason, { timeOut: 10000 });
@@ -1626,12 +1645,12 @@ export function getChatCompletionModel(source = null) {
1626 return oai_settings.groq_model;1645 return oai_settings.groq_model;
1627 case chat_completion_sources.ZEROONEAI:1646 case chat_completion_sources.ZEROONEAI:
1628 return oai_settings.zerooneai_model;1647 return oai_settings.zerooneai_model;
1629 case chat_completion_sources.BLOCKENTROPY:
1630 return oai_settings.blockentropy_model;
1631 case chat_completion_sources.NANOGPT:1648 case chat_completion_sources.NANOGPT:
1632 return oai_settings.nanogpt_model;1649 return oai_settings.nanogpt_model;
1633 case chat_completion_sources.DEEPSEEK:1650 case chat_completion_sources.DEEPSEEK:
1634 return oai_settings.deepseek_model;1651 return oai_settings.deepseek_model;
1652 case chat_completion_sources.XAI:
1653 return oai_settings.xai_model;
1635 default:1654 default:
1636 throw new Error(`Unknown chat completion source: ${activeSource}`);1655 throw new Error(`Unknown chat completion source: ${activeSource}`);
1637 }1656 }
@@ -1675,6 +1694,11 @@ function calculateOpenRouterCost() {
1675 }1694 }
1676 }1695 }
16771696
1697 if (oai_settings.enable_web_search) {
1698 const webSearchCost = (0.02).toFixed(2);
1699 cost = t`${cost} + $${webSearchCost}`;
1700 }
1701
1678 $('#openrouter_max_prompt_cost').text(cost);1702 $('#openrouter_max_prompt_cost').text(cost);
1679}1703}
16801704
@@ -1746,23 +1770,6 @@ function saveModelList(data) {
1746 $('#model_01ai_select').val(oai_settings.zerooneai_model).trigger('change');1770 $('#model_01ai_select').val(oai_settings.zerooneai_model).trigger('change');
1747 }1771 }
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
1766 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {1773 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {
1767 /** @type {HTMLSelectElement} */1774 /** @type {HTMLSelectElement} */
1768 const mistralModelSelect = document.querySelector('#model_mistralai_select');1775 const mistralModelSelect = document.querySelector('#model_mistralai_select');
@@ -1966,13 +1973,14 @@ async function sendOpenAIRequest(type, messages, signal) {
1966 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;1973 const is01AI = oai_settings.chat_completion_source == chat_completion_sources.ZEROONEAI;
1967 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;1974 const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
1968 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;1975 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
1976 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
1969 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);1977 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
1970 const isQuiet = type === 'quiet';1978 const isQuiet = type === 'quiet';
1971 const isImpersonate = type === 'impersonate';1979 const isImpersonate = type === 'impersonate';
1972 const isContinue = type === 'continue';1980 const isContinue = type === 'continue';
1973 const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model));1981 const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model));
1974 const useLogprobs = !!power_user.request_token_probabilities;1982 const useLogprobs = !!power_user.request_token_probabilities;
1975 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom);1983 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom || isXAI);
19761984
1977 // If we're using the window.ai extension, use that instead1985 // If we're using the window.ai extension, use that instead
1978 // Doesn't support logit bias yet1986 // Doesn't support logit bias yet
@@ -2018,6 +2026,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2018 'reasoning_effort': String(oai_settings.reasoning_effort),2026 'reasoning_effort': String(oai_settings.reasoning_effort),
2019 'enable_web_search': Boolean(oai_settings.enable_web_search),2027 'enable_web_search': Boolean(oai_settings.enable_web_search),
2020 'request_images': Boolean(oai_settings.request_images),2028 'request_images': Boolean(oai_settings.request_images),
2029 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,
2021 };2030 };
20222031
2023 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {2032 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
@@ -2030,14 +2039,14 @@ async function sendOpenAIRequest(type, messages, signal) {
2030 }2039 }
20312040
2032 // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite2041 // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite
2033 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].includes(oai_settings.chat_completion_source)) {2042 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)) {
2034 await validateReverseProxy();2043 await validateReverseProxy();
2035 generate_data['reverse_proxy'] = oai_settings.reverse_proxy;2044 generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
2036 generate_data['proxy_password'] = oai_settings.proxy_password;2045 generate_data['proxy_password'] = oai_settings.proxy_password;
2037 }2046 }
20382047
2039 // Add logprobs request (currently OpenAI only, max 5 on their side)2048 // Add logprobs request (currently OpenAI only, max 5 on their side)
2040 if (useLogprobs && (isOAI || isCustom || isDeepSeek)) {2049 if (useLogprobs && (isOAI || isCustom || isDeepSeek || isXAI)) {
2041 generate_data['logprobs'] = 5;2050 generate_data['logprobs'] = 5;
2042 }2051 }
20432052
@@ -2048,7 +2057,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2048 delete generate_data.stop;2057 delete generate_data.stop;
2049 delete generate_data.logprobs;2058 delete generate_data.logprobs;
2050 }2059 }
2051 if (isOAI && oai_settings.openai_model.includes('gpt-4.5-preview') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5-preview')) {2060 if (isOAI && oai_settings.openai_model.includes('gpt-4.5') || isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5')) {
2052 delete generate_data.logprobs;2061 delete generate_data.logprobs;
2053 }2062 }
20542063
@@ -2098,7 +2107,6 @@ async function sendOpenAIRequest(type, messages, signal) {
2098 generate_data['custom_include_body'] = oai_settings.custom_include_body;2107 generate_data['custom_include_body'] = oai_settings.custom_include_body;
2099 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;2108 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;
2100 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;2109 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;
2101 generate_data['custom_prompt_post_processing'] = oai_settings.custom_prompt_post_processing;
2102 }2110 }
21032111
2104 if (isCohere) {2112 if (isCohere) {
@@ -2157,29 +2165,42 @@ async function sendOpenAIRequest(type, messages, signal) {
2157 }2165 }
2158 }2166 }
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 }
2162 }2177 }
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';
2168 }2181 }
2169 });2182
2183 if (isOAI && /^(o1|o3|o4)/.test(oai_settings.openai_model)) {
2170 generate_data.max_completion_tokens = generate_data.max_tokens;2184 generate_data.max_completion_tokens = generate_data.max_tokens;
2171 delete generate_data.max_tokens;2185 delete generate_data.max_tokens;
2172 delete generate_data.logprobs;2186 delete generate_data.logprobs;
2173 delete generate_data.top_logprobs;2187 delete generate_data.top_logprobs;
2174 delete generate_data.n;2188 delete generate_data.stop;
2189 delete generate_data.logit_bias;
2175 delete generate_data.temperature;2190 delete generate_data.temperature;
2176 delete generate_data.top_p;2191 delete generate_data.top_p;
2177 delete generate_data.frequency_penalty;2192 delete generate_data.frequency_penalty;
2178 delete generate_data.presence_penalty;2193 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;
2179 delete generate_data.tools;2201 delete generate_data.tools;
2180 delete generate_data.tool_choice;2202 delete generate_data.tool_choice;
2181 delete generate_data.stop;2203 }
2182 delete generate_data.logit_bias;
2183 }2204 }
21842205
2185 await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);2206 await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
@@ -2215,7 +2236,8 @@ async function sendOpenAIRequest(type, messages, signal) {
22152236
2216 if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {2237 if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
2217 const swipeIndex = parsed.choices[0].index - 1;2238 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 });
2219 } else {2241 } else {
2220 text += getStreamingReply(parsed, state);2242 text += getStreamingReply(parsed, state);
2221 }2243 }
@@ -2253,37 +2275,48 @@ async function sendOpenAIRequest(type, messages, signal) {
2253 * Extracts the reply from the response data from a chat completions-like source2275 * Extracts the reply from the response data from a chat completions-like source
2254 * @param {object} data Response data from the chat completions-like source2276 * @param {object} data Response data from the chat completions-like source
2255 * @param {object} state Additional state to keep track of2277 * @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
2256 * @returns {string} The reply extracted from the response data2281 * @returns {string} The reply extracted from the response data
2257 */2282 */
2258function getStreamingReply(data, state) {2283export 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) {
2261 state.reasoning += data?.delta?.thinking || '';2289 state.reasoning += data?.delta?.thinking || '';
2262 }2290 }
2263 return data?.delta?.text || '';2291 return data?.delta?.text || '';
2264 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {2292 } else if (chat_completion_source === chat_completion_sources.MAKERSUITE) {
2265 const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData;2293 const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData;
2266 if (inlineData) {2294 if (inlineData) {
2267 state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`;2295 state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`;
2268 }2296 }
2269 if (oai_settings.show_thoughts) {2297 if (show_thoughts) {
2270 state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');2298 state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');
2271 }2299 }
2272 return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';2300 return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';
2273 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {2301 } else if (chat_completion_source === chat_completion_sources.COHERE) {
2274 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';2302 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
2275 } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {2303 } else if (chat_completion_source === chat_completion_sources.DEEPSEEK) {
2276 if (oai_settings.show_thoughts) {2304 if (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) {
2277 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');2310 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
2278 }2311 }
2279 return data.choices?.[0]?.delta?.content || '';2312 return data.choices?.[0]?.delta?.content || '';
2280 } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) {2313 } else if (chat_completion_source === chat_completion_sources.OPENROUTER) {
2281 if (oai_settings.show_thoughts) {2314 if (show_thoughts) {
2282 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');2315 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
2283 }2316 }
2284 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2317 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2285 } else if (oai_settings.chat_completion_source === chat_completion_sources.CUSTOM) {2318 } else if (chat_completion_source === chat_completion_sources.CUSTOM) {
2286 if (oai_settings.show_thoughts) {2319 if (show_thoughts) {
2287 state.reasoning +=2320 state.reasoning +=
2288 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??2321 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
2289 data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning ??2322 data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning ??
@@ -2309,6 +2342,7 @@ function parseChatCompletionLogprobs(data) {
2309 switch (oai_settings.chat_completion_source) {2342 switch (oai_settings.chat_completion_source) {
2310 case chat_completion_sources.OPENAI:2343 case chat_completion_sources.OPENAI:
2311 case chat_completion_sources.DEEPSEEK:2344 case chat_completion_sources.DEEPSEEK:
2345 case chat_completion_sources.XAI:
2312 case chat_completion_sources.CUSTOM:2346 case chat_completion_sources.CUSTOM:
2313 if (!data.choices?.length) {2347 if (!data.choices?.length) {
2314 return null;2348 return null;
@@ -3229,8 +3263,8 @@ function loadOpenAISettings(data, settings) {
3229 oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;3263 oai_settings.groq_model = settings.groq_model ?? default_settings.groq_model;
3230 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;3264 oai_settings.nanogpt_model = settings.nanogpt_model ?? default_settings.nanogpt_model;
3231 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;3265 oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
3232 oai_settings.blockentropy_model = settings.blockentropy_model ?? default_settings.blockentropy_model;
3233 oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;3266 oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
3267 oai_settings.xai_model = settings.xai_model ?? default_settings.xai_model;
3234 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;3268 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
3235 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;3269 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
3236 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;3270 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@@ -3316,7 +3350,8 @@ function loadOpenAISettings(data, settings) {
3316 $('#model_deepseek_select').val(oai_settings.deepseek_model);3350 $('#model_deepseek_select').val(oai_settings.deepseek_model);
3317 $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);3351 $(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
3318 $('#model_01ai_select').val(oai_settings.zerooneai_model);3352 $('#model_01ai_select').val(oai_settings.zerooneai_model);
3319 $('#model_blockentropy_select').val(oai_settings.blockentropy_model);3353 $('#model_xai_select').val(oai_settings.xai_model);
3354 $(`#model_xai_select option[value="${oai_settings.xai_model}"`).attr('selected', true);
3320 $('#custom_model_id').val(oai_settings.custom_model);3355 $('#custom_model_id').val(oai_settings.custom_model);
3321 $('#custom_api_url_text').val(oai_settings.custom_url);3356 $('#custom_api_url_text').val(oai_settings.custom_url);
3322 $('#openai_max_context').val(oai_settings.openai_max_context);3357 $('#openai_max_context').val(oai_settings.openai_max_context);
@@ -3476,7 +3511,7 @@ async function getStatusOpen() {
3476 let status;3511 let status;
34773512
3478 if ('ai' in window) {3513 if ('ai' in window) {
3479 status = 'Valid';3514 status = t`Valid`;
3480 }3515 }
3481 else {3516 else {
3482 showWindowExtensionError();3517 showWindowExtensionError();
@@ -3513,7 +3548,7 @@ async function getStatusOpen() {
3513 chat_completion_source: oai_settings.chat_completion_source,3548 chat_completion_source: oai_settings.chat_completion_source,
3514 };3549 };
35153550
3516 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].includes(oai_settings.chat_completion_source)) {3551 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)) {
3517 await validateReverseProxy();3552 await validateReverseProxy();
3518 }3553 }
35193554
@@ -3525,7 +3560,7 @@ async function getStatusOpen() {
35253560
3526 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;3561 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
3527 if (canBypass) {3562 if (canBypass) {
3528 setOnlineStatus('Status check bypassed');3563 setOnlineStatus(t`Status check bypassed`);
3529 }3564 }
35303565
3531 try {3566 try {
@@ -3547,7 +3582,7 @@ async function getStatusOpen() {
3547 saveModelList(responseData.data);3582 saveModelList(responseData.data);
3548 }3583 }
3549 if (!('error' in responseData)) {3584 if (!('error' in responseData)) {
3550 setOnlineStatus('Valid');3585 setOnlineStatus(t`Valid`);
3551 }3586 }
3552 } catch (error) {3587 } catch (error) {
3553 console.error(error);3588 console.error(error);
@@ -3596,7 +3631,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
3596 perplexity_model: settings.perplexity_model,3631 perplexity_model: settings.perplexity_model,
3597 groq_model: settings.groq_model,3632 groq_model: settings.groq_model,
3598 zerooneai_model: settings.zerooneai_model,3633 zerooneai_model: settings.zerooneai_model,
3599 blockentropy_model: settings.blockentropy_model,
3600 custom_model: settings.custom_model,3634 custom_model: settings.custom_model,
3601 custom_url: settings.custom_url,3635 custom_url: settings.custom_url,
3602 custom_include_body: settings.custom_include_body,3636 custom_include_body: settings.custom_include_body,
@@ -4094,9 +4128,15 @@ function getMaxContextOpenAI(value) {
4094 if (oai_settings.max_context_unlocked) {4128 if (oai_settings.max_context_unlocked) {
4095 return unlocked_max;4129 return unlocked_max;
4096 }4130 }
4097 else if (value.startsWith('o1') || value.startsWith('o3')) {4131 else if (value.includes('gpt-4.1')) {
4132 return max_1mil;
4133 }
4134 else if (value.startsWith('o1')) {
4098 return max_128k;4135 return max_128k;
4099 }4136 }
4137 else if (value.startsWith('o4') || value.startsWith('o3')) {
4138 return max_200k;
4139 }
4100 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')) {4140 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')) {
4101 return max_128k;4141 return max_128k;
4102 }4142 }
@@ -4168,6 +4208,80 @@ function getMaxContextWindowAI(value) {
4168}4208}
41694209
4170/**4210/**
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 */
4216function 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/**
4171 * Get the maximum context size for the Groq model4285 * Get the maximum context size for the Groq model
4172 * @param {string} model Model identifier4286 * @param {string} model Model identifier
4173 * @param {boolean} isUnlocked Whether context limits are unlocked4287 * @param {boolean} isUnlocked Whether context limits are unlocked
@@ -4195,6 +4309,9 @@ function getGroqMaxContext(model, isUnlocked) {
4195 'qwen-2.5-32b': max_128k,4309 'qwen-2.5-32b': max_128k,
4196 'deepseek-r1-distill-qwen-32b': max_128k,4310 'deepseek-r1-distill-qwen-32b': max_128k,
4197 'deepseek-r1-distill-llama-70b-specdec': max_128k,4311 '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,
4198 };4315 };
41994316
4200 // Return context size if model found, otherwise default to 128k4317 // Return context size if model found, otherwise default to 128k
@@ -4305,18 +4422,17 @@ async function onModelChange() {
4305 oai_settings.zerooneai_model = value;4422 oai_settings.zerooneai_model = value;
4306 }4423 }
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
4314 if (value && $(this).is('#model_custom_select')) {4425 if (value && $(this).is('#model_custom_select')) {
4315 console.log('Custom model changed to', value);4426 console.log('Custom model changed to', value);
4316 oai_settings.custom_model = value;4427 oai_settings.custom_model = value;
4317 $('#custom_model_id').val(value).trigger('input');4428 $('#custom_model_id').val(value).trigger('input');
4318 }4429 }
43194430
4431 if ($(this).is('#model_xai_select')) {
4432 console.log('XAI model changed to', value);
4433 oai_settings.xai_model = value;
4434 }
4435
4320 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {4436 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
4321 if (oai_settings.max_context_unlocked) {4437 if (oai_settings.max_context_unlocked) {
4322 $('#openai_max_context').attr('max', unlocked_max);4438 $('#openai_max_context').attr('max', unlocked_max);
@@ -4335,7 +4451,7 @@ async function onModelChange() {
4335 $('#openai_max_context').attr('max', max_32k);4451 $('#openai_max_context').attr('max', max_32k);
4336 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) {4452 } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) {
4337 $('#openai_max_context').attr('max', max_2mil);4453 $('#openai_max_context').attr('max', max_2mil);
4338 } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash')) {4454 } 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')) {
4339 $('#openai_max_context').attr('max', max_1mil);4455 $('#openai_max_context').attr('max', max_1mil);
4340 } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') {4456 } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') {
4341 $('#openai_max_context').attr('max', max_32k);4457 $('#openai_max_context').attr('max', max_32k);
@@ -4433,27 +4549,10 @@ async function onModelChange() {
4433 }4549 }
44344550
4435 if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI) {4551 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);
4437 $('#openai_max_context').attr('max', unlocked_max);4553 $('#openai_max_context').attr('max', maxContext);
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 }
4453 oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));4554 oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
4454 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');4555 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
4455
4456 //mistral also caps temp at 1.0
4457 oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);4556 oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
4458 $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');4557 $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
4459 }4558 }
@@ -4560,29 +4659,6 @@ async function onModelChange() {
4560 oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);4659 oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
4561 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');4660 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4562 }4661 }
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
4587 if (oai_settings.chat_completion_source === chat_completion_sources.NANOGPT) {4663 if (oai_settings.chat_completion_source === chat_completion_sources.NANOGPT) {
4588 if (oai_settings.max_context_unlocked) {4664 if (oai_settings.max_context_unlocked) {
@@ -4612,6 +4688,22 @@ async function onModelChange() {
4612 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');4688 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4613 }4689 }
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
4615 if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {4707 if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
4616 oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);4708 oai_settings.pres_pen_openai = Math.min(Math.max(0, oai_settings.pres_pen_openai), 1);
4617 $('#pres_pen_openai').attr('max', 1).attr('min', 0).val(oai_settings.pres_pen_openai).trigger('input');4709 $('#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) {
4849 return;4941 return;
4850 }4942 }
4851 }4943 }
4852 if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
4853 const api_key_blockentropy = String($('#api_key_blockentropy').val()).trim();
48544944
4855 if (api_key_blockentropy.length) {4945 if (oai_settings.chat_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);
4857 }4950 }
48584951
4859 if (!secret_state[SECRET_KEYS.BLOCKENTROPY]) {4952 if (!secret_state[SECRET_KEYS.XAI] && !oai_settings.reverse_proxy) {
4860 console.log('No secret key saved for Block Entropy');4953 console.log('No secret key saved for XAI');
4861 return;4954 return;
4862 }4955 }
4863 }4956 }
@@ -4915,12 +5008,12 @@ function toggleChatCompletionForms() {
4915 else if (oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) {5008 else if (oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) {
4916 $('#model_custom_select').trigger('change');5009 $('#model_custom_select').trigger('change');
4917 }5010 }
4918 else if (oai_settings.chat_completion_source == chat_completion_sources.BLOCKENTROPY) {
4919 $('#model_blockentropy_select').trigger('change');
4920 }
4921 else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {5011 else if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
4922 $('#model_deepseek_select').trigger('change');5012 $('#model_deepseek_select').trigger('change');
4923 }5013 }
5014 else if (oai_settings.chat_completion_source == chat_completion_sources.XAI) {
5015 $('#model_xai_select').trigger('change');
5016 }
4924 $('[data-source]').each(function () {5017 $('[data-source]').each(function () {
4925 const validSources = $(this).data('source').split(',');5018 const validSources = $(this).data('source').split(',');
4926 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));5019 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
@@ -5006,8 +5099,11 @@ export function isImageInliningSupported() {
5006 // gultra just isn't being offered as multimodal, thanks google.5099 // gultra just isn't being offered as multimodal, thanks google.
5007 const visionSupportedModels = [5100 const visionSupportedModels = [
5008 'gpt-4-vision',5101 'gpt-4-vision',
5102 'gemini-2.5-pro-exp-03-25',
5103 'gemini-2.5-pro-preview-03-25',
5009 'gemini-2.0-pro-exp',5104 'gemini-2.0-pro-exp',
5010 'gemini-2.0-pro-exp-02-05',5105 'gemini-2.0-pro-exp-02-05',
5106 'gemini-2.5-flash-preview-04-17',
5011 'gemini-2.0-flash-lite-preview',5107 'gemini-2.0-flash-lite-preview',
5012 'gemini-2.0-flash-lite-preview-02-05',5108 'gemini-2.0-flash-lite-preview-02-05',
5013 'gemini-2.0-flash',5109 'gemini-2.0-flash',
@@ -5047,7 +5143,9 @@ export function isImageInliningSupported() {
5047 'o1-2024-12-17',5143 'o1-2024-12-17',
5048 'chatgpt-4o-latest',5144 'chatgpt-4o-latest',
5049 'yi-vision',5145 'yi-vision',
5050 'pixtral-latest',5146 'mistral-large-pixtral-2411',
5147 'mistral-small-2503',
5148 'mistral-small-latest',
5051 'pixtral-12b-latest',5149 'pixtral-12b-latest',
5052 'pixtral-12b',5150 'pixtral-12b',
5053 'pixtral-12b-2409',5151 'pixtral-12b-2409',
@@ -5055,11 +5153,18 @@ export function isImageInliningSupported() {
5055 'pixtral-large-2411',5153 'pixtral-large-2411',
5056 'c4ai-aya-vision-8b',5154 'c4ai-aya-vision-8b',
5057 'c4ai-aya-vision-32b',5155 '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',
5058 ];5163 ];
50595164
5060 switch (oai_settings.chat_completion_source) {5165 switch (oai_settings.chat_completion_source) {
5061 case chat_completion_sources.OPENAI:5166 case chat_completion_sources.OPENAI:
5062 return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));5167 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'));
5063 case chat_completion_sources.MAKERSUITE:5168 case chat_completion_sources.MAKERSUITE:
5064 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));5169 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
5065 case chat_completion_sources.CLAUDE:5170 case chat_completion_sources.CLAUDE:
@@ -5074,6 +5179,8 @@ export function isImageInliningSupported() {
5074 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));5179 return visionSupportedModels.some(model => oai_settings.mistralai_model.includes(model));
5075 case chat_completion_sources.COHERE:5180 case chat_completion_sources.COHERE:
5076 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));5181 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));
5077 default:5184 default:
5078 return false;5185 return false;
5079 }5186 }
@@ -5614,6 +5721,7 @@ export function initOpenAI() {
56145721
5615 $('#openai_enable_web_search').on('input', function () {5722 $('#openai_enable_web_search').on('input', function () {
5616 oai_settings.enable_web_search = !!$(this).prop('checked');5723 oai_settings.enable_web_search = !!$(this).prop('checked');
5724 calculateOpenRouterCost();
5617 saveSettingsDebounced();5725 saveSettingsDebounced();
5618 });5726 });
56195727
@@ -5669,8 +5777,8 @@ export function initOpenAI() {
5669 $('#model_nanogpt_select').on('change', onModelChange);5777 $('#model_nanogpt_select').on('change', onModelChange);
5670 $('#model_deepseek_select').on('change', onModelChange);5778 $('#model_deepseek_select').on('change', onModelChange);
5671 $('#model_01ai_select').on('change', onModelChange);5779 $('#model_01ai_select').on('change', onModelChange);
5672 $('#model_blockentropy_select').on('change', onModelChange);
5673 $('#model_custom_select').on('change', onModelChange);5780 $('#model_custom_select').on('change', onModelChange);
5781 $('#model_xai_select').on('change', onModelChange);
5674 $('#settings_preset_openai').on('change', onSettingsPresetChange);5782 $('#settings_preset_openai').on('change', onSettingsPresetChange);
5675 $('#new_oai_preset').on('click', onNewPresetClick);5783 $('#new_oai_preset').on('click', onNewPresetClick);
5676 $('#delete_oai_preset').on('click', onDeletePresetClick);5784 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/personas.js+9 -14
@@ -111,6 +111,7 @@ export function setUserAvatar(imgfile, { toastPersonaNameChange = true, navigate
111 reloadUserAvatar();111 reloadUserAvatar();
112 updatePersonaUIStates({ navigateToCurrent: navigateToCurrent });112 updatePersonaUIStates({ navigateToCurrent: navigateToCurrent });
113 selectCurrentPersona({ toastPersonaNameChange: toastPersonaNameChange });113 selectCurrentPersona({ toastPersonaNameChange: toastPersonaNameChange });
114 retriggerFirstMessageOnEmptyChat();
114 saveSettingsDebounced();115 saveSettingsDebounced();
115 $('.zoomed_avatar[forchar]').remove();116 $('.zoomed_avatar[forchar]').remove();
116}117}
@@ -465,7 +466,7 @@ export function initPersona(avatarId, personaName, personaDescription) {
465 * @returns {Promise<boolean>} A promise that resolves to true if the character was converted, false otherwise.466 * @returns {Promise<boolean>} A promise that resolves to true if the character was converted, false otherwise.
466 */467 */
467export async function convertCharacterToPersona(characterId = null) {468export async function convertCharacterToPersona(characterId = null) {
468 if (null === characterId) characterId = this_chid;469 if (null === characterId) characterId = Number(this_chid);
469470
470 const avatarUrl = characters[characterId]?.avatar;471 const avatarUrl = characters[characterId]?.avatar;
471 if (!avatarUrl) {472 if (!avatarUrl) {
@@ -800,7 +801,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
800 chat_metadata['persona'] = user_avatar;801 chat_metadata['persona'] = user_avatar;
801 console.log(`Auto locked persona to ${user_avatar}`);802 console.log(`Auto locked persona to ${user_avatar}`);
802 if (toastPersonaNameChange && power_user.persona_show_notifications) {803 if (toastPersonaNameChange && power_user.persona_show_notifications) {
803 toastr.success(`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);804 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
804 }805 }
805 saveMetadataDebounced();806 saveMetadataDebounced();
806 updatePersonaUIStates();807 updatePersonaUIStates();
@@ -1243,7 +1244,7 @@ function getPersonaStates(avatarId) {
1243 /** @type {PersonaConnection[]} */1244 /** @type {PersonaConnection[]} */
1244 const connections = power_user.persona_descriptions[avatarId]?.connections;1245 const connections = power_user.persona_descriptions[avatarId]?.connections;
1245 const hasCharLock = !!connections?.some(c =>1246 const hasCharLock = !!connections?.some(c =>
1246 (!selected_group && c.type === 'character' && c.id === characters[this_chid]?.avatar)1247 (!selected_group && c.type === 'character' && c.id === characters[Number(this_chid)]?.avatar)
1247 || (selected_group && c.type === 'group' && c.id === selected_group));1248 || (selected_group && c.type === 'group' && c.id === selected_group));
12481249
1249 return {1250 return {
@@ -1481,7 +1482,7 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
1481 * @returns {string[]} - An array of persona keys that are connected to the given character key1482 * @returns {string[]} - An array of persona keys that are connected to the given character key
1482 */1483 */
1483export function getConnectedPersonas(characterKey = undefined) {1484export function getConnectedPersonas(characterKey = undefined) {
1484 characterKey ??= selected_group || characters[this_chid]?.avatar;1485 characterKey ??= selected_group || characters[Number(this_chid)]?.avatar;
1485 const connectedPersonas = Object.entries(power_user.persona_descriptions)1486 const connectedPersonas = Object.entries(power_user.persona_descriptions)
1486 .filter(([_, desc]) => desc.connections?.some(conn => conn.type === 'character' && conn.id === characterKey))1487 .filter(([_, desc]) => desc.connections?.some(conn => conn.type === 'character' && conn.id === characterKey))
1487 .map(([key, _]) => key);1488 .map(([key, _]) => key);
@@ -1513,7 +1514,7 @@ export async function showCharConnections() {
1513 console.log(`Unlocking persona ${personaId} from current character ${name2}`);1514 console.log(`Unlocking persona ${personaId} from current character ${name2}`);
1514 power_user.persona_descriptions[personaId].connections = connections.filter(c => {1515 power_user.persona_descriptions[personaId].connections = connections.filter(c => {
1515 if (menu_type == 'group_edit' && c.type == 'group' && c.id == selected_group) return false;1516 if (menu_type == 'group_edit' && c.type == 'group' && c.id == selected_group) return false;
1516 else if (c.type == 'character' && c.id == characters[this_chid]?.avatar) return false;1517 else if (c.type == 'character' && c.id == characters[Number(this_chid)]?.avatar) return false;
1517 return true;1518 return true;
1518 });1519 });
1519 saveSettingsDebounced();1520 saveSettingsDebounced();
@@ -1545,8 +1546,8 @@ export async function showCharConnections() {
1545export function getCurrentConnectionObj() {1546export function getCurrentConnectionObj() {
1546 if (selected_group)1547 if (selected_group)
1547 return { type: 'group', id: selected_group };1548 return { type: 'group', id: selected_group };
1548 if (characters[this_chid]?.avatar)1549 if (characters[Number(this_chid)]?.avatar)
1549 return { type: 'character', id: characters[this_chid]?.avatar };1550 return { type: 'character', id: characters[Number(this_chid)]?.avatar };
1550 return null;1551 return null;
1551}1552}
15521553
@@ -1664,7 +1665,7 @@ async function syncUserNameToPersona() {
1664 * Only works if only the first message is present, and not in group mode.1665 * Only works if only the first message is present, and not in group mode.
1665 */1666 */
1666export function retriggerFirstMessageOnEmptyChat() {1667export function retriggerFirstMessageOnEmptyChat() {
1667 if (this_chid >= 0 && !selected_group && chat.length === 1) {1668 if (Number(this_chid) >= 0 && !selected_group && chat.length === 1) {
1668 $('#firstmessage_textarea').trigger('input');1669 $('#firstmessage_textarea').trigger('input');
1669 }1670 }
1670}1671}
@@ -1782,7 +1783,6 @@ function setNameCallback({ mode = 'all' }, name) {
1782 if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];1783 if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
1783 if (persona) {1784 if (persona) {
1784 autoSelectPersona(persona);1785 autoSelectPersona(persona);
1785 retriggerFirstMessageOnEmptyChat();
1786 return '';1786 return '';
1787 } else if (mode === 'lookup') {1787 } else if (mode === 'lookup') {
1788 toastr.warning(`Persona ${name} not found`);1788 toastr.warning(`Persona ${name} not found`);
@@ -1793,7 +1793,6 @@ function setNameCallback({ mode = 'all' }, name) {
1793 if (['temp', 'all'].includes(mode)) {1793 if (['temp', 'all'].includes(mode)) {
1794 // Otherwise, set just the name1794 // Otherwise, set just the name
1795 setUserName(name); //this prevented quickReply usage1795 setUserName(name); //this prevented quickReply usage
1796 retriggerFirstMessageOnEmptyChat();
1797 }1796 }
17981797
1799 return '';1798 return '';
@@ -1944,9 +1943,6 @@ export async function initPersonas() {
1944 $(document).on('click', '#user_avatar_block .avatar-container', function () {1943 $(document).on('click', '#user_avatar_block .avatar-container', function () {
1945 const imgfile = $(this).attr('data-avatar-id');1944 const imgfile = $(this).attr('data-avatar-id');
1946 setUserAvatar(imgfile);1945 setUserAvatar(imgfile);
1947
1948 // force firstMes {{user}} update on persona switch
1949 retriggerFirstMessageOnEmptyChat();
1950 });1946 });
19511947
1952 $('#persona_rename_button').on('click', () => renamePersona(user_avatar));1948 $('#persona_rename_button').on('click', () => renamePersona(user_avatar));
@@ -1979,4 +1975,3 @@ export async function initPersonas() {
1979 eventSource.on(event_types.CHAT_CHANGED, loadPersonaForCurrentChat);1975 eventSource.on(event_types.CHAT_CHANGED, loadPersonaForCurrentChat);
1980 switchPersonaGridView();1976 switchPersonaGridView();
1981}1977}
1982
public/scripts/power-user.js+17 -6
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
55import { loadSystemPrompts } from './sysprompt.js';55import { loadSystemPrompts } from './sysprompt.js';
56import { fuzzySearchCategories } from './filters.js';56import { fuzzySearchCategories } from './filters.js';
57import { accountStorage } from './util/AccountStorage.js';57import { accountStorage } from './util/AccountStorage.js';
58import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
5859
59export {60export {
60 loadPowerUserSettings,61 loadPowerUserSettings,
@@ -70,8 +71,8 @@ export {
7071
71export const MAX_CONTEXT_DEFAULT = 8192;72export const MAX_CONTEXT_DEFAULT = 8192;
72export const MAX_RESPONSE_DEFAULT = 2048;73export const MAX_RESPONSE_DEFAULT = 2048;
73const MAX_CONTEXT_UNLOCKED = 200 * 1024;74const MAX_CONTEXT_UNLOCKED = 512 * 1024;
74const MAX_RESPONSE_UNLOCKED = 32 * 1024;75const MAX_RESPONSE_UNLOCKED = 64 * 1024;
75const unlockedMaxContextStep = 512;76const unlockedMaxContextStep = 512;
76const maxContextMin = 512;77const maxContextMin = 512;
77const maxContextStep = 64;78const maxContextStep = 64;
@@ -218,7 +219,9 @@ let power_user = {
218 system_sequence: '',219 system_sequence: '',
219 system_suffix: '',220 system_suffix: '',
220 last_system_sequence: '',221 last_system_sequence: '',
222 first_input_sequence: '',
221 first_output_sequence: '',223 first_output_sequence: '',
224 last_input_sequence: '',
222 last_output_sequence: '',225 last_output_sequence: '',
223 system_sequence_prefix: '',226 system_sequence_prefix: '',
224 system_sequence_suffix: '',227 system_sequence_suffix: '',
@@ -255,6 +258,7 @@ let power_user = {
255 },258 },
256259
257 reasoning: {260 reasoning: {
261 name: DEFAULT_REASONING_TEMPLATE,
258 auto_parse: false,262 auto_parse: false,
259 add_to_prompts: false,263 add_to_prompts: false,
260 auto_expand: false,264 auto_expand: false,
@@ -1622,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
1622 await loadInstructMode(data);1626 await loadInstructMode(data);
1623 await loadContextSettings();1627 await loadContextSettings();
1624 await loadSystemPrompts(data);1628 await loadSystemPrompts(data);
1629 await loadReasoningTemplates(data);
1625 loadMaxContextUnlocked();1630 loadMaxContextUnlocked();
1626 switchWaifuMode();1631 switchWaifuMode();
1627 switchSpoilerMode();1632 switchSpoilerMode();
@@ -1983,15 +1988,21 @@ export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
1983/**1988/**
1984 * Renders a story string template with the given parameters.1989 * Renders a story string template with the given parameters.
1985 * @param {object} params Template parameters.1990 * @param {object} params Template parameters.
1991 * @param {object} [options] Additional options.
1992 * @param {string} [options.customStoryString] Custom story string template.
1993 * @param {InstructSettings} [options.customInstructSettings] Custom instruct settings.
1986 * @returns {string} The rendered story string.1994 * @returns {string} The rendered story string.
1987 */1995 */
1988export function renderStoryString(params) {1996export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
1989 try {1997 try {
1998 const storyString = customStoryString ?? power_user.context.story_string;
1999 const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
2000
1990 // Validate and log possible warnings/errors2001 // Validate and log possible warnings/errors
1991 validateStoryString(power_user.context.story_string, params);2002 validateStoryString(storyString, params);
19922003
1993 // compile the story string template into a function, with no HTML escaping2004 // compile the story string template into a function, with no HTML escaping
1994 const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true });2005 const compiledTemplate = Handlebars.compile(storyString, { noEscape: true });
19952006
1996 // render the story string template with the given params2007 // render the story string template with the given params
1997 let output = compiledTemplate(params);2008 let output = compiledTemplate(params);
@@ -2004,7 +2015,7 @@ export function renderStoryString(params) {
20042015
2005 // add a newline to the end of the story string if it doesn't have one2016 // add a newline to the end of the story string if it doesn't have one
2006 if (output.length > 0 && !output.endsWith('\n')) {2017 if (output.length > 0 && !output.endsWith('\n')) {
2007 if (!power_user.instruct.enabled || power_user.instruct.wrap) {2018 if (!instructSettings.enabled || instructSettings.wrap) {
2008 output += '\n';2019 output += '\n';
2009 }2020 }
2010 }2021 }
public/scripts/preset-manager.js+58 -3
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
21import { instruct_presets } from './instruct-mode.js';21import { instruct_presets } from './instruct-mode.js';
22import { kai_settings } from './kai-settings.js';22import { kai_settings } from './kai-settings.js';
23import { convertNovelPreset } from './nai-settings.js';23import { convertNovelPreset } from './nai-settings.js';
24import { openai_settings, openai_setting_names, oai_settings } from './openai.js';24import { openai_settings, openai_setting_names } from './openai.js';
25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
26import { context_presets, getContextSettings, power_user } from './power-user.js';26import { context_presets, getContextSettings, power_user } from './power-user.js';
27import { SlashCommand } from './slash-commands/SlashCommand.js';27import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -36,8 +36,9 @@ import {
36 textgenerationwebui_presets,36 textgenerationwebui_presets,
37 textgenerationwebui_settings as textgen_settings,37 textgenerationwebui_settings as textgen_settings,
38} from './textgen-settings.js';38} from './textgen-settings.js';
39import { download, parseJsonFile, waitUntilCondition } from './utils.js';39import { download, equalsIgnoreCaseAndAccents, parseJsonFile, waitUntilCondition } from './utils.js';
40import { t } from './i18n.js';40import { t } from './i18n.js';
41import { reasoning_templates } from './reasoning.js';
4142
42const presetManagers = {};43const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168 },169 },
169 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170 },171 },
172 'reasoning': {
173 name: 'Reasoning Formatting',
174 getData: () => {
175 const manager = getPresetManager('reasoning');
176 const name = manager.getSelectedPresetName();
177 return manager.getPresetSettings(name);
178 },
179 setData: (data) => {
180 const manager = getPresetManager('reasoning');
181 const name = data.name;
182 return manager.savePreset(name, data);
183 },
184 isValid: (data) => PresetManager.isPossiblyReasoningData(data),
185 },
171 };186 };
172187
173 static isPossiblyInstructData(data) {188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191 }206 }
192207
208 static isPossiblyReasoningData(data) {
209 const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
210 return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
211 }
212
193 /**213 /**
194 * Imports master settings from JSON data.214 * Imports master settings from JSON data.
195 * @param {object} data Data to import215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228 }248 }
229249
250 // 5. Reasoning Template
251 if (this.isPossiblyReasoningData(data)) {
252 toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
253 return await getPresetManager('reasoning').savePreset(data.name, data);
254 }
255
230 const validSections = [];256 const validSections = [];
231 for (const [key, section] of Object.entries(this.masterSections)) {257 for (const [key, section] of Object.entries(this.masterSections)) {
232 if (key in data && section.isValid(data[key])) {258 if (key in data && section.isValid(data[key])) {
@@ -428,6 +454,9 @@ class PresetManager {
428454
429 async renamePreset(newName) {455 async renamePreset(newName) {
430 const oldName = this.getSelectedPresetName();456 const oldName = this.getSelectedPresetName();
457 if (equalsIgnoreCaseAndAccents(oldName, newName)) {
458 throw new Error('New name must be different from old name');
459 }
431 try {460 try {
432 await this.savePreset(newName);461 await this.savePreset(newName);
433 await this.deletePreset(oldName);462 await this.deletePreset(oldName);
@@ -478,6 +507,10 @@ class PresetManager {
478 presets = system_prompts;507 presets = system_prompts;
479 preset_names = system_prompts.map(x => x.name);508 preset_names = system_prompts.map(x => x.name);
480 break;509 break;
510 case 'reasoning':
511 presets = reasoning_templates;
512 preset_names = reasoning_templates.map(x => x.name);
513 break;
481 default:514 default:
482 console.warn(`Unknown API ID ${api}`);515 console.warn(`Unknown API ID ${api}`);
483 }516 }
@@ -490,7 +523,7 @@ class PresetManager {
490 }523 }
491524
492 isAdvancedFormatting() {525 isAdvancedFormatting() {
493 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';526 return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494 }527 }
495528
496 updateList(name, preset) {529 updateList(name, preset) {
@@ -553,6 +586,11 @@ class PresetManager {
553 sysprompt_preset['name'] = name || power_user.sysprompt.preset;586 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554 return sysprompt_preset;587 return sysprompt_preset;
555 }588 }
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 }
556 default:594 default:
557 console.warn(`Unknown API ID ${apiId}`);595 console.warn(`Unknown API ID ${apiId}`);
558 return {};596 return {};
@@ -599,6 +637,13 @@ class PresetManager {
599 'include_reasoning',637 'include_reasoning',
600 'global_banned_tokens',638 'global_banned_tokens',
601 'send_banned_tokens',639 'send_banned_tokens',
640
641 // Reasoning exclusions
642 'auto_parse',
643 'add_to_prompts',
644 'auto_expand',
645 'show_hidden',
646 'max_additions',
602 ];647 ];
603 const settings = Object.assign({}, getSettingsByApiId(this.apiId));648 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
604649
@@ -850,9 +895,19 @@ export async function initPresetManager() {
850 console.debug(!presetManager.isAdvancedFormatting() ? 'Preset rename cancelled' : 'Template rename cancelled');895 console.debug(!presetManager.isAdvancedFormatting() ? 'Preset rename cancelled' : 'Template rename cancelled');
851 return;896 return;
852 }897 }
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
854 await presetManager.renamePreset(newName);903 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
856 const successToast = !presetManager.isAdvancedFormatting() ? t`Preset renamed` : t`Template renamed`;911 const successToast = !presetManager.isAdvancedFormatting() ? t`Preset renamed` : t`Template renamed`;
857 toastr.success(successToast);912 toastr.success(successToast);
858 });913 });
public/scripts/reasoning.js+199 -23
@@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
7import { MacrosParser } from './macros.js';7import { MacrosParser } from './macros.js';
8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
9import { Popup } from './popup.js';9import { Popup } from './popup.js';
10import { power_user } from './power-user.js';10import { performFuzzySearch, power_user } from './power-user.js';
11import { getPresetManager } from './preset-manager.js';
11import { SlashCommand } from './slash-commands/SlashCommand.js';12import { SlashCommand } from './slash-commands/SlashCommand.js';
12import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';13import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
13import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';14import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
14import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';15import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
15import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';16import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
16import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';17import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
17import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';18import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
19
20/**
21 * @typedef {object} ReasoningTemplate
22 * @property {string} name - The name of the template
23 * @property {string} prefix - Reasoning prefix
24 * @property {string} suffix - Reasoning suffix
25 * @property {string} separator - Reasoning separator
26 */
27
28/**
29 * @type {ReasoningTemplate[]} List of reasoning templates
30 */
31export const reasoning_templates = [];
32
33export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
34
35/**
36 * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
37 * @readonly
38 */
39const UI = {
40 $select: $('#reasoning_select'),
41 $suffix: $('#reasoning_suffix'),
42 $prefix: $('#reasoning_prefix'),
43 $separator: $('#reasoning_separator'),
44 $autoParse: $('#reasoning_auto_parse'),
45 $autoExpand: $('#reasoning_auto_expand'),
46 $showHidden: $('#reasoning_show_hidden'),
47 $addToPrompts: $('#reasoning_add_to_prompts'),
48 $maxAdditions: $('#reasoning_max_additions'),
49};
1850
19/**51/**
20 * Enum representing the type of the reasoning for a message (where it came from)52 * Enum representing the type of the reasoning for a message (where it came from)
@@ -57,21 +89,28 @@ function toggleReasoningAutoExpand() {
57 * @param {object} data Response data89 * @param {object} data Response data
58 * @returns {string} Extracted reasoning90 * @returns {string} Extracted reasoning
59 */91 */
60export function extractReasoningFromData(data) {92export function extractReasoningFromData(data, {
61 switch (main_api) {93 mainApi = null,
94 ignoreShowThoughts = false,
95 textGenType = null,
96 chatCompletionSource = null,
97} = {}) {
98 switch (mainApi ?? main_api) {
62 case 'textgenerationwebui':99 case 'textgenerationwebui':
63 switch (textgenerationwebui_settings.type) {100 switch (textGenType ?? textgenerationwebui_settings.type) {
64 case textgen_types.OPENROUTER:101 case textgen_types.OPENROUTER:
65 return data?.choices?.[0]?.reasoning ?? '';102 return data?.choices?.[0]?.reasoning ?? '';
66 }103 }
67 break;104 break;
68105
69 case 'openai':106 case 'openai':
70 if (!oai_settings.show_thoughts) break;107 if (!ignoreShowThoughts && !oai_settings.show_thoughts) break;
71108
72 switch (oai_settings.chat_completion_source) {109 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
73 case chat_completion_sources.DEEPSEEK:110 case chat_completion_sources.DEEPSEEK:
74 return data?.choices?.[0]?.message?.reasoning_content ?? '';111 return data?.choices?.[0]?.message?.reasoning_content ?? '';
112 case chat_completion_sources.XAI:
113 return data?.choices?.[0]?.message?.reasoning_content ?? '';
75 case chat_completion_sources.OPENROUTER:114 case chat_completion_sources.OPENROUTER:
76 return data?.choices?.[0]?.message?.reasoning ?? '';115 return data?.choices?.[0]?.message?.reasoning ?? '';
77 case chat_completion_sources.MAKERSUITE:116 case chat_completion_sources.MAKERSUITE:
@@ -664,57 +703,102 @@ export class PromptReasoning {
664}703}
665704
666function loadReasoningSettings() {705function loadReasoningSettings() {
667 $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);706 UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
668 $('#reasoning_add_to_prompts').on('change', function () {707 UI.$addToPrompts.on('change', function () {
669 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');708 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
670 saveSettingsDebounced();709 saveSettingsDebounced();
671 });710 });
672711
673 $('#reasoning_prefix').val(power_user.reasoning.prefix);712 UI.$prefix.val(power_user.reasoning.prefix);
674 $('#reasoning_prefix').on('input', function () {713 UI.$prefix.on('input', function () {
675 power_user.reasoning.prefix = String($(this).val());714 power_user.reasoning.prefix = String($(this).val());
676 saveSettingsDebounced();715 saveSettingsDebounced();
677 });716 });
678717
679 $('#reasoning_suffix').val(power_user.reasoning.suffix);718 UI.$suffix.val(power_user.reasoning.suffix);
680 $('#reasoning_suffix').on('input', function () {719 UI.$suffix.on('input', function () {
681 power_user.reasoning.suffix = String($(this).val());720 power_user.reasoning.suffix = String($(this).val());
682 saveSettingsDebounced();721 saveSettingsDebounced();
683 });722 });
684723
685 $('#reasoning_separator').val(power_user.reasoning.separator);724 UI.$separator.val(power_user.reasoning.separator);
686 $('#reasoning_separator').on('input', function () {725 UI.$separator.on('input', function () {
687 power_user.reasoning.separator = String($(this).val());726 power_user.reasoning.separator = String($(this).val());
688 saveSettingsDebounced();727 saveSettingsDebounced();
689 });728 });
690729
691 $('#reasoning_max_additions').val(power_user.reasoning.max_additions);730 UI.$maxAdditions.val(power_user.reasoning.max_additions);
692 $('#reasoning_max_additions').on('input', function () {731 UI.$maxAdditions.on('input', function () {
693 power_user.reasoning.max_additions = Number($(this).val());732 power_user.reasoning.max_additions = Number($(this).val());
694 saveSettingsDebounced();733 saveSettingsDebounced();
695 });734 });
696735
697 $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);736 UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
698 $('#reasoning_auto_parse').on('change', function () {737 UI.$autoParse.on('change', function () {
699 power_user.reasoning.auto_parse = !!$(this).prop('checked');738 power_user.reasoning.auto_parse = !!$(this).prop('checked');
700 saveSettingsDebounced();739 saveSettingsDebounced();
701 });740 });
702741
703 $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);742 UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
704 $('#reasoning_auto_expand').on('change', function () {743 UI.$autoExpand.on('change', function () {
705 power_user.reasoning.auto_expand = !!$(this).prop('checked');744 power_user.reasoning.auto_expand = !!$(this).prop('checked');
706 toggleReasoningAutoExpand();745 toggleReasoningAutoExpand();
707 saveSettingsDebounced();746 saveSettingsDebounced();
708 });747 });
709 toggleReasoningAutoExpand();748 toggleReasoningAutoExpand();
710749
711 $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);750 UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
712 $('#reasoning_show_hidden').on('change', function () {751 UI.$showHidden.on('change', function () {
713 power_user.reasoning.show_hidden = !!$(this).prop('checked');752 power_user.reasoning.show_hidden = !!$(this).prop('checked');
714 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);753 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
715 saveSettingsDebounced();754 saveSettingsDebounced();
716 });755 });
717 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);756 $('#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
778function 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
718}802}
719803
720function registerReasoningSlashCommands() {804function registerReasoningSlashCommands() {
@@ -750,6 +834,12 @@ function registerReasoningSlashCommands() {
750 typeList: ARGUMENT_TYPE.NUMBER,834 typeList: ARGUMENT_TYPE.NUMBER,
751 enumProvider: commonEnumProviders.messages(),835 enumProvider: commonEnumProviders.messages(),
752 }),836 }),
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 }),
753 ],843 ],
754 unnamedArgumentList: [844 unnamedArgumentList: [
755 SlashCommandArgument.fromProps({845 SlashCommandArgument.fromProps({
@@ -774,6 +864,9 @@ function registerReasoningSlashCommands() {
774864
775 closeMessageEditor('reasoning');865 closeMessageEditor('reasoning');
776 updateMessageBlock(messageId, message);866 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', '');
777 return message.extra.reasoning;870 return message.extra.reasoning;
778 },871 },
779 }));872 }));
@@ -848,6 +941,42 @@ function registerReasoningSlashCommands() {
848 : parsedReasoning.reasoning;941 : parsedReasoning.reasoning;
849 },942 },
850 }));943 }));
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 }));
851}980}
852981
853function registerReasoningMacros() {982function registerReasoningMacros() {
@@ -1207,6 +1336,53 @@ function registerReasoningAppEvents() {
1207 }1336 }
1208}1337}
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 */
1345export 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 */
1210export function initReasoning() {1386export function initReasoning() {
1211 loadReasoningSettings();1387 loadReasoningSettings();
1212 setReasoningEventHandlers();1388 setReasoningEventHandlers();
public/scripts/samplerSelect.js+2 -19
@@ -9,6 +9,7 @@ import { power_user } from './power-user.js';
9//import { getSortableDelay, onlyUnique } from './utils.js';9//import { getSortableDelay, onlyUnique } from './utils.js';
10//import { getCfgPrompt } from './cfg-scale.js';10//import { getCfgPrompt } from './cfg-scale.js';
11import { setting_names } from './textgen-settings.js';11import { setting_names } from './textgen-settings.js';
12import { renderTemplateAsync } from './templates.js';
1213
1314
14const TGsamplerNames = setting_names;15const TGsamplerNames = setting_names;
@@ -25,25 +26,7 @@ async function showSamplerSelectPopup() {
25 const html = $(document.createElement('div'));26 const html = $(document.createElement('div'));
26 html.attr('id', 'sampler_view_list')27 html.attr('id', 'sampler_view_list')
27 .addClass('flex-container flexFlowColumn');28 .addClass('flex-container flexFlowColumn');
28 html.append(`29 html.append(await renderTemplateAsync('samplerSelector'));
29 <div class="title_restorable flexFlowColumn alignItemsBaseline">
30 <div class="flex-container justifyCenter">
31 <h3>Sampler Select</h3>
32 <div class="flex-container alignItemsBaseline">
33 <div id="resetSelectedSamplers" class="menu_button menu_button_icon" title="Reset custom sampler selection">
34 <i class="fa-solid fa-recycle"></i>
35 </div>
36 </div>
37 <!--<div class="flex-container alignItemsBaseline">
38 <div class="menu_button menu_button_icon" title="Create a new sampler">
39 <i class="fa-solid fa-plus"></i>
40 <span data-i18n="Create">Create</span>
41 </div>
42 </div>-->
43 </div>
44 <small>Here you can toggle the display of individual samplers. (WIP)</small>
45 </div>
46 <hr>`);
4730
48 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');31 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
49 const APISamplers = await listSamplers(main_api);32 const APISamplers = await listSamplers(main_api);
public/scripts/secrets.js+10 -6
@@ -1,5 +1,6 @@
1import { DOMPurify } from '../lib.js';1import { DOMPurify } from '../lib.js';
2import { callPopup, getRequestHeaders } from '../script.js';2import { callPopup, getRequestHeaders } from '../script.js';
3import { t } from './i18n.js';
34
4export const SECRET_KEYS = {5export const SECRET_KEYS = {
5 HORDE: 'api_key_horde',6 HORDE: 'api_key_horde',
@@ -33,7 +34,6 @@ export const SECRET_KEYS = {
33 ZEROONEAI: 'api_key_01ai',34 ZEROONEAI: 'api_key_01ai',
34 HUGGINGFACE: 'api_key_huggingface',35 HUGGINGFACE: 'api_key_huggingface',
35 STABILITY: 'api_key_stability',36 STABILITY: 'api_key_stability',
36 BLOCKENTROPY: 'api_key_blockentropy',
37 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',37 CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts',
38 NANOGPT: 'api_key_nanogpt',38 NANOGPT: 'api_key_nanogpt',
39 TAVILY: 'api_key_tavily',39 TAVILY: 'api_key_tavily',
@@ -42,6 +42,7 @@ export const SECRET_KEYS = {
42 DEEPSEEK: 'api_key_deepseek',42 DEEPSEEK: 'api_key_deepseek',
43 SERPER: 'api_key_serper',43 SERPER: 'api_key_serper',
44 FALAI: 'api_key_falai',44 FALAI: 'api_key_falai',
45 XAI: 'api_key_xai',
45};46};
4647
47const INPUT_MAP = {48const INPUT_MAP = {
@@ -73,10 +74,10 @@ const INPUT_MAP = {
73 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',74 [SECRET_KEYS.FEATHERLESS]: '#api_key_featherless',
74 [SECRET_KEYS.ZEROONEAI]: '#api_key_01ai',75 [SECRET_KEYS.ZEROONEAI]: '#api_key_01ai',
75 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',76 [SECRET_KEYS.HUGGINGFACE]: '#api_key_huggingface',
76 [SECRET_KEYS.BLOCKENTROPY]: '#api_key_blockentropy',
77 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',77 [SECRET_KEYS.NANOGPT]: '#api_key_nanogpt',
78 [SECRET_KEYS.GENERIC]: '#api_key_generic',78 [SECRET_KEYS.GENERIC]: '#api_key_generic',
79 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',79 [SECRET_KEYS.DEEPSEEK]: '#api_key_deepseek',
80 [SECRET_KEYS.XAI]: '#api_key_xai',
80};81};
8182
82async function clearSecret() {83async function clearSecret() {
@@ -104,7 +105,7 @@ async function viewSecrets() {
104 });105 });
105106
106 if (response.status == 403) {107 if (response.status == 403) {
107 callPopup('<h3>Forbidden</h3><p>To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.</p>', 'text');108 callPopup('<h3>' + t`Forbidden` + '</h3><p>' + t`To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.` + '</p>', 'text');
108 return;109 return;
109 }110 }
110111
@@ -188,14 +189,17 @@ export async function findSecret(key) {
188}189}
189190
190function authorizeOpenRouter() {191function authorizeOpenRouter() {
191 const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(location.origin)}`;192 const redirectUrl = new URL('/callback/openrouter', window.location.origin);
193 const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(redirectUrl.toString())}`;
192 location.href = openRouterUrl;194 location.href = openRouterUrl;
193}195}
194196
195async function checkOpenRouterAuth() {197async function checkOpenRouterAuth() {
196 const params = new URLSearchParams(location.search);198 const params = new URLSearchParams(location.search);
197 if (params.has('code')) {199 const source = params.get('source');
198 const code = params.get('code');200 if (source === 'openrouter') {
201 const query = new URLSearchParams(params.get('query'));
202 const code = query.get('code');
199 try {203 try {
200 const response = await fetch('https://openrouter.ai/api/v1/auth/keys', {204 const response = await fetch('https://openrouter.ai/api/v1/auth/keys', {
201 method: 'POST',205 method: 'POST',
public/scripts/showdown-underscore.js+4 -4
@@ -8,10 +8,10 @@ export const markdownUnderscoreExt = () => {
88
9 return [{9 return [{
10 type: 'output',10 type: 'output',
11 regex: new RegExp('(<code(?:\\s+[^>]*)?>[\\s\\S]*?<\\/code>)|\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'g'),11 regex: new RegExp('(<code(?:\\s+[^>]*)?>[\\s\\S]*?<\\/code>|<style(?:\\s+[^>]*)?>[\\s\\S]*?<\\/style>)|\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'gi'),
12 replace: function(match, codeContent, italicContent) {12 replace: function(match, tagContent, italicContent) {
13 if (codeContent) {13 if (tagContent) {
14 // If it's inside <code> tags, return unchanged14 // If it's inside <code> or <style> tags, return unchanged
15 return match;15 return match;
16 } else if (italicContent) {16 } else if (italicContent) {
17 // If it's an italic group, apply the replacement17 // 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
75import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';75import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
76import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';76import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
77import { accountStorage } from './util/AccountStorage.js';77import { accountStorage } from './util/AccountStorage.js';
78import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
79import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
80import { t } from './i18n.js';
78export {81export {
79 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,82 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
80};83};
@@ -224,6 +227,7 @@ export function initDefaultSlashCommands() {
224 }));227 }));
225 SlashCommandParser.addCommandObject(SlashCommand.fromProps({228 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
226 name: 'sendas',229 name: 'sendas',
230 rawQuotes: true,
227 callback: sendMessageAs,231 callback: sendMessageAs,
228 returns: 'Optionally the text of the sent message, if specified in the "return" argument',232 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
229 namedArgumentList: [233 namedArgumentList: [
@@ -290,6 +294,7 @@ export function initDefaultSlashCommands() {
290 }));294 }));
291 SlashCommandParser.addCommandObject(SlashCommand.fromProps({295 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
292 name: 'sys',296 name: 'sys',
297 rawQuotes: true,
293 callback: sendNarratorMessage,298 callback: sendNarratorMessage,
294 aliases: ['nar'],299 aliases: ['nar'],
295 returns: 'Optionally the text of the sent message, if specified in the "return" argument',300 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
@@ -354,6 +359,7 @@ export function initDefaultSlashCommands() {
354 }));359 }));
355 SlashCommandParser.addCommandObject(SlashCommand.fromProps({360 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
356 name: 'comment',361 name: 'comment',
362 rawQuotes: true,
357 callback: sendCommentMessage,363 callback: sendCommentMessage,
358 returns: 'Optionally the text of the sent message, if specified in the "return" argument',364 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
359 namedArgumentList: [365 namedArgumentList: [
@@ -571,6 +577,7 @@ export function initDefaultSlashCommands() {
571 }));577 }));
572 SlashCommandParser.addCommandObject(SlashCommand.fromProps({578 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
573 name: 'send',579 name: 'send',
580 rawQuotes: true,
574 callback: sendUserMessageCallback,581 callback: sendUserMessageCallback,
575 returns: 'Optionally the text of the sent message, if specified in the "return" argument',582 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
576 namedArgumentList: [583 namedArgumentList: [
@@ -933,6 +940,7 @@ export function initDefaultSlashCommands() {
933 }));940 }));
934 SlashCommandParser.addCommandObject(SlashCommand.fromProps({941 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
935 name: 'echo',942 name: 'echo',
943 rawQuotes: true,
936 callback: echoCallback,944 callback: echoCallback,
937 returns: 'the text',945 returns: 'the text',
938 namedArgumentList: [946 namedArgumentList: [
@@ -1554,16 +1562,28 @@ export function initDefaultSlashCommands() {
1554 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1562 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1555 name: 'buttons',1563 name: 'buttons',
1556 callback: buttonsCallback,1564 callback: buttonsCallback,
1557 returns: 'clicked button label',1565 returns: 'clicked button label (or array of labels if multiple is enabled)',
1558 namedArgumentList: [1566 namedArgumentList: [
1559 new SlashCommandNamedArgument(1567 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 }),
1562 ],1580 ],
1563 unnamedArgumentList: [1581 unnamedArgumentList: [
1564 new SlashCommandArgument(1582 SlashCommandArgument.fromProps({
1565 'text', [ARGUMENT_TYPE.STRING], true,1583 description: 'text',
1566 ),1584 typeList: [ARGUMENT_TYPE.STRING],
1585 isRequired: true,
1586 }),
1567 ],1587 ],
1568 helpString: `1588 helpString: `
1569 <div>1589 <div>
@@ -2375,6 +2395,18 @@ async function trimTokensCallback(arg, value) {
2375 }2395 }
2376}2396}
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 */
2378async function buttonsCallback(args, text) {2410async function buttonsCallback(args, text) {
2379 try {2411 try {
2380 /** @type {string[]} */2412 /** @type {string[]} */
@@ -2385,6 +2417,10 @@ async function buttonsCallback(args, text) {
2385 return '';2417 return '';
2386 }2418 }
23872419
2420 /** @type {Set<number>} */
2421 const multipleToggledState = new Set();
2422 const multiple = isTrueBoolean(args?.multiple);
2423
2388 // Map custom buttons to results. Start at 2 because 1 and 0 are reserved for ok and cancel2424 // Map custom buttons to results. Start at 2 because 1 and 0 are reserved for ok and cancel
2389 const resultToButtonMap = new Map(buttons.map((button, index) => [index + 2, button]));2425 const resultToButtonMap = new Map(buttons.map((button, index) => [index + 2, button]));
23902426
@@ -2402,11 +2438,24 @@ async function buttonsCallback(args, text) {
24022438
2403 for (const [result, button] of resultToButtonMap) {2439 for (const [result, button] of resultToButtonMap) {
2404 const buttonElement = document.createElement('div');2440 const buttonElement = document.createElement('div');
2405 buttonElement.classList.add('menu_button', 'result-control', 'wide100p');2441 buttonElement.classList.add('menu_button', 'wide100p');
2406 buttonElement.dataset.result = String(result);2442
2443 if (multiple) {
2444 buttonElement.classList.add('toggleable');
2445 buttonElement.dataset.toggleValue = String(result);
2407 buttonElement.addEventListener('click', async () => {2446 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 }
2409 });2453 });
2454 } else {
2455 buttonElement.classList.add('result-control');
2456 buttonElement.dataset.result = String(result);
2457 }
2458
2410 buttonElement.innerText = button;2459 buttonElement.innerText = button;
2411 buttonContainer.appendChild(buttonElement);2460 buttonContainer.appendChild(buttonElement);
2412 }2461 }
@@ -2422,10 +2471,19 @@ async function buttonsCallback(args, text) {
2422 popupContainer.style.flexDirection = 'column';2471 popupContainer.style.flexDirection = 'column';
2423 popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup2472 popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup
24242473
2425 popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: 'Cancel', allowVerticalScrolling: true });2474 popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: multiple ? t`Ok` : t`Cancel`, allowVerticalScrolling: true });
2426 popup.show()2475 popup.show()
2427 .then((result => resolve(typeof result === 'number' ? resultToButtonMap.get(result) ?? '' : '')))2476 .then((result => resolve(getResult(result))))
2428 .catch(() => resolve(''));2477 .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 }
2429 });2487 });
2430 } catch {2488 } catch {
2431 return '';2489 return '';
@@ -3883,8 +3941,8 @@ function getModelOptions(quiet) {
3883 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },3941 { id: 'model_groq_select', api: 'openai', type: chat_completion_sources.GROQ },
3884 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },3942 { id: 'model_nanogpt_select', api: 'openai', type: chat_completion_sources.NANOGPT },
3885 { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },3943 { id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
3886 { id: 'model_blockentropy_select', api: 'openai', type: chat_completion_sources.BLOCKENTROPY },
3887 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },3944 { id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
3945 { id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI },
3888 { id: 'model_novel_select', api: 'novel', type: null },3946 { id: 'model_novel_select', api: 'novel', type: null },
3889 { id: 'horde_model', api: 'koboldhorde', type: null },3947 { id: 'horde_model', api: 'koboldhorde', type: null },
3890 ];3948 ];
@@ -4345,7 +4403,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
4345 * @prop {boolean} [handleParserErrors] (true) Whether to handle parser errors (show toast on error) or throw.4403 * @prop {boolean} [handleParserErrors] (true) Whether to handle parser errors (show toast on error) or throw.
4346 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.4404 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
4347 * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw4405 * @prop {boolean} [handleExecutionErrors] (false) Whether to handle execution errors (show toast on error) or throw
4348 * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply4406 * @prop {import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
4349 * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution4407 * @prop {SlashCommandAbortController} [abortController] (null) Controller used to abort or pause command execution
4350 * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution4408 * @prop {SlashCommandDebugController} [debugController] (null) Controller used to control debug execution
4351 * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events4409 * @prop {(done:number, total:number)=>void} [onProgress] (null) Callback to handle progress events
@@ -4355,7 +4413,7 @@ const clearCommandProgressDebounced = debounce(clearCommandProgress);
4355/**4413/**
4356 * @typedef ExecuteSlashCommandsOnChatInputOptions4414 * @typedef ExecuteSlashCommandsOnChatInputOptions
4357 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.4415 * @prop {SlashCommandScope} [scope] (null) The scope to be used when executing the commands.
4358 * @prop {{[id:PARSER_FLAG]:boolean}} [parserFlags] (null) Parser flags to apply4416 * @prop {import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] (null) Parser flags to apply
4359 * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea4417 * @prop {boolean} [clearChatInput] (false) Whether to clear the chat input textarea
4360 * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)4418 * @prop {string} [source] (null) String indicating where the code come from (e.g., QR name)
4361 */4419 */
public/scripts/slash-commands/SlashCommand.js+13 -5
@@ -1,18 +1,15 @@
1import { hljs } from '../../lib.js';1import { hljs } from '../../lib.js';
2import { t } from '../i18n.js';
2import { SlashCommandAbortController } from './SlashCommandAbortController.js';3import { SlashCommandAbortController } from './SlashCommandAbortController.js';
3import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';4import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
4import { SlashCommandClosure } from './SlashCommandClosure.js';5import { SlashCommandClosure } from './SlashCommandClosure.js';
5import { SlashCommandDebugController } from './SlashCommandDebugController.js';6import { SlashCommandDebugController } from './SlashCommandDebugController.js';
6import { PARSER_FLAG } from './SlashCommandParser.js';
7import { SlashCommandScope } from './SlashCommandScope.js';7import { SlashCommandScope } from './SlashCommandScope.js';
88
9
10
11
12/**9/**
13 * @typedef {{10 * @typedef {{
14 * _scope:SlashCommandScope,11 * _scope:SlashCommandScope,
15 * _parserFlags:{[id:PARSER_FLAG]:boolean},12 * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
16 * _abortController:SlashCommandAbortController,13 * _abortController:SlashCommandAbortController,
17 * _debugController:SlashCommandDebugController,14 * _debugController:SlashCommandDebugController,
18 * _hasUnnamedArgument:boolean,15 * _hasUnnamedArgument:boolean,
@@ -40,6 +37,7 @@ export class SlashCommand {
40 * @param {string} [props.helpString]37 * @param {string} [props.helpString]
41 * @param {boolean} [props.splitUnnamedArgument]38 * @param {boolean} [props.splitUnnamedArgument]
42 * @param {Number} [props.splitUnnamedArgumentCount]39 * @param {Number} [props.splitUnnamedArgumentCount]
40 * @param {boolean} [props.rawQuotes] If set to true, does not remove wrapping quotes from the unnamed argument.
43 * @param {string[]} [props.aliases]41 * @param {string[]} [props.aliases]
44 * @param {string} [props.returns]42 * @param {string} [props.returns]
45 * @param {SlashCommandNamedArgument[]} [props.namedArgumentList]43 * @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
@@ -58,6 +56,7 @@ export class SlashCommand {
58 /**@type {string}*/ helpString;56 /**@type {string}*/ helpString;
59 /**@type {boolean}*/ splitUnnamedArgument = false;57 /**@type {boolean}*/ splitUnnamedArgument = false;
60 /**@type {Number}*/ splitUnnamedArgumentCount;58 /**@type {Number}*/ splitUnnamedArgumentCount;
59 /** @type {boolean} */ rawQuotes = false;
61 /**@type {string[]}*/ aliases = [];60 /**@type {string[]}*/ aliases = [];
62 /**@type {string}*/ returns;61 /**@type {string}*/ returns;
63 /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];62 /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
@@ -262,6 +261,15 @@ export class SlashCommand {
262 ].filter(it=>it).join('\n');261 ].filter(it=>it).join('\n');
263 head.append(src);262 head.append(src);
264 }263 }
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 }
265 specs.append(head);273 specs.append(head);
266 }274 }
267 const body = document.createElement('div'); {275 const body = document.createElement('div'); {
public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js+0 -3
@@ -1,9 +1,6 @@
1import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';1import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
2import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';
3import { AutoCompleteSecondaryNameResult } from '../autocomplete/AutoCompleteSecondaryNameResult.js';2import { AutoCompleteSecondaryNameResult } from '../autocomplete/AutoCompleteSecondaryNameResult.js';
4import { SlashCommand } from './SlashCommand.js';3import { SlashCommand } from './SlashCommand.js';
5import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
6import { SlashCommandClosure } from './SlashCommandClosure.js';
7import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAutoCompleteOption.js';4import { SlashCommandCommandAutoCompleteOption } from './SlashCommandCommandAutoCompleteOption.js';
8import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';5import { SlashCommandEnumAutoCompleteOption } from './SlashCommandEnumAutoCompleteOption.js';
9import { SlashCommandExecutor } from './SlashCommandExecutor.js';6import { SlashCommandExecutor } from './SlashCommandExecutor.js';
public/scripts/slash-commands/SlashCommandClosure.js+0 -3
@@ -2,7 +2,6 @@ import { substituteParams } from '../../script.js';
2import { delay, escapeRegex, uuidv4 } from '../utils.js';2import { delay, escapeRegex, uuidv4 } from '../utils.js';
3import { SlashCommand } from './SlashCommand.js';3import { SlashCommand } from './SlashCommand.js';
4import { SlashCommandAbortController } from './SlashCommandAbortController.js';4import { SlashCommandAbortController } from './SlashCommandAbortController.js';
5import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
6import { SlashCommandBreak } from './SlashCommandBreak.js';5import { SlashCommandBreak } from './SlashCommandBreak.js';
7import { SlashCommandBreakController } from './SlashCommandBreakController.js';6import { SlashCommandBreakController } from './SlashCommandBreakController.js';
8import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';7import { SlashCommandBreakPoint } from './SlashCommandBreakPoint.js';
@@ -16,9 +15,7 @@ import { SlashCommandScope } from './SlashCommandScope.js';
16export class SlashCommandClosure {15export class SlashCommandClosure {
17 /** @type {SlashCommandScope} */ scope;16 /** @type {SlashCommandScope} */ scope;
18 /** @type {boolean} */ executeNow = false;17 /** @type {boolean} */ executeNow = false;
19 // @ts-ignore
20 /** @type {SlashCommandNamedArgumentAssignment[]} */ argumentList = [];18 /** @type {SlashCommandNamedArgumentAssignment[]} */ argumentList = [];
21 // @ts-ignore
22 /** @type {SlashCommandNamedArgumentAssignment[]} */ providedArgumentList = [];19 /** @type {SlashCommandNamedArgumentAssignment[]} */ providedArgumentList = [];
23 /** @type {SlashCommandExecutor[]} */ executorList = [];20 /** @type {SlashCommandExecutor[]} */ executorList = [];
24 /** @type {SlashCommandAbortController} */ abortController;21 /** @type {SlashCommandAbortController} */ abortController;
public/scripts/slash-commands/SlashCommandDebugController.js+0 -0
public/scripts/slash-commands/SlashCommandEnumValue.js+0 -4
@@ -1,7 +1,3 @@
1import { SlashCommandExecutor } from './SlashCommandExecutor.js';
2import { SlashCommandScope } from './SlashCommandScope.js';
3
4
5/**1/**
6 * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType2 * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType
7 */3 */
public/scripts/slash-commands/SlashCommandExecutor.js+1 -6
@@ -1,11 +1,7 @@
1// eslint-disable-next-line no-unused-vars
2import { uuidv4 } from '../utils.js';1import { uuidv4 } from '../utils.js';
3import { SlashCommand } from './SlashCommand.js';2import { SlashCommand } from './SlashCommand.js';
4// eslint-disable-next-line no-unused-vars
5import { SlashCommandClosure } from './SlashCommandClosure.js';3import { SlashCommandClosure } from './SlashCommandClosure.js';
6import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';4import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
7// eslint-disable-next-line no-unused-vars
8import { PARSER_FLAG } from './SlashCommandParser.js';
9import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';5import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
106
11export class SlashCommandExecutor {7export class SlashCommandExecutor {
@@ -29,10 +25,9 @@ export class SlashCommandExecutor {
29 }25 }
30 }26 }
31 /** @type {SlashCommand} */ command;27 /** @type {SlashCommand} */ command;
32 // @ts-ignore
33 /** @type {SlashCommandNamedArgumentAssignment[]} */ namedArgumentList = [];28 /** @type {SlashCommandNamedArgumentAssignment[]} */ namedArgumentList = [];
34 /** @type {SlashCommandUnnamedArgumentAssignment[]} */ unnamedArgumentList = [];29 /** @type {SlashCommandUnnamedArgumentAssignment[]} */ unnamedArgumentList = [];
35 /**@type {{[id:PARSER_FLAG]:boolean}} */ parserFlags;30 /** @type {import('./SlashCommandParser.js').ParserFlags} */ parserFlags;
3631
37 get commandCount() {32 get commandCount() {
38 return 133 return 1
public/scripts/slash-commands/SlashCommandNamedArgumentAssignment.js+0 -0
public/scripts/slash-commands/SlashCommandNamedArgumentAutoCompleteOption.js+0 -1
@@ -1,7 +1,6 @@
1import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';1import { AutoCompleteOption } from '../autocomplete/AutoCompleteOption.js';
2import { SlashCommand } from './SlashCommand.js';2import { SlashCommand } from './SlashCommand.js';
3import { SlashCommandNamedArgument } from './SlashCommandArgument.js';3import { SlashCommandNamedArgument } from './SlashCommandArgument.js';
4import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
54
6export class SlashCommandNamedArgumentAutoCompleteOption extends AutoCompleteOption {5export class SlashCommandNamedArgumentAutoCompleteOption extends AutoCompleteOption {
7 /** @type {SlashCommandNamedArgument} */ arg;6 /** @type {SlashCommandNamedArgument} */ arg;
public/scripts/slash-commands/SlashCommandParser.js+8 -9
@@ -8,11 +8,9 @@ import { SlashCommandExecutor } from './SlashCommandExecutor.js';
8import { SlashCommandParserError } from './SlashCommandParserError.js';8import { SlashCommandParserError } from './SlashCommandParserError.js';
9import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';9import { AutoCompleteNameResult } from '../autocomplete/AutoCompleteNameResult.js';
10import { SlashCommandQuickReplyAutoCompleteOption } from './SlashCommandQuickReplyAutoCompleteOption.js';10import { SlashCommandQuickReplyAutoCompleteOption } from './SlashCommandQuickReplyAutoCompleteOption.js';
11// eslint-disable-next-line no-unused-vars
12import { SlashCommandScope } from './SlashCommandScope.js';11import { SlashCommandScope } from './SlashCommandScope.js';
13import { SlashCommandVariableAutoCompleteOption } from './SlashCommandVariableAutoCompleteOption.js';12import { SlashCommandVariableAutoCompleteOption } from './SlashCommandVariableAutoCompleteOption.js';
14import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';13import { SlashCommandNamedArgumentAssignment } from './SlashCommandNamedArgumentAssignment.js';
15// eslint-disable-next-line no-unused-vars
16import { SlashCommandAbortController } from './SlashCommandAbortController.js';14import { SlashCommandAbortController } from './SlashCommandAbortController.js';
17import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNameResult.js';15import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNameResult.js';
18import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';16import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js';
@@ -28,15 +26,17 @@ import { t } from '../i18n.js';
28/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */26/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */
29/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */27/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */
3028
31/**@readonly*/29/**
32/**@enum {Number}*/30 * @enum {Number}
31 * @readonly
32 * @typedef {{[id:PARSER_FLAG]:boolean}} ParserFlags
33 */
33export const PARSER_FLAG = {34export const PARSER_FLAG = {
34 'STRICT_ESCAPING': 1,35 'STRICT_ESCAPING': 1,
35 'REPLACE_GETVAR': 2,36 'REPLACE_GETVAR': 2,
36};37};
3738
38export class SlashCommandParser {39export class SlashCommandParser {
39 // @ts-ignore
40 /** @type {Object.<string, SlashCommand>} */ static commands = {};40 /** @type {Object.<string, SlashCommand>} */ static commands = {};
4141
42 /**42 /**
@@ -101,7 +101,6 @@ export class SlashCommandParser {
101 get commands() {101 get commands() {
102 return SlashCommandParser.commands;102 return SlashCommandParser.commands;
103 }103 }
104 // @ts-ignore
105 /** @type {Object.<string, string>} */ helpStrings = {};104 /** @type {Object.<string, string>} */ helpStrings = {};
106 /** @type {boolean} */ verifyCommandNames = true;105 /** @type {boolean} */ verifyCommandNames = true;
107 /** @type {string} */ text;106 /** @type {string} */ text;
@@ -976,7 +975,7 @@ export class SlashCommandParser {
976 cmd.startUnnamedArgs = this.index - (/\s(\s*)$/s.exec(this.behind)?.[1]?.length ?? 0);975 cmd.startUnnamedArgs = this.index - (/\s(\s*)$/s.exec(this.behind)?.[1]?.length ?? 0);
977 cmd.endUnnamedArgs = this.index;976 cmd.endUnnamedArgs = this.index;
978 if (this.testUnnamedArgument()) {977 if (this.testUnnamedArgument()) {
979 cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument, cmd?.command?.splitUnnamedArgumentCount);978 cmd.unnamedArgumentList = this.parseUnnamedArgument(cmd.command?.unnamedArgumentList?.length && cmd?.command?.splitUnnamedArgument, cmd?.command?.splitUnnamedArgumentCount, cmd?.command?.rawQuotes);
980 cmd.endUnnamedArgs = this.index;979 cmd.endUnnamedArgs = this.index;
981 if (cmd.name == 'let') {980 if (cmd.name == 'let') {
982 const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key');981 const keyArg = cmd.namedArgumentList.find(it=>it.name == 'key');
@@ -1036,7 +1035,7 @@ export class SlashCommandParser {
1036 testUnnamedArgumentEnd() {1035 testUnnamedArgumentEnd() {
1037 return this.testCommandEnd();1036 return this.testCommandEnd();
1038 }1037 }
1039 parseUnnamedArgument(split, splitCount = null) {1038 parseUnnamedArgument(split, splitCount = null, rawQuotes = false) {
1040 const wasSplit = split;1039 const wasSplit = split;
1041 /**@type {SlashCommandClosure|String}*/1040 /**@type {SlashCommandClosure|String}*/
1042 let value = this.jumpedEscapeSequence ? this.take() : ''; // take the first, already tested, char if it is an escaped one1041 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 {
1046 /**@type {SlashCommandUnnamedArgumentAssignment}*/1045 /**@type {SlashCommandUnnamedArgumentAssignment}*/
1047 let assignment = new SlashCommandUnnamedArgumentAssignment();1046 let assignment = new SlashCommandUnnamedArgumentAssignment();
1048 assignment.start = this.index;1047 assignment.start = this.index;
1049 if (!split && this.testQuotedValue()) {1048 if (!split && !rawQuotes && this.testQuotedValue()) {
1050 // if the next bit is a quoted value, take the whole value and gather contents as a list1049 // if the next bit is a quoted value, take the whole value and gather contents as a list
1051 assignment.value = this.parseQuotedValue();1050 assignment.value = this.parseQuotedValue();
1052 assignment.end = this.index;1051 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 {
49 clearChat,49 clearChat,
50 unshallowCharacter,50 unshallowCharacter,
51 deleteLastMessage,51 deleteLastMessage,
52 getCharacterCardFields,
52} from '../script.js';53} from '../script.js';
53import {54import {
54 extension_settings,55 extension_settings,
@@ -78,9 +79,11 @@ import { ToolManager } from './tool-calling.js';
78import { accountStorage } from './util/AccountStorage.js';79import { accountStorage } from './util/AccountStorage.js';
79import { timestampToMoment, uuidv4 } from './utils.js';80import { timestampToMoment, uuidv4 } from './utils.js';
80import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';81import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
81import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';82import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, reloadEditor, saveWorldInfo, updateWorldInfoList } from './world-info.js';
82import { ChatCompletionService, TextCompletionService } from './custom-request.js';83import { ChatCompletionService, TextCompletionService } from './custom-request.js';
84import { ConnectionManagerRequestService } from './extensions/shared.js';
83import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';85import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';
86import { IGNORE_SYMBOL } from './constants.js';
8487
85export function getContext() {88export function getContext() {
86 return {89 return {
@@ -188,6 +191,7 @@ export function getContext() {
188 textCompletionSettings: textgenerationwebui_settings,191 textCompletionSettings: textgenerationwebui_settings,
189 powerUserSettings: power_user,192 powerUserSettings: power_user,
190 getCharacters,193 getCharacters,
194 getCharacterCardFields,
191 uuidv4,195 uuidv4,
192 humanizedDateTime,196 humanizedDateTime,
193 updateMessageBlock,197 updateMessageBlock,
@@ -204,8 +208,10 @@ export function getContext() {
204 },208 },
205 loadWorldInfo,209 loadWorldInfo,
206 saveWorldInfo,210 saveWorldInfo,
211 reloadWorldInfoEditor: reloadEditor,
207 updateWorldInfoList,212 updateWorldInfoList,
208 convertCharacterBook,213 convertCharacterBook,
214 getWorldInfoPrompt,
209 CONNECT_API_MAP,215 CONNECT_API_MAP,
210 getTextGenServer,216 getTextGenServer,
211 extractMessageFromData,217 extractMessageFromData,
@@ -215,10 +221,14 @@ export function getContext() {
215 clearChat,221 clearChat,
216 ChatCompletionService,222 ChatCompletionService,
217 TextCompletionService,223 TextCompletionService,
224 ConnectionManagerRequestService,
218 updateReasoningUI,225 updateReasoningUI,
219 parseReasoningFromString,226 parseReasoningFromString,
220 unshallowCharacter,227 unshallowCharacter,
221 unshallowGroupMembers,228 unshallowGroupMembers,
229 symbols: {
230 ignore: IGNORE_SYMBOL,
231 },
222 };232 };
223}233}
224234
public/scripts/tags.js+0 -1
@@ -13,7 +13,6 @@ import {
13 DEFAULT_PRINT_TIMEOUT,13 DEFAULT_PRINT_TIMEOUT,
14 printCharacters,14 printCharacters,
15} from '../script.js';15} from '../script.js';
16// eslint-disable-next-line no-unused-vars
17import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';16import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
1817
19import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';18import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
public/scripts/templates/assistantNote.html+2 -2
@@ -1,9 +1,9 @@
1<div data-type="assistant_note">1<div data-type="assistant_note">
2 <div>2 <div>
3 <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>3 <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
4 <span>Click the button to save it as a file.</span>4 <span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
5 </div>5 </div>
6 <div class="assistant_note_export menu_button menu_button_icon" title="Export as JSONL">6 <div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
7 <i class="fa-solid fa-file-export"></i>7 <i class="fa-solid fa-file-export"></i>
8 </div>8 </div>
9</div>9</div>
public/scripts/templates/macros.html+2 -1
@@ -20,7 +20,8 @@
20 <li><tt>&lcub;&lcub;summary&rcub;&rcub;</tt> – <span data-i18n="help_macros_summary">the latest chat summary generated by the "Summarize" extension (if available).</span></li>20 <li><tt>&lcub;&lcub;summary&rcub;&rcub;</tt> – <span data-i18n="help_macros_summary">the latest chat summary generated by the "Summarize" extension (if available).</span></li>
21 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>21 <li><tt>&lcub;&lcub;user&rcub;&rcub;</tt> – <span data-i18n="help_macros_15">your current Persona username</span></li>
22 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>22 <li><tt>&lcub;&lcub;char&rcub;&rcub;</tt> – <span data-i18n="help_macros_16">the Character's name</span></li>
23 <li><tt>&lcub;&lcub;char_version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>23 <li><tt>&lcub;&lcub;version&rcub;&rcub;</tt> – <span data-i18n="help_macros_17">the Character's version number</span></li>
24 <li><tt>&lcub;&lcub;charDepthPrompt&rcub;&rcub;</tt> – <span data-i18n="help_macros_charDepthPrompt">the Character's @ Depth Note</span></li>
24 <li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names (including muted) or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>25 <li><tt>&lcub;&lcub;group&rcub;&rcub;</tt> – <span data-i18n="help_macros_18">a comma-separated list of group member names (including muted) or the character name in solo chats. Alias: &lcub;&lcub;charIfNotGroup&rcub;&rcub;</span></li>
25 <li><tt>&lcub;&lcub;groupNotMuted&rcub;&rcub;</tt> – <span data-i18n="help_groupNotMuted">the same as &lcub;&lcub;group&rcub;&rcub;, but excludes muted members</span></li>26 <li><tt>&lcub;&lcub;groupNotMuted&rcub;&rcub;</tt> – <span data-i18n="help_groupNotMuted">the same as &lcub;&lcub;group&rcub;&rcub;, but excludes muted members</span></li>
26 <li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> – <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b data-i18n="Can be inaccurate!">Can be inaccurate!</b></li>27 <li><tt>&lcub;&lcub;model&rcub;&rcub;</tt> – <span data-i18n="help_macros_19">a text generation model name for the currently selected API. </span><b data-i18n="Can be inaccurate!">Can be inaccurate!</b></li>
public/scripts/templates/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>
\ No newline at end of file18 \ No newline at end of file
public/scripts/templates/worldInfoKeywordHeaders.html+1 -1
@@ -1,4 +1,4 @@
1<div id="WIEntryHeaderTitlesPC" class="flex-container wide100p spaceBetween justifyCenter textAlignCenter" style="padding:0 4.5em;">1<div id="WIEntryHeaderTitlesPC" class="flex-container wide100p spaceBetween justifyCenter textAlignCenter" style="padding:0 7.0em;">
2 <small class="flex1" data-i18n="Title/Memo">Title/Memo</small>2 <small class="flex1" data-i18n="Title/Memo">Title/Memo</small>
3 <small style="width: calc(3.5em + 10px)" data-i18n="Strategy">Strategy</small>3 <small style="width: calc(3.5em + 10px)" data-i18n="Strategy">Strategy</small>
4 <small style="width: calc(3.5em + 20px)" data-i18n="Position">Position</small>4 <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 = [
58 'Minimax',58 'Minimax',
59 'Nineteen',59 'Nineteen',
60 'Liquid',60 'Liquid',
61 'Stealth',
62 'NCompass',
61 'InferenceNet',63 'InferenceNet',
62 'Friendli',64 'Friendli',
63 'AionLabs',65 'AionLabs',
@@ -69,6 +71,9 @@ const OPENROUTER_PROVIDERS = [
69 'Targon',71 'Targon',
70 'Ubicloud',72 'Ubicloud',
71 'Parasail',73 'Parasail',
74 'Phala',
75 'Cent-ML',
76 'Venice',
72 '01.AI',77 '01.AI',
73 'HuggingFace',78 'HuggingFace',
74 'Mancer',79 'Mancer',
@@ -923,6 +928,10 @@ export function getCurrentDreamGenModelTokenizer() {
923 return tokenizers.YI;928 return tokenizers.YI;
924 } else if (model.id.startsWith('opus-v1-xl')) {929 } else if (model.id.startsWith('opus-v1-xl')) {
925 return tokenizers.LLAMA;930 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;
926 } else {935 } else {
927 return tokenizers.MISTRAL;936 return tokenizers.MISTRAL;
928 }937 }
public/scripts/textgen-settings.js+1 -1
@@ -86,7 +86,7 @@ const OOBA_DEFAULT_ORDER = [
86 'encoder_repetition_penalty',86 'encoder_repetition_penalty',
87 'no_repeat_ngram',87 'no_repeat_ngram',
88];88];
89const APHRODITE_DEFAULT_ORDER = [89export const APHRODITE_DEFAULT_ORDER = [
90 'dry',90 'dry',
91 'penalties',91 'penalties',
92 'no_repeat_ngram',92 'no_repeat_ngram',
public/scripts/tokenizers.js+0 -9
@@ -729,15 +729,6 @@ export function getTokenizerModel() {
729 return yiTokenizer;729 return yiTokenizer;
730 }730 }
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
741 // Default to Turbo 3.5732 // Default to Turbo 3.5
742 return turboTokenizer;733 return turboTokenizer;
743}734}
public/scripts/tool-calling.js+1 -0
@@ -586,6 +586,7 @@ export class ToolManager {
586 chat_completion_sources.DEEPSEEK,586 chat_completion_sources.DEEPSEEK,
587 chat_completion_sources.MAKERSUITE,587 chat_completion_sources.MAKERSUITE,
588 chat_completion_sources.AI21,588 chat_completion_sources.AI21,
589 chat_completion_sources.XAI,
589 ];590 ];
590 return supportedSources.includes(oai_settings.chat_completion_source);591 return supportedSources.includes(oai_settings.chat_completion_source);
591 }592 }
public/scripts/user.js+2 -2
@@ -9,8 +9,8 @@ import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './uti
9export let currentUser = null;9export let currentUser = null;
10export let accountsEnabled = false;10export let accountsEnabled = false;
1111
12// Extend the session every 30 minutes12// Extend the session every 10 minutes
13const SESSION_EXTEND_INTERVAL = 30 * 60 * 1000;13const SESSION_EXTEND_INTERVAL = 10 * 60 * 1000;
1414
15/**15/**
16 * Enable or disable user account controls in the UI.16 * Enable or disable user account controls in the UI.
public/scripts/utils.js+2 -0
@@ -1437,6 +1437,8 @@ export async function ensureImageFormatSupported(file) {
1437 'image/tiff',1437 'image/tiff',
1438 'image/gif',1438 'image/gif',
1439 'image/apng',1439 'image/apng',
1440 'image/webp',
1441 'image/avif',
1440 ];1442 ];
14411443
1442 if (supportedTypes.includes(file.type) || !file.type.startsWith('image/')) {1444 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';
9import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';9import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
10import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';10import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
11import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';11import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
12import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';12import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
13import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';13import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
14import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';14import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
15import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';15import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
@@ -583,7 +583,7 @@ export function evalBoolean(rule, a, b) {
583 * Executes a slash command from a string (may be enclosed in quotes) and returns the result.583 * Executes a slash command from a string (may be enclosed in quotes) and returns the result.
584 * @param {string} command Command to execute. May contain escaped macro and batch separators.584 * @param {string} command Command to execute. May contain escaped macro and batch separators.
585 * @param {SlashCommandScope} [scope] The scope to use.585 * @param {SlashCommandScope} [scope] The scope to use.
586 * @param {{[id:PARSER_FLAG]:boolean}} [parserFlags] The parser flags to use.586 * @param {import('./slash-commands/SlashCommandParser.js').ParserFlags} [parserFlags] The parser flags to use.
587 * @param {SlashCommandAbortController} [abortController] The abort controller to use.587 * @param {SlashCommandAbortController} [abortController] The abort controller to use.
588 * @returns {Promise<SlashCommandClosureResult>} Closure execution result588 * @returns {Promise<SlashCommandClosureResult>} Closure execution result
589 */589 */
public/scripts/world-info.js+233 -14
@@ -1,7 +1,7 @@
1import { Fuse } from '../lib.js';1import { Fuse } from '../lib.js';
22
3import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';3import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
4import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique } from './utils.js';4import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
5import { extension_settings, getContext } from './extensions.js';5import { extension_settings, getContext } from './extensions.js';
6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
7import { isMobile } from './RossAscends-mods.js';7import { isMobile } from './RossAscends-mods.js';
@@ -753,10 +753,17 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
753753
754/**754/**
755 * Gets the world info based on chat messages.755 * Gets the world info based on chat messages.
756 * @param {string[]} chat The chat messages to scan, in reverse order.756 * @param {string[]} chat - The chat messages to scan, in reverse order.
757 * @param {number} maxContext The maximum context size of the generation.757 * @param {number} maxContext - The maximum context size of the generation.
758 * @param {boolean} isDryRun If true, the function will not emit any events.758 * @param {boolean} isDryRun - If true, the function will not emit any events.
759 * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult759 * @typedef {object} WIPromptResult
760 * @property {string} worldInfoString - Complete world info string
761 * @property {string} worldInfoBefore - World info that goes before the prompt
762 * @property {string} worldInfoAfter - World info that goes after the prompt
763 * @property {Array} worldInfoExamples - Array of example entries
764 * @property {Array} worldInfoDepth - Array of depth entries
765 * @property {Array} anBefore - Array of entries before Author's Note
766 * @property {Array} anAfter - Array of entries after Author's Note
760 * @returns {Promise<WIPromptResult>} The world info string and depth.767 * @returns {Promise<WIPromptResult>} The world info string and depth.
761 */768 */
762export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {769export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@@ -778,6 +785,8 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
778 worldInfoAfter,785 worldInfoAfter,
779 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],786 worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
780 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],787 worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
788 anBefore: activatedWorldInfo.ANBeforeEntries ?? [],
789 anAfter: activatedWorldInfo.ANAfterEntries ?? [],
781 };790 };
782}791}
783792
@@ -899,13 +908,12 @@ export function setWorldInfoSettings(settings, data) {
899 registerWorldInfoSlashCommands();908 registerWorldInfoSlashCommands();
900}909}
901910
902function registerWorldInfoSlashCommands() {
903/**911/**
904 * Reloads the editor with the specified world info file912 * Reloads the editor with the specified world info file
905 * @param {string} file - The file to load in the editor913 * @param {string} file - The file to load in the editor
906 * @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected914 * @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
907 */915 */
908 function reloadEditor(file, loadIfNotSelected = false) {916export function reloadEditor(file, loadIfNotSelected = false) {
909 const currentIndex = Number($('#world_editor_select').val());917 const currentIndex = Number($('#world_editor_select').val());
910 const selectedIndex = world_names.indexOf(file);918 const selectedIndex = world_names.indexOf(file);
911 if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {919 if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
@@ -913,6 +921,7 @@ function registerWorldInfoSlashCommands() {
913 }921 }
914}922}
915923
924function registerWorldInfoSlashCommands() {
916 /**925 /**
917 * Gets a *rough* approximation of the current chat context.926 * Gets a *rough* approximation of the current chat context.
918 * Normally, it is provided externally by the prompt builder.927 * Normally, it is provided externally by the prompt builder.
@@ -1329,6 +1338,18 @@ function registerWorldInfoSlashCommands() {
1329 }1338 }
1330 }1339 }
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
1332 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1353 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1333 name: 'world',1354 name: 'world',
1334 callback: onWorldInfoChange,1355 callback: onWorldInfoChange,
@@ -1371,6 +1392,13 @@ function registerWorldInfoSlashCommands() {
1371 aliases: ['getchatlore', 'getchatwi'],1392 aliases: ['getchatlore', 'getchatwi'],
1372 }));1393 }));
1373 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1394 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({
1374 name: 'getpersonabook',1402 name: 'getpersonabook',
1375 callback: getPersonaBookCallback,1403 callback: getPersonaBookCallback,
1376 returns: 'lorebook name',1404 returns: 'lorebook name',
@@ -2199,7 +2227,7 @@ function verifyWorldInfoSearchSortRule() {
2199 * Use `originalWIDataKeyMap` to find the correct value to be set.2227 * Use `originalWIDataKeyMap` to find the correct value to be set.
2200 *2228 *
2201 * @param {object} data - The data object containing the original data entries.2229 * @param {object} data - The data object containing the original data entries.
2202 * @param {string} uid - The unique identifier of the data entry.2230 * @param {number} uid - The unique identifier of the data entry.
2203 * @param {string} key - The key of the value to be set.2231 * @param {string} key - The key of the value to be set.
2204 * @param {any} value - The value to be set.2232 * @param {any} value - The value to be set.
2205 */2233 */
@@ -2223,7 +2251,9 @@ export function setWIOriginalDataValue(data, uid, key, value) {
2223 */2251 */
2224export function deleteWIOriginalDataValue(data, uid) {2252export function deleteWIOriginalDataValue(data, uid) {
2225 if (data.originalData && Array.isArray(data.originalData.entries)) {2253 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
2228 if (originalIndex >= 0) {2258 if (originalIndex >= 0) {
2229 data.originalData.entries.splice(originalIndex, 1);2259 data.originalData.entries.splice(originalIndex, 1);
@@ -2671,8 +2701,10 @@ export async function getWorldEntry(name, data, entry) {
2671 $(counter).text(numberOfTokens);2701 $(counter).text(numberOfTokens);
2672 }, debounce_timeout.relaxed);2702 }, debounce_timeout.relaxed);
26732703
2704 const contentInputId = `world_entry_content_${entry.uid}`;
2674 const contentInput = template.find('textarea[name="content"]');2705 const contentInput = template.find('textarea[name="content"]');
2675 contentInput.data('uid', entry.uid);2706 contentInput.data('uid', entry.uid);
2707 contentInput.attr('id', contentInputId);
2676 contentInput.on('input', async function (_, { skipCount } = {}) {2708 contentInput.on('input', async function (_, { skipCount } = {}) {
2677 const uid = $(this).data('uid');2709 const uid = $(this).data('uid');
2678 const value = $(this).val();2710 const value = $(this).val();
@@ -2689,7 +2721,9 @@ export async function getWorldEntry(name, data, entry) {
2689 countTokensDebounced(counter, value);2721 countTokensDebounced(counter, value);
2690 });2722 });
2691 contentInput.val(entry.content).trigger('input', { skipCount: true });2723 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
2694 template.find('.inline-drawer-toggle').on('click', function () {2728 template.find('.inline-drawer-toggle').on('click', function () {
2695 if (counter.data('first-run')) {2729 if (counter.data('first-run')) {
@@ -3130,6 +3164,84 @@ export async function getWorldEntry(name, data, entry) {
3130 updateEditor(navigation_option.previous);3164 updateEditor(navigation_option.previous);
3131 });3165 });
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
3133 // scan depth3245 // scan depth
3134 const scanDepthInput = template.find('input[name="scanDepth"]');3246 const scanDepthInput = template.find('input[name="scanDepth"]');
3135 scanDepthInput.data('uid', entry.uid);3247 scanDepthInput.data('uid', entry.uid);
@@ -3493,6 +3605,10 @@ async function renameWorldInfo(name, data) {
3493 console.debug('World info rename cancelled');3605 console.debug('World info rename cancelled');
3494 return;3606 return;
3495 }3607 }
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
3497 const entryPreviouslySelected = selected_world_info.findIndex((e) => e === oldName);3613 const entryPreviouslySelected = selected_world_info.findIndex((e) => e === oldName);
34983614
@@ -3862,7 +3978,14 @@ function parseDecorators(content) {
3862 * @param {string[]} chat The chat messages to scan, in reverse order.3978 * @param {string[]} chat The chat messages to scan, in reverse order.
3863 * @param {number} maxContext The maximum context size of the generation.3979 * @param {number} maxContext The maximum context size of the generation.
3864 * @param {boolean} isDryRun Whether to perform a dry run.3980 * @param {boolean} isDryRun Whether to perform a dry run.
3865 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated3981 * @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.
3866 * @returns {Promise<WIActivated>} The world info activated.3989 * @returns {Promise<WIActivated>} The world info activated.
3867 */3990 */
3868export async function checkWorldInfo(chat, maxContext, isDryRun) {3991export async function checkWorldInfo(chat, maxContext, isDryRun) {
@@ -3906,7 +4029,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3906 timedEffects.checkTimedEffects();4029 timedEffects.checkTimedEffects();
39074030
3908 if (sortedEntries.length === 0) {4031 if (sortedEntries.length === 0) {
3909 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };4032 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], ANBeforeEntries: [], ANAfterEntries: [], allActivatedEntries: new Set() };
3910 }4033 }
39114034
3912 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */4035 /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
@@ -4355,7 +4478,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4355 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));4478 console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values()));
4356 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);4479 console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`);
43574480
4358 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };4481 return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, ANBeforeEntries: ANTopEntries, ANAfterEntries: ANBottomEntries, allActivatedEntries: new Set(allActivatedEntries.values()) };
4359}4482}
43604483
4361/**4484/**
@@ -5251,3 +5374,99 @@ jQuery(() => {
5251 });5374 });
5252 });5375 });
5253});5376});
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 */
5386export 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 {
2086 }2086 }
2087 }2087 }
20882088
2089 >.head>.rawQuotes {
2090 padding: 0 0.5em;
2091 cursor: help;
2092
2093 &:hover {
2094 text-decoration: 1px dotted underline;
2095 }
2096 }
2097
2089 >.head>.source {2098 >.head>.source {
2090 padding: 0 0.5em;2099 padding: 0 0.5em;
2091 cursor: help;2100 cursor: help;
@@ -2911,6 +2920,35 @@ select option:not(:checked) {
2911 pointer-events: none;2920 pointer-events: none;
2912}2921}
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
2914.fav_on {2952.fav_on {
2915 color: var(--golden) !important;2953 color: var(--golden) !important;
2916}2954}
@@ -2921,7 +2959,7 @@ select option:not(:checked) {
2921}2959}
29222960
2923.menu_button.togglable:not(.toggleEnabled) {2961.menu_button.togglable:not(.toggleEnabled) {
2924 color: red;2962 color: var(--fullred);
2925}2963}
29262964
2927.displayBlock {2965.displayBlock {
@@ -4957,6 +4995,7 @@ a:hover {
4957 max-width: 100%;4995 max-width: 100%;
4958 max-height: 40vh;4996 max-height: 40vh;
4959 image-rendering: -webkit-optimize-contrast;4997 image-rendering: -webkit-optimize-contrast;
4998 cursor: pointer;
4960}4999}
49615000
4962.mes_img_swipes,5001.mes_img_swipes,
server.js+21 -2
@@ -20,6 +20,7 @@ import bodyParser from 'body-parser';
20import open from 'open';20import open from 'open';
2121
22// local library imports22// local library imports
23import './src/fetch-patch.js';
23import { serverEvents, EVENT_NAMES } from './src/server-events.js';24import { serverEvents, EVENT_NAMES } from './src/server-events.js';
24import { CommandLineParser } from './src/command-line.js';25import { CommandLineParser } from './src/command-line.js';
25import { loadPlugins } from './src/plugin-loader.js';26import { loadPlugins } from './src/plugin-loader.js';
@@ -66,6 +67,7 @@ import { init as statsInit, onExit as statsOnExit } from './src/endpoints/stats.
66import { checkForNewContent } from './src/endpoints/content-manager.js';67import { checkForNewContent } from './src/endpoints/content-manager.js';
67import { init as settingsInit } from './src/endpoints/settings.js';68import { init as settingsInit } from './src/endpoints/settings.js';
68import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from './src/server-startup.js';69import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from './src/server-startup.js';
70import { diskCache } from './src/endpoints/characters.js';
6971
70// Unrestrict console logs display limit72// Unrestrict console logs display limit
71util.inspect.defaultOptions.maxArrayLength = null;73util.inspect.defaultOptions.maxArrayLength = null;
@@ -149,7 +151,7 @@ if (cliArgs.enableCorsProxy) {
149151
150app.use(cookieSession({152app.use(cookieSession({
151 name: getCookieSessionName(),153 name: getCookieSessionName(),
152 sameSite: 'strict',154 sameSite: 'lax',
153 httpOnly: true,155 httpOnly: true,
154 maxAge: getSessionCookieAge(),156 maxAge: getSessionCookieAge(),
155 secret: getCookieSecret(globalThis.DATA_ROOT),157 secret: getCookieSecret(globalThis.DATA_ROOT),
@@ -212,6 +214,17 @@ app.get('/', getCacheBusterMiddleware(), (request, response) => {
212 return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') });214 return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') });
213});215});
214216
217// Callback endpoint for OAuth PKCE flows (e.g. OpenRouter)
218app.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
215// Host login page228// Host login page
216app.get('/login', loginPageMiddleware);229app.get('/login', loginPageMiddleware);
217230
@@ -267,7 +280,8 @@ async function preSetupTasks() {
267280
268 const directories = await getUserDirectoriesList();281 const directories = await getUserDirectoriesList();
269 await checkForNewContent(directories);282 await checkForNewContent(directories);
270 await ensureThumbnailCache();283 await ensureThumbnailCache(directories);
284 await diskCache.verify(directories);
271 cleanUploads();285 cleanUploads();
272 migrateAccessLog();286 migrateAccessLog();
273287
@@ -286,6 +300,7 @@ async function preSetupTasks() {
286 if (typeof cleanupPlugins === 'function') {300 if (typeof cleanupPlugins === 'function') {
287 await cleanupPlugins();301 await cleanupPlugins();
288 }302 }
303 diskCache.dispose();
289 setWindowTitle(consoleTitle);304 setWindowTitle(consoleTitle);
290 process.exit();305 process.exit();
291 };306 };
@@ -315,8 +330,12 @@ async function postSetupTasks(result) {
315 const autorunUrl = cliArgs.getAutorunUrl(autorunHostname);330 const autorunUrl = cliArgs.getAutorunUrl(autorunHostname);
316331
317 if (cliArgs.autorun) {332 if (cliArgs.autorun) {
333 try {
318 console.log('Launching in a browser...');334 console.log('Launching in a browser...');
319 await open(autorunUrl.toString());335 await open(autorunUrl.toString());
336 } catch (error) {
337 console.error('Failed to launch the browser. Open the URL manually.');
338 }
320 }339 }
321340
322 setWindowTitle('SillyTavern WebServer');341 setWindowTitle('SillyTavern WebServer');
src/character-card-parser.js+3 -3
@@ -1,7 +1,7 @@
1import fs from 'node:fs';1import fs from 'node:fs';
2import { Buffer } from 'node:buffer';2import { Buffer } from 'node:buffer';
33
4import encode from 'png-chunks-encode';4import encode from './png/encode.js';
5import extract from 'png-chunks-extract';5import extract from 'png-chunks-extract';
6import PNGtext from 'png-chunk-text';6import PNGtext from 'png-chunk-text';
77
@@ -81,9 +81,9 @@ export const read = (image) => {
81 * Parses a card image and returns the character metadata.81 * Parses a card image and returns the character metadata.
82 * @param {string} cardUrl Path to the card image82 * @param {string} cardUrl Path to the card image
83 * @param {string} format File format83 * @param {string} format File format
84 * @returns {string} Character data84 * @returns {Promise<string>} Character data
85 */85 */
86export const parse = (cardUrl, format) => {86export const parse = async (cardUrl, format) => {
87 let fileFormat = format === undefined ? 'png' : format;87 let fileFormat = format === undefined ? 'png' : format;
8888
89 switch (fileFormat) {89 switch (fileFormat) {
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
43 vectors: 'vectors',43 vectors: 'vectors',
44 backups: 'backups',44 backups: 'backups',
45 sysprompt: 'sysprompt',45 sysprompt: 'sysprompt',
46 reasoning: 'reasoning',
46});47});
4748
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