Merge pull request #4794 from SillyTavern/staging Staging

9c9be90821ffd6132b40b5f04982522a61d7ad30

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

Signed
175 files changed, +3462 -4378Ignore whitespace
.github/workflows/pr-auto-manager.yml+32 -0
@@ -56,6 +56,38 @@ jobs:
5656 dist/
5757 lib/
5858
59+ run-tests:
60+ name: ✅ Run Unit Tests on PR
61+ runs-on: ubuntu-latest
62+ # Only needs to run when code is changed
63+ if: always() && (github.event.action == 'opened' || github.event.action == 'synchronize')
64+
65+ steps:
66+ - name: Checkout Repository
67+ # Checkout
68+ # https://github.com/marketplace/actions/checkout
69+ uses: actions/checkout@v4.2.2
70+ with:
71+ ref: ${{ github.event.pull_request.head.sha }}
72+ repository: ${{ github.event.pull_request.head.repo.full_name }}
73+
74+ - name: Setup Node.js
75+ # Setup Node.js environment
76+ # https://github.com/marketplace/actions/setup-node-js-environment
77+ uses: actions/setup-node@v4.3.0
78+ with:
79+ node-version: 20
80+ cache: 'npm'
81+
82+ - name: Install root dependencies
83+ run: npm ci
84+
85+ - name: Install test dependencies
86+ run: npm ci --prefix tests
87+
88+ - name: Run unit tests
89+ run: npm run test:unit --prefix tests
90+
5991 label-by-size:
6092 name: 🏷️ Label PR by Size
6193 # This job should run after all others, to prevent possible concurrency issues
.gitignore+2 -1
@@ -55,4 +55,5 @@ public/scripts/extensions/third-party
5555.env
5656/StartDev.bat
5757yarn.lock
58-*.code-workspace
58 \ No newline at end of file
58+*.code-workspace
59+test-results/
default/content/index.json+2 -218
@@ -140,75 +140,11 @@
140140 "type": "workflow"
141141 },
142142 {
143- "filename": "presets/kobold/Ace of Spades.json",
144- "type": "kobold_preset"
145- },
146- {
147- "filename": "presets/kobold/Basic Coherence.json",
148- "type": "kobold_preset"
149- },
150- {
151- "filename": "presets/kobold/Best Guess.json",
152- "type": "kobold_preset"
153- },
154- {
155- "filename": "presets/kobold/Coherent Creativity.json",
156- "type": "kobold_preset"
157- },
158- {
159143 "filename": "presets/kobold/Deterministic.json",
160144 "type": "kobold_preset"
161145 },
162146 {
163147 "filename": "presets/kobold/GenesisNeutral.json",
164- "type": "kobold_preset"
165- },
166- {
167- "filename": "presets/kobold/Godlike.json",
168- "type": "kobold_preset"
169- },
170- {
171- "filename": "presets/kobold/Good Winds.json",
172- "type": "kobold_preset"
173- },
174- {
175- "filename": "presets/kobold/Liminal Drift.json",
176- "type": "kobold_preset"
177- },
178- {
179- "filename": "presets/kobold/Low Rider.json",
180- "type": "kobold_preset"
181- },
182- {
183- "filename": "presets/kobold/Luna Moth.json",
184- "type": "kobold_preset"
185- },
186- {
187- "filename": "presets/kobold/Mayday.json",
188- "type": "kobold_preset"
189- },
190- {
191- "filename": "presets/kobold/Miro Bronze.json",
192- "type": "kobold_preset"
193- },
194- {
195- "filename": "presets/kobold/Miro Gold.json",
196- "type": "kobold_preset"
197- },
198- {
199- "filename": "presets/kobold/Miro Silver.json",
200- "type": "kobold_preset"
201- },
202- {
203- "filename": "presets/kobold/Ouroboros.json",
204- "type": "kobold_preset"
205- },
206- {
207- "filename": "presets/kobold/Pleasing Results.json",
208- "type": "kobold_preset"
209- },
210- {
211- "filename": "presets/kobold/Pro Writer.json",
212148 "type": "kobold_preset"
213149 },
214150 {
@@ -216,22 +152,6 @@
216152 "type": "kobold_preset"
217153 },
218154 {
219- "filename": "presets/kobold/Space Alien.json",
220- "type": "kobold_preset"
221- },
222- {
223- "filename": "presets/kobold/Storywriter.json",
224- "type": "kobold_preset"
225- },
226- {
227- "filename": "presets/kobold/TFS-with-Top-A.json",
228- "type": "kobold_preset"
229- },
230- {
231- "filename": "presets/kobold/Titanic.json",
232- "type": "kobold_preset"
233- },
234- {
235155 "filename": "presets/kobold/Universal-Creative.json",
236156 "type": "kobold_preset"
237157 },
@@ -244,10 +164,6 @@
244164 "type": "kobold_preset"
245165 },
246166 {
247- "filename": "presets/kobold/simple-proxy-for-tavern.json",
248- "type": "kobold_preset"
249- },
250- {
251167 "filename": "presets/novel/Asper-Kayra.json",
252168 "type": "novel_preset"
253169 },
@@ -344,22 +260,6 @@
344260 "type": "novel_preset"
345261 },
346262 {
347- "filename": "presets/textgen/Asterism.json",
348- "type": "textgen_preset"
349- },
350- {
351- "filename": "presets/textgen/Beam Search.json",
352- "type": "textgen_preset"
353- },
354- {
355- "filename": "presets/textgen/Big O.json",
356- "type": "textgen_preset"
357- },
358- {
359- "filename": "presets/textgen/Contrastive Search.json",
360- "type": "textgen_preset"
361- },
362- {
363263 "filename": "presets/textgen/Default.json",
364264 "type": "textgen_preset"
365265 },
@@ -368,99 +268,7 @@
368268 "type": "textgen_preset"
369269 },
370270 {
371271 "filename": "presets/textgen/Divine IntellectNeutral.json",
372- "type": "textgen_preset"
373- },
374- {
375- "filename": "presets/textgen/Kobold (Godlike).json",
376- "type": "textgen_preset"
377- },
378- {
379- "filename": "presets/textgen/Kobold (Liminal Drift).json",
380- "type": "textgen_preset"
381- },
382- {
383- "filename": "presets/textgen/LLaMa-Precise.json",
384- "type": "textgen_preset"
385- },
386- {
387- "filename": "presets/textgen/Midnight Enigma.json",
388- "type": "textgen_preset"
389- },
390- {
391- "filename": "presets/textgen/Miro Bronze.json",
392- "type": "textgen_preset"
393- },
394- {
395- "filename": "presets/textgen/Miro Gold.json",
396- "type": "textgen_preset"
397- },
398- {
399- "filename": "presets/textgen/Miro Silver.json",
400- "type": "textgen_preset"
401- },
402- {
403- "filename": "presets/textgen/Mirostat.json",
404- "type": "textgen_preset"
405- },
406- {
407- "filename": "presets/textgen/Naive.json",
408- "type": "textgen_preset"
409- },
410- {
411- "filename": "presets/textgen/NovelAI (Best Guess).json",
412- "type": "textgen_preset"
413- },
414- {
415- "filename": "presets/textgen/NovelAI (Decadence).json",
416- "type": "textgen_preset"
417- },
418- {
419- "filename": "presets/textgen/NovelAI (Genesis).json",
420- "type": "textgen_preset"
421- },
422- {
423- "filename": "presets/textgen/NovelAI (Lycaenidae).json",
424- "type": "textgen_preset"
425- },
426- {
427- "filename": "presets/textgen/NovelAI (Ouroboros).json",
428- "type": "textgen_preset"
429- },
430- {
431- "filename": "presets/textgen/NovelAI (Pleasing Results).json",
432- "type": "textgen_preset"
433- },
434- {
435- "filename": "presets/textgen/NovelAI (Sphinx Moth).json",
436- "type": "textgen_preset"
437- },
438- {
439- "filename": "presets/textgen/NovelAI (Storywriter).json",
440- "type": "textgen_preset"
441- },
442- {
443- "filename": "presets/textgen/Shortwave.json",
444- "type": "textgen_preset"
445- },
446- {
447- "filename": "presets/textgen/Simple-1.json",
448- "type": "textgen_preset"
449- },
450- {
451- "filename": "presets/textgen/Space Alien.json",
452- "type": "textgen_preset"
453- },
454- {
455- "filename": "presets/textgen/StarChat.json",
456- "type": "textgen_preset"
457- },
458- {
459- "filename": "presets/textgen/TFS-with-Top-A.json",
460- "type": "textgen_preset"
461- },
462- {
463- "filename": "presets/textgen/Titanic.json",
464272 "type": "textgen_preset"
465273 },
466274 {
@@ -476,14 +284,6 @@
476284 "type": "textgen_preset"
477285 },
478286 {
479- "filename": "presets/textgen/Yara.json",
480- "type": "textgen_preset"
481- },
482- {
483- "filename": "presets/textgen/simple-proxy-for-tavern.json",
484- "type": "textgen_preset"
485- },
486- {
487287 "filename": "presets/openai/Default.json",
488288 "type": "openai_preset"
489289 },
@@ -508,14 +308,6 @@
508308 "type": "context"
509309 },
510310 {
511- "filename": "presets/context/DreamGen Role-Play V1 ChatML.json",
512- "type": "context"
513- },
514- {
515- "filename": "presets/context/DreamGen Role-Play V1 Llama3.json",
516- "type": "context"
517- },
518- {
519311 "filename": "presets/context/Libra-32B.json",
520312 "type": "context"
521313 },
@@ -588,14 +380,6 @@
588380 "type": "instruct"
589381 },
590382 {
591- "filename": "presets/instruct/DreamGen Role-Play V1 ChatML.json",
592- "type": "instruct"
593- },
594- {
595- "filename": "presets/instruct/DreamGen Role-Play V1 Llama3.json",
596- "type": "instruct"
597- },
598- {
599383 "filename": "presets/instruct/Koala.json",
600384 "type": "instruct"
601385 },
default/content/presets/context/DreamGen Role-Play V1 ChatML.json+0 -14
@@ -1,14 +0,0 @@
1-{
2- "story_string": "{{#if anchorBefore}}{{anchorBefore}}\n{{/if}}{{#if system}}{{system}}\n\n\n{{/if}}## Overall plot description:\n\n{{#if scenario}}{{scenario}}{{else}}Conversation between {{char}} and {{user}}.{{/if}}{{#if wiBefore}}\n\n{{wiBefore}}{{/if}}\n\n\n## Characters:\n\n### {{char}}\n\n{{#if description}}{{description}}\n\n{{/if}}{{#if personality}}{{personality}}\n\n{{/if}}### {{user}}\n\n{{#if persona}}{{persona}}{{else}}{{user}} is the protagonist of the role-play.{{/if}}{{#if wiAfter}}\n\n{{wiAfter}}{{/if}}{{#if mesExamples}}\n\n{{mesExamples}}{{/if}}{{#if anchorAfter}}{{anchorAfter}}\n{{/if}}{{trim}}",
3- "example_separator": "",
4- "chat_start": "",
5- "use_stop_strings": false,
6- "names_as_stop_strings": true,
7- "story_string_position": 0,
8- "story_string_depth": 1,
9- "story_string_role": 0,
10- "always_force_name2": false,
11- "trim_sentences": true,
12- "single_line": false,
13- "name": "DreamGen Role-Play V1 ChatML"
14-}
default/content/presets/context/DreamGen Role-Play V1 Llama3.json+0 -14
@@ -1,14 +0,0 @@
1-{
2- "story_string": "{{#if anchorBefore}}{{anchorBefore}}\n{{/if}}{{#if system}}{{system}}\n\n\n{{/if}}## Overall plot description:\n\n{{#if scenario}}{{scenario}}{{else}}Conversation between {{char}} and {{user}}.{{/if}}{{#if wiBefore}}\n\n{{wiBefore}}{{/if}}\n\n\n## Characters:\n\n### {{char}}\n\n{{#if description}}{{description}}\n\n{{/if}}{{#if personality}}{{personality}}\n\n{{/if}}### {{user}}\n\n{{#if persona}}{{persona}}{{else}}{{user}} is the protagonist of the role-play.{{/if}}{{#if wiAfter}}\n\n{{wiAfter}}{{/if}}{{#if mesExamples}}\n\n{{mesExamples}}{{/if}}{{#if anchorAfter}}{{anchorAfter}}\n{{/if}}{{trim}}",
3- "example_separator": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nWrite an example narrative / conversation that is not part of the main story.",
4- "chat_start": "<|eot_id|>\n<|start_header_id|>user<|end_header_id|>\n\nStart the role-play between {{char}} and {{user}}.",
5- "use_stop_strings": false,
6- "names_as_stop_strings": true,
7- "story_string_position": 0,
8- "story_string_depth": 1,
9- "story_string_role": 0,
10- "always_force_name2": false,
11- "trim_sentences": true,
12- "single_line": false,
13- "name": "DreamGen Role-Play V1 Llama3"
14-}
default/content/presets/instruct/DreamGen Role-Play V1 ChatML.json+0 -25
@@ -1,25 +0,0 @@
1-{
2- "input_sequence": "\n<|im_start|>text names= {{name}}\n",
3- "output_sequence": "\n<|im_start|>text names= {{name}}\n",
4- "last_output_sequence": "",
5- "system_sequence": "",
6- "stop_sequence": "\n<|im_start|>",
7- "wrap": false,
8- "macro": true,
9- "names_behavior": "none",
10- "activation_regex": "",
11- "first_output_sequence": "",
12- "skip_examples": false,
13- "output_suffix": "<|im_end|>",
14- "input_suffix": "<|im_end|>",
15- "system_suffix": "",
16- "user_alignment_message": "",
17- "system_same_as_user": true,
18- "last_system_sequence": "",
19- "first_input_sequence": "",
20- "last_input_sequence": "",
21- "sequences_as_stop_strings": true,
22- "story_string_prefix": "<|im_start|>system\n",
23- "story_string_suffix": "<|im_end|>",
24- "name": "DreamGen Role-Play V1 ChatML"
25-}
default/content/presets/instruct/DreamGen Role-Play V1 Llama3.json+0 -25
@@ -1,25 +0,0 @@
1-{
2- "input_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{user}}<|end_header_id|>\n\n",
3- "output_sequence": "<|eot_id|>\n<|start_header_id|>writer character: {{char}}<|end_header_id|>\n\n",
4- "last_output_sequence": "",
5- "system_sequence": "",
6- "stop_sequence": "",
7- "wrap": false,
8- "macro": true,
9- "names_behavior": "none",
10- "activation_regex": "",
11- "first_output_sequence": "",
12- "skip_examples": false,
13- "output_suffix": "",
14- "input_suffix": "",
15- "system_suffix": "",
16- "user_alignment_message": "",
17- "system_same_as_user": false,
18- "last_system_sequence": "",
19- "first_input_sequence": "",
20- "last_input_sequence": "",
21- "sequences_as_stop_strings": true,
22- "story_string_prefix": "<|start_header_id|>system<|end_header_id|>\n",
23- "story_string_suffix": "",
24- "name": "DreamGen Role-Play V1 Llama3"
25-}
default/content/presets/kobold/Ace of Spades.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.15,
3- "rep_pen": 1.05,
4- "rep_pen_range": 2048,
5- "top_p": 0.95,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.8,
10- "rep_pen_slope": 7,
11- "sampler_order": [
12- 6,
13- 3,
14- 2,
15- 0,
16- 5,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Basic Coherence.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.59,
3- "rep_pen": 1.1,
4- "rep_pen_range": 2048,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.87,
10- "rep_pen_slope": 0.3,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Best Guess.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.8,
3- "rep_pen": 1.15,
4- "rep_pen_range": 2048,
5- "top_p": 0.9,
6- "top_a": 0,
7- "top_k": 100,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 3.4,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Coherent Creativity.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.51,
3- "rep_pen": 1.2,
4- "rep_pen_range": 2048,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.99,
10- "rep_pen_slope": 0,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Deterministic.json+5 -3
@@ -1,8 +1,9 @@
11{
22 "temp": 0,
33 "rep_pen": 1.18,
44 "rep_pen_range": 20480,
55 "top_p": 0,
6+ "min_p": 0,
67 "top_a": 0,
78 "top_k": 1,
89 "typical": 1,
@@ -20,5 +21,6 @@
2021 "mirostat": 0,
2122 "mirostat_tau": 5,
2223 "mirostat_eta": 0.1,
24+ "use_default_badwordsids": false,
2325 "grammar": ""
24-}
24 \ No newline at end of file
26+}
default/content/presets/kobold/Genesis.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.63,
3- "rep_pen": 1.05,
4- "rep_pen_range": 2048,
5- "top_p": 0.98,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.98,
10- "rep_pen_slope": 0.1,
11- "sampler_order": [
12- 6,
13- 2,
14- 0,
15- 3,
16- 5,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Godlike.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.7,
3- "rep_pen": 1.1,
4- "rep_pen_range": 1024,
5- "top_p": 0.5,
6- "top_a": 0.75,
7- "top_k": 0,
8- "typical": 0.19,
9- "tfs": 0.97,
10- "rep_pen_slope": 0.7,
11- "sampler_order": [
12- 6,
13- 5,
14- 4,
15- 3,
16- 2,
17- 1,
18- 0
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Liminal Drift.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.66,
3- "rep_pen": 1.1,
4- "rep_pen_range": 1024,
5- "top_p": 1,
6- "top_a": 0.96,
7- "top_k": 0,
8- "typical": 0.6,
9- "tfs": 1,
10- "rep_pen_slope": 0.7,
11- "sampler_order": [
12- 6,
13- 4,
14- 5,
15- 1,
16- 0,
17- 2,
18- 3
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Low Rider.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.94,
3- "rep_pen": 1.05,
4- "rep_pen_range": 2048,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 12,
8- "typical": 1,
9- "tfs": 0.94,
10- "rep_pen_slope": 0.2,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Luna Moth.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.5,
3- "rep_pen": 1.1,
4- "rep_pen_range": 2048,
5- "top_p": 0.24,
6- "top_a": 0,
7- "top_k": 85,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Mayday.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.05,
3- "rep_pen": 1.1,
4- "rep_pen_range": 1024,
5- "top_p": 0.95,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0.7,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 2,
16- 3,
17- 4,
18- 5
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Miro Bronze.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.06,
3- "rep_pen": 1,
4- "rep_pen_range": 0,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0.9,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 2,
21- "mirostat_tau": 9.61,
22- "mirostat_eta": 1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Miro Gold.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.17,
3- "rep_pen": 1,
4- "rep_pen_range": 0,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0.9,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 2,
21- "mirostat_tau": 9.91,
22- "mirostat_eta": 1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Miro Silver.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.17,
3- "rep_pen": 1,
4- "rep_pen_range": 0,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0.9,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 2,
21- "mirostat_tau": 9.62,
22- "mirostat_eta": 1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Good Winds.json → default/content/presets/kobold/Neutral.json+9 -7
@@ -1,24 +1,26 @@
11{
22 "temp": 0.71,
33 "rep_pen": 1.1,
44 "rep_pen_range": 10240,
55 "top_p": 1,
6+ "min_p": 0,
67 "top_a": 0,
78 "top_k": 0,
89 "typical": 1,
910 "tfs": 0.91,
1011 "rep_pen_slope": 0.7,
1112 "sampler_order": [
1213 6,
1314 0,
1415 1,
15- 2,
1616 3,
1717 4,
18+ 2,
1819 5
1920 ],
2021 "mirostat": 0,
2122 "mirostat_tau": 5,
2223 "mirostat_eta": 0.1,
24+ "use_default_badwordsids": false,
2325 "grammar": ""
24-}
24 \ No newline at end of file
26+}
default/content/presets/kobold/Ouroboros.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.07,
3- "rep_pen": 1.05,
4- "rep_pen_range": 404,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 100,
8- "typical": 1,
9- "tfs": 0.93,
10- "rep_pen_slope": 0.8,
11- "sampler_order": [
12- 6,
13- 0,
14- 5,
15- 3,
16- 2,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Pleasing Results.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.44,
3- "rep_pen": 1.15,
4- "rep_pen_range": 2048,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.9,
10- "rep_pen_slope": 6.8,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Pro Writer.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.35,
3- "rep_pen": 1.15,
4- "rep_pen_range": 2048,
5- "top_p": 1,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.69,
10- "rep_pen_slope": 0.1,
11- "sampler_order": [
12- 6,
13- 3,
14- 2,
15- 5,
16- 0,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/RecoveredRuins.json+3 -1
@@ -3,6 +3,7 @@
33 "rep_pen": 1.1,
44 "rep_pen_range": 600,
55 "top_p": 0.95,
6+ "min_p": 0.01,
67 "top_a": 0,
78 "top_k": 0,
89 "typical": 1,
@@ -20,5 +21,6 @@
2021 "mirostat": 0,
2122 "mirostat_tau": 5,
2223 "mirostat_eta": 0.1,
24+ "use_default_badwordsids": false,
2325 "grammar": ""
24-}
24 \ No newline at end of file
26+}
default/content/presets/kobold/Space Alien.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.31,
3- "rep_pen": 1.09,
4- "rep_pen_range": 2048,
5- "top_p": 0.29,
6- "top_a": 0,
7- "top_k": 72,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Storywriter.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.72,
3- "rep_pen": 1.1,
4- "rep_pen_range": 2048,
5- "top_p": 0.73,
6- "top_a": 0,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0.2,
11- "sampler_order": [
12- 6,
13- 5,
14- 0,
15- 2,
16- 3,
17- 1,
18- 4
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/TFS-with-Top-A.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.7,
3- "rep_pen": 1.15,
4- "rep_pen_range": 2048,
5- "top_p": 1,
6- "top_a": 0.2,
7- "top_k": 0,
8- "typical": 1,
9- "tfs": 0.95,
10- "rep_pen_slope": 0,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Titanic.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 1.01,
3- "rep_pen": 1.21,
4- "rep_pen_range": 2048,
5- "top_p": 0.21,
6- "top_a": 0.75,
7- "top_k": 91,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/kobold/Universal-Creative.json+1 -1
@@ -1,7 +1,7 @@
11{
22 "temp": 1.5,
33 "rep_pen": 1,
44 "rep_pen_range": 6000,
55 "top_p": 1,
66 "min_p": 0.1,
77 "top_a": 0,
default/content/presets/kobold/Universal-Light.json+1 -1
@@ -1,7 +1,7 @@
11{
22 "temp": 1.25,
33 "rep_pen": 1,
44 "rep_pen_range": 6000,
55 "top_p": 1,
66 "min_p": 0.1,
77 "top_a": 0,
default/content/presets/kobold/Universal-Super-Creative.json+1 -1
@@ -1,7 +1,7 @@
11{
22 "temp": 2,
33 "rep_pen": 1,
44 "rep_pen_range": 6000,
55 "top_p": 1,
66 "min_p": 0.1,
77 "top_a": 0,
default/content/presets/kobold/simple-proxy-for-tavern.json+0 -24
@@ -1,24 +0,0 @@
1-{
2- "temp": 0.65,
3- "rep_pen": 1.18,
4- "rep_pen_range": 2048,
5- "top_p": 0.47,
6- "top_a": 0,
7- "top_k": 42,
8- "typical": 1,
9- "tfs": 1,
10- "rep_pen_slope": 0,
11- "sampler_order": [
12- 6,
13- 0,
14- 1,
15- 3,
16- 4,
17- 2,
18- 5
19- ],
20- "mirostat": 0,
21- "mirostat_tau": 5,
22- "mirostat_eta": 0.1,
23- "grammar": ""
24-}
24 \ No newline at end of file
default/content/presets/textgen/Asterism.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.68,
3- "temperature_last": true,
4- "top_p": 0.17,
5- "top_k": 77,
6- "top_a": 0.42,
7- "tfs": 0.97,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.02,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Beam Search.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.7,
3- "temperature_last": true,
4- "top_p": 0.92,
5- "top_k": 150,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 3,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 2,
17- "penalty_alpha": 0,
18- "num_beams": 10,
19- "length_penalty": 1.4,
20- "min_length": 200,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": true,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Big O.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.87,
3- "temperature_last": true,
4- "top_p": 0.99,
5- "top_k": 85,
6- "top_a": 0,
7- "tfs": 0.68,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 0.68,
11- "min_p": 0,
12- "rep_pen": 1.01,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Contrastive Search.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 4,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0.6,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": false,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Default.json+40 -7
@@ -1,15 +1,15 @@
11{
22 "temp": 0.71,
33 "temperature_last": true,
44 "top_p": 0.595,
55 "top_k": 400,
66 "top_a": 0,
77 "tfs": 1,
88 "epsilon_cutoff": 0,
99 "eta_cutoff": 0,
1010 "typical_p": 1,
1111 "min_p": 0.01,
1212 "rep_pen": 1.21,
1313 "rep_pen_range": 0,
1414 "rep_pen_decay": 0,
1515 "rep_pen_slope": 1,
@@ -44,12 +44,17 @@
4444 "guidance_scale": 1,
4545 "negative_prompt": "",
4646 "grammar_string": "",
4747 "json_schema": {}null,
4848 "banned_tokens": "",
4949 "sampler_priority": [
50+ "repetition_penalty",
51+ "presence_penalty",
52+ "frequency_penalty",
53+ "dry",
5054 "temperature",
5155 "dynamic_temperature",
5256 "quadratic_sampling",
57+ "top_n_sigma",
5358 "top_k",
5459 "top_p",
5560 "typical_p",
@@ -58,16 +63,40 @@
5863 "tfs",
5964 "top_a",
6065 "min_p",
6166 "mirostat",
67+ "xtc",
68+ "encoder_repetition_penalty",
69+ "no_repeat_ngram"
6270 ],
6371 "samplers": [
72+ "penalties",
73+ "dry",
74+ "top_n_sigma",
6475 "top_k",
76+ "typ_p",
6577 "tfs_z",
6678 "typical_p",
79+ "xtc",
6780 "top_p",
6881 "min_p",
6982 "temperature"
7083 ],
84+ "samplers_priorities": [
85+ "dry",
86+ "penalties",
87+ "no_repeat_ngram",
88+ "temperature",
89+ "top_nsigma",
90+ "top_p_top_k",
91+ "top_a",
92+ "min_p",
93+ "tfs",
94+ "eta_cutoff",
95+ "epsilon_cutoff",
96+ "typical_p",
97+ "quadratic",
98+ "xtc"
99+ ],
71100 "ignore_eos_token": false,
72101 "spaces_between_special_tokens": true,
73102 "speculative_ngram": false,
@@ -81,5 +110,9 @@
81110 5
82111 ],
83112 "logit_bias": [],
113+ "xtc_threshold": 0.1,
114+ "xtc_probability": 0,
115+ "nsigma": 0,
116+ "min_keep": 0,
84117 "rep_pen_size": 0
85118}
default/content/presets/textgen/Deterministic.json+36 -3
@@ -9,7 +9,7 @@
99 "eta_cutoff": 0,
1010 "typical_p": 1,
1111 "min_p": 0,
1212 "rep_pen": 1.18,
1313 "rep_pen_range": 0,
1414 "rep_pen_decay": 0,
1515 "rep_pen_slope": 1,
@@ -44,12 +44,17 @@
4444 "guidance_scale": 1,
4545 "negative_prompt": "",
4646 "grammar_string": "",
4747 "json_schema": {}null,
4848 "banned_tokens": "",
4949 "sampler_priority": [
50+ "repetition_penalty",
51+ "presence_penalty",
52+ "frequency_penalty",
53+ "dry",
5054 "temperature",
5155 "dynamic_temperature",
5256 "quadratic_sampling",
57+ "top_n_sigma",
5358 "top_k",
5459 "top_p",
5560 "typical_p",
@@ -58,16 +63,40 @@
5863 "tfs",
5964 "top_a",
6065 "min_p",
6166 "mirostat",
67+ "xtc",
68+ "encoder_repetition_penalty",
69+ "no_repeat_ngram"
6270 ],
6371 "samplers": [
72+ "penalties",
73+ "dry",
74+ "top_n_sigma",
6475 "top_k",
76+ "typ_p",
6577 "tfs_z",
6678 "typical_p",
79+ "xtc",
6780 "top_p",
6881 "min_p",
6982 "temperature"
7083 ],
84+ "samplers_priorities": [
85+ "dry",
86+ "penalties",
87+ "no_repeat_ngram",
88+ "temperature",
89+ "top_nsigma",
90+ "top_p_top_k",
91+ "top_a",
92+ "min_p",
93+ "tfs",
94+ "eta_cutoff",
95+ "epsilon_cutoff",
96+ "typical_p",
97+ "quadratic",
98+ "xtc"
99+ ],
71100 "ignore_eos_token": false,
72101 "spaces_between_special_tokens": true,
73102 "speculative_ngram": false,
@@ -81,5 +110,9 @@
81110 5
82111 ],
83112 "logit_bias": [],
113+ "xtc_threshold": 0.1,
114+ "xtc_probability": 0,
115+ "nsigma": 0,
116+ "min_keep": 0,
84117 "rep_pen_size": 0
85118}
default/content/presets/textgen/Divine Intellect.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.31,
3- "temperature_last": true,
4- "top_p": 0.14,
5- "top_k": 49,
6- "top_a": 0.52,
7- "tfs": 1,
8- "epsilon_cutoff": 1.49,
9- "eta_cutoff": 10.42,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.17,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Kobold (Godlike).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.7,
3- "temperature_last": true,
4- "top_p": 0.5,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 0.19,
11- "min_p": 0,
12- "rep_pen": 1.1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Kobold (Liminal Drift).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.66,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 0.6,
11- "min_p": 0,
12- "rep_pen": 1.1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/LLaMa-Precise.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.7,
3- "temperature_last": true,
4- "top_p": 0.1,
5- "top_k": 40,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.18,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 200,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Midnight Enigma.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.98,
3- "temperature_last": true,
4- "top_p": 0.37,
5- "top_k": 100,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.18,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Miro Bronze.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.06,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 2,
42- "mirostat_tau": 9.61,
43- "mirostat_eta": 1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Miro Gold.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.17,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 2,
42- "mirostat_tau": 9.91,
43- "mirostat_eta": 1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Miro Silver.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.17,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 2,
42- "mirostat_tau": 9.62,
43- "mirostat_eta": 1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Naive.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.7,
3- "temperature_last": true,
4- "top_p": 0.85,
5- "top_k": 50,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Mirostat.json → default/content/presets/textgen/Neutral.json+37 -4
@@ -38,18 +38,23 @@
3838 "add_bos_token": true,
3939 "ban_eos_token": false,
4040 "skip_special_tokens": true,
4141 "mirostat_mode": 20,
4242 "mirostat_tau": 85,
4343 "mirostat_eta": 0.1,
4444 "guidance_scale": 1,
4545 "negative_prompt": "",
4646 "grammar_string": "",
4747 "json_schema": {}null,
4848 "banned_tokens": "",
4949 "sampler_priority": [
50+ "repetition_penalty",
51+ "presence_penalty",
52+ "frequency_penalty",
53+ "dry",
5054 "temperature",
5155 "dynamic_temperature",
5256 "quadratic_sampling",
57+ "top_n_sigma",
5358 "top_k",
5459 "top_p",
5560 "typical_p",
@@ -58,16 +63,40 @@
5863 "tfs",
5964 "top_a",
6065 "min_p",
6166 "mirostat",
67+ "xtc",
68+ "encoder_repetition_penalty",
69+ "no_repeat_ngram"
6270 ],
6371 "samplers": [
72+ "penalties",
73+ "dry",
74+ "top_n_sigma",
6475 "top_k",
76+ "typ_p",
6577 "tfs_z",
6678 "typical_p",
79+ "xtc",
6780 "top_p",
6881 "min_p",
6982 "temperature"
7083 ],
84+ "samplers_priorities": [
85+ "dry",
86+ "penalties",
87+ "no_repeat_ngram",
88+ "temperature",
89+ "top_nsigma",
90+ "top_p_top_k",
91+ "top_a",
92+ "min_p",
93+ "tfs",
94+ "eta_cutoff",
95+ "epsilon_cutoff",
96+ "typical_p",
97+ "quadratic",
98+ "xtc"
99+ ],
71100 "ignore_eos_token": false,
72101 "spaces_between_special_tokens": true,
73102 "speculative_ngram": false,
@@ -81,5 +110,9 @@
81110 5
82111 ],
83112 "logit_bias": [],
113+ "xtc_threshold": 0.1,
114+ "xtc_probability": 0,
115+ "nsigma": 0,
116+ "min_keep": 0,
84117 "rep_pen_size": 0
85118}
default/content/presets/textgen/NovelAI (Best Guess).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.8,
3- "temperature_last": true,
4- "top_p": 0.9,
5- "top_k": 100,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.15,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Decadence).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.99,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 100,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 0.97,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Genesis).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.63,
3- "temperature_last": true,
4- "top_p": 0.98,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.05,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Lycaenidae).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.99,
3- "temperature_last": true,
4- "top_p": 0.85,
5- "top_k": 12,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.15,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Ouroboros).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.07,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 100,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.05,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Pleasing Results).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.44,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.15,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Sphinx Moth).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.99,
3- "temperature_last": true,
4- "top_p": 0.18,
5- "top_k": 30,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.15,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/NovelAI (Storywriter).json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.72,
3- "temperature_last": true,
4- "top_p": 0.73,
5- "top_k": 0,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Shortwave.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.53,
3- "temperature_last": true,
4- "top_p": 0.64,
5- "top_k": 33,
6- "top_a": 0.04,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.07,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Simple-1.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.7,
3- "temperature_last": true,
4- "top_p": 0.9,
5- "top_k": 20,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.15,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Space Alien.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.31,
3- "temperature_last": true,
4- "top_p": 0.29,
5- "top_k": 72,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.09,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/StarChat.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.02,
3- "temperature_last": true,
4- "top_p": 0.95,
5- "top_k": 50,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/TFS-with-Top-A.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.7,
3- "temperature_last": true,
4- "top_p": 1,
5- "top_k": 0,
6- "top_a": 0.2,
7- "tfs": 0.95,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.15,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Titanic.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 1.01,
3- "temperature_last": true,
4- "top_p": 0.21,
5- "top_k": 91,
6- "top_a": 0.75,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 10.78,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.21,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1.07,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/Universal-Creative.json+35 -2
@@ -44,12 +44,17 @@
4444 "guidance_scale": 1,
4545 "negative_prompt": "",
4646 "grammar_string": "",
4747 "json_schema": {}null,
4848 "banned_tokens": "",
4949 "sampler_priority": [
50+ "repetition_penalty",
51+ "presence_penalty",
52+ "frequency_penalty",
53+ "dry",
5054 "temperature",
5155 "dynamic_temperature",
5256 "quadratic_sampling",
57+ "top_n_sigma",
5358 "top_k",
5459 "top_p",
5560 "typical_p",
@@ -58,16 +63,40 @@
5863 "tfs",
5964 "top_a",
6065 "min_p",
6166 "mirostat",
67+ "xtc",
68+ "encoder_repetition_penalty",
69+ "no_repeat_ngram"
6270 ],
6371 "samplers": [
72+ "penalties",
73+ "dry",
74+ "top_n_sigma",
6475 "top_k",
76+ "typ_p",
6577 "tfs_z",
6678 "typical_p",
79+ "xtc",
6780 "top_p",
6881 "min_p",
6982 "temperature"
7083 ],
84+ "samplers_priorities": [
85+ "dry",
86+ "penalties",
87+ "no_repeat_ngram",
88+ "temperature",
89+ "top_nsigma",
90+ "top_p_top_k",
91+ "top_a",
92+ "min_p",
93+ "tfs",
94+ "eta_cutoff",
95+ "epsilon_cutoff",
96+ "typical_p",
97+ "quadratic",
98+ "xtc"
99+ ],
71100 "ignore_eos_token": false,
72101 "spaces_between_special_tokens": true,
73102 "speculative_ngram": false,
@@ -81,5 +110,9 @@
81110 4
82111 ],
83112 "logit_bias": [],
113+ "xtc_threshold": 0.1,
114+ "xtc_probability": 0,
115+ "nsigma": 0,
116+ "min_keep": 0,
84117 "rep_pen_size": 0
85118}
default/content/presets/textgen/Universal-Light.json+35 -2
@@ -44,12 +44,17 @@
4444 "guidance_scale": 1,
4545 "negative_prompt": "",
4646 "grammar_string": "",
4747 "json_schema": {}null,
4848 "banned_tokens": "",
4949 "sampler_priority": [
50+ "repetition_penalty",
51+ "presence_penalty",
52+ "frequency_penalty",
53+ "dry",
5054 "temperature",
5155 "dynamic_temperature",
5256 "quadratic_sampling",
57+ "top_n_sigma",
5358 "top_k",
5459 "top_p",
5560 "typical_p",
@@ -58,16 +63,40 @@
5863 "tfs",
5964 "top_a",
6065 "min_p",
6166 "mirostat",
67+ "xtc",
68+ "encoder_repetition_penalty",
69+ "no_repeat_ngram"
6270 ],
6371 "samplers": [
72+ "penalties",
73+ "dry",
74+ "top_n_sigma",
6475 "top_k",
76+ "typ_p",
6577 "tfs_z",
6678 "typical_p",
79+ "xtc",
6780 "top_p",
6881 "min_p",
6982 "temperature"
7083 ],
84+ "samplers_priorities": [
85+ "dry",
86+ "penalties",
87+ "no_repeat_ngram",
88+ "temperature",
89+ "top_nsigma",
90+ "top_p_top_k",
91+ "top_a",
92+ "min_p",
93+ "tfs",
94+ "eta_cutoff",
95+ "epsilon_cutoff",
96+ "typical_p",
97+ "quadratic",
98+ "xtc"
99+ ],
71100 "ignore_eos_token": false,
72101 "spaces_between_special_tokens": true,
73102 "speculative_ngram": false,
@@ -81,5 +110,9 @@
81110 4
82111 ],
83112 "logit_bias": [],
113+ "xtc_threshold": 0.1,
114+ "xtc_probability": 0,
115+ "nsigma": 0,
116+ "min_keep": 0,
84117 "rep_pen_size": 0
85118}
default/content/presets/textgen/Universal-Super-Creative.json+35 -2
@@ -44,12 +44,17 @@
4444 "guidance_scale": 1,
4545 "negative_prompt": "",
4646 "grammar_string": "",
4747 "json_schema": {}null,
4848 "banned_tokens": "",
4949 "sampler_priority": [
50+ "repetition_penalty",
51+ "presence_penalty",
52+ "frequency_penalty",
53+ "dry",
5054 "temperature",
5155 "dynamic_temperature",
5256 "quadratic_sampling",
57+ "top_n_sigma",
5358 "top_k",
5459 "top_p",
5560 "typical_p",
@@ -58,16 +63,40 @@
5863 "tfs",
5964 "top_a",
6065 "min_p",
6166 "mirostat",
67+ "xtc",
68+ "encoder_repetition_penalty",
69+ "no_repeat_ngram"
6270 ],
6371 "samplers": [
72+ "penalties",
73+ "dry",
74+ "top_n_sigma",
6475 "top_k",
76+ "typ_p",
6577 "tfs_z",
6678 "typical_p",
79+ "xtc",
6780 "top_p",
6881 "min_p",
6982 "temperature"
7083 ],
84+ "samplers_priorities": [
85+ "dry",
86+ "penalties",
87+ "no_repeat_ngram",
88+ "temperature",
89+ "top_nsigma",
90+ "top_p_top_k",
91+ "top_a",
92+ "min_p",
93+ "tfs",
94+ "eta_cutoff",
95+ "epsilon_cutoff",
96+ "typical_p",
97+ "quadratic",
98+ "xtc"
99+ ],
71100 "ignore_eos_token": false,
72101 "spaces_between_special_tokens": true,
73102 "speculative_ngram": false,
@@ -81,5 +110,9 @@
81110 4
82111 ],
83112 "logit_bias": [],
113+ "xtc_threshold": 0.1,
114+ "xtc_probability": 0,
115+ "nsigma": 0,
116+ "min_keep": 0,
84117 "rep_pen_size": 0
85118}
default/content/presets/textgen/Yara.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.82,
3- "temperature_last": true,
4- "top_p": 0.21,
5- "top_k": 72,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.19,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 5,
76- 6,
77- 0,
78- 1,
79- 2,
80- 3,
81- 4
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/presets/textgen/simple-proxy-for-tavern.json+0 -85
@@ -1,85 +0,0 @@
1-{
2- "temp": 0.65,
3- "temperature_last": true,
4- "top_p": 0.47,
5- "top_k": 42,
6- "top_a": 0,
7- "tfs": 1,
8- "epsilon_cutoff": 0,
9- "eta_cutoff": 0,
10- "typical_p": 1,
11- "min_p": 0,
12- "rep_pen": 1.18,
13- "rep_pen_range": 0,
14- "rep_pen_decay": 0,
15- "rep_pen_slope": 1,
16- "no_repeat_ngram_size": 0,
17- "penalty_alpha": 0,
18- "num_beams": 1,
19- "length_penalty": 1,
20- "min_length": 0,
21- "encoder_rep_pen": 1,
22- "freq_pen": 0,
23- "presence_pen": 0,
24- "skew": 0,
25- "do_sample": true,
26- "early_stopping": false,
27- "dynatemp": false,
28- "min_temp": 0,
29- "max_temp": 2,
30- "dynatemp_exponent": 1,
31- "smoothing_factor": 0,
32- "smoothing_curve": 1,
33- "dry_allowed_length": 2,
34- "dry_multiplier": 0,
35- "dry_base": 1.75,
36- "dry_sequence_breakers": "[\"\\n\", \":\", \"\\\"\", \"*\"]",
37- "dry_penalty_last_n": 0,
38- "add_bos_token": true,
39- "ban_eos_token": false,
40- "skip_special_tokens": true,
41- "mirostat_mode": 0,
42- "mirostat_tau": 5,
43- "mirostat_eta": 0.1,
44- "guidance_scale": 1,
45- "negative_prompt": "",
46- "grammar_string": "",
47- "json_schema": {},
48- "banned_tokens": "",
49- "sampler_priority": [
50- "temperature",
51- "dynamic_temperature",
52- "quadratic_sampling",
53- "top_k",
54- "top_p",
55- "typical_p",
56- "epsilon_cutoff",
57- "eta_cutoff",
58- "tfs",
59- "top_a",
60- "min_p",
61- "mirostat"
62- ],
63- "samplers": [
64- "top_k",
65- "tfs_z",
66- "typical_p",
67- "top_p",
68- "min_p",
69- "temperature"
70- ],
71- "ignore_eos_token": false,
72- "spaces_between_special_tokens": true,
73- "speculative_ngram": false,
74- "sampler_order": [
75- 6,
76- 0,
77- 1,
78- 3,
79- 4,
80- 2,
81- 5
82- ],
83- "logit_bias": [],
84- "rep_pen_size": 0
85-}
default/content/settings.json+4 -2
@@ -22,14 +22,15 @@
2222 "world_info_budget_cap": 0
2323 },
2424 "textgenerationwebui_settings": {
2525 "temp": 0.51,
2626 "top_p": 0.995,
2727 "top_k": 0,
2828 "top_a": 0,
2929 "tfs": 1,
3030 "epsilon_cutoff": 0,
3131 "eta_cutoff": 0,
3232 "typical_p": 1,
33+ "min_p": 0.01,
3334 "rep_pen": 1.1,
3435 "rep_pen_range": 0,
3536 "no_repeat_ngram_size": 0,
@@ -430,6 +431,7 @@
430431 "rep_pen": 1.1,
431432 "rep_pen_range": 600,
432433 "top_p": 0.95,
434+ "min_p": 0.01,
433435 "top_a": 0,
434436 "top_k": 0,
435437 "typical": 1,
package-lock.json+425 -15
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.1314.50",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.1314.50",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
@@ -133,6 +133,7 @@
133133 "@types/yargs": "^17.0.33",
134134 "@types/yauzl": "^2.10.3",
135135 "eslint": "^8.57.1",
136+ "eslint-plugin-jest": "^27.9.0",
136137 "eslint-plugin-jsdoc": "^48.10.0"
137138 },
138139 "engines": {
@@ -186,17 +187,20 @@
186187 }
187188 },
188189 "node_modules/@eslint-community/eslint-utils": {
189190 "version": "4.49.0",
190191 "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.49.0.tgz",
191192 "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9payVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/AfAQ9R3OAlL4g==",
192193 "dev": true,
193194 "license": "MIT",
194195 "dependencies": {
195196 "eslint-visitor-keys": "^3.34.03"
196197 },
197198 "engines": {
198199 "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
199200 },
201+ "funding": {
202+ "url": "https://opencollective.com/eslint"
203+ },
200204 "peerDependencies": {
201205 "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
202206 }
@@ -2105,6 +2109,13 @@
21052109 "@types/jquery": "*"
21062110 }
21072111 },
2112+ "node_modules/@types/semver": {
2113+ "version": "7.7.1",
2114+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz",
2115+ "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==",
2116+ "dev": true,
2117+ "license": "MIT"
2118+ },
21082119 "node_modules/@types/send": {
21092120 "version": "0.17.4",
21102121 "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
@@ -2188,6 +2199,160 @@
21882199 "@types/node": "*"
21892200 }
21902201 },
2202+ "node_modules/@typescript-eslint/scope-manager": {
2203+ "version": "5.62.0",
2204+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
2205+ "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
2206+ "dev": true,
2207+ "license": "MIT",
2208+ "dependencies": {
2209+ "@typescript-eslint/types": "5.62.0",
2210+ "@typescript-eslint/visitor-keys": "5.62.0"
2211+ },
2212+ "engines": {
2213+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2214+ },
2215+ "funding": {
2216+ "type": "opencollective",
2217+ "url": "https://opencollective.com/typescript-eslint"
2218+ }
2219+ },
2220+ "node_modules/@typescript-eslint/types": {
2221+ "version": "5.62.0",
2222+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
2223+ "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
2224+ "dev": true,
2225+ "license": "MIT",
2226+ "engines": {
2227+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2228+ },
2229+ "funding": {
2230+ "type": "opencollective",
2231+ "url": "https://opencollective.com/typescript-eslint"
2232+ }
2233+ },
2234+ "node_modules/@typescript-eslint/typescript-estree": {
2235+ "version": "5.62.0",
2236+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
2237+ "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
2238+ "dev": true,
2239+ "license": "BSD-2-Clause",
2240+ "dependencies": {
2241+ "@typescript-eslint/types": "5.62.0",
2242+ "@typescript-eslint/visitor-keys": "5.62.0",
2243+ "debug": "^4.3.4",
2244+ "globby": "^11.1.0",
2245+ "is-glob": "^4.0.3",
2246+ "semver": "^7.3.7",
2247+ "tsutils": "^3.21.0"
2248+ },
2249+ "engines": {
2250+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2251+ },
2252+ "funding": {
2253+ "type": "opencollective",
2254+ "url": "https://opencollective.com/typescript-eslint"
2255+ },
2256+ "peerDependenciesMeta": {
2257+ "typescript": {
2258+ "optional": true
2259+ }
2260+ }
2261+ },
2262+ "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
2263+ "version": "4.4.3",
2264+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
2265+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
2266+ "dev": true,
2267+ "license": "MIT",
2268+ "dependencies": {
2269+ "ms": "^2.1.3"
2270+ },
2271+ "engines": {
2272+ "node": ">=6.0"
2273+ },
2274+ "peerDependenciesMeta": {
2275+ "supports-color": {
2276+ "optional": true
2277+ }
2278+ }
2279+ },
2280+ "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": {
2281+ "version": "2.1.3",
2282+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2283+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
2284+ "dev": true,
2285+ "license": "MIT"
2286+ },
2287+ "node_modules/@typescript-eslint/utils": {
2288+ "version": "5.62.0",
2289+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
2290+ "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
2291+ "dev": true,
2292+ "license": "MIT",
2293+ "dependencies": {
2294+ "@eslint-community/eslint-utils": "^4.2.0",
2295+ "@types/json-schema": "^7.0.9",
2296+ "@types/semver": "^7.3.12",
2297+ "@typescript-eslint/scope-manager": "5.62.0",
2298+ "@typescript-eslint/types": "5.62.0",
2299+ "@typescript-eslint/typescript-estree": "5.62.0",
2300+ "eslint-scope": "^5.1.1",
2301+ "semver": "^7.3.7"
2302+ },
2303+ "engines": {
2304+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2305+ },
2306+ "funding": {
2307+ "type": "opencollective",
2308+ "url": "https://opencollective.com/typescript-eslint"
2309+ },
2310+ "peerDependencies": {
2311+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
2312+ }
2313+ },
2314+ "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
2315+ "version": "5.1.1",
2316+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
2317+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
2318+ "dev": true,
2319+ "license": "BSD-2-Clause",
2320+ "dependencies": {
2321+ "esrecurse": "^4.3.0",
2322+ "estraverse": "^4.1.1"
2323+ },
2324+ "engines": {
2325+ "node": ">=8.0.0"
2326+ }
2327+ },
2328+ "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
2329+ "version": "4.3.0",
2330+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
2331+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
2332+ "dev": true,
2333+ "license": "BSD-2-Clause",
2334+ "engines": {
2335+ "node": ">=4.0"
2336+ }
2337+ },
2338+ "node_modules/@typescript-eslint/visitor-keys": {
2339+ "version": "5.62.0",
2340+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
2341+ "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
2342+ "dev": true,
2343+ "license": "MIT",
2344+ "dependencies": {
2345+ "@typescript-eslint/types": "5.62.0",
2346+ "eslint-visitor-keys": "^3.3.0"
2347+ },
2348+ "engines": {
2349+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
2350+ },
2351+ "funding": {
2352+ "type": "opencollective",
2353+ "url": "https://opencollective.com/typescript-eslint"
2354+ }
2355+ },
21912356 "node_modules/@ungap/structured-clone": {
21922357 "version": "1.2.0",
21932358 "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -2590,9 +2755,9 @@
25902755 }
25912756 },
25922757 "node_modules/archiver-utils/node_modules/glob": {
25932758 "version": "10.4.5.0",
25942759 "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.0.tgz",
25952760 "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHgDfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
25962761 "license": "ISC",
25972762 "dependencies": {
25982763 "foreground-child": "^3.1.0",
@@ -2739,6 +2904,16 @@
27392904 "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
27402905 "license": "MIT"
27412906 },
2907+ "node_modules/array-union": {
2908+ "version": "2.1.0",
2909+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
2910+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
2911+ "dev": true,
2912+ "license": "MIT",
2913+ "engines": {
2914+ "node": ">=8"
2915+ }
2916+ },
27422917 "node_modules/ast-types": {
27432918 "version": "0.13.4",
27442919 "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
@@ -2913,6 +3088,19 @@
29133088 "concat-map": "0.0.1"
29143089 }
29153090 },
3091+ "node_modules/braces": {
3092+ "version": "3.0.3",
3093+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
3094+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
3095+ "dev": true,
3096+ "license": "MIT",
3097+ "dependencies": {
3098+ "fill-range": "^7.1.1"
3099+ },
3100+ "engines": {
3101+ "node": ">=8"
3102+ }
3103+ },
29163104 "node_modules/browserslist": {
29173105 "version": "4.24.0",
29183106 "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
@@ -3906,6 +4094,19 @@
39064094 "md5": "^2.3.0"
39074095 }
39084096 },
4097+ "node_modules/dir-glob": {
4098+ "version": "3.0.1",
4099+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
4100+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
4101+ "dev": true,
4102+ "license": "MIT",
4103+ "dependencies": {
4104+ "path-type": "^4.0.0"
4105+ },
4106+ "engines": {
4107+ "node": ">=8"
4108+ }
4109+ },
39094110 "node_modules/doctrine": {
39104111 "version": "3.0.0",
39114112 "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -4257,6 +4458,32 @@
42574458 "url": "https://opencollective.com/eslint"
42584459 }
42594460 },
4461+ "node_modules/eslint-plugin-jest": {
4462+ "version": "27.9.0",
4463+ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz",
4464+ "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==",
4465+ "dev": true,
4466+ "license": "MIT",
4467+ "dependencies": {
4468+ "@typescript-eslint/utils": "^5.10.0"
4469+ },
4470+ "engines": {
4471+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
4472+ },
4473+ "peerDependencies": {
4474+ "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0",
4475+ "eslint": "^7.0.0 || ^8.0.0",
4476+ "jest": "*"
4477+ },
4478+ "peerDependenciesMeta": {
4479+ "@typescript-eslint/eslint-plugin": {
4480+ "optional": true
4481+ },
4482+ "jest": {
4483+ "optional": true
4484+ }
4485+ }
4486+ },
42604487 "node_modules/eslint-plugin-jsdoc": {
42614488 "version": "48.10.0",
42624489 "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.10.0.tgz",
@@ -4609,6 +4836,23 @@
46094836 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
46104837 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
46114838 },
4839+ "node_modules/fast-glob": {
4840+ "version": "3.3.3",
4841+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
4842+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
4843+ "dev": true,
4844+ "license": "MIT",
4845+ "dependencies": {
4846+ "@nodelib/fs.stat": "^2.0.2",
4847+ "@nodelib/fs.walk": "^1.2.3",
4848+ "glob-parent": "^5.1.2",
4849+ "merge2": "^1.3.0",
4850+ "micromatch": "^4.0.8"
4851+ },
4852+ "engines": {
4853+ "node": ">=8.6.0"
4854+ }
4855+ },
46124856 "node_modules/fast-json-stable-stringify": {
46134857 "version": "2.1.0",
46144858 "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -4702,6 +4946,19 @@
47024946 "url": "https://github.com/sindresorhus/file-type?sponsor=1"
47034947 }
47044948 },
4949+ "node_modules/fill-range": {
4950+ "version": "7.1.1",
4951+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
4952+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
4953+ "dev": true,
4954+ "license": "MIT",
4955+ "dependencies": {
4956+ "to-regex-range": "^5.0.1"
4957+ },
4958+ "engines": {
4959+ "node": ">=8"
4960+ }
4961+ },
47054962 "node_modules/finalhandler": {
47064963 "version": "1.3.1",
47074964 "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
@@ -5039,6 +5296,19 @@
50395296 "url": "https://github.com/sponsors/isaacs"
50405297 }
50415298 },
5299+ "node_modules/glob-parent": {
5300+ "version": "5.1.2",
5301+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
5302+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
5303+ "dev": true,
5304+ "license": "ISC",
5305+ "dependencies": {
5306+ "is-glob": "^4.0.1"
5307+ },
5308+ "engines": {
5309+ "node": ">= 6"
5310+ }
5311+ },
50425312 "node_modules/glob-to-regexp": {
50435313 "version": "0.4.1",
50445314 "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@@ -5071,6 +5341,27 @@
50715341 "url": "https://github.com/sponsors/sindresorhus"
50725342 }
50735343 },
5344+ "node_modules/globby": {
5345+ "version": "11.1.0",
5346+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
5347+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
5348+ "dev": true,
5349+ "license": "MIT",
5350+ "dependencies": {
5351+ "array-union": "^2.1.0",
5352+ "dir-glob": "^3.0.1",
5353+ "fast-glob": "^3.2.9",
5354+ "ignore": "^5.2.0",
5355+ "merge2": "^1.4.1",
5356+ "slash": "^3.0.0"
5357+ },
5358+ "engines": {
5359+ "node": ">=10"
5360+ },
5361+ "funding": {
5362+ "url": "https://github.com/sponsors/sindresorhus"
5363+ }
5364+ },
50745365 "node_modules/google-translate-api-browser": {
50755366 "version": "3.0.1",
50765367 "resolved": "https://registry.npmjs.org/google-translate-api-browser/-/google-translate-api-browser-3.0.1.tgz",
@@ -5633,6 +5924,16 @@
56335924 "url": "https://github.com/sponsors/sindresorhus"
56345925 }
56355926 },
5927+ "node_modules/is-number": {
5928+ "version": "7.0.0",
5929+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
5930+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
5931+ "dev": true,
5932+ "license": "MIT",
5933+ "engines": {
5934+ "node": ">=0.12.0"
5935+ }
5936+ },
56365937 "node_modules/is-path-inside": {
56375938 "version": "3.0.3",
56385939 "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
@@ -5762,9 +6063,9 @@
57626063 "license": "BSD-3-Clause"
57636064 },
57646065 "node_modules/js-yaml": {
57656066 "version": "4.1.01",
57666067 "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.01.tgz",
57676068 "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRAqQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
57686069 "dev": true,
57696070 "license": "MIT",
57706071 "dependencies": {
@@ -6076,6 +6377,16 @@
60766377 "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
60776378 "license": "MIT"
60786379 },
6380+ "node_modules/merge2": {
6381+ "version": "1.4.1",
6382+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
6383+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
6384+ "dev": true,
6385+ "license": "MIT",
6386+ "engines": {
6387+ "node": ">= 8"
6388+ }
6389+ },
60796390 "node_modules/methods": {
60806391 "version": "1.1.2",
60816392 "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -6085,6 +6396,20 @@
60856396 "node": ">= 0.6"
60866397 }
60876398 },
6399+ "node_modules/micromatch": {
6400+ "version": "4.0.8",
6401+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
6402+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
6403+ "dev": true,
6404+ "license": "MIT",
6405+ "dependencies": {
6406+ "braces": "^3.0.3",
6407+ "picomatch": "^2.3.1"
6408+ },
6409+ "engines": {
6410+ "node": ">=8.6"
6411+ }
6412+ },
60886413 "node_modules/mime": {
60896414 "version": "1.6.0",
60906415 "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -6140,9 +6465,10 @@
61406465 }
61416466 },
61426467 "node_modules/min-document": {
61436468 "version": "2.19.02",
61446469 "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.02.tgz",
61456470 "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU8S5I8db/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+ILmOuvhLkOK6wJtCQJ8MtwmoOz3TmvaGu5A==",
6471+ "license": "MIT",
61466472 "dependencies": {
61476473 "dom-walk": "^0.1.0"
61486474 }
@@ -6765,6 +7091,16 @@
67657091 "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
67667092 "license": "MIT"
67677093 },
7094+ "node_modules/path-type": {
7095+ "version": "4.0.0",
7096+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
7097+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
7098+ "dev": true,
7099+ "license": "MIT",
7100+ "engines": {
7101+ "node": ">=8"
7102+ }
7103+ },
67687104 "node_modules/peek-readable": {
67697105 "version": "4.1.0",
67707106 "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
@@ -6802,6 +7138,19 @@
68027138 "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
68037139 "license": "ISC"
68047140 },
7141+ "node_modules/picomatch": {
7142+ "version": "2.3.1",
7143+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
7144+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
7145+ "dev": true,
7146+ "license": "MIT",
7147+ "engines": {
7148+ "node": ">=8.6"
7149+ },
7150+ "funding": {
7151+ "url": "https://github.com/sponsors/jonschlinkert"
7152+ }
7153+ },
68057154 "node_modules/platform": {
68067155 "version": "1.3.6",
68077156 "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
@@ -7656,6 +8005,16 @@
76568005 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
76578006 "license": "MIT"
76588007 },
8008+ "node_modules/slash": {
8009+ "version": "3.0.0",
8010+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
8011+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
8012+ "dev": true,
8013+ "license": "MIT",
8014+ "engines": {
8015+ "node": ">=8"
8016+ }
8017+ },
76598018 "node_modules/slashes": {
76608019 "version": "3.0.12",
76618020 "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz",
@@ -8028,6 +8387,19 @@
80288387 "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
80298388 "license": "MIT"
80308389 },
8390+ "node_modules/to-regex-range": {
8391+ "version": "5.0.1",
8392+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
8393+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
8394+ "dev": true,
8395+ "license": "MIT",
8396+ "dependencies": {
8397+ "is-number": "^7.0.0"
8398+ },
8399+ "engines": {
8400+ "node": ">=8.0"
8401+ }
8402+ },
80318403 "node_modules/toidentifier": {
80328404 "version": "1.0.1",
80338405 "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -8089,6 +8461,29 @@
80898461 "node": ">=0.6.x"
80908462 }
80918463 },
8464+ "node_modules/tsutils": {
8465+ "version": "3.21.0",
8466+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
8467+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
8468+ "dev": true,
8469+ "license": "MIT",
8470+ "dependencies": {
8471+ "tslib": "^1.8.1"
8472+ },
8473+ "engines": {
8474+ "node": ">= 6"
8475+ },
8476+ "peerDependencies": {
8477+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
8478+ }
8479+ },
8480+ "node_modules/tsutils/node_modules/tslib": {
8481+ "version": "1.14.1",
8482+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
8483+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
8484+ "dev": true,
8485+ "license": "0BSD"
8486+ },
80928487 "node_modules/type-check": {
80938488 "version": "0.4.0",
80948489 "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -8146,6 +8541,21 @@
81468541 "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
81478542 "license": "MIT"
81488543 },
8544+ "node_modules/typescript": {
8545+ "version": "5.9.3",
8546+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
8547+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
8548+ "dev": true,
8549+ "license": "Apache-2.0",
8550+ "peer": true,
8551+ "bin": {
8552+ "tsc": "bin/tsc",
8553+ "tsserver": "bin/tsserver"
8554+ },
8555+ "engines": {
8556+ "node": ">=14.17"
8557+ }
8558+ },
81498559 "node_modules/uglify-js": {
81508560 "version": "3.19.3",
81518561 "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
package.json+3 -2
@@ -113,7 +113,7 @@
113113 "type": "git",
114114 "url": "https://github.com/SillyTavern/SillyTavern.git"
115115 },
116116 "version": "1.1314.50",
117117 "scripts": {
118118 "start": "node server.js",
119119 "debug": "node --inspect server.js",
@@ -164,6 +164,7 @@
164164 "@types/yargs": "^17.0.33",
165165 "@types/yauzl": "^2.10.3",
166166 "eslint": "^8.57.1",
167167 "eslint-plugin-jsdoc": "^48.10.0",
168+ "eslint-plugin-jest": "^27.9.0"
168169 }
169170}
public/css/file-form.css+103 -0
@@ -25,6 +25,16 @@
2525 border-radius: 15px;
2626}
2727
28+.mes .mes_file_wrapper:empty {
29+ display: none;
30+}
31+
32+.mes .mes_file_wrapper {
33+ display: flex;
34+ flex-direction: column;
35+ gap: 0.5em;
36+}
37+
2838.mes_file_container .right_menu_button {
2939 padding-right: 0;
3040}
@@ -55,3 +65,96 @@
5565 display: flex;
5666 text-align: left;
5767}
68+
69+/* Audio Player Styles */
70+.mes_audio_container {
71+ cursor: default;
72+ display: flex;
73+ width: fit-content;
74+ min-width: min(350px, 100%);
75+ max-width: 100%;
76+ background-color: var(--white20a);
77+ border: 2px solid var(--SmartThemeBorderColor);
78+ padding: 0.5em 1em;
79+ border-radius: 15px;
80+}
81+
82+.mes_audio_container .mes_img_swipes {
83+ position: unset;
84+ opacity: unset;
85+ background: none;
86+ padding: 0;
87+}
88+
89+.mes_audio_container .mes_img_swipes .right_menu_button {
90+ filter: brightness(75%);
91+ text-shadow: none;
92+}
93+
94+.mes_audio_container .mes_img_swipes .mes_img_swipe_counter {
95+ filter: none;
96+ text-shadow: none;
97+}
98+
99+.audio-player {
100+ display: flex;
101+ flex-direction: column;
102+ gap: 5px;
103+}
104+
105+.audio-player-header {
106+ display: flex;
107+ flex-direction: row;
108+ gap: 15px;
109+ align-items: center;
110+ justify-content: space-between;
111+}
112+
113+.audio-player-title {
114+ overflow: hidden;
115+ text-overflow: ellipsis;
116+ white-space: nowrap;
117+}
118+
119+.audio-player-controls {
120+ display: flex;
121+ align-items: center;
122+ gap: 10px;
123+}
124+
125+.audio-player-play-pause,
126+.audio-player-volume {
127+ background: none;
128+ border: none;
129+}
130+
131+.audio-player-time-separator {
132+ font-size: 0.9em;
133+ text-align: center;
134+}
135+
136+.audio-player-current-time,
137+.audio-player-total-time {
138+ font-size: 0.9em;
139+ color: var(--SmartThemeQuoteColor);
140+ min-width: 40px;
141+ text-align: center;
142+}
143+
144+.audio-player-progress {
145+ flex: 1;
146+ height: 6px;
147+ background-color: var(--black30a);
148+ border-radius: 3px;
149+ cursor: pointer;
150+ position: relative;
151+ overflow: hidden;
152+}
153+
154+.audio-player-progress-bar {
155+ height: 100%;
156+ background-color: var(--SmartThemeEmColor);
157+ border-radius: 3px;
158+ transition: width 0.1s linear;
159+ width: 0%;
160+}
public/css/toggle-dependent.css+18 -0
@@ -544,3 +544,21 @@ label[for="bind_preset_to_connection"]:has(input:checked) {
544544#openai_settings [data-cc-toggle="false"]>.icon-unsupported {
545545 color: var(--golden);
546546}
547+
548+#advanced-formatting-cc-notice {
549+ display: none;
550+ gap: 5px;
551+ align-items: baseline;
552+ margin: 5px 0;
553+ padding: 5px 10px;
554+}
555+
556+#top-settings-holder:has(#main_api option[value="openai"]:checked) #advanced-formatting-cc-notice {
557+ display: flex;
558+}
559+
560+#top-settings-holder:has(#main_api option[value="openai"]:checked) #AdvancedFormatting [data-cc-null] {
561+ opacity: 0.5;
562+ filter: grayscale(0.5);
563+ cursor: not-allowed;
564+}
public/css/world-info.css+8 -0
@@ -237,6 +237,14 @@
237237 display: none;
238238}
239239
240+.world_entry .select2-selection--multiple,
241+.world_entry textarea.keyprimarytextpole,
242+.world_entry textarea.keysecondarytextpole {
243+ max-height: 160px;
244+ overflow-y: auto;
245+ overflow-x: hidden;
246+}
247+
240248span.select2-container .select2-selection__choice__display:has(> .regex_item),
241249span.select2-container .select2-results__option:has(> .result_block .regex_item) {
242250 background-color: #D27D2D30;
public/global.d.ts+84 -0
@@ -2,12 +2,96 @@ import libs from './lib';
22import getContext from './scripts/st-context';
33import { power_user } from './scripts/power-user';
44import { QuickReplyApi } from './scripts/extensions/quick-reply/api/QuickReplyApi';
5+import { oai_settings } from './scripts/openai';
6+import { textgenerationwebui_settings } from './scripts/textgen-settings';
7+import { FileAttachment } from './scripts/chats';
8+import { ReasoningMessageExtra } from './scripts/reasoning';
59
610declare global {
711 // Custom types
812 type InstructSettings = typeof power_user.instruct;
913 type ContextSettings = typeof power_user.context;
1014 type ReasoningSettings = typeof power_user.reasoning;
15+ type ChatCompletionSettings = typeof oai_settings;
16+ type TextCompletionSettings = typeof textgenerationwebui_settings;
17+ type MessageTimestamp = string | number | Date;
18+
19+ interface ChatMessage {
20+ name?: string;
21+ mes?: string;
22+ title?: string;
23+ gen_started?: MessageTimestamp;
24+ gen_finished?: MessageTimestamp;
25+ send_date?: MessageTimestamp;
26+ is_user?: boolean;
27+ is_system?: boolean;
28+ force_avatar?: string;
29+ original_avatar?: string;
30+ swipes?: string[];
31+ swipe_info?: Record<string, any>;
32+ swipe_id?: number;
33+ extra?: ChatMessageExtra & Partial<ReasoningMessageExtra> & Record<string, any>;
34+ };
35+
36+ interface ChatMessageExtra {
37+ bias?: string;
38+ uses_system_ui?: boolean;
39+ memory?: string;
40+ display_text?: string;
41+ reasoning_display_text?: string;
42+ tool_invocations?: any[];
43+ title?: string;
44+ isSmallSys?: boolean;
45+ token_count?: number;
46+ files?: FileAttachment[];
47+ inline_image?: boolean;
48+ media_display?: string;
49+ media_index?: number;
50+ media?: MediaAttachment[],
51+ /** @deprecated Use `files` instead */
52+ file?: FileAttachment;
53+ /** @deprecated Use `media` instead */
54+ image?: string;
55+ /** @deprecated Use `media` instead */
56+ video?: string;
57+ /** @deprecated Use `media` with `media_display = 'gallery'` instead */
58+ image_swipes?: string[];
59+ /** @deprecated Use `MediaAttachment.append_title` instead */
60+ append_title?: boolean;
61+ /** @deprecated Use `MediaAttachment.generation_type` instead */
62+ generationType?: number;
63+ /** @deprecated Use `MediaAttachment.negative` instead */
64+ negative?: string;
65+ }
66+
67+ type MediaAttachment = MediaAttachmentProps & ImageGenerationAttachmentProps & ImageCaptionAttachmentProps;
68+
69+ interface MediaAttachmentProps {
70+ url: string;
71+ title?: string;
72+ type: string;
73+ source?: string;
74+ }
75+
76+ interface ImageGenerationAttachmentProps {
77+ generation_type?: number;
78+ negative?: string;
79+ }
80+
81+ interface ImageCaptionAttachmentProps {
82+ /** Append title to the message text in case of non-inline captions. */
83+ append_title?: boolean;
84+ /** Marker for captioned images to prevent auto-caption from firing again. */
85+ captioned?: boolean;
86+ }
87+
88+ /** Media playback state */
89+ interface MediaState {
90+ /** Current playback time */
91+ currentTime: number;
92+ /** Whether the media is paused */
93+ paused: boolean;
94+ }
1195
1296 // Global namespace modules
1397 interface Window {
public/img/siliconflow.svg+4 -0
@@ -0,0 +1,4 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<svg width="142.22" height="68.010002" viewBox="0 0 142.22 68.010002" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
3+ <path d="M 136.05,0 H 74.21 c -3.42,0 -6.18,2.77 -6.18,6.18 v 18.55 c 0,3.42 -2.77,6.18 -6.18,6.18 H 6.18 C 2.76,30.91 0,33.68 0,37.09 v 24.74 c 0,3.42 2.77,6.18 6.18,6.18 h 61.84 c 3.42,0 6.18,-2.77 6.18,-6.18 V 43.28 c 0,-3.42 2.77,-6.18 6.18,-6.18 h 55.66 c 3.42,0 6.18,-2.77 6.18,-6.18 V 6.18 C 142.22,2.76 139.45,0 136.04,0 Z" />
4+</svg>
public/img/zai.svg+3 -0
@@ -0,0 +1,3 @@
1+<svg version="1.1" x="0" y="0" xml:space="preserve" id="svg6" width="18.60156" height="15.82031" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.60156 15.82031">
2+ <path id="path2" d="M.4707 0v2.33008h7.09961c.36 0 .7004-.1807.9004-.4707L9.77147.00977S.4607.01.4707 0zm10.68946.00977L0 15.8203h7.4414L18.60157.00977Zm-.11914 13.48046c-.36 0-.7004.1807-.9004.4707l-1.31054 1.85938h9.30078v-2.33008z"/>
3+</svg>
public/index.html+266 -133
@@ -648,7 +648,7 @@
648648 Max Response Length (tokens)
649649 </div>
650650 <div class="wide100p">
651651 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536128000" step="1">
652652 </div>
653653 </div>
654654 <div class="range-block" data-source="openai,custom,xai,aimlapi,moonshot,azure_openai">
@@ -693,7 +693,7 @@
693693 </span>
694694 </div>
695695 </div>
696696 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">
697697 <div class="range-block-title" data-i18n="Temperature">
698698 Temperature
699699 </div>
@@ -706,7 +706,7 @@
706706 </div>
707707 </div>
708708 </div>
709709 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">
710710 <div class="range-block-title" data-i18n="Frequency Penalty">
711711 Frequency Penalty
712712 </div>
@@ -719,7 +719,7 @@
719719 </div>
720720 </div>
721721 </div>
722722 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">
723723 <div class="range-block-title" data-i18n="Presence Penalty">
724724 Presence Penalty
725725 </div>
@@ -745,7 +745,7 @@
745745 </div>
746746 </div>
747747 </div>
748748 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">
749749 <div class="range-block-title" data-i18n="Top P">
750750 Top P
751751 </div>
@@ -1246,12 +1246,12 @@
12461246 <div class="fa-solid fa-circle-info opacity50p" title="Customize displayed samplers or add custom samplers." data-i18n="[title]Customize displayed samplers or add custom samplers."></div>
12471247 </small>
12481248 </div>
12491249 <div data-tg-type="mancer, vllm, aphrodite, tabby, infermaticai" data-tg-samplers="n" class="flex-container flexFlowColumn alignitemscenter flexBasis100p flexGrow flexShrink gap0">
12501250 <small data-i18n="Multiple swipes per generation">Multiple swipes per generation</small>
12511251 <input type="number" id="n_textgenerationwebui" class="text_pole textAlignCenter" min="1" value="1" step="1" />
12521252 </div>
12531253 <div class="flex-container gap10h5v justifyCenter">
12541254 <div data-tg-samplers="temp" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12551255 <small>
12561256 <span data-i18n="Temperature">Temperature</span>
12571257 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Temperature controls the randomness in token selection" title="Temperature controls the randomness in token selection:&#13;- low temperature (<1.0) leads to more predictable text, favoring higher probability tokens.&#13;- high temperature (>1.0) increases creativity and diversity in the output by giving lower probability tokens a better chance.&#13;Set to 1.0 for the original probabilities."></div>
@@ -1260,7 +1260,7 @@
12601260 <input class="neo-range-input" type="number" min="0.0" max="5.0" step="0.01" data-for="temp_textgenerationwebui" id="temp_counter_textgenerationwebui">
12611261 </div>
12621262 <!-- Note: "except" mode = show for all BUT types in data-tg-type -->
12631263 <div data-tg-type-mode="except" data-tg-type="generic" data-tg-samplers="top_k" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12641264 <small>
12651265 <span data-i18n="Top K">Top K</span>
12661266 <div class="fa-solid fa-circle-info opacity50p" title="Top K sets a maximum amount of top tokens that can be chosen from.&#13;E.g Top K is 20, this means only the 20 highest ranking tokens will be kept (regardless of their probabilities being diverse or limited).&#13;Set to 0 (or -1, depending on your backend) to disable." data-i18n="[title]Top_K_desc"></div>
@@ -1268,7 +1268,7 @@
12681268 <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="200" step="1">
12691269 <input class="neo-range-input" type="number" min="-1" max="200" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui">
12701270 </div>
12711271 <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12721272 <small>
12731273 <span data-i18n="Top P">Top P</span>
12741274 <div class="fa-solid fa-circle-info opacity50p" title="Top P (a.k.a. nucleus sampling) adds up all the top tokens required to add up to the target percentage.&#13;E.g If the Top 2 tokens are both 25%, and Top P is 0.50, only the Top 2 tokens are considered.&#13;Set to 1.0 to disable." data-i18n="[title]Top_P_desc"></div>
@@ -1276,7 +1276,7 @@
12761276 <input class="neo-range-slider" type="range" id="top_p_textgenerationwebui" name="volume" min="0" max="1" step="0.01">
12771277 <input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="top_p_textgenerationwebui" id="top_p_counter_textgenerationwebui">
12781278 </div>
12791279 <div data-tg-type-mode="except" data-tg-type="generic" data-tg-samplers="typical_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12801280 <small>
12811281 <span data-i18n="Typical P">Typical P</span>
12821282 <div class="fa-solid fa-circle-info opacity50p" title="Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set.&#13;It maintains tokens whose cumulative probability is close to a predefined threshold (e.g., 0.5), emphasizing those with average information content.&#13;Set to 1.0 to disable." data-i18n="[title]Typical_P_desc"></div>
@@ -1284,7 +1284,7 @@
12841284 <input class="neo-range-slider" type="range" id="typical_p_textgenerationwebui" name="volume" min="0" max="1" step="0.01">
12851285 <input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="typical_p_textgenerationwebui" id="typical_p_counter_textgenerationwebui">
12861286 </div>
12871287 <div data-tg-type-mode="except" data-tg-type="generic" data-tg-samplers="min_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12881288 <small>
12891289 <span data-i18n="Min P">Min P</span>
12901290 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Min_P_desc" title="Min P sets a base minimum probability. This is scaled according to the top token's probability.&#13;E.g If Top token is 80% probability, and Min P is 0.1, only tokens higher than 8% would be considered.&#13;Set to 0 to disable."></div>
@@ -1292,7 +1292,7 @@
12921292 <input class="neo-range-slider" type="range" id="min_p_textgenerationwebui" name="volume" min="0" max="1" step="0.001">
12931293 <input class="neo-range-input" type="number" min="0" max="1" step="0.001" data-for="min_p_textgenerationwebui" id="min_p_counter_textgenerationwebui">
12941294 </div>
12951295 <div data-tg-type-mode="except" data-tg-type="generic,llamacpp" data-tg-samplers="top_a" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12961296 <small>
12971297 <span data-i18n="Top A">Top A</span>
12981298 <div class="fa-solid fa-circle-info opacity50p" title="Top A sets a threshold for token selection based on the square of the highest token probability.&#13;E.g if the Top-A value is 0.2 and the top token's probability is 50%, tokens with probabilities below 5% (0.2 * 0.5^2) are excluded.&#13;Set to 0 to disable." data-i18n="[title]Top_A_desc"></div>
@@ -1300,7 +1300,7 @@
13001300 <input class="neo-range-slider" type="range" id="top_a_textgenerationwebui" name="volume" min="0" max="1" step="0.01">
13011301 <input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="top_a_textgenerationwebui" id="top_a_counter_textgenerationwebui">
13021302 </div>
13031303 <div data-tg-type-mode="except" data-tg-type="generic,llamacpp" data-tg-samplers="tfs" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13041304 <small>
13051305 <span data-i18n="TFS">TFS</span>
13061306 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Tail_Free_Sampling_desc" title="Tail-Free Sampling (TFS) searches for a tail of low-probability tokens in the distribution,&#13;by analyzing the rate of change in token probabilities using derivatives. It retains tokens up to a threshold (e.g., 0.3) based on the normalized second derivative.&#13;The closer to 0, the more discarded tokens. Set to 1.0 to disable."></div>
@@ -1308,7 +1308,7 @@
13081308 <input class="neo-range-slider" type="range" id="tfs_textgenerationwebui" name="volume" min="0" max="1" step="0.001">
13091309 <input class="neo-range-input" type="number" min="0" max="1" step="0.001" data-for="tfs_textgenerationwebui" id="tfs_counter_textgenerationwebui">
13101310 </div>
13111311 <div data-tg-type="ooba,mancer,aphrodite" data-tg-samplers="epsilon_cutoff" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13121312 <small>
13131313 <span data-i18n="Epsilon Cutoff">Epsilon Cutoff</span>
13141314 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled" title="Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled.&#13;In units of 1e-4; a reasonable value is 3.&#13;Set to 0 to disable."></div>
@@ -1316,7 +1316,7 @@
13161316 <input class="neo-range-slider" type="range" id="epsilon_cutoff_textgenerationwebui" name="volume" min="0" max="9" step="0.01">
13171317 <input class="neo-range-input" type="number" min="0" max="9" step="0.01" data-for="epsilon_cutoff_textgenerationwebui" id="epsilon_cutoff_counter_textgenerationwebui">
13181318 </div>
13191319 <div data-tg-type="ooba,aphrodite,koboldcpp,llamacpp" data-tg-samplers="nsigma" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13201320 <small>
13211321 <span data-i18n="Top nsigma">Top nsigma</span>
13221322 <div class="fa-solid fa-circle-info opacity50p" title="A sampling method that filters logits based on their statistical properties. It keeps tokens within n standard deviations of the maximum logit value, providing a simpler alternative to top-p/top-k sampling while maintaining sampling stability across different temperatures."></div>
@@ -1324,7 +1324,7 @@
13241324 <input class="neo-range-slider" type="range" id="nsigma_textgenerationwebui" name="volume" min="0" max="5" step="0.01">
13251325 <input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="nsigma_textgenerationwebui" id="nsigma_counter_textgenerationwebui">
13261326 </div>
13271327 <div data-tg-type="llamacpp" data-tg-samplers="min_keep" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13281328 <small>
13291329 <span data-i18n="Min Keep">Min Keep</span>
13301330 <div class="fa-solid fa-circle-info opacity50p" title="A sampling modifier that ensures that truncation samplers such as top-p, min-p, typical-p, and xtc return at least this many tokens. Set to 0 to disable."></div>
@@ -1332,7 +1332,7 @@
13321332 <input class="neo-range-slider" type="range" id="min_keep_textgenerationwebui" name="volume" min="0" max="50" step="1">
13331333 <input class="neo-range-input" type="number" min="0" max="50" step="1" data-for="min_keep_textgenerationwebui" id="min_keep_counter_textgenerationwebui">
13341334 </div>
13351335 <div data-tg-type="ooba,mancer,aphrodite" data-tg-samplers="eta_cutoff" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13361336 <small>
13371337 <span data-i18n="Eta Cutoff">Eta Cutoff</span>
13381338 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Eta_Cutoff_desc" title="Eta cutoff is the main parameter of the special Eta Sampling technique.&#13;In units of 1e-4; a reasonable value is 3.&#13;Set to 0 to disable.&#13;See the paper Truncation Sampling as Language Model Desmoothing by Hewitt et al. (2022) for details."></div>
@@ -1340,62 +1340,62 @@
13401340 <input class="neo-range-slider" type="range" id="eta_cutoff_textgenerationwebui" name="volume" min="0" max="20" step="0.01">
13411341 <input class="neo-range-input" type="number" min="0" max="20" step="0.01" data-for="eta_cutoff_textgenerationwebui" id="eta_cutoff_counter_textgenerationwebui">
13421342 </div>
13431343 <div data-tg-type-mode="except" data-tg-type="generic" data-tg-samplers="rep_pen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13441344 <small data-i18n="rep.pen">Repetition Penalty</small>
13451345 <input class="neo-range-slider" type="range" id="rep_pen_textgenerationwebui" name="volume" min="1" max="3" step="0.01">
13461346 <input class="neo-range-input" type="number" min="1" max="3" step="0.01" data-for="rep_pen_textgenerationwebui" id="rep_pen_counter_textgenerationwebui">
13471347 </div>
13481348 <div data-tg-type="ooba, koboldcpp, tabby, ollama, llamacpp" data-tg-samplers="rep_pen_range" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13491349 <small data-i18n="rep.pen range">Rep Pen Range</small>
13501350 <input class="neo-range-slider" type="range" id="rep_pen_range_textgenerationwebui" name="volume" min="-1" max="8192" step="1">
13511351 <input class="neo-range-input" type="number" min="-1" max="8192" step="1" data-for="rep_pen_range_textgenerationwebui" id="rep_pen_range_counter_textgenerationwebui">
13521352 </div>
13531353 <div data-tg-type="koboldcpp" data-tg-samplers="rep_pen_slope" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13541354 <small data-i18n="Rep. Pen. Slope">Rep Pen Slope</small>
13551355 <input class="neo-range-slider" type="range" id="rep_pen_slope_textgenerationwebui" name="volume" min="0" max="10" step="0.01">
13561356 <input class="neo-range-input" type="number" min="0" max="10" step="0.01" data-for="rep_pen_slope_textgenerationwebui" id="rep_pen_slope_counter_textgenerationwebui">
13571357 </div>
13581358 <div data-tg-type="tabby" data-tg-samplers="rep_pen_decay" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13591359 <small data-i18n="rep.pen decay">Rep Pen Decay</small>
13601360 <input class="neo-range-slider" type="range" id="rep_pen_decay_textgenerationwebui" name="volume" min="-1" max="8192" step="1">
13611361 <input class="neo-range-input" type="number" min="-1" max="8192" step="1" data-for="rep_pen_decay_textgenerationwebui" id="rep_pen_decay_counter_textgenerationwebui">
13621362 </div>
13631363 <div data-tg-type="ooba" data-tg-samplers="encoder_rep_pen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13641364 <small data-i18n="Encoder Rep. Pen.">Encoder Penalty</small>
13651365 <input class="neo-range-slider" type="range" id="encoder_rep_pen_textgenerationwebui" name="volume" min="0.8" max="1.5" step="0.01" />
13661366 <input class="neo-range-input" type="number" min="0.8" max="1.5" step="0.01" data-for="encoder_rep_pen_textgenerationwebui" id="encoder_rep_pen_counter_textgenerationwebui">
13671367 </div>
13681368 <div data-tg-samplers="freq_pen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13691369 <small data-i18n="Frequency Penalty">Frequency Penalty</small>
13701370 <input class="neo-range-slider" type="range" id="freq_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" />
13711371 <input class="neo-range-input" type="number" data-for="freq_pen_textgenerationwebui" min="-2" max="2" step="0.01" id="freq_pen_counter_textgenerationwebui">
13721372 </div>
13731373 <div data-tg-samplers="presence_pen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13741374 <small data-i18n="Presence Penalty">Presence Penalty</small>
13751375 <input class="neo-range-slider" type="range" id="presence_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" />
13761376 <input class="neo-range-input" type="number" min="-2" max="2" step="0.01" data-for="presence_pen_textgenerationwebui" id="presence_pen_counter_textgenerationwebui">
13771377 </div>
13781378 <div data-tg-type="aphrodite, ooba" data-tg-samplers="no_repeat_ngram_size" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13791379 <small data-i18n="No Repeat Ngram Size">No Repeat Ngram Size</small>
13801380 <input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">
13811381 <input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="no_repeat_ngram_size_textgenerationwebui" id="no_repeat_ngram_size_counter_textgenerationwebui">
13821382 </div>
13831383 <div data-tg-type="tabby, aphrodite" data-tg-samplers="skew" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13841384 <small data-i18n="Skew">Skew</small>
13851385 <input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />
13861386 <input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui">
13871387 </div>
13881388 <div data-tg-type="mancer, ooba, tabby, dreamgen, infermaticai, aphrodite" data-tg-samplers="min_length" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13891389 <small data-i18n="Min Length">Min Length</small>
13901390 <input class="neo-range-slider" type="range" id="min_length_textgenerationwebui" name="volume" min="0" max="2000" step="1" />
13911391 <input class="neo-range-input" type="number" min="0" max="2000" step="1" data-for="min_length_textgenerationwebui" id="min_length_counter_textgenerationwebui">
13921392 </div>
13931393 <div data-tg-type="ooba" data-tg-samplers="max_tokens_second" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13941394 <small data-i18n="Max Tokens Second">Maximum tokens/second</small>
13951395 <input class="neo-range-slider" type="range" id="max_tokens_second_textgenerationwebui" name="volume" min="0" max="20" step="1" />
13961396 <input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="max_tokens_second_textgenerationwebui" id="max_tokens_second_counter_textgenerationwebui">
13971397 </div>
13981398 <div data-tg-type="mancer, ooba, koboldcpp, aphrodite, tabby" data-tg-samplers="smoothing_factor" id="smoothingBlock" name="smoothingBlock" class="wide100p">
13991399 <h4 class="wide100p textAlignCenter">
14001400 <label data-i18n="Smooth Sampling">Smooth Sampling</label>
14011401 <div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Smooth_Sampling_desc" title="Allows you to use quadratic/cubic transformations to adjust the distribution. Lower Smoothing Factor values will be more creative, usually between 0.2-0.3 is the sweetspot (assuming the curve = 1). Higher Smoothing Curve values will make the curve steeper, which will punish low probability choices more aggressively. 1.0 curve is equivalent to only using Smoothing Factor."></div>
@@ -1406,7 +1406,7 @@
14061406 <input class="neo-range-slider" type="range" id="smoothing_factor_textgenerationwebui" name="volume" min="0" max="10" step="0.01" />
14071407 <input class="neo-range-input" type="number" min="0" max="10" step="0.01" data-for="smoothing_factor_textgenerationwebui" id="smoothing_factor_counter_textgenerationwebui">
14081408 </div>
14091409 <div data-tg-type="mancer, ooba, koboldcpp, aphrodite" data-tg-samplers="smoothing_curve" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
14101410 <small data-i18n="Smoothing Curve">Smoothing Curve</small>
14111411 <input class="neo-range-slider" type="range" id="smoothing_curve_textgenerationwebui" name="volume" min="1" max="10" step="0.01" />
14121412 <input class="neo-range-input" type="number" min="1" max="10" step="0.01" data-for="smoothing_curve_textgenerationwebui" id="smoothing_curve_counter_textgenerationwebui">
@@ -1414,7 +1414,7 @@
14141414 </div>
14151415 </div>
14161416
14171417 <div data-tg-type="koboldcpp, aphrodite, mancer, tabby, ooba, llamacpp" data-tg-samplers="xtc_probability" id="xtc_block" class="wide100p">
14181418 <h4 class="wide100p textAlignCenter">
14191419 <label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>
14201420 <a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">
@@ -1422,7 +1422,7 @@
14221422 </a>
14231423 </h4>
14241424 <div class="flex-container flexFlowRow gap10px flexShrink">
14251425 <div data-tg-samplers="xtc_threshold" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
14261426 <small data-i18n="Threshold">Threshold</small>
14271427 <input class="neo-range-slider" type="range" id="xtc_threshold_textgenerationwebui" name="volume" min="0" max="0.5" step="0.01" />
14281428 <input class="neo-range-input" type="number" min="0" max="0.5" step="0.01" data-for="xtc_threshold_textgenerationwebui" id="xtc_threshold_counter_textgenerationwebui">
@@ -1435,7 +1435,7 @@
14351435 </div>
14361436 </div>
14371437
14381438 <div data-tg-type="aphrodite, mancer, ooba, koboldcpp, tabby, llamacpp, dreamgen" data-tg-samplers="dry_multiplier" id="dryBlock" class="wide100p">
14391439 <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">
14401440 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
14411441 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -1448,30 +1448,30 @@
14481448 <input class="neo-range-slider" type="range" id="dry_multiplier_textgenerationwebui" min="0" max="5" step="0.01" />
14491449 <input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="dry_multiplier_textgenerationwebui" id="dry_multiplier_counter_textgenerationwebui">
14501450 </div>
14511451 <div data-tg-samplers="dry_base" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" title="Controls how fast the penalty grows with increasing sequence length." data-i18n="[title]DRY_Base_desc">
14521452 <small data-i18n="Base">Base</small>
14531453 <input class="neo-range-slider" type="range" id="dry_base_textgenerationwebui" min="1" max="4" step="0.01" />
14541454 <input class="neo-range-input" type="number" min="1" max="4" step="0.01" data-for="dry_base_textgenerationwebui" id="dry_base_counter_textgenerationwebui">
14551455 </div>
14561456 <div data-tg-samplers="dry_allowed_length" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" title="Longest sequence that can be repeated without being penalized." data-i18n="[title]DRY_Allowed_Length_desc">
14571457 <small data-i18n="Allowed Length">Allowed Length</small>
14581458 <input class="neo-range-slider" type="range" id="dry_allowed_length_textgenerationwebui" min="1" max="20" step="1" />
14591459 <input class="neo-range-input" type="number" min="1" max="20" step="1" data-for="dry_allowed_length_textgenerationwebui" id="dry_allowed_length_counter_textgenerationwebui">
14601460 </div>
14611461 <div data-tg-samplers="dry_penalty_last_n" class="alignItemsCenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" data-tg-type="llamacpp, koboldcpp, tabby, aphrodite">
14621462 <small data-i18n="Penalty Range">Penalty Range</small>
14631463 <input class="neo-range-slider" type="range" id="dry_penalty_last_n_textgenerationwebui" min="0" max="8192" step="1" />
14641464 <input class="neo-range-input" type="number" min="0" max="8192" step="1" data-for="dry_penalty_last_n_textgenerationwebui" id="dry_penalty_last_n_counter_textgenerationwebui">
14651465 </div>
14661466 </div>
14671467 <div data-tg-samplers="dry_sequence_breakers" class="range-block marginTop5" title="Tokens across which sequence matching is not continued. Specified as a comma-separated list of quoted strings." data-i18n="[title]DRY_Sequence_Breakers_desc">
14681468 <div class="range-block-title textAlignCenter flex-container justifyCenter alignitemscenter">
14691469 <small data-i18n="Sequence Breakers">Sequence Breakers</small>
14701470 </div>
14711471 <textarea id="dry_sequence_breakers_textgenerationwebui" class="text_pole textarea_compact" name="sequence_breakers" rows="3" data-i18n="[placeholder]JSON-serialized array of strings." placeholder="JSON-serialized array of strings."></textarea>
14721472 </div>
14731473 </div>
14741474 <div data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp, aphrodite" data-tg-samplers="dynatemp" id="dynatemp_block_ooba" class="wide100p">
14751475 <h4 class="wide100p textAlignCenter">
14761476 <div class="flex-container alignitemscenter justifyCenter">
14771477 <div class="checkbox_label" for="dynatemp_textgenerationwebui">
@@ -1482,24 +1482,24 @@
14821482 </div>
14831483 </h4>
14841484 <div class="flex-container flexFlowRow alignitemscenter gap10px flexShrink">
14851485 <div data-tg-samplers="min_temp" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexGrow flexShrink gap0">
14861486 <small data-i18n="Minimum Temp">Minimum Temp</small>
14871487 <input class="neo-range-slider" type="range" id="min_temp_textgenerationwebui" name="volume" min="0" max="5" step="0.01" />
14881488 <input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="min_temp_textgenerationwebui" id="min_temp_counter_textgenerationwebui">
14891489 </div>
14901490 <div data-tg-samplers="max_temp" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexGrow flexShrink gap0">
14911491 <small data-i18n="Maximum Temp">Maximum Temp</small>
14921492 <input class="neo-range-slider" type="range" id="max_temp_textgenerationwebui" name="volume" min="0" max="5" step="0.01" />
14931493 <input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="max_temp_textgenerationwebui" id="max_temp_counter_textgenerationwebui">
14941494 </div>
14951495 <div data-tg-samplers="dynatemp_exponent" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexGrow flexShrink gap0">
14961496 <small data-i18n="Exponent">Exponent</small>
14971497 <input class="neo-range-slider" type="range" id="dynatemp_exponent_textgenerationwebui" name="volume" min="0.001" max="10" step="0.001" />
14981498 <input class="neo-range-input" type="number" min="0.001" max="10" step="0.001" data-for="dynatemp_exponent_textgenerationwebui" id="dynatemp_exponent_counter_textgenerationwebui">
14991499 </div>
15001500 </div>
15011501 </div>
15021502 <div data-tg-type="ooba,infermaticai,koboldcpp,llamacpp,tabby" data-tg-samplers="mirostat_mode" id="mirostat_block_ooba" class="wide100p">
15031503 <h4 class="wide100p textAlignCenter">
15041504 <label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label>
15051505 <div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Mirostat_desc" title="Mirostat is a thermostat for output perplexity.&#13;Mirostat matches the output perplexity to that of the input, thus avoiding the repetition trap&#13;(where, as the autoregressive inference produces text, the perplexity of the output tends toward zero)&#13;and the confusion trap (where the perplexity diverges).&#13;For details, see the paper Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020).&#13;Mode chooses the Mirostat version. 0=disable, 1=Mirostat 1.0 (llama.cpp only), 2=Mirostat 2.0.&#13;Tau = Variability parameter for Mirostat outputs.&#13;Eta = Learning rate of Mirostat."></div>
@@ -1510,19 +1510,19 @@
15101510 <input class="neo-range-slider" type="range" id="mirostat_mode_textgenerationwebui" name="volume" min="0" max="2" step="1" />
15111511 <input class="neo-range-input" type="number" min="0" max="2" step="1" data-for="mirostat_mode_textgenerationwebui" id="mirostat_mode_counter_textgenerationwebui">
15121512 </div>
15131513 <div data-tg-samplers="mirostat_tau" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexGrow flexShrink gap0">
15141514 <small data-i18n="Mirostat Tau">Tau</small>
15151515 <input class="neo-range-slider" type="range" id="mirostat_tau_textgenerationwebui" name="volume" min="0" max="20" step="0.01" />
15161516 <input class="neo-range-input" type="number" min="0" max="20" step="0.01" data-for="mirostat_tau_textgenerationwebui" id="mirostat_tau_counter_textgenerationwebui">
15171517 </div>
15181518 <div data-tg-samplers="mirostat_eta" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexGrow flexShrink gap0">
15191519 <small data-i18n="Mirostat Eta">Eta</small>
15201520 <input class="neo-range-slider" type="range" id="mirostat_eta_textgenerationwebui" name="volume" min="0" max="1" step="0.01" />
15211521 <input class="neo-range-input" type="number" min="0" max="1" step="0.01" data-for="mirostat_eta_textgenerationwebui" id="mirostat_eta_counter_textgenerationwebui">
15221522 </div>
15231523 </div>
15241524 </div>
15251525 <div data-tg-type="ooba" data-tg-samplers="num_beams" id="beamSearchBlock" name="beamSearchBlock" class="wide100p">
15261526 <h4 class="wide100p textAlignCenter">
15271527 <label>
15281528 <span data-i18n="Beam search">Beam Search</span>
@@ -1538,7 +1538,7 @@
15381538 <input class="neo-range-slider" type="range" id="num_beams_textgenerationwebui" name="volume" min="1" max="20" step="1" />
15391539 <input class="neo-range-input" type="number" min="1" max="20" step="1" data-for="num_beams_textgenerationwebui" id="num_beams_counter_textgenerationwebui">
15401540 </div>
15411541 <div data-tg-samplers="length_penalty" class="alignitemscenter flex-container marginBot5 flexFlowColumn flexGrow flexShrink gap0">
15421542 <label>
15431543 <small data-i18n="Length Penalty">Length Penalty</small>
15441544 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Penalize sequences based on their length." title="Penalize sequences based on their length."></div>
@@ -1546,7 +1546,7 @@
15461546 <input class="neo-range-slider" type="range" id="length_penalty_textgenerationwebui" name="volume" min="-5" max="5" step="0.1" />
15471547 <input class="neo-range-input" type="number" min="-5" max="5" step="0.1" data-for="length_penalty_textgenerationwebui" id="length_penalty_counter_textgenerationwebui">
15481548 </div>
15491549 <div data-tg-samplers="early_stopping" class="">
15501550 <label class="checkbox_label" for="early_stopping_textgenerationwebui">
15511551 <input type="checkbox" id="early_stopping_textgenerationwebui" />
15521552 <small data-i18n="Early Stopping">Early Stopping</small>
@@ -1555,7 +1555,7 @@
15551555 </div>
15561556 </div>
15571557 </div>
15581558 <div data-tg-type="ooba" data-tg-samplers="penalty_alpha" id="contrastiveSearchBlock" name="contrastiveSearchBlock" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
15591559 <h4 class="textAlignCenter">
15601560 <span data-i18n="Contrastive search">Contrastive Search</span>
15611561 <div class=" fa-solid fa-circle-info opacity50p " data-i18n="Contrastive_search_txt" title="A sampler that encourages diversity while maintaining coherence, by exploiting the isotropicity of the representation space of most LLMs. For details, see the paper A Contrastive Framework for Neural Text Generation by Su et al. (2022)."></div>
@@ -1571,64 +1571,64 @@
15711571 </div>
15721572 <div name="checkboxes" class="flex-container flexBasis48p justifyCenter flexGrow flexShrink ">
15731573 <div class="flex-container flexFlowColumn marginTop5">
15741574 <label data-tg-type="ooba" data-tg-samplers="do_sample" class="checkbox_label flexGrow flexShrink" for="do_sample_textgenerationwebui">
15751575 <input type="checkbox" id="do_sample_textgenerationwebui" />
15761576 <small data-i18n="Do Sample">Do Sample</small>
15771577 </label>
15781578 <label data-tg-type="ooba, tabby" data-tg-samplers="add_bos_token" class="checkbox_label flexGrow flexShrink" for="add_bos_token_textgenerationwebui">
15791579 <input type="checkbox" id="add_bos_token_textgenerationwebui" />
15801580 <small data-i18n="Add BOS Token">Add BOS Token</small>
15811581 <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>
15821582 </label>
15831583 <label data-tg-type="ooba, llamacpp, tabby, koboldcpp, dreamgen" data-tg-samplers="ban_eos_token" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui">
15841584 <input type="checkbox" id="ban_eos_token_textgenerationwebui" />
15851585 <small data-i18n="Ban EOS Token">Ban EOS Token</small>
15861586 <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ban the eos_token. This forces the model to never end the generation prematurely" title="Ban the eos_token. This forces the model to never end the generation prematurely."></div>
15871587 </label>
15881588 <label data-tg-type="vllm, aphrodite, infermaticai" data-tg-samplers="ignore_eos_token" class="checkbox_label" for="ignore_eos_token_textgenerationwebui">
15891589 <input type="checkbox" id="ignore_eos_token_textgenerationwebui" />
15901590 <small data-i18n="Ignore EOS Token">Ignore EOS Token</small>
15911591 <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ignore the EOS Token even if it generates." title="Ignore the EOS Token even if it generates."></div>
15921592 </label>
15931593 <label data-tg-type-mode="except" data-tg-type="generic" data-tg-samplers="skip_special_tokens" class="checkbox_label flexGrow flexShrink" for="skip_special_tokens_textgenerationwebui">
15941594 <input type="checkbox" id="skip_special_tokens_textgenerationwebui" />
15951595 <small data-i18n="Skip Special Tokens">Skip Special Tokens</small>
15961596 </label>
15971597 <label data-tg-type="openrouter" data-tg-samplers="include_reasoning" class="checkbox_label flexGrow flexShrink" for="include_reasoning_textgenerationwebui">
15981598 <input type="checkbox" id="include_reasoning_textgenerationwebui" />
15991599 <small data-i18n="Request Model Reasoning">Request Model Reasoning</small>
16001600 </label>
16011601 <label data-tg-type="ooba, aphrodite, tabby" data-tg-samplers="temperature_last" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui">
16021602 <input type="checkbox" id="temperature_last_textgenerationwebui" />
16031603 <small data-i18n="Temperature Last">Temperature Last</small>
16041604 <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Temperature_Last_desc" title="Use the temperature sampler last. This is almost always the sensible thing to do.&#13;When enabled: sample the set of plausible tokens first, then apply temperature to adjust their relative probabilities (technically, logits).&#13;When disabled: apply temperature to adjust the relative probabilities of ALL tokens first, then sample plausible tokens from that.&#13;Disabling Temperature Last boosts the probabilities in the tail of the distribution, which tends to amplify the chances of getting an incoherent response."></div>
16051605 </label>
16061606 <label data-tg-type="tabby" data-tg-samplers="speculative_ngram" class="checkbox_label flexGrow flexShrink" for="speculative_ngram_textgenerationwebui">
16071607 <input type="checkbox" id="speculative_ngram_textgenerationwebui" />
16081608 <small data-i18n="Speculative Ngram">Speculative Ngram</small>
16091609 <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Use a different speculative decoding method without a draft model" title="Use a different speculative decoding method without a draft model.&#13;Using a draft model is preferred. Speculative ngram is not as effective."></div>
16101610 </label>
16111611 <label data-tg-type="vllm, aphrodite, infermaticai" data-tg-samplers="spaces_between_special_tokens" class="checkbox_label" for="spaces_between_special_tokens_textgenerationwebui">
16121612 <input type="checkbox" id="spaces_between_special_tokens_textgenerationwebui" />
16131613 <small data-i18n="Spaces Between Special Tokens">Spaces Between Special Tokens</small>
16141614 </label>
16151615 </div>
16161616 </div>
16171617 <div data-tg-type="mancer, ooba, koboldcpp, vllm, aphrodite, llamacpp, ollama, infermaticai, huggingface, generic" data-tg-samplers="seed" class="flex-container flexFlowColumn alignitemscenter flexBasis48p flexGrow flexShrink gap0">
16181618 <label>
16191619 <small data-i18n="Seed">Seed</small>
16201620 <div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Seed_desc" title="A random seed to use for deterministic and reproducable outputs. Set to -1 to use a random seed."></div>
16211621 </label>
16221622 <input type="number" id="seed_textgenerationwebui" class="text_pole textAlignCenter" min="-1" value="-1" />
16231623 </div>
16241624 <div data-tg-type-mode="except" data-tg-type="generic" data-tg-samplers="banned_tokens" id="banned_tokens_block_ooba" class="wide100p">
16251625 <hr class="width100p">
16261626 <div class="range-block-title title_restorable">
16271627 <div>
16281628 <strong data-i18n="Banned Tokens">Banned Tokens/Strings</strong>
16291629 <div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]LLaMA / Mistral / Yi models only" title="Enter sequences you don't want to appear in the output.&#13;Unquoted text will be tokenized in the back end and banned as tokens.&#13;[token ids] will be banned as-is.&#13;Most tokens have a leading space. Use token counter (with the correct tokenizer selected first!) if you are unsure.&#13;Enclose text in double quotes to ban the entire string as a set.&#13;Quoted Strings and [Token ids] must be on their own line."></div>
16301630 </div>
16311631 <label data-tg-samplers="send_banned_tokens" id="send_banned_tokens_label" for="send_banned_tokens_textgenerationwebui" class="checkbox_label">
16321632 <input id="send_banned_tokens_textgenerationwebui" type="checkbox" style="display:none;" />
16331633 <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>
16341634 </label>
@@ -1637,7 +1637,7 @@
16371637 <div class="textAlignCenter">
16381638 <small data-i18n="Global list">Global list</small>
16391639 </div>
16401640 <div data-tg-samplers="global_banned_tokens" class="wide100p marginBot10">
16411641 <textarea id="global_banned_tokens_textgenerationwebui" class="text_pole textarea_compact" name="global_banned_tokens_textgenerationwebui" rows="3" data-i18n="[placeholder]Example: some text [42, 69, 1337]" placeholder='some text as tokens&#10;[420, 69, 1337]&#10;"Some verbatim string"'></textarea>
16421642 </div>
16431643 <div class="textAlignCenter">
@@ -1648,7 +1648,7 @@
16481648 </div>
16491649 </div>
16501650 </div>
16511651 <div data-tg-samplers="logit_bias" class="range-block wide100p" id="logit_bias_block_ooba">
16521652 <div id="logit_bias_textgenerationwebui" class="range-block-title title_restorable">
16531653 <h4>
16541654 <span data-i18n="Logit Bias">Logit Bias</span>
@@ -1665,7 +1665,7 @@
16651665 <div class="logit_bias_list"></div>
16661666 </div>
16671667 </div>
16681668 <div id="cfg_block_ooba" data-tg-type="ooba, tabby" data-tg-samplers="guidance_scale" class="flex-container flexFlowColumn wide100p">
16691669 <hr class="width100p">
16701670 <h4 class="textAlignCenter">
16711671 <span data-i18n="CFG">CFG</span>
@@ -1676,7 +1676,7 @@
16761676 <input class="neo-range-slider" type="range" id="guidance_scale_textgenerationwebui" name="volume" min="0.1" max="4" step="0.05">
16771677 <input class="neo-range-input" type="number" min="0.1" max="4" step="0.05" data-for="guidance_scale_textgenerationwebui" id="guidance_scale_counter_textgenerationwebui">
16781678 </div>
16791679 <div data-tg-samplers="negative_prompt" class="range-block">
16801680 <div class="alignitemscenter justifyCenter flex-container flexShrink">
16811681 <small data-i18n="Negative Prompt">Negative Prompt</small>
16821682 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Used if CFG Scale is unset globally, per chat or character" title="Used if CFG Scale is unset globally, per chat or character"></div>
@@ -1684,7 +1684,7 @@
16841684 <textarea id="negative_prompt_textgenerationwebui" class="wide100p text_pole textarea_compact" name="negative_prompt" rows="3" data-i18n="[placeholder]Add text here that would make the AI generate things you don't want in your outputs." placeholder="Add text here that would make the AI generate things you don't want in your outputs."></textarea>
16851685 </div>
16861686 </div>
16871687 <div id="grammar_block_ooba" data-tg-type="ooba,aphrodite,tabby" data-tg-samplers="grammar_string" class="wide100p">
16881688 <hr class="wide100p">
16891689 <h4 class="wide100p textAlignCenter">
16901690 <div class="flex-container justifyCenter alignitemscenter">
@@ -1697,7 +1697,7 @@
16971697 </h4>
16981698 <textarea id="grammar_string_textgenerationwebui" rows="4" class="text_pole textarea_compact monospace" data-i18n="[placeholder]Type in the desired custom grammar" placeholder="Type in the desired custom grammar"></textarea>
16991699 </div>
17001700 <div id="json_schema_block" data-tg-type="tabby, llamacpp, aphrodite" data-tg-samplers="json_schema" class="wide100p">
17011701 <hr class="wide100p">
17021702 <h4 class="wide100p textAlignCenter"><span data-i18n="JSON Schema">JSON Schema</span>
17031703 <a href="https://json-schema.org/learn/getting-started-step-by-step" target="_blank">
@@ -1708,7 +1708,7 @@
17081708 </h4>
17091709 <textarea id="tabby_json_schema" rows="4" class="text_pole textarea_compact monospace" data-i18n="[placeholder]Type in the desired JSON schema" placeholder="Type in the desired JSON schema"></textarea>
17101710 </div>
17111711 <div id="sampler_order_block_kcpp" data-tg-type="koboldcpp" data-tg-samplers="sampler_order" class="range-block flexFlowColumn wide100p">
17121712 <hr class="wide100p">
17131713 <div class="range-block-title">
17141714 <span data-i18n="Samplers Order">Samplers Order</span>
@@ -1751,7 +1751,7 @@
17511751 <span data-i18n="Load default order">Load default order</span>
17521752 </div>
17531753 </div>
17541754 <div id="sampler_order_block_lcpp" data-tg-type="llamacpp" data-tg-samplers="samplers" class="range-block flexFlowColumn wide100p">
17551755 <hr class="wide100p">
17561756 <h4 class="range-block-title justifyCenter">
17571757 <span data-i18n="Sampler Order">Sampler Order</span>
@@ -1775,7 +1775,7 @@
17751775 <span data-i18n="Load default order">Load default order</span>
17761776 </div>
17771777 </div>
17781778 <div id="sampler_priority_block_ooba" data-tg-type="ooba" data-tg-samplers="sampler_priority" class="range-block flexFlowColumn wide100p">
17791779 <hr class="wide100p">
17801780 <h4 class="range-block-title justifyCenter">
17811781 <span data-i18n="Sampler Priority">Sampler Priority</span>
@@ -1810,7 +1810,7 @@
18101810 <span data-i18n="Load default order">Load default order</span>
18111811 </div>
18121812 </div>
18131813 <div id="sampler_priority_block_aphrodite" data-tg-type="aphrodite" data-tg-samplers="samplers_priorities" class="range-block flexFlowColumn wide100p">
18141814 <hr class="wide100p">
18151815 <h4 class="range-block-title justifyCenter">
18161816 <span data-i18n="Sampler Order">Sampler Order</span>
@@ -1965,7 +1965,7 @@
19651965 </b>
19661966 </div>
19671967 </div>
19681968 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,azure_openai,zai">
19691969 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
19701970 <input id="openai_function_calling" type="checkbox" />
19711971 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1980,7 +1980,7 @@
19801980 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
19811981 </div>
19821982 </div>
19831983 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow">
19841984 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
19851985 <input id="openai_image_inlining" type="checkbox" />
19861986 <span data-i18n="Send inline images">Send inline images</span>
@@ -1990,13 +1990,9 @@
19901990 </div>
19911991 </label>
19921992 <div id="image_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
19931993 <span data-i18n="image_inlining_hint_1">Sends images in prompts if the model supports it. Use the</span>
1994- <code><i class="fa-solid fa-paperclip"></i></code>
1995- <span data-i18n="image_inlining_hint_2">action on any message or the</span>
1996- <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
1997- <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
19981994 </div>
19991995 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow">
20001996 <div class="flex-container oneline-dropdown">
20011997 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
20021998 Inline Image Quality
@@ -2009,7 +2005,7 @@
20092005 </div>
20102006 </div>
20112007 </div>
20122008 <div class="range-block" data-source="makersuite,vertexai,openrouter">
20132009 <label for="openai_video_inlining" class="checkbox_label flexWrap widthFreeExpand">
20142010 <input id="openai_video_inlining" type="checkbox" />
20152011 <span data-i18n="Send inline videos">Send inline videos</span>
@@ -2019,12 +2015,22 @@
20192015 </div>
20202016 </label>
20212017 <div id="video_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
20222018 <span data-i18n="video_inlining_hint_1">Sends videos in prompts if the model supports it. Use the</span>
2023- <code><i class="fa-solid fa-paperclip"></i></code>
2019+ <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long</strong>
2024- <span data-i18n="video_inlining_hint_2">action on any message or the</span>
2020+ </div>
2025- <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
2021+ </div>
2026- <span data-i18n="video_inlining_hint_3">menu to attach a video file to the chat.</span>
2022+ <div class="range-block" data-source="makersuite,vertexai,openrouter">
2027- <strong data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long</strong>
2023+ <label for="openai_audio_inlining" class="checkbox_label flexWrap widthFreeExpand">
2024+ <input id="openai_audio_inlining" type="checkbox" />
2025+ <span data-i18n="Send inline audio">Send inline audio</span>
2026+ <div id="openai_audio_inlining_supported" data-cc-toggle="false">
2027+ <i class="icon-supported fa-solid fa-circle-check" title="Supported by the current model" data-i18n="[title]Supported by the current model"></i>
2028+ <i class="icon-unsupported fa-solid fa-circle-exclamation" title="Unsupported by the current model" data-i18n="[title]Unsupported by the current model"></i>
2029+ </div>
2030+ </label>
2031+ <div id="audio_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
2032+ <span data-i18n="audio_inlining_hint_1">Sends audio in prompts if the model supports it.</span>
2033+ <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB</strong>
20282034 </div>
20292035 </div>
20302036 <div class="range-block" data-source="makersuite,vertexai">
@@ -2058,7 +2064,7 @@
20582064 </span>
20592065 </div>
20602066 </div>
20612067 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,azure_openai,nanogpt,zai">
20622068 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
20632069 <input id="openai_show_thoughts" type="checkbox" />
20642070 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2067,9 +2073,9 @@
20672073 <span data-i18n="Allows the model to return its thinking process.">
20682074 Allows the model to return its thinking process.
20692075 </span>
20702076 <spanstrong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai">
20712077 This setting affects visibility only.
20722078 </spanstrong>
20732079 </div>
20742080 </div>
20752081 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai">
@@ -2800,7 +2806,9 @@
28002806 <option value="openrouter">OpenRouter</option>
28012807 <option value="perplexity">Perplexity</option>
28022808 <option value="pollinations">Pollinations</option>
2809+ <option value="siliconflow">SiliconFlow</option>
28032810 <option value="xai">xAI (Grok)</option>
2811+ <option value="zai">Z.AI (GLM)</option>
28042812 </optgroup>
28052813 </select>
28062814 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">
@@ -2917,6 +2925,11 @@
29172925 <div>
29182926 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>
29192927 <select id="model_openai_select">
2928+ <optgroup label="GPT-5.1">
2929+ <option value="gpt-5.1">gpt-5.1</option>
2930+ <option value="gpt-5.1-2025-11-13">gpt-5.1-2025-11-13</option>
2931+ <option value="gpt-5.1-chat-latest">gpt-5.1-chat-latest</option>
2932+ </optgroup>
29202933 <optgroup label="GPT-5">
29212934 <option value="gpt-5">gpt-5</option>
29222935 <option value="gpt-5-2025-08-07">gpt-5-2025-08-07</option>
@@ -3154,6 +3167,10 @@
31543167 <div>
31553168 <h4 data-i18n="Google Model">Google Model</h4>
31563169 <select id="model_google_select">
3170+ <optgroup label="Gemini 3.0">
3171+ <option value="gemini-3-pro-preview">gemini-3-pro-preview</option>
3172+ <option value="gemini-3-pro-image-preview">gemini-3-pro-image-preview</option>
3173+ </optgroup>
31573174 <optgroup label="Gemini 2.5">
31583175 <option value="gemini-2.5-pro">gemini-2.5-pro</option>
31593176 <option value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option>
@@ -3322,6 +3339,10 @@
33223339 <h4 data-i18n="Google Model">Google Model</h4>
33233340 <select id="model_vertexai_select">
33243341 <!-- data-mode="full" is for models that require a service account -->
3342+ <optgroup label="Gemini 3.0">
3343+ <option value="gemini-3-pro-preview">gemini-3-pro-preview</option>
3344+ <option value="gemini-3-pro-image-preview">gemini-3-pro-image-preview</option>
3345+ </optgroup>
33253346 <optgroup label="Gemini 2.5">
33263347 <option value="gemini-2.5-pro">gemini-2.5-pro</option>
33273348 <option value="gemini-2.5-flash">gemini-2.5-flash</option>
@@ -3379,6 +3400,20 @@
33793400 <option value="mistral-saba-24b">mistral-saba-24b</option>
33803401 </select>
33813402 </div>
3403+ <div id="siliconflow_form" data-source="siliconflow">
3404+ <h4>SiliconFlow API Key</h4>
3405+ <div class="flex-container">
3406+ <input id="api_key_siliconflow" name="api_key_siliconflow" class="text_pole flex1" value="" type="text" autocomplete="off">
3407+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_siliconflow"></div>
3408+ </div>
3409+ <div data-for="api_key_siliconflow" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3410+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3411+ </div>
3412+ <h4>SiliconFlow Model</h4>
3413+ <select id="model_siliconflow_select">
3414+ <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3415+ </select>
3416+ </div>
33823417 <div id="electronhub_form" data-source="electronhub">
33833418 <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4>
33843419 <div>
@@ -3674,6 +3709,36 @@
36743709 <option value="kimi-thinking-preview">kimi-thinking-preview</option>
36753710 </select>
36763711 </div>
3712+ <div id="zai_form" data-source="zai">
3713+ <h4>
3714+ <a data-i18n="Z.AI API Key" href="https://z.ai/manage-apikey/apikey-list" target="_blank" rel="noopener noreferrer">
3715+ Z.AI API Key
3716+ </a>
3717+ </h4>
3718+ <div class="flex-container">
3719+ <input id="api_key_zai" name="api_key_zai" class="text_pole flex1" value="" type="text" autocomplete="off">
3720+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_zai"></div>
3721+ </div>
3722+ <div data-for="api_key_zai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3723+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3724+ </div>
3725+ <h4 data-i18n="Z.AI Endpoint">Z.AI Endpoint</h4>
3726+ <select id="zai_endpoint">
3727+ <option value="common" data-i18n="Common API">Common API</option>
3728+ <option value="coding" data-i18n="Coding API">Coding API</option>
3729+ </select>
3730+ <h4 data-i18n="Z.AI Model">Z.AI Model</h4>
3731+ <select id="model_zai_select">
3732+ <option value="glm-4.6">glm-4.6</option>
3733+ <option value="glm-4.5v">glm-4.5v</option>
3734+ <option value="glm-4.5">glm-4.5</option>
3735+ <option value="glm-4.5-air">glm-4.5-air</option>
3736+ <option value="glm-4.5-x">glm-4.5-x</option>
3737+ <option value="glm-4.5-airx">glm-4.5-airx</option>
3738+ <option value="glm-4.5-flash">glm-4.5-flash</option>
3739+ <option value="glm-4-32b-0414-128k">glm-4-32b-0414-128k</option>
3740+ </select>
3741+ </div>
36773742 <div id="azure_openai_settings" data-source="azure_openai">
36783743 <!-- Azure Base URL -->
36793744 <h4><span data-i18n="Azure Base URL">Azure Base URL</span></h4>
@@ -3779,7 +3844,7 @@
37793844 <span class="fa-solid fa-circle-question note-link-span"></span>
37803845 </a>
37813846 </h3>
37823847 <div class="flex-container" data-cc-null>
37833848 <input id="af_master_import_file" type="file" hidden accept=".json" class="displayNone">
37843849 <div id="af_master_import" class="menu_button menu_button_icon" title="Import Advanced Formatting settings&#10;&#10;You can also provide legacy files for Instruct and Context templates." data-i18n="[title]Import Advanced Formatting settings">
37853850 <i class="fa-solid fa-file-import"></i>
@@ -3791,10 +3856,16 @@
37913856 </div>
37923857 </div>
37933858 </div>
3859+ <div id="advanced-formatting-cc-notice" class="info-block warning">
3860+ <i class="fa-solid fa-triangle-exclamation"></i>
3861+ <span data-i18n="Grayed-out options have no effect when Chat Completion API is used.">
3862+ Grayed-out options have no effect when Chat Completion API is used.
3863+ </span>
3864+ </div>
37943865 <div class="flex-container spaceEvenly">
37953866 <div id="ContextSettings" class="flex-container flexNoGap flexFlowColumn flex1">
37963867 <div>
37973868 <h4 class="standoutHeader title_restorable" data-cc-null>
37983869 <div>
37993870 <span data-i18n="Context Template">Context Template</span>
38003871 </div>
@@ -3805,7 +3876,7 @@
38053876 </label>
38063877 </div>
38073878 </h4>
38083879 <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template" data-cc-null>
38093880 <select id="context_presets" data-preset-manager-for="context" class="flex1 text_pole"></select>
38103881 <div class="flex-container justifyCenter gap3px">
38113882 <input type="file" hidden data-preset-manager-file="context" accept=".json, .settings">
@@ -3819,12 +3890,14 @@
38193890 </div>
38203891 </div>
38213892 <div>
3822- <label for="context_story_string" class="flex-container">
3893+ <div data-cc-null>
3823- <small data-i18n="Story String">Story String</small>
3894+ <label for="context_story_string" class="flex-container">
3824- <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="context_story_string" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
3895+ <small data-i18n="Story String">Story String</small>
3825- </label>
3896+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="context_story_string" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
3826- <textarea id="context_story_string" class="text_pole textarea_compact autoSetHeight"></textarea>
3897+ </label>
3827- <div class="flex-container flexFlowColumn">
3898+ <textarea id="context_story_string" class="text_pole textarea_compact autoSetHeight"></textarea>
3899+ </div>
3900+ <div class="flex-container flexFlowColumn" data-cc-null>
38283901 <div id="context_story_string_position_block">
38293902 <label for="context_story_string_position">
38303903 <small data-i18n="Position:">Position:</small>
@@ -3853,7 +3926,7 @@
38533926 </div>
38543927 </div>
38553928 </div>
38563929 <div class="flex-container" data-cc-null>
38573930 <div class="flex1">
38583931 <label for="context_example_separator">
38593932 <small data-i18n="Example Separator">Example Separator</small>
@@ -3878,13 +3951,13 @@
38783951 </span>
38793952 </h4>
38803953
38813954 <label class="checkbox_label" for="always-force-name2-checkbox" data-cc-null>
38823955 <input id="always-force-name2-checkbox" type="checkbox" />
38833956 <small data-i18n="Always add character's name to prompt">
38843957 Always add character's name to prompt
38853958 </small>
38863959 </label>
38873960 <label class="checkbox_label" for="single_line" data-cc-null>
38883961 <input id="single_line" type="checkbox" />
38893962 <small data-i18n="Generate only one line per request">
38903963 Generate only one line per request
@@ -3907,11 +3980,11 @@
39073980 Trim Incomplete Sentences
39083981 </small>
39093982 </label>
39103983 <label class="checkbox_label" title="Add Chat Start and Example Separator to a list of stopping strings." data-i18n="[title]Add Chat Start and Example Separator to a list of stopping strings." data-cc-null>
39113984 <input id="context_use_stop_strings" type="checkbox" />
39123985 <small data-i18n="Separators as Stop Strings">Separators as Stop Strings</small>
39133986 </label>
39143987 <label class="checkbox_label" title="Add Character and User names to a list of stopping strings." data-i18n="[title]Add Character and User names to a list of stopping strings." data-cc-null>
39153988 <input id="context_names_as_stop_strings" type="checkbox" />
39163989 <small data-i18n="Names as Stop Strings">Names as Stop Strings</small>
39173990 </label>
@@ -3919,7 +3992,7 @@
39193992 </div>
39203993 </div>
39213994 </div>
39223995 <div id="InstructSettingsColumn" class="flex-container flexNoGap flexFlowColumn flex1" data-cc-null>
39233996 <h4 class="standoutHeader title_restorable justifySpaceBetween">
39243997 <div class="flex-container">
39253998 <span data-i18n="Instruct Template">Instruct Template</span>
@@ -4113,7 +4186,7 @@
41134186 </div>
41144187 </div>
41154188 <div id="SystemPromptColumn" class="flex-container flexNoGap flexFlowColumn flex1">
41164189 <h4 class="standoutHeader title_restorable justifySpaceBetween" data-cc-null>
41174190 <div class="flex-container">
41184191 <span data-i18n="System Prompt">System Prompt</span>
41194192 </div>
@@ -4124,7 +4197,7 @@
41244197 </label>
41254198 </div>
41264199 </h4>
41274200 <div id="SystemPromptBlock" class="marginBot10" data-cc-null>
41284201 <div class="flex-container" title="Select your current System Prompt" data-i18n="[title]Select your current System Prompt">
41294202 <select id="sysprompt_select" data-preset-manager-for="sysprompt" class="flex1 text_pole"></select>
41304203 <div class="flex-container margin0 justifyCenter gap3px">
@@ -4182,7 +4255,7 @@
41824255 </label>
41834256 </div>
41844257
41854258 <div name="tokenizerSettingsBlock" data-cc-null>
41864259 <div name="tokenizerSelectorBlock">
41874260 <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>
41884261 <a href="https://docs.sillytavern.app/usage/prompts/tokenizer/" class="notes-link" target="_blank">
@@ -4295,7 +4368,7 @@
42954368 </div>
42964369 <div>
42974370 <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4>
42984371 <div name="bindModelPresetBlock" data-cc-null>
42994372 <label for="bind_model_templates" class="checkbox_label">
43004373 <input id="bind_model_templates" type="checkbox" />
43014374 <small data-i18n="Bind Model to Templates">Bind Model to Templates</small>
@@ -4637,6 +4710,13 @@
46374710 <option value="2" data-i18n="Document">Document</option>
46384711 </select>
46394712 </div>
4713+ <div class="flex-container alignitemscenter" title="Default display style for media attachments in chat messages. Extensions can override this setting." data-i18n="[title]Default display style for media attachments in chat messages. Extensions can override this setting.">
4714+ <span data-i18n="Media Style:">Media Style:</span>
4715+ <select id="media_display" class="widthNatural flex1 margin0 text_pole">
4716+ <option value="list" data-i18n="List">List</option>
4717+ <option value="gallery" data-i18n="Gallery">Gallery</option>
4718+ </select>
4719+ </div>
46404720 <div class="flex-container alignItemsBaseline">
46414721 <span data-i18n="Notifications:">Notifications:</span>
46424722 <select id="toastr_position" class="widthNatural flex1 margin0 text_pole">
@@ -5010,7 +5090,7 @@
50105090 </div>
50115091 </div>
50125092 <div id="examples-behavior-block">
50135093 <label for="example_messages_behavior">
50145094 <small data-i18n="Example Messages Behavior">
50155095 Example Messages Behavior:
50165096 </small>
@@ -5021,6 +5101,17 @@
50215101 <option value="strip" data-i18n="Never include examples">Never include examples</option>
50225102 </select>
50235103 </div>
5104+ <div>
5105+ <label for="image_overswipe">
5106+ <small data-i18n="Image Swipe Behavior:">
5107+ Image Swipe Behavior:
5108+ </small>
5109+ </label>
5110+ <select id="image_overswipe">
5111+ <option value="generate" data-i18n="Generate new">Generate new</option>
5112+ <option value="rollover" data-i18n="Roll over">Roll over</option>
5113+ </select>
5114+ </div>
50245115 <div class="flex-container alignitemscenter">
50255116 <small data-i18n="Send on Enter">
50265117 Enter to Send:
@@ -5664,7 +5755,7 @@
56645755 <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore&#10;Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div>
56655756 <div id="char_connections_button" class="menu_button fa-solid fa-face-smile" title="Connected Personas" data-i18n="[title]Connected Personas"></div>
56665757 <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div>
56675758 <!-- <div id="set_chat_scenarioset_chat_character_settings" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> -->
56685759 <!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> -->
56695760 <div id="dupe_button" class="menu_button fa-solid fa-clone " title="Duplicate Character" data-i18n="[title]Duplicate Character"></div>
56705761 <label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character" data-i18n="[title]Create Character">
@@ -5681,8 +5772,8 @@
56815772 <option id="import_character_info" data-i18n="Import Card Lore">
56825773 Import Card Lore
56835774 </option>
56845775 <option id="set_chat_scenarioset_chat_character_settings" data-i18n="ScenarioCharacter OverrideSettings Overrides">
5685- Scenario Override
5776+ Character Settings Overrides
56865777 </option>
56875778 <option id="convert_to_persona" data-i18n="Convert to Persona">
56885779 Convert to Persona
@@ -5873,7 +5964,7 @@
58735964 </div>
58745965 <div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly flex1">
58755966 <div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-left-long"></div>
58765967 <div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-scroll" title="Set a group chat scenariocharacter settings overrides" data-i18n="[title]Set a group chat scenariocharacter settings overrides"></div>
58775968 <div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
58785969 <input id="rm_group_fav" type="hidden" />
58795970 <div id="group_open_media_overrides" class="heightFitContent margin0 menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this group." data-i18n="[title]Click to allow/forbid the use of external media for this group.">
@@ -6971,6 +7062,8 @@
69717062 <div title="Prompt" class="mes_button mes_prompt fa-solid fa-square-poll-horizontal " data-i18n="[title]Prompt" style="display: none;"></div>
69727063 <div title="Exclude message from prompts" class="mes_button mes_hide fa-solid fa-eye" data-i18n="[title]Exclude message from prompts"></div>
69737064 <div title="Include message in prompts" class="mes_button mes_unhide fa-solid fa-eye-slash" data-i18n="[title]Include message in prompts"></div>
7065+ <div title="Toggle media display style" class="mes_button mes_media_gallery fa-solid fa-photo-film" data-i18n="[title]Toggle media display style"></div>
7066+ <div title="Toggle media display style" class="mes_button mes_media_list fa-solid fa-table-cells-large" data-i18n="[title]Toggle media display style"></div>
69747067 <div title="Embed file or image" class="mes_button mes_embed fa-solid fa-paperclip" data-i18n="[title]Embed file or image"></div>
69757068 <div title="Create checkpoint" class="mes_button mes_create_bookmark fa-regular fa-solid fa-flag-checkered" data-i18n="[title]Create checkpoint"></div>
69767069 <div title="Create branch" class="mes_button mes_create_branch fa-regular fa-code-branch" data-i18n="[title]Create Branch"></div>
@@ -7009,19 +7102,8 @@
70097102 <div class="mes_reasoning"></div>
70107103 </details>
70117104 <div class="mes_text"></div>
70127105 <div class="mes_img_containermes_media_wrapper"></div>
70137106 <div class="mes_img_controlsmes_file_wrapper"></div>
7014- <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>
7015- <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
7016- <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_img_delete" data-i18n="[title]Delete"></div>
7017- </div>
7018- <div class="mes_img_swipes">
7019- <div title="Swipe left" class="right_menu_button fa-lg fa-solid fa-chevron-left mes_img_swipe_left" data-i18n="[title]Swipe left"></div>
7020- <div class="mes_img_swipe_counter">1/1</div>
7021- <div title="Swipe right" class="right_menu_button fa-lg fa-solid fa-chevron-right mes_img_swipe_right" data-i18n="[title]Swipe right"></div>
7022- </div>
7023- <img class="mes_img" src="" />
7024- </div>
70257107 <div class="mes_bias"></div>
70267108 </div>
70277109 <div class="flex-container swipeRightBlock flexFlowColumn flexNoGap">
@@ -7208,11 +7290,18 @@
72087290 <div class="alternate_greeting">
72097291 <details open>
72107292 <summary>
72117293 <div class="title_restorable gap5px">
72127294 <div class="flex-container alignItemsCenter">
72137295 <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
72147296 <i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
72157297 </div>
7298+ <span class="expander"></span>
7299+ <div class="menu_button menu_button_icon move_up_alternate_greeting" title="Move up" data-i18n="[title]Move up">
7300+ <i class="fa-solid fa-chevron-up"></i>
7301+ </div>
7302+ <div class="menu_button menu_button_icon move_down_alternate_greeting" title="Move down" data-i18n="[title]Move down">
7303+ <i class="fa-solid fa-chevron-down"></i>
7304+ </div>
72167305 <div class="menu_button menu_button_icon delete_alternate_greeting">
72177306 <i class="fa-solid fa-trash-alt"></i>
72187307 <span data-i18n="Delete">Delete</span>
@@ -7224,9 +7313,9 @@
72247313 </div>
72257314 </div>
72267315
72277316 <!-- chat and inputMedia barTemplates -->
72287317 <div id="message_file_template" class="template_element">
72297318 <div class="mes_media_container mes_file_container">
72307319 <div class="fa-lg fa-solid fa-file-alt mes_file_icon"></div>
72317320 <div class="mes_file_name"></div>
72327321 <div class="mes_file_size"></div>
@@ -7235,15 +7324,59 @@
72357324 </div>
72367325 </div>
72377326
7327+ <div id="message_image_template" class="template_element">
7328+ <div class="mes_media_container mes_img_container">
7329+ <div class="mes_img_controls">
7330+ <div title="Expand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_media_enlarge" data-i18n="[title]Expand and zoom"></div>
7331+ <div title="Caption" class="right_menu_button fa-lg fa-solid fa-envelope-open-text mes_img_caption" data-i18n="[title]Caption"></div>
7332+ <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_media_delete" data-i18n="[title]Delete"></div>
7333+ </div>
7334+ <img class="mes_img" src="" />
7335+ </div>
7336+ </div>
7337+
72387338 <div id="message_video_template" class="template_element">
72397339 <div class="mes_media_container mes_video_container">
72407340 <div class="mes_video_controls">
7241- <div><!-- Placeholder --></div>
7341+ <div title="Expand and zoom" class="right_menu_button fa-lg fa-solid fa-magnifying-glass mes_media_enlarge" data-i18n="[title]Expand and zoom"></div>
72427342 <div title="DeleteCaption" class="right_menu_button fa-lg fa-solid fa-trashenvelope-canopen-text mes_video_deletemes_img_caption" data-i18n="[title]DeleteCaption"></div>
7343+ <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_media_delete" data-i18n="[title]Delete"></div>
72437344 </div>
72447345 <video class="mes_video" controls preload="metadata"></video>
72457346 </div>
72467347 </div>
7348+
7349+ <div id="message_gallery_controls" class="template_element">
7350+ <div class="mes_img_swipes">
7351+ <div title="Swipe left" class="right_menu_button fa-lg fa-solid fa-chevron-left mes_img_swipe_left" data-i18n="[title]Swipe left"></div>
7352+ <div class="mes_img_swipe_counter">1/1</div>
7353+ <div title="Swipe right" class="right_menu_button fa-lg fa-solid fa-chevron-right mes_img_swipe_right" data-i18n="[title]Swipe right"></div>
7354+ </div>
7355+ </div>
7356+
7357+ <div id="message_audio_template" class="template_element">
7358+ <div class="mes_media_container mes_audio_container audio-player">
7359+ <audio class="mes_audio" preload="auto" hidden></audio>
7360+ <div class="audio-player-header">
7361+ <div class="audio-player-title">Audio</div>
7362+ <div class="right_menu_button mes_media_delete fa-fw fa-solid fa-trash-can" title="Delete" data-i18n="[title]Delete"></div>
7363+ </div>
7364+ <div class="audio-player-controls">
7365+ <button class="audio-player-play-pause right_menu_button fa-fw fa-solid fa-play" title="Play" data-i18n="[title]Play"></button>
7366+ <div class="audio-player-time">
7367+ <span class="audio-player-current-time">0:00</span>
7368+ <span class="audio-player-time-separator">/</span>
7369+ <span class="audio-player-total-time">0:00</span>
7370+ </div>
7371+ <div class="audio-player-progress">
7372+ <div class="audio-player-progress-bar"></div>
7373+ </div>
7374+ <div class="audio-player-volume-control">
7375+ <button class="audio-player-volume right_menu_button fa-fw fa-solid fa-volume-high" title="Mute" data-i18n="[title]Mute"></button>
7376+ </div>
7377+ </div>
7378+ </div>
7379+ </div>
72477380 </div>
72487381 <div id="movingDivs">
72497382 <div id="floatingPrompt" class="drawer-content flexGap5">
@@ -7600,8 +7733,8 @@
76007733 <div id="send_form" class="no-connection">
76017734 <form id="file_form" class="wide100p displayNone">
76027735 <div class="file_attached">
76037736 <input id="file_form_input" type="file" multiple hidden>
76047737 <input id="embed_file_input" type="file" multiple hidden>
76057738 <i class="fa-solid fa-file-alt"></i>
76067739 <span class="file_name">File Name</span>
76077740 <span class="file_size">File Size</span>
public/locales/ar-sa.json+1 -1
@@ -235,7 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "يجمع الرسائل المتتالية للنظام في رسالة واحدة (باستثناء الحوارات المثالية). قد يحسن التتابع لبعض النماذج.",
236236 "Enable function calling": "تمكين استدعاء الوظيفة",
237237 "Send inline images": "إرسال الصور المضمنة",
238238 "image_inlining_hint_1": "يرسل الصور في المطالبات إذا كان النموذج يدعمها .\n استخدم ال",
239239 "image_inlining_hint_2": "الإجراء على أي رسالة أو",
240240 "image_inlining_hint_3": "القائمة لإرفاق ملف صورة للدردشة.",
241241 "Inline Image Quality": "جودة الصورة المضمنة",
public/locales/de-de.json+1 -1
@@ -235,7 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Kombiniert aufeinanderfolgende Systemnachrichten zu einer (ausschließlich Beispiel-Dialoge ausgeschlossen). Kann die Kohärenz für einige Modelle verbessern.",
236236 "Enable function calling": "Funktionsaufruf aktivieren",
237237 "Send inline images": "Inline-Bilder senden",
238238 "image_inlining_hint_1": "Sendet Bilder in Eingabeaufforderungen, wenn das Modell dies unterstützt.\nVerwenden Sie die",
239239 "image_inlining_hint_2": "Aktion auf eine Nachricht oder die",
240240 "image_inlining_hint_3": "Menü, um eine Bilddatei an den Chat anzuhängen.",
241241 "Inline Image Quality": "Inline-Bildqualität",
public/locales/es-es.json+1 -1
@@ -235,7 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combina mensajes del sistema consecutivos en uno solo (excluyendo diálogos de ejemplo). Puede mejorar la coherencia para algunos modelos.",
236236 "Enable function calling": "Habilitar llamada a función",
237237 "Send inline images": "Enviar imágenes en línea",
238238 "image_inlining_hint_1": "Envía imágenes en mensajes si el modelo lo admite.\n Utilizar el",
239239 "image_inlining_hint_2": "acción sobre cualquier mensaje o el",
240240 "image_inlining_hint_3": "menú para adjuntar un archivo de imagen al chat.",
241241 "Inline Image Quality": "Calidad de imagen en línea",
public/locales/fr-fr.json+1 -1
@@ -227,7 +227,7 @@
227227 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combine les messages système consécutifs en un seul (à l'exclusion des dialogues d'exemple). Peut améliorer la cohérence pour certains modèles.",
228228 "Enable function calling": "Activer l'appel de fonction",
229229 "Send inline images": "Envoyer des images en ligne",
230230 "image_inlining_hint_1": "Envoie des images dans les prompts si le modèle le prend en charge.\nUtilisez le",
231231 "image_inlining_hint_2": "action sur n'importe quel message ou le",
232232 "image_inlining_hint_3": "menu pour joindre un fichier image au chat.",
233233 "Inline Image Quality": "Qualité d'image en ligne",
public/locales/is-is.json+1 -1
@@ -235,7 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Sameinar samhliða kerfisskilaboð í eitt (sem er utan umsagna dæmum). Getur bætt samfelldni fyrir sumar módel.",
236236 "Enable function calling": "Virkja aðgerðarkall",
237237 "Send inline images": "Senda myndir í línu",
238238 "image_inlining_hint_1": "Sendir myndir í skilaboðum ef líkanið styður það.\n Nota",
239239 "image_inlining_hint_2": "aðgerð á hvaða skilaboðum sem er eða",
240240 "image_inlining_hint_3": "valmynd til að hengja myndskrá við spjallið.",
241241 "Inline Image Quality": "Innbyggð myndgæði",
public/locales/it-it.json+1 -1
@@ -235,7 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combina i messaggi di sistema consecutivi in uno solo (escludendo i dialoghi di esempio). Potrebbe migliorare la coerenza per alcuni modelli.",
236236 "Enable function calling": "Abilita la chiamata alla funzione",
237237 "Send inline images": "Invia immagini inline",
238238 "image_inlining_hint_1": "Invia immagini nei prompt se il modello lo supporta.\n Usa il",
239239 "image_inlining_hint_2": "azione su qualsiasi messaggio o il",
240240 "image_inlining_hint_3": "menu per allegare un file immagine alla chat.",
241241 "Inline Image Quality": "Qualità dell'immagine in linea",
public/locales/ko-kr.json+1 -3
@@ -237,9 +237,7 @@
237237 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "연속된 시스템 메시지를 하나로 결합합니다(예제 대화 제외). 일부 모델의 일관성을 향상시킬 수 있습니다.",
238238 "Enable function calling": "함수 호출 활성화",
239239 "Send inline images": "인라인 이미지 전송",
240240 "image_inlining_hint_1": "모델이 지원하는 경우 메시지로 이미지를 보냅니다.\n 사용",
241- "image_inlining_hint_2": "메시지에 대한 조치 또는",
242- "image_inlining_hint_3": "채팅에 이미지 파일을 첨부하는 메뉴입니다.",
243241 "Inline Image Quality": "인라인 이미지 품질",
244242 "openai_inline_image_quality_auto": "자동",
245243 "openai_inline_image_quality_low": "낮은",
public/locales/nl-nl.json+1 -3
@@ -235,9 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combineert opeenvolgende systeemberichten tot één (exclusief voorbeeld dialogen). Kan de coherentie verbeteren voor sommige modellen.",
236236 "Enable function calling": "Schakel functieaanroepen in",
237237 "Send inline images": "Inline afbeeldingen verzenden",
238238 "image_inlining_hint_1": "Verzendt afbeeldingen in prompts als het model dit ondersteunt.\n Gebruik de",
239- "image_inlining_hint_2": "actie op elk bericht of de",
240- "image_inlining_hint_3": "menu om een ​​afbeeldingsbestand aan de chat toe te voegen.",
241239 "Inline Image Quality": "Inline-beeldkwaliteit",
242240 "openai_inline_image_quality_auto": "Auto",
243241 "openai_inline_image_quality_low": "Laag",
public/locales/pt-pt.json+1 -3
@@ -235,9 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combina mensagens do sistema consecutivas em uma (excluindo diálogos de exemplo). Pode melhorar a coerência para alguns modelos.",
236236 "Enable function calling": "Habilitar chamada de função",
237237 "Send inline images": "Enviar imagens inline",
238238 "image_inlining_hint_1": "Envia imagens em prompts se o modelo suportar.\n Use o",
239- "image_inlining_hint_2": "ação em qualquer mensagem ou",
240- "image_inlining_hint_3": "menu para anexar um arquivo de imagem ao chat.",
241239 "Inline Image Quality": "Qualidade de imagem embutida",
242240 "openai_inline_image_quality_auto": "Auto",
243241 "openai_inline_image_quality_low": "Baixo",
public/locales/ru-ru.json+2 -6
@@ -1020,9 +1020,7 @@
10201020 "The prompt to be sent.": "Текст промпта.",
10211021 "prompt_manager_forbid_overrides": "Запретить перезапись",
10221022 "This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.",
10231023 "image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель. Чтобы добавить в чат изображение, используйте на нужном сообщении действие",
1024- "image_inlining_hint_2": ". Также это можно сделать через меню",
1025- "image_inlining_hint_3": ".",
10261024 "Contest Winners": "Победители конкурса",
10271025 "Rename Background": "Переименовать фон",
10281026 "Lock": "Закрепить",
@@ -2445,9 +2443,7 @@
24452443 "Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.": "Отводит часть ответа под рассуждения (Минимальные: 10%, Обычные: 25%, Подробные: 50%, Максимальные: 95%), но минимум 1024 токена. При выборе значение Авто рассуждения не запрашиваются.",
24462444 "Use system prompt": "Включить системный промпт",
24472445 "Send inline videos": "Отправлять inline-видео",
24482446 "video_inlining_hint_1": "Отправляет модели видео, если она поддерживает такую возможность. Чтобы прикрепить видео к чату, используйте на любом сообщении кнопку",
2449- "video_inlining_hint_2": "либо меню",
2450- "video_inlining_hint_3": ".",
24512447 "video_inlining_hint_4": "Видео должно весить не более 20 Мб и длиться не дольше 1 минуты.",
24522448 "Allocates a portion of the response length for thinking (Flash 2.5/Pro 2.5) (min: 0/128 tokens, low: 10%, medium: 25%, high: 50%, max: 24576/32768 tokens). Auto lets the model decide.": "Отводит часть ответа под рассуждения (Flash 2.5/Pro 2.5) (Минимальные: 0/128 токенов, Поверхностные: 10%, Обычные: 25%, Подробные: 50%, Максимальные: 24576/32768 токенов). При выборе значения Авто модель определяет объём самостоятельно.",
24532449 "Google Vertex AI Configuration": "Настройки Google Vertex AI",
public/locales/th-th.json+2 -4
@@ -241,9 +241,7 @@
241241 "Allows the model to return its thinking process.": "อนุญาตให้โมเดลส่งคืนกระบวนการคิดของตัวเอง",
242242 "This setting affects visibility only.": "การตั้งค่านี้มีผลต่อการมองเห็นเท่านั้น",
243243 "Send inline images": "ส่งรูปภาพแบบ inline",
244244 "image_inlining_hint_1": "ส่งรูปภาพในพรอมต์หากโมเดลรองรับ โดยใช้",
245- "image_inlining_hint_2": "เพื่อดำเนินการในข้อความนั้นๆ หรือใช้ตัวเลือก",
246- "image_inlining_hint_3": "เพื่อแนบไฟล์รูปภาพลงในแชท",
247245 "Inline Image Quality": "คุณภาพรูปภาพ Inline",
248246 "openai_inline_image_quality_auto": "อัตโนมัติ",
249247 "openai_inline_image_quality_low": "ต่ำ",
@@ -1458,4 +1456,4 @@
14581456 "Stop Inspecting": "Stop Inspecting",
14591457 "Inspect Prompts": "ตรวจสอบ Prompt",
14601458 "Toggle prompt inspection": "สลับการตรวจสอบ Prompt"
1461-}
1461 \ No newline at end of file
1459+}
public/locales/uk-ua.json+1 -3
@@ -235,9 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Об'єднує послідовні системні повідомлення в одне (крім прикладів діалогів). Може покращити співпрацю для деяких моделей.",
236236 "Enable function calling": "Увімкнути виклик функцій",
237237 "Send inline images": "Надсилати вбудовані зображення",
238238 "image_inlining_hint_1": "Надсилає зображення у підказках, якщо модель це підтримує.\n Використовувати",
239- "image_inlining_hint_2": "дії з будь-яким повідомленням або",
240- "image_inlining_hint_3": "меню, щоб прикріпити файл зображення до чату.",
241239 "Inline Image Quality": "Якість вбудованого зображення",
242240 "openai_inline_image_quality_auto": "Авто",
243241 "openai_inline_image_quality_low": "Низький",
public/locales/vi-vn.json+1 -3
@@ -235,9 +235,7 @@
235235 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Kết hợp các tin nhắn hệ thống liên tiếp thành một (loại bỏ các đoạn hội thoại mẫu). Có thể cải thiện tính nhất quán cho một số model.",
236236 "Enable function calling": "Sử dụng tính năng gọi hàm (function calling)",
237237 "Send inline images": "Gửi hình ảnh nội bộ",
238238 "image_inlining_hint_1": "Gửi hình ảnh theo Prompt nếu kiểu máy hỗ trợ.\n Sử dụng",
239- "image_inlining_hint_2": "hành động đối với bất kỳ tin nhắn nào hoặc",
240- "image_inlining_hint_3": "menu để đính kèm tệp hình ảnh vào cuộc trò chuyện.",
241239 "Inline Image Quality": "Chất lượng hình ảnh nội tuyến",
242240 "openai_inline_image_quality_auto": "Tự động",
243241 "openai_inline_image_quality_low": "Thấp",
public/locales/zh-cn.json+6 -6
@@ -269,17 +269,13 @@
269269 "enable_functions_desc_3": "可以被各种扩展利用来提供附加功能。",
270270 "enable_functions_desc_4": "当提示词后处理没有选择工具时不支持。",
271271 "Send inline images": "发送图片",
272272 "image_inlining_hint_1": "如果模型支持,就可以在提示词中发送图片。\n发送消息时,点击",
273- "image_inlining_hint_2": "在这里(",
274- "image_inlining_hint_3": ")将图片添加到消息中。",
275273 "Inline Image Quality": "图片画质",
276274 "openai_inline_image_quality_auto": "自动",
277275 "openai_inline_image_quality_low": "低",
278276 "openai_inline_image_quality_high": "高",
279277 "Send inline videos": "发送视频",
280278 "video_inlining_hint_1": "当模型支持时,将视频发送给模型。使用",
281- "video_inlining_hint_2": "在任意消息上添加视频,或",
282- "video_inlining_hint_3": "菜单来添加视频。",
283279 "video_inlining_hint_4": "视频必须在 20MB 以下且时长不超过1分钟。",
284280 "Request inline images": "请求图片返回",
285281 "Allows the model to return image attachments.": "允许模型返回图片附件。",
@@ -2276,6 +2272,10 @@
22762272 "Drag handle to reorder. Click name to rename. Click color to change display.": "拖拽左侧三条横线以排序,点击名字以重命名,点击调色盘以切换颜色。",
22772273 "Click on the folder icon to use this tag as a folder.": "点击文件夹图标来将此标签作为一个文件夹。",
22782274 "Use alphabetical sorting": "按字母顺序排列",
2275+ "Sort mode": "排序模式",
2276+ "Manual (Drag & Drop)": "手动 (拖放)",
2277+ "Alphabetical (A-Z)": "按字母 (A-Z)",
2278+ "Most Used (By Count)": "按使用次数",
22792279 "tags_sorting_desc": "启用后,标签在创建或重命名时会自动按字母顺序排序。\n禁用后,新标签会追加到末尾。\n\n如果通过拖动手动重新排列标签,则自动排序将被禁用。",
22802280 "Are you sure you want to delete the theme?": "你确定要删除这个主题吗?",
22812281 "Hi,": "嗨,",
public/locales/zh-tw.json+6 -6
@@ -236,9 +236,7 @@
236236 "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "將連續的系統訊息合併為一個(不包括對話範例)。可能會提高某些模型的一致性。",
237237 "Enable function calling": "啟用函式呼叫",
238238 "Send inline images": "傳送內嵌圖片",
239239 "image_inlining_hint_1": "如果模型支援,則在提示詞中傳送圖片。\n使用任何訊息上的",
240- "image_inlining_hint_2": "動作或",
241- "image_inlining_hint_3": "選單來附加圖片文件到聊天中。",
242240 "Inline Image Quality": "內嵌圖片品質",
243241 "openai_inline_image_quality_auto": "自動",
244242 "openai_inline_image_quality_low": "低",
@@ -2109,6 +2107,10 @@
21092107 "Drag handle to reorder. Click name to rename. Click color to change display.": "拖動以重新排序。點選名稱重新命名。點選顏色更改顯示。",
21102108 "Click on the folder icon to use this tag as a folder.": "點選資料夾圖示以將此標籤作為資料夾。",
21112109 "Use alphabetical sorting": "按字母順序排序 ",
2110+ "Sort mode": "排序模式",
2111+ "Manual (Drag & Drop)": "手動(拖放)",
2112+ "Alphabetical (A-Z)": "按字母(A-Z)",
2113+ "Most Used (By Count)": "按使用次數",
21122114 "tags_sorting_desc": "啟用後,標籤將在建立或重新命名時將自動按字母排序。\n停用時,新標籤將附加到結尾。\n若標籤被手動拖動重新排序,則自動排序將被停用。",
21132115 "and connect to an": "並連線到",
21142116 "You can add more": "您可加入更多",
@@ -2646,9 +2648,7 @@
26462648 "Min Keep": "最小保留",
26472649 "enable_functions_desc_4": "當使用「無工具」提示後處理時不支援!",
26482650 "Send inline videos": "傳送內嵌影片",
26492651 "video_inlining_hint_1": "若模型支援,則在提示中傳送影片。使用",
2650- "video_inlining_hint_2": "操作於任何訊息或使用",
2651- "video_inlining_hint_3": "選單以上傳影片檔案到聊天中。",
26522652 "video_inlining_hint_4": "影片大小必須小於 20 MB 且長度低於 1 分鐘",
26532653 "This setting affects visibility only.": "此設定僅影響可見性。",
26542654 "Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.": "為推理部分分配回應長度(最大回應長度至少需 1024 符元,低:10%、中:25%、高:50%、最高:95%)。選擇「自動」將不請求推理。",
public/script.js+1382 -757
@@ -179,8 +179,11 @@ import {
179179 importFromExternalUrl,
180180 shiftUpByOne,
181181 shiftDownByOne,
182+ canUseNegativeLookbehind,
183+ trimSpaces,
184+ clamp,
182185} from './scripts/utils.js';
183186import { debounce_timeout, GENERATION_TYPE_TRIGGERS, IGNORE_SYMBOL, inject_ids, MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR, SWIPE_DIRECTION } from './scripts/constants.js';
184187
185188import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
186189import { COMMENT_NAME_DEFAULT, CONNECT_API_MAP, executeSlashCommandsOnChatInput, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, stopScriptExecution, UNIQUE_APIS } from './scripts/slash-commands.js';
@@ -273,6 +276,8 @@ import { event_types, eventSource } from './scripts/events.js';
273276import { initAccessibility } from './scripts/a11y.js';
274277import { applyStreamFadeIn } from './scripts/util/stream-fadein.js';
275278import { initDomHandlers } from './scripts/dom-handlers.js';
279+import { SimpleMutex } from './scripts/util/SimpleMutex.js';
280+import { AudioPlayer } from './scripts/audio-player.js';
276281
277282// API OBJECT FOR EXTERNAL WIRING
278283globalThis.SillyTavern = {
@@ -310,6 +315,10 @@ export {
310315 getSystemMessageByType,
311316 event_types,
312317 eventSource,
318+ /** @deprecated Use setCharacterSettingsOverrides instead. */
319+ setCharacterSettingsOverrides as setScenarioOverride,
320+ /** @deprecated Use appendMediaToMessage instead. */
321+ appendMediaToMessage as appendImageToMessage,
313322};
314323
315324/**
@@ -362,7 +371,9 @@ export const neutralCharacterName = 'Assistant';
362371let default_user_name = 'User';
363372export let name1 = default_user_name;
364373export let name2 = systemUserName;
374+/** @type {ChatMessage[]} */
365375export let chat = [];
376+export let isSwipingAllowed = true; //false when a swipe is in progress, or swiping is blocked.
366377let chatSaveTimeout;
367378let importFlashTimeout;
368379export let isChatSaving = false;
@@ -554,9 +565,10 @@ export let is_send_press = false; //Send generation
554565
555566let this_del_mes = -1;
556567
557-//message editing
568+/** @type {string} */
558569varlet this_edit_mes_chname = '';
559-var this_edit_mes_id;
570+/** @type {number|undefined} */
571+let this_edit_mes_id = undefined;
560572
561573//settings
562574export let settings;
@@ -1400,32 +1412,46 @@ export async function printMessages() {
14001412 addOneMessage(item, { scroll: false, forceId: i, showSwipes: false });
14011413 }
14021414
1403- // Scroll to bottom when all images are loaded
1415+ chatElement.find('.mes').removeClass('last_mes');
1404- const images = document.querySelectorAll('#chat .mes img');
1416+ chatElement.find('.mes').last().addClass('last_mes');
1405- let imagesLoaded = 0;
1417+ refreshSwipeButtons();
1418+ applyStylePins();
1419+ scrollChatToBottom();
1420+ delay(debounce_timeout.short).then(() => scrollOnMediaLoad());
1421+}
1422+
1423+function scrollOnMediaLoad() {
1424+ const started = Date.now();
1425+ const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray();
1426+ let mediaLoaded = 0;
14061427
1407- for (let i = 0; i < images.length; i++) {
1428+ for (const currentElement of media) {
1408- const image = images[i];
1429+ if (currentElement instanceof HTMLImageElement) {
14091430 if (image instanceof HTMLImageElementcurrentElement.complete) {
1410- if (image.complete) {
1431+ incrementAndCheck();
1432+ } else {
1433+ currentElement.addEventListener('load', incrementAndCheck);
1434+ currentElement.addEventListener('error', incrementAndCheck);
1435+ }
1436+ }
1437+ if (currentElement instanceof HTMLMediaElement) {
1438+ if (currentElement.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
14111439 incrementAndCheck();
14121440 } else {
14131441 imagecurrentElement.addEventListener('loadloadeddata', incrementAndCheck);
1442+ currentElement.addEventListener('error', incrementAndCheck);
14141443 }
14151444 }
14161445 }
14171446
1418- chatElement.find('.mes').removeClass('last_mes');
1419- chatElement.find('.mes').last().addClass('last_mes');
1420- hideSwipeButtons();
1421- showSwipeButtons();
1422- scrollChatToBottom();
1423- applyStylePins();
1424-
14251447 function incrementAndCheck() {
1426- imagesLoaded++;
1448+ const MAX_DELAY = 1000; // 1 second
1427- if (imagesLoaded === images.length) {
1449+ if ((Date.now() - started) > MAX_DELAY) {
14281450 scrollChatToBottom()return;
1451+ }
1452+ mediaLoaded++;
1453+ if (mediaLoaded === media.length) {
1454+ scrollChatToBottom({ waitForFrame: true });
14291455 }
14301456 }
14311457}
@@ -1465,6 +1491,68 @@ export async function deleteLastMessage() {
14651491 await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
14661492}
14671493
1494+/**
1495+ * Deletes a message from the chat by its ID, optionally asking for confirmation.
1496+ * @param {number} id The ID of the message to delete.
1497+ * @param {number} [swipeDeletionIndex] Deletes the swipe with that index.
1498+ * @param {boolean} [askConfirmation=false] Whether to ask for confirmation before deleting.
1499+ */
1500+export async function deleteMessage(id, swipeDeletionIndex = undefined, askConfirmation = false) {
1501+ const canDeleteSwipe = swipeDeletionIndex !== undefined && swipeDeletionIndex !== null;
1502+ if (canDeleteSwipe) {
1503+ if (swipeDeletionIndex < 0) {
1504+ throw new Error('Swipe index cannot be negative');
1505+ }
1506+ if (!Array.isArray(chat[id].swipes)) {
1507+ throw new Error('Message has no swipes to delete');
1508+ }
1509+ if (chat[id].swipes.length <= swipeDeletionIndex) {
1510+ throw new Error('Swipe index out of bounds');
1511+ }
1512+ }
1513+
1514+ const minId = getFirstDisplayedMessageId();
1515+ const messageElement = chatElement.find(`.mes[mesid="${id}"]`);
1516+ if (messageElement.length === 0) {
1517+ return;
1518+ }
1519+
1520+ let deleteOnlySwipe = canDeleteSwipe;
1521+ if (askConfirmation) {
1522+ const result = await callGenericPopup(t`Are you sure you want to delete this message?`, POPUP_TYPE.CONFIRM, null, {
1523+ okButton: canDeleteSwipe ? t`Delete Swipe` : t`Delete Message`,
1524+ cancelButton: 'Cancel',
1525+ customButtons: canDeleteSwipe ? [t`Delete Message`] : null,
1526+ });
1527+ if (!result) {
1528+ return;
1529+ }
1530+ deleteOnlySwipe = canDeleteSwipe && result === POPUP_RESULT.AFFIRMATIVE; // Default button, not the custom one
1531+ }
1532+
1533+ if (deleteOnlySwipe) {
1534+ await deleteSwipe(swipeDeletionIndex, id);
1535+ return;
1536+ }
1537+
1538+ chat.splice(id, 1);
1539+ messageElement.remove();
1540+
1541+ chat_metadata['tainted'] = true;
1542+
1543+ const startIndex = [0, minId].includes(id) ? id : null;
1544+ updateViewMessageIds(startIndex);
1545+ saveChatDebounced();
1546+
1547+ if (this_edit_mes_id === id) {
1548+ this_edit_mes_id = undefined;
1549+ }
1550+
1551+ refreshSwipeButtons();
1552+
1553+ await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
1554+}
1555+
14681556export async function reloadCurrentChat() {
14691557 preserveNeutralChat();
14701558 await clearChat();
@@ -1484,8 +1572,7 @@ export async function reloadCurrentChat() {
14841572 await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
14851573 }
14861574
14871575 hideSwipeButtonsrefreshSwipeButtons();
1488- showSwipeButtons();
14891576}
14901577
14911578/**
@@ -1494,9 +1581,8 @@ export async function reloadCurrentChat() {
14941581export async function sendTextareaMessage() {
14951582 if (is_send_press) return;
14961583 if (isExecutingCommandsFromChatInput) return;
1497- if (this_edit_mes_id) return; // don't proceed if editing a message
14981584
14991585 let generateType = 'normal';
15001586 // "Continue on send" is activated when the user hits "send" (or presses enter) on an empty chat box, and the last
15011587 // message was sent from a character (not the user or the system).
15021588 const textareaText = String($('#send_textarea').val());
@@ -1515,7 +1601,7 @@ export async function sendTextareaMessage() {
15151601 await newAssistantChat({ temporary: false });
15161602 }
15171603
15181604 return await Generate(generateType);
15191605}
15201606
15211607/**
@@ -1597,7 +1683,9 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
15971683 }
15981684
15991685 if (!isSystem && power_user.encode_tags) {
1600- mes = mes.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
1686+ mes = canUseNegativeLookbehind()
1687+ ? mes.replaceAll('<', '&lt;').replace(new RegExp('(?<!^|\\n\\s*)>', 'g'), '&gt;')
1688+ : mes.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
16011689 }
16021690
16031691 // Make sure reasoning strings are always shown, even if they include "<" or ">"
@@ -1817,110 +1905,436 @@ export function updateMessageBlock(messageId, message, { rerenderMessage = true
18171905}
18181906
18191907/**
1820- * Appends image or file to the message element.
1908+ * Ensures that the message media properties are arrays, adding getters/setters for single media items.
18211909 * @param {objectChatMessage} mes Message object
1822- * @param {JQuery<HTMLElement>} messageElement Message element
1823- * @param {boolean} [adjustScroll=true] Whether to adjust the scroll position after appending the media
18241910 */
18251911export function appendMediaToMessageensureMessageMediaIsArray(mes, messageElement, adjustScroll = true) {
1826- // Add image to message
1912+ /**
1827- if (mes.extra?.image) {
1913+ * Determines if a property of an object is a plain property (not a getter/setter or non-enumerable).
1828- const container = messageElement.find('.mes_img_container');
1914+ * @param {object} obj Object to check
1829- const chatHeight = chatElement.prop('scrollHeight');
1915+ * @param {string} name Property name
1830- const image = messageElement.find('.mes_img');
1916+ * @returns {boolean} True if the property is a plain property, false otherwise
1831- const text = messageElement.find('.mes_text');
1917+ */
1832- const isInline = !!mes.extra?.inline_image;
1918+ function isPlainObjectProperty(obj, name) {
18331919 const doAdjustScrollhasProperty = Object.hasOwn() =>obj, {name);
18341920 if (!adjustScrollhasProperty) {
1835- return;
1921+ const descriptor = Object.getOwnPropertyDescriptor(obj, name);
1836- }
1922+ return descriptor && descriptor.enumerable && descriptor.configurable && descriptor.writable;
1837- const scrollPosition = chatElement.scrollTop();
1923+ }
1838- const newChatHeight = chatElement.prop('scrollHeight');
1924+ return false;
1839- const diff = newChatHeight - chatHeight;
1925+ }
1840- chatElement.scrollTop(scrollPosition + diff);
1926+
1841- };
1927+ /**
1842- image.off('load').on('load', function () {
1928+ * Determines if a property of an object is a getter (not a plain property).
1843- image.removeAttr('alt');
1929+ * @param {object} obj Object to check
1844- image.removeClass('error');
1930+ * @param {string} name Property name
1845- doAdjustScroll();
1931+ * @returns {boolean} True if the property is a getter, false otherwise
1846- });
1932+ */
1847- image.off('error').on('error', function () {
1933+ function isGetterObjectProperty(obj, name) {
1848- image.attr('alt', '');
1934+ const hasProperty = Object.hasOwn(obj, name);
1849- image.addClass('error');
1935+ if (hasProperty) {
1850- doAdjustScroll();
1936+ const descriptor = Object.getOwnPropertyDescriptor(obj, name);
1937+ return descriptor && typeof descriptor.get === 'function';
1938+ }
1939+ return false;
1940+ }
1941+
1942+ /**
1943+ * Adds a plain property to an object that wraps around an array property.
1944+ * @param {object} obj Object to add property to
1945+ * @param {string} plainProperty Plain property name
1946+ * @param {string} arrayProperty Array property to back the plain property
1947+ * @param {(value: any) => boolean} [filterFn] Optional filter function to apply when getting/setting the plain property
1948+ * @param {(value: any) => any} [mapFn] Optional map function to apply when getting/setting the plain property
1949+ */
1950+ function addArrayAutoWrapper(obj, plainProperty, arrayProperty, filterFn = () => true, mapFn = (t) => t) {
1951+ // If the plain property is already a getter, do nothing.
1952+ const hasGetterProperty = isGetterObjectProperty(obj, plainProperty);
1953+ if (hasGetterProperty) {
1954+ return;
1955+ }
1956+
1957+ // Define the plain property as a getter/setter that wraps around the array property.
1958+ Object.defineProperty(obj, plainProperty, {
1959+ // Getting the plain property returns the first item in the array property, or undefined if the array is empty.
1960+ get: function () {
1961+ console.trace(`Attempting to GET an array-wrapped property '${plainProperty}'. Use the array property '${arrayProperty}' instead.`);
1962+ const array = Array.isArray(this[arrayProperty]) ? this[arrayProperty].filter(filterFn).map(mapFn) : [];
1963+ return array.length > 0 ? array[0] : void 0;
1964+ },
1965+ // Setting the plain property is not supported, as it would be ambiguous.
1966+ set: function () {
1967+ console.trace(`Attempting to SET an array-wrapped property '${plainProperty}'. Use the array property '${arrayProperty}' instead.`);
1968+ },
1969+ // Exclude the property from JSON serialization and from being listed in for...in loops.
1970+ enumerable: false,
1971+ // Make the property non-configurable to prevent deletion or redefinition.
1972+ configurable: false,
18511973 });
1852- image.attr('src', mes.extra?.image);
1974+ }
1853- image.attr('title', mes.extra?.title || mes.title || '');
1854- container.addClass('img_extra');
1855- image.toggleClass('img_inline', isInline);
1856- text.toggleClass('displayNone', !isInline);
1857-
1858- const imageSwipes = mes.extra.image_swipes;
1859- if (Array.isArray(imageSwipes) && imageSwipes.length > 0) {
1860- container.addClass('img_swipes');
1861- const counter = container.find('.mes_img_swipe_counter');
1862- const currentImage = imageSwipes.indexOf(mes.extra.image) + 1;
1863- counter.text(`${currentImage}/${imageSwipes.length}`);
1864-
1865- const swipeLeft = container.find('.mes_img_swipe_left');
1866- swipeLeft.off('click').on('click', function () {
1867- eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'left' });
1868- });
18691975
1870- const swipeRight = container.find('.mes_img_swipe_right');
1976+ /**
1871- swipeRight.off('click').on('click', function () {
1977+ * Migrates image swipes from a single image property to an array.
1872- eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'right' });
1978+ * @param {ChatMessageExtra} obj
1873- });
1979+ */
1980+ function migrateMediaToArray(obj) {
1981+ if (isPlainObjectProperty(obj, 'file')) {
1982+ if (!Array.isArray(obj.files)) {
1983+ obj.files = [];
1984+ }
1985+ const fileValue = obj.file;
1986+ delete obj.file;
1987+ if (fileValue) {
1988+ obj.files.push(fileValue);
1989+ }
18741990 }
1875- } else {
1991+
1876- const container = messageElement.find('.mes_img_container');
1992+ if (Array.isArray(obj.image_swipes)) {
1877- container.removeClass('img_extra img_swipes');
1993+ if (!Array.isArray(obj.media)) {
1878- const text = messageElement.find('.mes_text');
1994+ obj.media = [];
1879- text.removeClass('displayNone');
1880- }
1881-
1882- // Add video to message
1883- if (mes.extra?.video) {
1884- const container = $('#message_video_template .mes_video_container').clone();
1885- messageElement.find('.mes_video_container').remove();
1886- messageElement.find('.mes_block').append(container);
1887- const chatHeight = chatElement.prop('scrollHeight');
1888- const video = container.find('.mes_video');
1889- video.off('loadedmetadata').on('loadedmetadata', function () {
1890- if (!adjustScroll) {
1891- return;
18921995 }
1893- const scrollPosition = chatElement.scrollTop();
1996+ for (const swipe of obj.image_swipes) {
1894- const newChatHeight = chatElement.prop('scrollHeight');
1997+ if (swipe && typeof swipe === 'string') {
1895- const diff = newChatHeight - chatHeight;
1998+ obj.media_display = MEDIA_DISPLAY.GALLERY;
1896- chatElement.scrollTop(scrollPosition + diff);
1999+ obj.media.push({ type: MEDIA_TYPE.IMAGE, url: swipe });
1897- });
2000+ }
2001+ }
2002+ delete obj.image_swipes;
2003+ }
18982004
1899- video.attr('src', mes.extra?.video);
2005+ if (isPlainObjectProperty(obj, 'image')) {
1900- } else {
2006+ if (!Array.isArray(obj.media)) {
1901- messageElement.find('.mes_video_container').remove();
2007+ obj.media = [];
19022008 }
1903-
2009+ const imageValue = obj.image;
1904- // Add file to message
2010+ delete obj.image;
1905- if (mes.extra?.file) {
2011+ if (imageValue && typeof imageValue === 'string') {
1906- messageElement.find('.mes_file_container').remove();
2012+ obj.media.push({ type: MEDIA_TYPE.IMAGE, url: imageValue });
1907- const messageId = messageElement.attr('mesid');
2013+ }
1908- const template = $('#message_file_template .mes_file_container').clone();
2014+ if (obj.media_display === MEDIA_DISPLAY.GALLERY) {
1909- template.find('.mes_file_name').text(mes.extra.file.name);
2015+ const selectedIndex = obj.media.findIndex(t => t.url === imageValue);
1910- template.find('.mes_file_size').text(humanFileSize(mes.extra.file.size));
2016+ if (selectedIndex > -1) {
1911- template.find('.mes_file_download').attr('mesid', messageId);
2017+ obj.media_index = selectedIndex;
1912- template.find('.mes_file_delete').attr('mesid', messageId);
2018+ }
1913- messageElement.find('.mes_block').append(template);
2019+ }
1914- } else {
2020+ obj.media = obj.media.filter((v, i, a) => i === a.findIndex(t => t.url === v.url));
1915- messageElement.find('.mes_file_container').remove();
2021+ }
2022+
2023+ if (isPlainObjectProperty(obj, 'video')) {
2024+ if (!Array.isArray(obj.media)) {
2025+ obj.media = [];
2026+ }
2027+ const videoValue = obj.video;
2028+ delete obj.video;
2029+ if (videoValue && typeof videoValue === 'string') {
2030+ obj.media.push({ type: MEDIA_TYPE.VIDEO, url: videoValue });
2031+ }
2032+ }
2033+ }
2034+
2035+ if (!mes || !mes.extra || typeof mes.extra !== 'object') {
2036+ return;
2037+ }
2038+
2039+ migrateMediaToArray(mes.extra);
2040+ addArrayAutoWrapper(mes.extra, 'file', 'files');
2041+ addArrayAutoWrapper(mes.extra, 'image', 'media', (t) => t.type === MEDIA_TYPE.IMAGE, (t) => t.url);
2042+ addArrayAutoWrapper(mes.extra, 'video', 'media', (t) => t.type === MEDIA_TYPE.VIDEO, (t) => t.url);
2043+}
2044+
2045+/**
2046+ * Gets the media display setting for a message.
2047+ * @param {ChatMessage} mes Message object
2048+ * @returns {MEDIA_DISPLAY} Media display setting
2049+ */
2050+export function getMediaDisplay(mes) {
2051+ const value = mes?.extra?.media_display || power_user.media_display || MEDIA_DISPLAY.LIST;
2052+ return Object.values(MEDIA_DISPLAY).includes(value) ? value : MEDIA_DISPLAY.LIST;
2053+}
2054+
2055+/**
2056+ * Gets the media index for a message.
2057+ * @param {ChatMessage} mes Message object
2058+ * @returns {number} Media index
2059+ */
2060+export function getMediaIndex(mes) {
2061+ if (!Array.isArray(mes?.extra?.media)) {
2062+ return 0;
19162063 }
2064+ const value = mes.extra?.media_index;
2065+ if (isNaN(value) || value < 0 || value >= mes.extra.media.length) {
2066+ return 0;
2067+ }
2068+ return value;
19172069}
19182070
19192071/**
1920- * @deprecated Use appendMediaToMessage instead.
2072+ * Appends image or file to the message element.
2073+ * @param {ChatMessage} mes Message object
2074+ * @param {JQuery<HTMLElement>} messageElement Message element
2075+ * @param {string} [scrollBehavior] Scroll behavior when adjusting scroll position
19212076 */
19222077export function appendImageToMessageappendMediaToMessage(mes, messageElement, scrollBehavior = SCROLL_BEHAVIOR.ADJUST) {
19232078 appendMediaToMessageensureMessageMediaIsArray(mes, messageElement);
2079+
2080+ const fileWrapper = messageElement.find('.mes_file_wrapper');
2081+ const mediaWrapper = messageElement.find('.mes_media_wrapper');
2082+
2083+ const hasMedia = Array.isArray(mes?.extra?.media) && mes.extra.media.length > 0;
2084+ const hasFiles = Array.isArray(mes?.extra?.files) && mes.extra.files.length > 0;
2085+ const mediaDisplay = hasMedia ? getMediaDisplay(mes) : null;
2086+ const hideMessageText = hasMedia && mes?.extra?.inline_image === false;
2087+
2088+ const mediaBlocks = [];
2089+ const mediaPromises = [];
2090+
2091+ const chatHeight = (hasMedia || hasFiles) ? chatElement.prop('scrollHeight') : 0;
2092+ const scrollPosition = (hasMedia || hasFiles) ? chatElement.scrollTop() : 0;
2093+ const doAdjustScroll = () => {
2094+ if (!hasMedia && !hasFiles) {
2095+ return;
2096+ }
2097+ if (scrollBehavior === SCROLL_BEHAVIOR.NONE) {
2098+ return;
2099+ }
2100+ if (scrollBehavior === SCROLL_BEHAVIOR.KEEP) {
2101+ chatElement.scrollTop(scrollPosition);
2102+ return;
2103+ }
2104+ const newChatHeight = chatElement.prop('scrollHeight');
2105+ const diff = newChatHeight - chatHeight;
2106+ chatElement.scrollTop(scrollPosition + diff);
2107+ };
2108+
2109+ // Set media display attribute
2110+ messageElement.attr('data-media-display', mediaDisplay);
2111+ // Toggle text visibility
2112+ messageElement.find('.mes_text').toggleClass('displayNone', hideMessageText);
2113+
2114+ /**
2115+ * Appends a single image attachment to the message element.
2116+ * @param {MediaAttachment} attachment Image attachment object
2117+ * @param {number} index Index of the image attachment
2118+ * @returns {JQuery<HTMLElement>} The appended image container element
2119+ */
2120+ function appendImageAttachment(attachment, index) {
2121+ const template = $('#message_image_template .mes_img_container').clone();
2122+ template.attr('data-index', index);
2123+
2124+ const image = template.find('.mes_img');
2125+ image.attr('src', attachment.url);
2126+ image.attr('title', attachment.title || mes.extra.title || '');
2127+ mediaPromises.push(new Promise((resolve) => {
2128+ function onLoad() {
2129+ image.removeAttr('alt');
2130+ image.removeClass('error');
2131+ resolve();
2132+ }
2133+ function onError() {
2134+ image.attr('alt', '');
2135+ image.addClass('error');
2136+ resolve();
2137+ }
2138+ if (image.prop('complete')) {
2139+ onLoad();
2140+ } else {
2141+ image.off('load').on('load', onLoad);
2142+ image.off('error').on('error', onError);
2143+ }
2144+ }));
2145+
2146+ mediaBlocks.push(template);
2147+ return template;
2148+ }
2149+
2150+ /**
2151+ * Appends a single video attachment to the message element.
2152+ * @param {MediaAttachment} attachment Video attachment object
2153+ * @param {number} index Index of the video attachment
2154+ * @returns {JQuery<HTMLElement>} The appended video container element
2155+ */
2156+ function appendVideoAttachment(attachment, index) {
2157+ const template = $('#message_video_template .mes_video_container').clone();
2158+ template.attr('data-index', index);
2159+
2160+ const video = template.find('.mes_video');
2161+ video.attr('src', attachment.url);
2162+ video.attr('title', attachment.title || mes.extra.title || '');
2163+ mediaPromises.push(new Promise((resolve) => {
2164+ function onLoad() {
2165+ resolve();
2166+ }
2167+ function onError() {
2168+ video.addClass('error');
2169+ resolve();
2170+ }
2171+ if (video.prop('readyState') >= HTMLMediaElement.HAVE_CURRENT_DATA) {
2172+ onLoad();
2173+ } else {
2174+ video.off('loadeddata').on('loadeddata', onLoad);
2175+ video.off('error').on('error', onError);
2176+ }
2177+ }));
2178+
2179+ mediaBlocks.push(template);
2180+ return template;
2181+ }
2182+
2183+ /**
2184+ * Appends a single audio attachment to the message element.
2185+ * @param {MediaAttachment} attachment Audio attachment object
2186+ * @param {number} index Index of the audio attachment
2187+ * @returns {JQuery<HTMLElement>} The appended audio container element
2188+ */
2189+ function appendAudioAttachment(attachment, index) {
2190+ const template = $('#message_audio_template .mes_audio_container').clone();
2191+ template.attr('data-index', index);
2192+ const audio = template.find('.mes_audio');
2193+ audio.attr('src', attachment.url);
2194+ audio.attr('title', attachment.title || mes.extra.title || '');
2195+
2196+ mediaPromises.push(new Promise((resolve) => {
2197+ function onLoad() {
2198+ resolve();
2199+ }
2200+ function onError() {
2201+ audio.addClass('error');
2202+ resolve();
2203+ }
2204+ if (audio.prop('readyState') >= HTMLMediaElement.HAVE_CURRENT_DATA) {
2205+ onLoad();
2206+ } else {
2207+ audio.off('loadeddata').on('loadeddata', onLoad);
2208+ audio.off('error').on('error', onError);
2209+ }
2210+ }));
2211+
2212+ new AudioPlayer(audio.get(0), template.get(0));
2213+
2214+ mediaBlocks.push(template);
2215+ return template;
2216+ }
2217+
2218+ /**
2219+ * Appends a media attachment to the message element.
2220+ * @param {MediaAttachment} attachment Media attachment object
2221+ * @param {number} index Index of the media attachment
2222+ * @returns {JQuery<HTMLElement>} The appended media container element
2223+ */
2224+ function appendMediaAttachment(attachment, index) {
2225+ if (!attachment.type) {
2226+ attachment.type = MEDIA_TYPE.IMAGE;
2227+ }
2228+ switch (attachment.type) {
2229+ case MEDIA_TYPE.IMAGE:
2230+ return appendImageAttachment(attachment, index);
2231+ case MEDIA_TYPE.VIDEO:
2232+ return appendVideoAttachment(attachment, index);
2233+ case MEDIA_TYPE.AUDIO:
2234+ return appendAudioAttachment(attachment, index);
2235+ }
2236+
2237+ console.warn(`Unknown media type: ${attachment.type}, defaulting to image.`, attachment);
2238+ return appendImageAttachment(attachment, index);
2239+ }
2240+
2241+ /**
2242+ * Saves the current playback times of media elements in the message.
2243+ * @returns {Map<string, MediaState>} Media playback times by source URL
2244+ */
2245+ function saveMediaStates() {
2246+ const states = new Map();
2247+ const media = mediaWrapper.find('video, audio');
2248+ media.each((_, element) => {
2249+ if (element instanceof HTMLMediaElement) {
2250+ if (!element.currentSrc || element.readyState === HTMLMediaElement.HAVE_NOTHING) {
2251+ return;
2252+ }
2253+ const state = { currentTime: element.currentTime, paused: element.paused };
2254+ states.set(element.currentSrc, state);
2255+ }
2256+ });
2257+ return states;
2258+ }
2259+
2260+ /**
2261+ * Restores the playback times of media elements in the message.
2262+ * @param {Map<string, MediaState>} states Media playback times by source URL
2263+ */
2264+ function restoreMediaStates(states) {
2265+ const media = mediaWrapper.find('video, audio');
2266+ media.each((_, element) => {
2267+ if (element instanceof HTMLMediaElement) {
2268+ const restoreState = () => {
2269+ if (!states.has(element.currentSrc)) {
2270+ return;
2271+ }
2272+ const state = states.get(element.currentSrc);
2273+ element.currentTime = state.currentTime;
2274+ if (!state.paused) {
2275+ element.play();
2276+ }
2277+ };
2278+ if (element.readyState < HTMLMediaElement.HAVE_METADATA) {
2279+ element.addEventListener('loadedmetadata', () => restoreState(), { once: true });
2280+ } else {
2281+ restoreState();
2282+ }
2283+ }
2284+ });
2285+ }
2286+
2287+ // Add media gallery to message
2288+ if (hasMedia && mediaDisplay === MEDIA_DISPLAY.GALLERY) {
2289+ const mediaIndex = getMediaIndex(mes);
2290+ const selectedMedia = mes.extra.media[mediaIndex];
2291+
2292+ const galleryControls = $('#message_gallery_controls .mes_img_swipes').clone();
2293+ const counter = galleryControls.find('.mes_img_swipe_counter');
2294+ counter.text(`${mediaIndex + 1}/${mes.extra.media.length}`);
2295+
2296+ const template = appendMediaAttachment(selectedMedia, mediaIndex);
2297+ template.addClass('img_swipes');
2298+ template.append(galleryControls);
2299+ }
2300+
2301+ // Add media as a list to message
2302+ if (hasMedia && mediaDisplay === MEDIA_DISPLAY.LIST) {
2303+ for (let index = 0; index < mes.extra.media.length; index++) {
2304+ const attachment = mes.extra.media[index];
2305+ appendMediaAttachment(attachment, index);
2306+ }
2307+ }
2308+
2309+ // Remove existing file containers
2310+ fileWrapper.empty();
2311+
2312+ // Add files to message
2313+ if (hasFiles) {
2314+ for (let index = 0; index < mes.extra.files.length; index++) {
2315+ const file = mes.extra.files[index];
2316+ const template = $('#message_file_template .mes_file_container').clone();
2317+ template.attr('data-index', index);
2318+ template.find('.mes_file_name').text(file.name).attr('title', file.name);
2319+ template.find('.mes_file_size').text(humanFileSize(file.size)).attr('title', file.size);
2320+ fileWrapper.append(template);
2321+ }
2322+ }
2323+
2324+ // Early return if no media
2325+ if (!hasMedia) {
2326+ mediaWrapper.empty();
2327+ doAdjustScroll();
2328+ return;
2329+ }
2330+
2331+ // TODO: Consider making this awaitable
2332+ Promise.race([Promise.all(mediaPromises), delay(debounce_timeout.short)]).then(() => {
2333+ const states = saveMediaStates();
2334+ mediaWrapper.empty().append(mediaBlocks);
2335+ restoreMediaStates(states);
2336+ doAdjustScroll();
2337+ });
19242338}
19252339
19262340export function addCopyToCodeBlocks(messageElement) {
@@ -1945,7 +2359,7 @@ export function addCopyToCodeBlocks(messageElement) {
19452359
19462360/**
19472361 * Adds a single message to the chat.
19482362 * @param {objectChatMessage} mes Message object
19492363 * @param {object} [options] Options
19502364 * @param {string} [options.type='normal'] Message type
19512365 * @param {number} [options.insertAfter=null] Message ID to insert the new message after
@@ -1997,8 +2411,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
19972411 avatarImg = mes['force_avatar'];
19982412 }
19992413
20002414 // if mes.extra.uses_system_ui is true, set an override on the sanitizer options
20012415 const sanitizerOverrides = mes.extra?.uses_system_ui ? { MESSAGE_ALLOW_SYSTEM_UI: true } : {};
20022416
20032417 messageText = messageFormatting(
20042418 messageText,
@@ -2076,12 +2490,13 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
20762490 });
20772491
20782492 if (type === 'swipe') {
20792493 const swipeMessagemessageId = chatElement.find(`[mesid="${forceId ?? chat.length - 1}"]`);
2494+ const swipeMessage = chatElement.find(`[mesid="${messageId}"]`);
20802495 swipeMessage.attr('swipeid', params.swipeId);
20812496 swipeMessage.find('.mes_text').html(messageText).attr('title', title);
20822497 swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
20832498 updateReasoningUI(swipeMessage);
20842499 appendMediaToMessage(mes, swipeMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
20852500 if (power_user.timestamp_model_icon && params.extra?.api) {
20862501 insertSVGIcon(swipeMessage, params.extra);
20872502 }
@@ -2096,7 +2511,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
20962511 } else {
20972512 const messageId = forceId ?? chat.length - 1;
20982513 chatElement.find(`[mesid="${messageId}"] .mes_text`).append(messageText);
20992514 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
21002515 showSwipes && hideSwipeButtons();
21012516 }
21022517
@@ -2112,8 +2527,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
21122527 if (showSwipes) {
21132528 chatElement.find('.mes').last().addClass('last_mes');
21142529 chatElement.find('.mes').eq(-2).removeClass('last_mes');
21152530 hideSwipeButtonsrefreshSwipeButtons();
2116- showSwipeButtons();
21172531 }
21182532
21192533 // Don't scroll if not inserting last
@@ -2122,6 +2536,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
21222536 }
21232537
21242538 applyCharacterTagsToMessageDivs({ mesIds: newMessageId });
2539+ updateEditArrowClasses();
21252540}
21262541
21272542/**
@@ -2146,8 +2561,8 @@ export function formatCharacterAvatar(characterAvatar) {
21462561
21472562/**
21482563 * Formats the title for the generation timer.
21492564 * @param {DateMessageTimestamp} gen_started Date when generation was started
21502565 * @param {DateMessageTimestamp} gen_finished Date when generation was finished
21512566 * @param {number} tokenCount Number of tokens generated (0 if not available)
21522567 * @param {number?} [reasoningDuration=null] Reasoning duration (null if no reasoning was done)
21532568 * @param {number?} [timeToFirstToken=null] Time to first token
@@ -2183,8 +2598,19 @@ function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningD
21832598 return { timerValue, timerTitle };
21842599}
21852600
2186-export function scrollChatToBottom() {
2601+let requestId = null;
2187- if (power_user.auto_scroll_chat_to_bottom) {
2602+
2603+/**
2604+ * Scrolls the chat to the bottom if configured to do so.
2605+ * @param {object} [options] Options
2606+ * @param {boolean} [options.waitForFrame] If true, waits for the animation frame before scrolling
2607+ */
2608+export function scrollChatToBottom({ waitForFrame } = {}) {
2609+ if (!power_user.auto_scroll_chat_to_bottom) {
2610+ return;
2611+ }
2612+
2613+ const doScroll = () => {
21882614 let position = chatElement[0].scrollHeight;
21892615
21902616 if (power_user.waifuMode) {
@@ -2196,7 +2622,23 @@ export function scrollChatToBottom() {
21962622 }
21972623
21982624 chatElement.scrollTop(position);
2625+ requestId = null;
2626+ };
2627+
2628+ // Do not check truthiness. requestId can loop to zero.
2629+ if (requestId !== null) {
2630+ cancelAnimationFrame(requestId);
2631+ }
2632+
2633+ if (!waitForFrame) {
2634+ doScroll();
2635+ return;
21992636 }
2637+
2638+ // This prevents layout thrashing.
2639+ // https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value
2640+ // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#file-what-forces-layout-md
2641+ requestId = requestAnimationFrame(() => doScroll());
22002642}
22012643
22022644/**
@@ -2690,11 +3132,14 @@ export function getCharacterCardFields({ chid = null } = {}) {
26903132 }
26913133
26923134 const scenarioText = chat_metadata['scenario'] || character.scenario || '';
3135+ const exampleDialog = chat_metadata['mes_example'] || character.mes_example || '';
3136+ const systemPrompt = chat_metadata['system_prompt'] || character.data?.system_prompt || '';
3137+
26933138 result.description = baseChatReplace(character.description?.trim(), name1, name2);
26943139 result.personality = baseChatReplace(character.personality?.trim(), name1, name2);
26953140 result.scenario = baseChatReplace(scenarioText.trim(), name1, name2);
26963141 result.mesExamples = baseChatReplace(character.mes_example?exampleDialog.trim(), name1, name2);
26973142 result.system = power_user.prefer_character_prompt ? baseChatReplace(character.data?.system_prompt?systemPrompt.trim(), name1, name2) : '';
26983143 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
26993144 result.version = character.data?.character_version ?? '';
27003145 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
@@ -2800,8 +3245,8 @@ class StreamingProcessor {
28003245 this.reasoningHandler = new ReasoningHandler(timeStarted);
28013246 /** @type {PromptReasoning} */
28023247 this.promptReasoning = promptReasoning;
28033248 /** @type {string[]} */
28043249 this.imageimages = ''[];
28053250 }
28063251
28073252 /**
@@ -2854,8 +3299,8 @@ class StreamingProcessor {
28543299 await this.#checkDomElements(messageId, continueOnReasoning);
28553300 this.markUIGenStarted();
28563301 }
28573302 hideSwipeButtons({ hideCounters: true });
28583303 scrollChatToBottom({ waitForFrame: true });
28593304 return messageId;
28603305 }
28613306
@@ -2958,7 +3403,7 @@ class StreamingProcessor {
29583403 }
29593404
29603405 if (!scrollLock) {
29613406 scrollChatToBottom({ waitForFrame: true });
29623407 }
29633408 }
29643409
@@ -2987,8 +3432,8 @@ class StreamingProcessor {
29873432 chat[messageId].swipe_info.push(...swipeInfoArray);
29883433 }
29893434
2990- if (this.image) {
3435+ if (Array.isArray(this.images) && this.images.length > 0) {
29913436 await processImageAttachment(chat[messageId], { imageUrlimageUrls: this.imageimages });
29923437 appendMediaToMessage(chat[messageId], $(this.messageDom));
29933438 }
29943439
@@ -3086,7 +3531,7 @@ class StreamingProcessor {
30863531 }
30873532 // Get the updated reasoning string into the handler
30883533 this.reasoningHandler.updateReasoning(this.messageId, state?.reasoning);
30893534 this.imageimages = state?.imageimages ?? ''[];
30903535 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
30913536 await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text));
30923537 }
@@ -3587,7 +4032,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
35874032 const prevStarted = chat[chat.length - 1]['gen_started'];
35884033
35894034 if (prevFinished && prevStarted) {
35904035 const timePassed = Number(prevFinished) - Number(prevStarted);
35914036 generation_started = new Date(Date.now() - timePassed);
35924037 chat[chat.length - 1]['gen_started'] = generation_started;
35934038 }
@@ -3667,7 +4112,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
36674112 coreChat.pop();
36684113 }
36694114
36704115 coreChat = await Promise.all(coreChat.map(async (/** @type {ChatMessage} */ chatItem, index) => {
36714116 let message = chatItem.mes;
36724117 let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT;
36734118 let options = { isPrompt: true, depth: (coreChat.length - index - (isContinue ? 2 : 1)) };
@@ -3675,8 +4120,19 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
36754120 let regexedMessage = getRegexedString(message, regexType, options);
36764121 regexedMessage = await appendFileContent(chatItem, regexedMessage);
36774122
4123+ const titles = [];
36784124 if (chatItem?.extra?.append_title && chatItem?.extra?.title) {
3679- regexedMessage = `${regexedMessage}\n\n${chatItem.extra.title}`;
4125+ titles.push(chatItem.extra.title);
4126+ }
4127+ if (Array.isArray(chatItem?.extra?.media)) {
4128+ for (const mediaItem of chatItem.extra.media) {
4129+ if (mediaItem?.title && mediaItem?.append_title) {
4130+ titles.push(mediaItem.title);
4131+ }
4132+ }
4133+ }
4134+ if (titles.length > 0) {
4135+ regexedMessage = `${regexedMessage}\n\n${titles.join('\n\n')}`;
36804136 }
36814137
36824138 return {
@@ -4637,7 +5093,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
46375093 let getMessage = extractMessageFromData(data);
46385094 let title = extractTitleFromData(data);
46395095 let reasoning = extractReasoningFromData(data);
46405096 let imageUrlimageUrls = extractImageFromDataextractImagesFromData(data);
46415097 kobold_horde_model = title;
46425098
46435099 const swipes = extractMultiSwipes(data, type);
@@ -4681,10 +5137,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
46815137 else {
46825138 // Without streaming we'll be having a full message on continuation. Treat it as a last chunk.
46835139 if (originalType !== 'continue') {
46845140 ({ type, getMessage } = await saveReply({ type, getMessage, title, swipes, reasoning, imageUrlimageUrls }));
46855141 }
46865142 else {
46875143 ({ type, getMessage } = await saveReply({ type: 'appendFinal', getMessage, title, swipes, reasoning, imageUrlimageUrls }));
46885144 }
46895145
46905146 // This relies on `saveReply` having been called to add the message to the chat, so it must be last.
@@ -5197,7 +5653,7 @@ function setInContextMessages(msgInContextCount, type) {
51975653 msgInContextCount++;
51985654 }
51995655
52005656 const lastMessageBlock = chatElement.find('.mes:not([is_system="true"]), .mes.toolCall').eq(-msgInContextCount);
52015657 lastMessageBlock.addClass('lastInContext');
52025658
52035659 if (lastMessageBlock.length === 0) {
@@ -5308,23 +5764,23 @@ function extractTitleFromData(data) {
53085764 * @param {object} [options] Extraction options
53095765 * @param {string} [options.mainApi] Main API to use
53105766 * @param {string} [options.chatCompletionSource] Chat completion source
53115767 * @returns {string[]} Extracted imageimages or empty array
53125768 */
53135769function extractImageFromDataextractImagesFromData(data, { mainApi = null, chatCompletionSource = null } = {}) {
53145770 switch (mainApi ?? main_api) {
53155771 case 'openai': {
53165772 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
53175773 case chat_completion_sources.VERTEXAI:
53185774 case chat_completion_sources.MAKERSUITE: {
53195775 const inlineData = data?.responseContent?.parts?.findfilter(x => x.inlineData && !x.thought)?.map(x => x.inlineData);
53205776 if (Array.isArray(inlineData) && inlineData.length > 0) {
53215777 return inlineData.map(x => `data:${inlineDatax.mimeType};base64,${inlineDatax.data}`).filter(isDataURL);
53225778 }
53235779 } break;
53245780 case chat_completion_sources.OPENROUTER: {
53255781 const imageUrl = data?.choices[0]?.message?.images?.findfilter(x => x.type === 'image_url')?.map(x => x?.image_url?.url);
53265782 if (isDataURLArray.isArray(imageUrl) && imageUrl.length > 0) {
53275783 return imageUrl.filter(isDataURL);
53285784 }
53295785 // TODO: Handle remote URLs
53305786 }
@@ -5332,7 +5788,7 @@ function extractImageFromData(data, { mainApi = null, chatCompletionSource = nul
53325788 } break;
53335789 }
53345790
53355791 return undefined[];
53365792}
53375793
53385794/**
@@ -5453,6 +5909,7 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
54535909 case chat_completion_sources.XAI:
54545910 case chat_completion_sources.ELECTRONHUB:
54555911 case chat_completion_sources.AZURE_OPENAI:
5912+ case chat_completion_sources.ZAI:
54565913 default:
54575914 result = tryParse(text);
54585915 break;
@@ -5678,22 +6135,28 @@ export function cleanUpMessage({ getMessage, isImpersonate, isContinue, displayI
56786135 * Adds an image to the message.
56796136 * @param {object} message Message object
56806137 * @param {object} sources Image sources
56816138 * @param {string[]} [sources.imageUrlimageUrls] Image URLURLs
56826139 *
56836140 * @returns {Promise<void>}
56846141 */
56856142async function processImageAttachment(message, { imageUrlimageUrls }) {
5686- if (!imageUrl) {
6143+ if (!Array.isArray(imageUrls) || imageUrls.length === 0) {
56876144 return;
56886145 }
56896146
5690- let url = imageUrl;
6147+ for (const [index, imageUrl] of imageUrls.filter(onlyUnique).entries()) {
56916148 if (isDataURL(url)!imageUrl) {
5692- const fileName = `inline_image_${Date.now().toString()}`;
6149+ continue;
5693- const [mime, base64] = /^data:(.*?);base64,(.*)$/.exec(imageUrl).slice(1);
6150+ }
5694- url = await saveBase64AsFile(base64, message.name, fileName, mime.split('/')[1]);
6151+
6152+ let url = imageUrl;
6153+ if (isDataURL(url)) {
6154+ const fileName = `inline_image_${Date.now().toString()}_${index}`;
6155+ const [mime, base64] = /^data:(.*?);base64,(.*)$/.exec(imageUrl).slice(1);
6156+ url = await saveBase64AsFile(base64, message.name, fileName, mime.split('/')[1]);
6157+ }
6158+ saveImageToMessage({ image: url, inline: true }, message);
56956159 }
5696- saveImageToMessage({ image: url, inline: true }, message);
56976160}
56986161
56996162/**
@@ -5708,17 +6171,17 @@ async function processImageAttachment(message, { imageUrl }) {
57086171 * @property {string} [title] Message tooltip
57096172 * @property {string[]} [swipes] Extra swipes
57106173 * @property {string} [reasoning] Message reasoning
57116174 * @property {string[]} [imageUrlimageUrls] LinkLinks to an imageimages
57126175 *
57136176 * @typedef {object} SaveReplyResult
57146177 * @property {string} type Type of generation
57156178 * @property {string} getMessage Generated message
57166179 */
57176180export async function saveReply({ type, getMessage, fromStreaming = false, title = '', swipes = [], reasoning = '', imageUrlimageUrls = ''[] }) {
57186181 // Backward compatibility
57196182 if (arguments.length > 1 && typeof arguments[0] !== 'object') {
57206183 console.trace('saveReply called with positional arguments. Please use an object instead.');
57216184 [type, getMessage, fromStreaming, title, swipes, reasoning, imageUrlimageUrls] = arguments;
57226185 }
57236186
57246187 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
@@ -5754,7 +6217,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
57546217 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
57556218 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
57566219 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
57576220 await processImageAttachment(chat[chat.length - 1], { imageUrlimageUrls });
57586221 if (power_user.message_token_count_enabled) {
57596222 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
57606223 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -5778,7 +6241,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
57786241 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
57796242 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
57806243 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
57816244 await processImageAttachment(chat[chat.length - 1], { imageUrlimageUrls });
57826245 if (power_user.message_token_count_enabled) {
57836246 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
57846247 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -5798,7 +6261,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
57986261 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
57996262 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
58006263 chat[chat.length - 1]['extra']['reasoning'] += reasoning;
58016264 await processImageAttachment(chat[chat.length - 1], { imageUrlimageUrls });
58026265 // We don't know if the reasoning duration extended, so we don't update it here on purpose.
58036266 if (power_user.message_token_count_enabled) {
58046267 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
@@ -5844,7 +6307,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
58446307 chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;
58456308 }
58466309
58476310 await processImageAttachment(chat[chat.length - 1], { imageUrlimageUrls });
58486311 const chat_id = (chat.length - 1);
58496312
58506313 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);
@@ -5930,7 +6393,8 @@ export function syncMesToSwipe(messageId = null) {
59306393 return false;
59316394 }
59326395 // If the swipe is not present yet, exit out (will likely be copied later)
5933- if (!targetMessage.swipes[targetMessage.swipe_id] || !targetMessage.swipe_info[targetMessage.swipe_id]) {
6396+ // "" is falsy. An empty string is a valid message.
6397+ if (typeof targetMessage.swipes[targetMessage.swipe_id] !== 'string' || !targetMessage.swipe_info[targetMessage.swipe_id]) {
59346398 return false;
59356399 }
59366400
@@ -5987,26 +6451,36 @@ export function syncSwipeToMes(messageId = null, swipeId = null) {
59876451 return false;
59886452 }
59896453 // If swipes structure is invalid, exit out
59906454 if (!Array.isArray(targetMessage.swipe_info) || !Array.isArray(targetMessage.swipes)) {
59916455 return false;
59926456 }
59936457
6458+ // Backfill swipe_info if missing.
6459+ if (!Array.isArray(targetMessage.swipe_info)) {
6460+ targetMessage.swipe_info = targetMessage.swipes.map(_ => ({
6461+ send_date: targetMessage.send_date,
6462+ gen_started: void 0,
6463+ gen_finished: void 0,
6464+ extra: {},
6465+ }));
6466+ }
6467+
59946468 const targetSwipeId = targetMessage.swipe_id;
59956469 if (!typeof targetMessage.swipes[targetSwipeId] || !targetMessage.swipe_info[targetSwipeId]== 'string') {
59966470 console.warn(`[syncSwipeToMes] Invalid swipe ID: ${targetSwipeId}`);
59976471 return false;
59986472 }
59996473
60006474 const targetSwipeInfo = targetMessage?.swipe_info?.[targetSwipeId];
60016475 if (typeof targetSwipeInfo !== 'object') {
6002- return false;
6476+ console.warn(`[syncSwipeToMes] Invalid swipe info: ${targetSwipeId}`);
60036477 }
60046478
60056479 targetMessage.mes = targetMessage.swipes[targetSwipeId];
60066480 targetMessage.send_date = targetSwipeInfo?.send_date;
60076481 targetMessage.gen_started = targetSwipeInfo?.gen_started;
60086482 targetMessage.gen_finished = targetSwipeInfo?.gen_finished;
60096483 targetMessage.extra = structuredClone(targetSwipeInfo?.extra) ?? {};
60106484
60116485 return true;
60126486}
@@ -6014,7 +6488,7 @@ export function syncSwipeToMes(messageId = null, swipeId = null) {
60146488/**
60156489 * Saves the image to the message object.
60166490 * @param {ParsedImage} img Image object
60176491 * @param {objectChatMessage} mes Chat message object
60186492 * @typedef {{ image?: string, title?: string, inline?: boolean }} ParsedImage
60196493 */
60206494function saveImageToMessage(img, mes) {
@@ -6022,8 +6496,10 @@ function saveImageToMessage(img, mes) {
60226496 if (!mes.extra || typeof mes.extra !== 'object') {
60236497 mes.extra = {};
60246498 }
6025- mes.extra.image = img.image;
6499+ if (!Array.isArray(mes.extra.media)) {
60266500 mes.extra.titlemedia = img.title[];
6501+ }
6502+ mes.extra.media.push({ url: img.image, type: MEDIA_TYPE.IMAGE, title: img.title, source: MEDIA_SOURCE.API });
60276503 mes.extra.inline_image = img.inline;
60286504 }
60296505}
@@ -6209,11 +6685,11 @@ export async function renameCharacter(name = null, { silent = false, renameChats
62096685 const oldName = getCharaFilename(null, { manualAvatarKey: oldAvatar });
62106686 const newName = getCharaFilename(null, { manualAvatarKey: newAvatar });
62116687
62126688 // Replace other auxilleryauxiliary fields where was referenced by avatar key
62136689 // Tag List
62146690 renameTagKey(oldAvatar, newAvatar);
62156691
62166692 // AddtionalAdditional lore books
62176693 const charLore = world_info.charLore?.find(x => x.name == oldName);
62186694 if (charLore) {
62196695 charLore.name = newName;
@@ -6379,7 +6855,7 @@ export function saveChatDebounced() {
63796855 * @param {string} [options.chatName] The name of the chat file to save to
63806856 * @param {object} [options.withMetadata] Additional metadata to save with the chat
63816857 * @param {number} [options.mesId] The message ID to save the chat up to
63826858 * @param {boolean} [options.force] Force the saving despiredespite the integrity check result
63836859 *
63846860 * @returns {Promise<void>}
63856861 */
@@ -6649,6 +7125,7 @@ export async function getChat() {
66497125 chat_metadata = chat[0]['chat_metadata'] ?? {};
66507126
66517127 chat.shift();
7128+ chat.forEach(ensureMessageMediaIsArray);
66527129 } else {
66537130 chat_create_date = humanizedDateTime();
66547131 }
@@ -6875,7 +7352,7 @@ export function setUserName(value, { toastPersonaNameChange = true } = {}) {
68757352
68767353async function doOnboarding(avatarId) {
68777354 const template = $('#onboarding_template .onboarding');
68787355 let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { rows: 2, wider: true, cancelButton: false });
68797356
68807357 if (userName) {
68817358 userName = String(userName).replace('\n', ' ');
@@ -6936,8 +7413,7 @@ export async function getSettings() {
69367413
69377414 swipes = settings.swipes !== undefined ? !!settings.swipes : true; // enable swipes by default
69387415 $('#swipes-checkbox').prop('checked', swipes); /// swipecode
69397416 hideSwipeButtonsrefreshSwipeButtons();
6940- showSwipeButtons();
69417417
69427418 // Kobold
69437419 loadKoboldSettings(data, settings.kai_settings ?? settings, settings);
@@ -6946,7 +7422,7 @@ export async function getSettings() {
69467422 loadNovelSettings(data, settings.nai_settings ?? settings);
69477423
69487424 // TextGen
69497425 await loadTextGenSettings(data, settings);
69507426
69517427 // OpenAI
69527428 loadOpenAISettings(data, settings.oai_settings ?? settings);
@@ -7210,22 +7686,183 @@ function messageEditAuto(div) {
72107686 saveChatDebounced();
72117687}
72127688
7213-async function messageEditDone(div) {
7689+/**
7214- let { mesBlock, text, mes, bias } = updateMessage(div);
7690+ * Create the message edit UI.
7215- if (this_edit_mes_id == 0) {
7691+ * @param {number} editMessageId The ID of the message to edit
7216- text = substituteParams(text);
7692+ */
7693+export async function messageEdit(editMessageId) {
7694+ const editMessage = chat[editMessageId];
7695+ if (!editMessage) {
7696+ console.warn(`Message with id ${editMessageId} not found in chat array.`);
7697+ return;
72177698 }
72187699
7219- await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
7700+ const messageElement = chatElement.find(`.mes[mesid="${editMessageId}"]`);
7220- text = chat[this_edit_mes_id]?.mes ?? text;
7701+ if (messageElement.length === 0) {
7221- mesBlock.find('.mes_text').empty();
7702+ console.warn(`Message element with id ${editMessageId} not found in DOM.`);
7222- mesBlock.find('.mes_edit_buttons').css('display', 'none');
7703+ return;
7223- mesBlock.find('.mes_buttons').css('display', '');
7704+ }
7224- mesBlock.find('.mes_text').append(
7705+
7225- messageFormatting(
7706+ this_edit_mes_id = editMessageId;
7226- text,
7707+ this_edit_mes_chname = editMessage.name || (editMessage.is_user ? name1 : name2);
7227- this_edit_mes_chname,
7708+
7228- mes.is_system,
7709+ const hideCounters = editMessageId < chat.length - 1;
7710+ hideSwipeButtons({ hideCounters });
7711+
7712+ const chatScrollPosition = chatElement.scrollTop();
7713+ const messageBlock = messageElement.find('.mes_block');
7714+ const messageText = messageBlock.find('.mes_text');
7715+
7716+ messageText.empty();
7717+ messageBlock.find('.mes_buttons').css('display', 'none');
7718+ messageBlock.find('.mes_edit_buttons').css('display', 'inline-flex');
7719+
7720+ // Also edit reasoning, if it exists
7721+ const reasoningEdit = messageBlock.find('.mes_reasoning_edit:visible');
7722+ if (reasoningEdit.length > 0) {
7723+ reasoningEdit.trigger('click');
7724+ }
7725+
7726+ const editTextArea = document.createElement('textarea');
7727+ editTextArea.id = 'curEditTextarea';
7728+ editTextArea.className = 'edit_textarea mdHotkeys';
7729+ messageText.append(editTextArea);
7730+
7731+ const text = trimSpaces(editMessage.mes || '');
7732+ const $editTextArea = $(editTextArea);
7733+ $editTextArea.val(text);
7734+
7735+ const cssAutofit = CSS.supports('field-sizing', 'content');
7736+ if (!cssAutofit) {
7737+ $editTextArea.height(0);
7738+ $editTextArea.height(editTextArea.scrollHeight);
7739+ }
7740+
7741+ $editTextArea.trigger('focus');
7742+
7743+ // Sets the cursor at the end of the text
7744+ editTextArea.setSelectionRange(text.length, text.length);
7745+
7746+ if (Number(this_edit_mes_id) === chat.length - 1) {
7747+ chatElement.scrollTop(chatScrollPosition);
7748+ }
7749+
7750+ updateEditArrowClasses();
7751+}
7752+
7753+/**
7754+ * Close the open message editor.
7755+ * This deletes the user's unsaved changes.
7756+ * @param {number} [messageId=this_edit_mes_id]
7757+ */
7758+async function messageEditCancel(messageId = this_edit_mes_id) {
7759+ let text = chat[messageId]['mes'];
7760+ let thisMesDiv;
7761+ // If this is the button then select it's parent. Otherwise, select by messageId.
7762+ if (this?.classList?.contains('mes_edit_cancel')) {
7763+ thisMesDiv = $(this).closest('.mes');
7764+ } else {
7765+ thisMesDiv = chatElement.children().filter(`[mesid="${messageId}"]`);
7766+ }
7767+
7768+ const thisMesBlock = thisMesDiv.find('.mes_block');
7769+ thisMesBlock.find('.mes_text').empty();
7770+ thisMesDiv.find('.mes_edit_buttons').css('display', 'none');
7771+ thisMesBlock.find('.mes_buttons').css('display', '');
7772+ thisMesBlock.find('.mes_text')
7773+ .append(messageFormatting(
7774+ text,
7775+ this_edit_mes_chname,
7776+ chat[messageId].is_system,
7777+ chat[messageId].is_user,
7778+ messageId,
7779+ {},
7780+ false,
7781+ ));
7782+ appendMediaToMessage(chat[messageId], thisMesDiv);
7783+ addCopyToCodeBlocks(thisMesDiv);
7784+
7785+ const reasoningEditDone = thisMesBlock.find('.mes_reasoning_edit_cancel:visible');
7786+ if (reasoningEditDone.length > 0) {
7787+ reasoningEditDone.trigger('click');
7788+ }
7789+
7790+ await eventSource.emit(event_types.MESSAGE_UPDATED, messageId);
7791+ if (messageId == this_edit_mes_id) {
7792+ this_edit_mes_id = undefined;
7793+ }
7794+ else {
7795+ console.warn(`The message editor was closed on message #${messageId} while #${this_edit_mes_id} is being edited.`);
7796+ }
7797+
7798+ showSwipeButtons();
7799+}
7800+
7801+/**
7802+ * Swaps chat[sourceId] with chat[targetId]. They must be adjacent.
7803+ * @param {number} sourceId Index of the message to move
7804+ * @param {number} targetId Index of the target message
7805+ * @returns {Promise<boolean>} True if the messages were moved, false otherwise
7806+ */
7807+async function messageEditMove(sourceId, targetId) {
7808+ if (is_send_press) {
7809+ console.warn(`The message #${sourceId} was not moved to #${targetId} because a generation is in progress.`);
7810+ return false;
7811+ }
7812+
7813+ if (Math.abs(sourceId - targetId) !== 1) {
7814+ console.error(`Message #${sourceId} and #${targetId} are not adjacent.`);
7815+ return false;
7816+ }
7817+
7818+ const targetMessageDiv = chatElement.find(`.mes[mesid="${targetId}"]`);
7819+ const sourceMessageDiv = chatElement.find(`.mes[mesid="${sourceId}"]`);
7820+
7821+ if (sourceMessageDiv.length === 0 || targetMessageDiv.length === 0) {
7822+ console.error(`Message #${sourceId} or #${targetId} were not found.`);
7823+ return false;
7824+ }
7825+
7826+ if (sourceId <= targetId) {
7827+ sourceMessageDiv.insertAfter(targetMessageDiv);
7828+ }
7829+ else {
7830+ sourceMessageDiv.insertBefore(targetMessageDiv);
7831+ }
7832+
7833+ //Swap Ids.
7834+ targetMessageDiv.attr('mesid', sourceId);
7835+ sourceMessageDiv.attr('mesid', targetId);
7836+
7837+ // Swap chat array entries.
7838+ [chat[sourceId], chat[targetId]] = [chat[targetId], chat[sourceId]];
7839+
7840+ // Update edited message id
7841+ if (this_edit_mes_id === sourceId) {
7842+ this_edit_mes_id = targetId;
7843+ }
7844+
7845+ updateViewMessageIds();
7846+ await saveChatConditional();
7847+ return true;
7848+}
7849+
7850+async function messageEditDone(div) {
7851+ let { mesBlock, text, mes, bias } = updateMessage(div);
7852+ if (this_edit_mes_id == 0) {
7853+ text = substituteParams(text);
7854+ }
7855+
7856+ await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
7857+ text = chat[this_edit_mes_id]?.mes ?? text;
7858+ mesBlock.find('.mes_text').empty();
7859+ mesBlock.find('.mes_edit_buttons').css('display', 'none');
7860+ mesBlock.find('.mes_buttons').css('display', '');
7861+ mesBlock.find('.mes_text').append(
7862+ messageFormatting(
7863+ text,
7864+ this_edit_mes_chname,
7865+ mes.is_system,
72297866 mes.is_user,
72307867 this_edit_mes_id,
72317868 {},
@@ -7245,6 +7882,7 @@ async function messageEditDone(div) {
72457882 await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
72467883 this_edit_mes_id = undefined;
72477884 await saveChatConditional();
7885+ showSwipeButtons();
72487886}
72497887
72507888/**
@@ -7590,7 +8228,7 @@ export function select_selected_character(chid, { switchMenu = true } = {}) {
75908228 $('#char_connections_button').show();
75918229
75928230 // Hide the chat scenario button if we're peeking the group member defs
75938231 $('#set_chat_scenarioset_chat_character_settings').toggle(!selected_group);
75948232
75958233 // Don't update the navbar name if we're peeking the group member defs
75968234 if (!selected_group) {
@@ -7672,7 +8310,7 @@ function select_rm_create({ switchMenu = true } = {}) {
76728310
76738311 switchMenu && selectRightMenuWithAnimation('rm_ch_create_block');
76748312
76758313 $('#set_chat_scenarioset_chat_character_settings').hide();
76768314 $('#delete_button_div').css('display', 'none');
76778315 $('#delete_button').css('display', 'none');
76788316 $('#export_button').css('display', 'none');
@@ -7807,33 +8445,65 @@ function updateFavButtonState(state) {
78078445 $('#favorite_button').toggleClass('fav_off', !state);
78088446}
78098447
78108448export async function setScenarioOverridesetCharacterSettingsOverrides() {
78118449 if (!selected_group && (this_chid === undefined || !characters[this_chid])) {
78128450 console.warn('setScenarioOverridesetCharacterSettingsOverrides() -- no selected group or character');
78138451 return;
78148452 }
78158453
78168454 const metadataValuescenarioOverrideValue = chat_metadata['scenario'] || '';
8455+ const exampleMessagesValue = chat_metadata['mes_example'] || '';
8456+ const systemPromptValue = chat_metadata['system_prompt'] || '';
78178457 const isGroup = !!selected_group;
78188458
78198459 const $template = $(await renderTemplateAsync('scenarioOverride'));
78208460 $template.find('[data-group="true"]').toggle(isGroup);
78218461 $template.find('[data-character="true"]').toggle(!isGroup);
7822- // TODO: Why does this save on every character input? Save on popup close
8462+ const pendingChanges = {
7823- $template.find('.chat_scenario').val(metadataValue).on('input', onScenarioOverrideInput);
8463+ scenario: scenarioOverrideValue,
7824- $template.find('.remove_scenario_override').on('click', onScenarioOverrideRemoveClick);
8464+ examples: exampleMessagesValue,
8465+ system_prompt: systemPromptValue,
8466+ };
78258467
7826- await callGenericPopup($template, POPUP_TYPE.TEXT, '');
8468+ // Keep edits local until the popup is closed/confirmed
7827-}
8469+ const $scenario = $template.find('.chat_scenario');
8470+ $scenario.val(scenarioOverrideValue).on('input', function () {
8471+ pendingChanges.scenario = String($(this).val());
8472+ });
8473+ const $examples = $template.find('.chat_examples');
8474+ $examples.val(exampleMessagesValue).on('input', function () {
8475+ pendingChanges.examples = String($(this).val());
8476+ });
8477+ const $systemPrompt = $template.find('.chat_system_prompt');
8478+ $systemPrompt.val(systemPromptValue).on('input', function () {
8479+ pendingChanges.system_prompt = String($(this).val());
8480+ });
78288481
7829-function onScenarioOverrideInput() {
8482+ $template.find('.remove_scenario_override').on('click', async function () {
7830- const value = String($(this).val());
8483+ const confirm = await Popup.show.confirm(t`Are you sure you want to remove all overrides?`, t`This action cannot be undone.`);
7831- chat_metadata['scenario'] = value;
8484+ if (!confirm) {
7832- saveMetadataDebounced();
8485+ return;
78338486 }
78348487
7835-function onScenarioOverrideRemoveClick() {
8488+ $scenario.val('');
7836- $(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input');
8489+ pendingChanges.scenario = '';
8490+ $examples.val('');
8491+ pendingChanges.examples = '';
8492+ $systemPrompt.val('');
8493+ pendingChanges.system_prompt = '';
8494+ });
8495+
8496+ // Wait for popup close/confirm.
8497+ await callGenericPopup($template, POPUP_TYPE.TEXT, '', {
8498+ wide: true,
8499+ large: true,
8500+ allowVerticalScrolling: true,
8501+ });
8502+
8503+ chat_metadata['scenario'] = pendingChanges.scenario;
8504+ chat_metadata['mes_example'] = pendingChanges.examples;
8505+ chat_metadata['system_prompt'] = pendingChanges.system_prompt;
8506+ saveMetadataDebounced();
78378507}
78388508
78398509/**
@@ -7900,18 +8570,42 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w
79008570 });
79018571}
79028572
7903-export function showSwipeButtons() {
8573+/**
8574+ * Update the swipe counter for mesId.
8575+ * @param {Number} mesId
8576+ */
8577+export async function updateSwipeCounter(mesId) {
8578+ const swipeCounterText = formatSwipeCounter((chat[mesId]?.['swipe_id'] + 1), chat[mesId]?.['swipes']?.length);
8579+ const currentMessage = chatElement.children().filter(`[mesid="${mesId}"]`);
8580+ const swipeCounter = currentMessage.find('.swipes-counter');
8581+ swipeCounter.text(swipeCounterText).show();
8582+}
8583+
8584+/**
8585+ * Swipe buttons are often toggled to update their position.
8586+ * This should be replaced with a more efficient function.
8587+ */
8588+export function refreshSwipeButtons() {
8589+ hideSwipeButtons();
8590+ showSwipeButtons();
8591+}
8592+
8593+export function showSwipeButtons(mesId = chat.length - 1) {
8594+ isSwipingAllowed = true;
8595+
79048596 if (chat.length === 0) {
79058597 return;
79068598 }
79078599
79088600 if (
79098601 chat[chat.length - 1mesId].is_system ||
79108602 !swipes ||
79118603 Number($('.mes:last').attr('mesid')) < 0 ||
79128604 chat[chat.length - 1mesId].is_user ||
79138605 (selected_group && is_group_generating)
79148606 ) { return; }
8607+ return;
8608+ }
79158609
79168610 // swipe_id should be set if alternate greetings are added
79178611 if (chat.length == 1 && chat[0].swipe_id === undefined) {
@@ -7921,35 +8615,35 @@ export function showSwipeButtons() {
79218615 //had to add this to make the swipe counter work
79228616 //(copied from the onclick functions for swipe buttons..
79238617 //don't know why the array isn't set for non-swipe messages in Generate or addOneMessage..)
79248618 if (chat[chat.length - 1mesId]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
79258619 chat[chat.length - 1mesId]['swipe_id'] = 0; // set it to id 0
79268620 chat[chat.length - 1mesId]['swipes'] = []; // empty the array
79278621 chat[chat.length - 1mesId]['swipes'][0] = chat[chat.length - 1mesId]['mes']; //assign swipe array with last message from chat
79288622 chat[chat.length - 1mesId]['swipe_info'] = [];
79298623 chat[chat.length - 1mesId]['swipe_info'][0] = {
79308624 'send_date': chat[chat.length - 1mesId]['send_date'],
79318625 'gen_started': chat[chat.length - 1mesId]['gen_started'],
79328626 'gen_finished': chat[chat.length - 1mesId]['gen_finished'],
79338627 'extra': structuredClone(chat[chat.length - 1mesId]['extra']),
79348628 };
79358629 }
79368630
79378631 const currentMessage = chatElement.children().filter(`[mesid="${chat.length - 1mesId}"]`);
79388632 const swipeId = chat[chat.length - 1mesId].swipe_id;
79398633 const swipeCounterText = formatSwipeCounter((swipeId + 1), chat[chat.length - 1mesId].['swipes'].length);
79408634 const swipeRight = currentMessage.find('.swipe_right');
79418635 const swipeLeft = currentMessage.find('.swipe_left');
79428636 const swipeCounter = currentMessage.find('.swipes-counter');
79438637
79448638 if (swipeId !== undefined && (chat[chat.length - 1mesId].swipes.length > 1 || swipeId > 0)) {
79458639 swipeLeft.css('display', 'flex');
79468640 }
79478641 //only show right when generate is off, or when next right swipe would not make a generate happen
79488642 if (is_send_press === false || chat[chat.length - 1mesId].swipes.length >= swipeId) {
79498643 swipeRight.css('display', 'flex').css('opacity', '0.3');
79508644 swipeCounter.css('opacity', '0.3');
79518645 }
79528646 if ((chat[chat.length - 1mesId].swipes.length - swipeId) === 1) {
79538647 //chevron was moved out of hardcode in HTML to class toggle dependent on last_mes or not
79548648 //necessary for 'swipe_right' div in past messages to have no chevron if 'show swipes for all messages' is turned on
79558649 swipeRight.css('opacity', '0.7');
@@ -7961,10 +8655,17 @@ export function showSwipeButtons() {
79618655 lastSwipeCounter.text(swipeCounterText).show();
79628656}
79638657
7964-export function hideSwipeButtons() {
8658+/**
8659+ * @param {object} [options] Options
8660+ * @param {boolean} [options.hideCounters=false] Also hide the swipes counter.
8661+ */
8662+export function hideSwipeButtons({ hideCounters = false } = {}) {
8663+ isSwipingAllowed = false;
79658664 chatElement.find('.swipe_right').hide();
7966- chatElement.find('.last_mes .swipes-counter').hide();
79678665 chatElement.find('.swipe_left').hide();
8666+ if (hideCounters === true) {
8667+ chatElement.find('.last_mes .swipes-counter').hide();
8668+ }
79688669}
79698670
79708671/**
@@ -8008,6 +8709,8 @@ export async function deleteSwipe(swipeId = null, messageId = chat.length - 1) {
80088709 const newSwipeId = Math.min(swipeId, message.swipes.length - 1);
80098710 syncSwipeToMes(messageId, newSwipeId);
80108711
8712+ chat_metadata['tainted'] = true;
8713+
80118714 await eventSource.emit(event_types.MESSAGE_SWIPE_DELETED, { messageId, swipeId, newSwipeId });
80128715
80138716 await saveChatConditional();
@@ -8081,8 +8784,8 @@ async function importCharacterChat(formData, { refresh = true } = {}) {
80818784 return [];
80828785}
80838786
80848787export function updateViewMessageIds(startFromZerostartIndex = falsenull) {
80858788 const minId = startFromZerostartIndex ? 0 :? getFirstDisplayedMessageId();
80868789
80878790 chatElement.find('.mes').each(function (index, element) {
80888791 $(element).attr('mesid', minId + index);
@@ -8101,24 +8804,27 @@ export function getFirstDisplayedMessageId() {
81018804 return minId;
81028805}
81038806
81048807export function updateEditArrowClasses() {
8105- chatElement.find('.mes .mes_edit_up').removeClass('disabled');
8808+ if (!(this_edit_mes_id >= 0)) {
8106- chatElement.find('.mes .mes_edit_down').removeClass('disabled');
8809+ return;
8810+ }
8811+
8812+ const message = chatElement.find(`.mes[mesid="${this_edit_mes_id}"]`);
81078813
8108- if (this_edit_mes_id !== undefined) {
8814+ const downButton = message.find('.mes_edit_down');
8109- const down = chatElement.find(`.mes[mesid="${this_edit_mes_id}"] .mes_edit_down`);
8815+ const upButton = message.find('.mes_edit_up');
8110- const up = chatElement.find(`.mes[mesid="${this_edit_mes_id}"] .mes_edit_up`);
8816+ const copyButton = message.find('.mes_edit_copy');
81118817 const lastIddeleteButton = Number(chatElementmessage.find('.mes').last().attr('mesidmes_edit_delete'));
81128818 const firstIdlastId = Number(chatElement.find('.mes').firstlast().attr('mesid'));
8819+ const firstId = Number(chatElement.find('.mes').first().attr('mesid'));
81138820
8114- if (lastId == Number(this_edit_mes_id)) {
8821+ copyButton.removeClass('disabled');
81158822 down deleteButton.addClassremoveClass('disabled');
8116- }
81178823
8118- if (firstId == Number(this_edit_mes_id)) {
8824+ // The last message cannot be moved down.
8119- up.addClass('disabled');
8825+ downButton.toggleClass('disabled', lastId === Number(this_edit_mes_id));
8120- }
8826+ // The first message cannot be moved up.
8121- }
8827+ upButton.toggleClass('disabled', firstId === Number(this_edit_mes_id));
81228828}
81238829
81248830/**
@@ -8127,7 +8833,7 @@ function updateEditArrowClasses() {
81278833 */
81288834export function closeMessageEditor(what = 'all') {
81298835 if (what === 'message' || what === 'all') {
81308836 if (this_edit_mes_id >= 0) {
81318837 chatElement.find(`.mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).trigger('click');
81328838 }
81338839 }
@@ -8270,6 +8976,8 @@ function openAlternateGreetings() {
82708976 array.push('');
82718977 addAlternateGreeting(template, '', index, getArray, popup);
82728978 updateAlternateGreetingsHintVisibility(template);
8979+ const list = template.find('.alternate_greetings_list');
8980+ list.scrollTop(list.prop('scrollHeight'));
82738981 });
82748982
82758983 popup.show();
@@ -8286,6 +8994,7 @@ function openAlternateGreetings() {
82868994 */
82878995function addAlternateGreeting(template, greeting, index, getArray, popup) {
82888996 const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone();
8997+ greetingBlock.attr('data-index', index);
82898998 greetingBlock.find('.alternate_greeting_text')
82908999 .attr('id', `alternate_greeting_${index}`)
82919000 .on('input', async function () {
@@ -8299,15 +9008,57 @@ function addAlternateGreeting(template, greeting, index, getArray, popup) {
82999008 event.preventDefault();
83009009 event.stopPropagation();
83019010
83029011 ifconst (confirm = await callGenericPopup(t`Are you sure you want to delete this alternate greeting?`)), {POPUP_TYPE.CONFIRM);
8303- const array = getArray();
9012+ if (!confirm) {
8304- array.splice(index, 1);
9013+ return;
8305-
8306- // We need to reopen the popup to update the index numbers
8307- await popup.complete(POPUP_RESULT.AFFIRMATIVE);
8308- openAlternateGreetings();
83099014 }
9015+
9016+ const array = getArray();
9017+ array.splice(index, 1);
9018+
9019+ // We need to reopen the popup to update the index numbers
9020+ await popup.complete(POPUP_RESULT.AFFIRMATIVE);
9021+ openAlternateGreetings();
83109022 });
9023+ greetingBlock.find('.move_up_alternate_greeting').on('click', function (event) {
9024+ handleMoveAlternateGreeting(event, -1);
9025+ });
9026+ greetingBlock.find('.move_down_alternate_greeting').on('click', function (event) {
9027+ handleMoveAlternateGreeting(event, 1);
9028+ });
9029+
9030+ /**
9031+ * Handles moving an alternate greeting up or down in the list.
9032+ * @param {JQuery.ClickEvent} event - The click event
9033+ * @param {number} direction - Direction to move: -1 for up, 1 for down
9034+ */
9035+ function handleMoveAlternateGreeting(event, direction) {
9036+ event.preventDefault();
9037+ event.stopPropagation();
9038+
9039+ const array = getArray();
9040+ const index = Number(greetingBlock.attr('data-index'));
9041+ const newIndex = index + direction;
9042+
9043+ // Check bounds
9044+ if (direction === -1 && index <= 0) {
9045+ return;
9046+ }
9047+ if (direction === 1 && index >= array.length - 1) {
9048+ return;
9049+ }
9050+
9051+ // Swap the greetings
9052+ [array[index], array[newIndex]] = [array[newIndex], array[index]];
9053+
9054+ // Update current greeting
9055+ greetingBlock.find('.alternate_greeting_text').val(array[index]);
9056+
9057+ // Update adjacent greeting
9058+ const adjacentGreetingBlock = template.find(`.alternate_greeting[data-index="${newIndex}"]`);
9059+ adjacentGreetingBlock.find('.alternate_greeting_text').val(array[newIndex]);
9060+ }
9061+
83119062 template.find('.alternate_greetings_list').append(greetingBlock);
83129063}
83139064
@@ -8499,367 +9250,390 @@ export async function createOrEditCharacter(e) {
84999250 * @returns {string} The formatted counter.
85009251 */
85019252function formatSwipeCounter(current, total) {
85029253 if (isNaN(current) ||&& isNaN(total)) {
85039254 return '';
85049255 }
8505-
9256+ return `${!isNaN(current) ? current : '?'}\u200b/\u200b${!isNaN(total) ? total : '?'}`;
8506- return `${current}\u200b/\u200b${total}`;
85079257}
85089258
85099259/**
85109260 * Handles the swipe to the left event.
85119261 * @param {JQuery.Event} _event Event.
9262+ * @param {'left'|'right'} direction The direction to swipe.
85129263 * @param {object} params Additional parameters.
85139264 * @param {string} [params.source] The source of the swipe event.
85149265 * @param {boolean} [params.repeated] Is the swipe event repeated.
9266+ * @param {object} [params.message=chat[chat.length - 1]] The chat message to swipe.
85159267 */
85169268export async function swipe_leftswipe(_event, direction, { source, repeated, message = chat[chat.length - 1] } = {}) {
85179269 if (chat.length - 1 === Number(this_edit_mes_id)0) {
8518- closeMessageEditor();
9270+ console.warn('Swipe was called on an empty chat.');
9271+ return;
85199272 }
8520- if (isStreamingEnabled() && streamingProcessor) {
9273+
8521- streamingProcessor.onStopStreaming();
9274+ //Only allow one concurrent swipe.
9275+ if (!isSwipingAllowed) {
9276+ console.info('The swipe has been ignored because another is in progress.');
9277+ return;
9278+ }
9279+ isSwipingAllowed = false;
9280+
9281+ let generation;
9282+ let messageIndex;
9283+
9284+ //Only set messageIndex if message exists because -1 is truthy.
9285+ if (message) {
9286+ messageIndex = chat.indexOf(message);
9287+ if (messageIndex === -1) {
9288+ console.error(`The message must exist in chat. ${message};`);
9289+ return;
9290+ }
85229291 }
85239292
8524- // Make sure ad-hoc changes to extras are saved before swiping away
9293+ const mesId = Number($(this).closest('.mes').attr('mesid') ?? messageIndex ?? chat.length - 1);
8525- syncMesToSwipe();
85269294
8527- // If the user is holding down the key and we're at the first swipe, don't do anything
9295+ const thisMesDiv = chatElement.children().filter(`.mes[mesid="${mesId}"]`);
8528- if (source === 'keyboard' && repeated && chat[chat.length - 1].swipe_id === 0) {
9296+ const thisMesText = thisMesDiv.find('.mes_block .mes_text');
9297+ const thisMesDivHeight = thisMesDiv[0]?.scrollHeight;
9298+ const thisMesTextHeight = thisMesText[0]?.scrollHeight;
9299+ if (![thisMesDiv.length, thisMesText.length].every(num => num > 0)) {
9300+ console.error(`Message #${mesId}'s DOM element is not valid.`);
85299301 return;
85309302 }
9303+ const originalSwipeId = Number(chat[mesId]?.['swipe_id'] ?? 0);
9304+ let newSwipeId = Number(originalSwipeId);
9305+
9306+ const isPristine = !chat_metadata?.tainted;
9307+ const swipeDuration = Math.round(animation_duration * 1.25);
9308+ const swipeRange = direction === SWIPE_DIRECTION.RIGHT ? -700 : 700;
9309+
9310+ async function endSwipe() {
9311+ //Wait for the generation to end.
9312+ try {
9313+ await generation;
9314+ }
9315+ catch (error) {
9316+ console.warn(`Swipe failed, Swiping back. ${error}`);
9317+ }
9318+ //Allow for another swipe.
9319+ showSwipeButtons();
9320+
9321+ //Clamp Id between swipes.
9322+ let clampedId = clamp(chat[mesId]['swipe_id'], 0, Math.max(0, chat[mesId]['swipes'].length - 1));
9323+
9324+ //If the id is not within bounds, Swipe back.
9325+ if (chat[mesId]['swipe_id'] !== clampedId) {
9326+ chat[mesId]['swipe_id'] = clampedId;
9327+ syncSwipeToMes(mesId);
9328+ addOneMessage(chat[mesId], { type: 'swipe', forceId: mesId, scroll: false });
9329+ }
85319330
8532- const swipe_duration = 120;
9331+ await updateSwipeCounter(mesId);
8533- const swipe_range = '700px';
9332+ //Fallback.
8534- chat[chat.length - 1]['swipe_id']--;
9333+ if (mesId != chat.length - 1) {
9334+ await updateSwipeCounter(chat.length - 1);
9335+ }
9336+ }
85359337
8536- if (chat[chat.length - 1]['swipe_id'] < 0) {
9338+ async function standardSwipe() {
8537- chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length - 1;
9339+ //If swipe_id has changed, or the source is being deleted.
9340+ if (newSwipeId !== originalSwipeId || source == 'delete') {
9341+ //Update the chat.
9342+ await loadFromSwipeId(mesId, newSwipeId);
9343+ //Transition to the new chat.
9344+ await animateSwipe();
9345+ }
9346+ await endSwipe();
85389347 }
85399348
8540- if (chat[chat.length - 1]['swipe_id'] >= 0) {
9349+ /**
8541- /*$(this).parent().children('swipe_right').css('display', 'flex');
9350+ * Sets the message to the newSwipeId and loads it.
8542- if (chat[chat.length - 1]['swipe_id'] === 0) {
9351+ * @param {number} mesId
8543- $(this).css('display', 'none');
9352+ * @param {number} newSwipeId
8544- }*/ // Just in case
9353+ */
8545- if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {
9354+ async function loadFromSwipeId(mesId, newSwipeId) {
8546- chat[chat.length - 1]['swipe_info'] = [];
9355+ //Update the swipe_id.
8547- }
9356+ chat[mesId]['swipe_id'] = newSwipeId;
8548- let this_mes_div = $(this).parent();
9357+
8549- let this_mes_block = $(this).parent().children('.mes_block').children('.mes_text');
9358+ if (chat[mesId].extra && typeof chat[mesId].extra === 'object') {
8550- const this_mes_div_height = this_mes_div[0].scrollHeight;
9359+ delete chat[mesId].extra.memory;
8551- this_mes_div.css('height', this_mes_div_height);
9360+ delete chat[mesId].extra.display_text;
8552- const this_mes_block_height = this_mes_block[0].scrollHeight;
9361+ delete chat[mesId].extra.media;
8553- chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']];
9362+ delete chat[mesId].extra.inline_image;
8554- chat[chat.length - 1]['send_date'] = chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1].send_date; //load the last mes box with the latest generation
9363+ delete chat[mesId].extra.files;
8555- chat[chat.length - 1]['extra'] = structuredClone(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra);
9364+ delete chat[mesId].extra.fileLength;
8556-
9365+ delete chat[mesId].extra.generationType;
8557- if (chat[chat.length - 1].extra) {
9366+ delete chat[mesId].extra.negative;
8558- // if message has memory attached - remove it to allow regen
9367+ delete chat[mesId].extra.title;
8559- if (chat[chat.length - 1].extra.memory) {
9368+ delete chat[mesId].extra.append_title;
8560- delete chat[chat.length - 1].extra.memory;
9369+ }
8561- }
9370+ delete chat[mesId].gen_started;
8562- // ditto for display text
9371+ delete chat[mesId].gen_finished;
8563- if (chat[chat.length - 1].extra.display_text) {
9372+ //load from swipes.
8564- delete chat[chat.length - 1].extra.display_text;
9373+ syncSwipeToMes(mesId, chat[mesId]['swipe_id']);
85659374 }
8566- }
9375+
8567- $(this).parent().children('.mes_block').transition({
9376+ // Helper function to convert transition to promise
8568- x: swipe_range,
9377+ const transitionPromise = (element, properties) => {
8569- duration: animation_duration > 0 ? swipe_duration : 0,
9378+ return new Promise((resolve) => {
9379+ element.transition({
9380+ ...properties,
9381+ complete: resolve,
9382+ });
9383+ });
9384+ };
9385+
9386+ /**
9387+ * Animates a swipe for all messages >= mesId.
9388+ * @param {number} mesId
9389+ * @param {number} x
9390+ * @param {number} duration
9391+ */
9392+ async function animateSwipeTransition(mesId, x, duration) {
9393+ //Selects the swiped message.
9394+ const swipedMessagesDiv = chatElement.children().filter((index, div) => {
9395+ const $div = $(div);
9396+ return mesId === Number($div.attr('mesid'));
9397+ });
9398+ const swipedElementsDiv = swipedMessagesDiv.children('.mes_block, .mesAvatarWrapper');
9399+
9400+ //Swipe.
9401+ await transitionPromise(swipedElementsDiv, {
9402+ x: x,
9403+ duration: duration,
85709404 easing: animation_easing,
85719405 queue: false,
8572- complete: async function () {
9406+ });
8573- const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop('scrollHeight') - $('#chat').outerHeight()) - 10);
9407+ }
8574- //console.log('on left swipe click calling addOneMessage');
8575- addOneMessage(chat[chat.length - 1], { type: 'swipe' });
8576-
8577- if (power_user.message_token_count_enabled) {
8578- if (!chat[chat.length - 1].extra) {
8579- chat[chat.length - 1].extra = {};
8580- }
85819408
8582- const swipeMessage = $('#chat').find(`[mesid="${chat.length - 1}"]`);
9409+ function getMessageBottomHeight(thisMesDiv) {
8583- const tokenCountText = (chat[chat.length - 1]?.extra?.reasoning || '') + chat[chat.length - 1].mes;
9410+ const thisMesRect = thisMesDiv[0].getBoundingClientRect();
8584- const tokenCount = await getTokenCountAsync(tokenCountText, 0);
9411+ //Scroll position + Chat height = Bottom of chat height.
8585- chat[chat.length - 1]['extra']['token_count'] = tokenCount;
9412+ const chatBottom = chatElement.scrollTop() - chatElement.height();
8586- swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
9413+ //Message offset from viewport top + height = Bottom of message offset.
8587- }
9414+ const messageBottom = thisMesRect.top + thisMesDiv.height();
9415+ // Bottom of chat + Bottom of message offset = target scroll position.
9416+ const scrollHeight = (chatBottom + messageBottom);
9417+ return scrollHeight;
9418+ }
85889419
8589- let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
9420+ function expandNewMessage(thisMesDiv) {
8590- if (new_height < 103) new_height = 103;
9421+ //Only scroll if the view is not near the bottom.
8591- this_mes_div.animate({ height: new_height + 'px' }, {
9422+ const is_animation_scroll = (chatElement.scrollTop() >= (chatElement.prop('scrollHeight') - chatElement.outerHeight()) - 10);
8592- duration: 0, //used to be 100
8593- queue: false,
8594- progress: function () {
8595- // Scroll the chat down as the message expands
85969423
8597- if (is_animation_scroll) $('#chat').scrollTop($('#chat')[0].scrollHeight);
9424+ let new_height = thisMesDivHeight - (thisMesTextHeight - thisMesText[0].scrollHeight);
8598- },
9425+ if (new_height < 103) new_height = 103;
8599- complete: function () {
8600- this_mes_div.css('height', 'auto');
8601- // Scroll the chat down to the bottom once the animation is complete
8602- if (is_animation_scroll) $('#chat').scrollTop($('#chat')[0].scrollHeight);
8603- },
8604- });
8605- $(this).parent().children('.mes_block').transition({
8606- x: '-' + swipe_range,
8607- duration: 0,
8608- easing: animation_easing,
8609- queue: false,
8610- complete: function () {
8611- $(this).parent().children('.mes_block').transition({
8612- x: '0px',
8613- duration: animation_duration > 0 ? swipe_duration : 0,
8614- easing: animation_easing,
8615- queue: false,
8616- complete: async function () {
8617- appendMediaToMessage(chat[chat.length - 1], $(this).parent().children('.mes_block'));
8618- await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1));
8619- saveChatDebounced();
8620- },
8621- });
8622- },
8623- });
8624- },
8625- });
86269426
8627- $(this).parent().children('.avatar').transition({
9427+ //Keep the swipe buttons at the same height when scrolling is finished.
8628- x: swipe_range,
9428+
8629- duration: animation_duration > 0 ? swipe_duration : 0,
9429+ //Expand new message.
8630- easing: animation_easing,
9430+ thisMesDiv.animate({ height: new_height + 'px' }, {
9431+ duration: 0, //used to be 100 //Disabled on Cohee's request. https://github.com/SillyTavern/SillyTavern/pull/4610/files#r2408731744
86319432 queue: false,
86329433 completeprogress: function (animation, progress, remainingMs) {
8633- $(this).parent().children('.avatar').transition({
8634- x: '-' + swipe_range,
8635- duration: 0,
8636- easing: animation_easing,
8637- queue: false,
8638- complete: function () {
8639- $(this).parent().children('.avatar').transition({
8640- x: '0px',
8641- duration: animation_duration > 0 ? swipe_duration : 0,
8642- easing: animation_easing,
8643- queue: false,
8644- complete: function () {
86459434
8646- },
9435+ if (is_animation_scroll) chatElement.scrollTop(getMessageBottomHeight(thisMesDiv));
8647- });
9436+ },
8648- },
9437+ complete: function () {
8649- });
9438+ thisMesDiv.css('height', 'auto');
9439+ //Correct height auto offset.
9440+ if (is_animation_scroll) chatElement.scrollTop(getMessageBottomHeight(thisMesDiv));
86509441 },
86519442 });
86529443 }
8653- if (chat[chat.length - 1]['swipe_id'] < 0) {
9444+
8654- chat[chat.length - 1]['swipe_id'] = 0;
9445+ /**
9446+ * Anime a swipe, optionally running a generation.
9447+ * @param {boolean} run_generate
9448+ */
9449+ async function animateSwipe(run_generate = false) {
9450+
9451+ //Swipe out.
9452+ await animateSwipeTransition(mesId, swipeRange, swipeDuration);
9453+
9454+ if (run_generate) {
9455+ await updateSwipeCounter(mesId);
9456+ //shows "..." while generating
9457+ thisMesDiv.find('.mes_text').html('...');
9458+ // resets the timer
9459+ thisMesDiv.find('.mes_timer').html('');
9460+ thisMesDiv.find('.tokenCounterDisplay').text('');
9461+ updateReasoningUI(thisMesDiv, { reset: true });
9462+ } else {
9463+ //console.log('showing previously generated swipe candidate, or "..."');
9464+ //console.log('onclick right swipe calling addOneMessage');
9465+
9466+ //Only scroll when swiping the last message.
9467+ const scroll = (mesId == chat.length - 1);
9468+ addOneMessage(chat[mesId], { type: 'swipe', forceId: mesId, scroll: scroll });
9469+
9470+ if (power_user.message_token_count_enabled) {
9471+ if (!chat[mesId].extra) {
9472+ chat[mesId].extra = {};
9473+ }
9474+
9475+ const tokenCountText = (chat[mesId]?.extra?.reasoning || '') + chat[mesId].mes;
9476+ const tokenCount = await getTokenCountAsync(tokenCountText, 0);
9477+ chat[mesId]['extra']['token_count'] = tokenCount;
9478+ thisMesDiv.find('.tokenCounterDisplay').text(`${tokenCount}t`);
9479+ }
9480+ }
9481+
9482+ //Animate expanding to the new message height.
9483+ thisMesDiv.css('height', thisMesDivHeight);
9484+ expandNewMessage(thisMesDiv);
9485+
9486+
9487+ //Jump to the opposite side.
9488+ await animateSwipeTransition(mesId, -swipeRange, 0);
9489+
9490+ appendMediaToMessage(chat[mesId], thisMesDiv);
9491+
9492+ await eventSource.emit(event_types.MESSAGE_SWIPED, (mesId));
9493+
9494+ if (run_generate && !is_send_press) {
9495+ is_send_press = true;
9496+ generation = Generate('swipe');
9497+ } else if (Number(chat[mesId]['swipe_id']) !== chat[mesId]['swipes'].length) {
9498+ saveChatDebounced();
9499+ }
9500+
9501+ //Swipe in.
9502+ await animateSwipeTransition(mesId, 0, swipeDuration);
86559503 }
8656-}
86579504
8658-/**
9505+ if (mesId === Number(this_edit_mes_id)) {
8659- * Handles the swipe to the right event.
8660- * @param {JQuery.Event} [_event] Event.
8661- * @param {object} params Additional parameters.
8662- * @param {string} [params.source] The source of the swipe event.
8663- * @param {boolean} [params.repeated] Is the swipe event repeated.
8664- */
8665-//MARK: swipe_right
8666-export function swipe_right(_event = null, { source, repeated } = {}) {
8667- if (chat.length - 1 === Number(this_edit_mes_id)) {
86689506 closeMessageEditor();
86699507 }
9508+ if (isStreamingEnabled() && streamingProcessor) {
9509+ streamingProcessor.onStopStreaming();
9510+ }
86709511
86719512 if (isHordeGenerationNotAllowed()) {
86729513 return unblockGeneration();
86739514 }
86749515
8675- // Make sure ad-hoc changes to extras are saved before swiping away
9516+ //If the swipe is not being deleted.
8676- syncMesToSwipe();
9517+ if (source != 'delete') {
86779518
8678- const isPristine = !chat_metadata?.tainted;
9519+ // Make sure ad-hoc changes to extras are saved before swiping away
8679- const swipe_duration = 200;
9520+ syncMesToSwipe(mesId);
8680- const swipe_range = 700;
9521+
8681- //console.log(swipe_range);
9522+ if (chat[mesId]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
8682- let run_generate = false;
9523+ chat[mesId]['swipe_id'] = 0; // set it to id 0
8683- let run_swipe_right = false;
9524+ chat[mesId]['swipes'] = []; // empty the array
8684- if (chat[chat.length - 1]['swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
9525+ chat[mesId]['swipe_info'] = [];
8685- chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0
9526+ chat[mesId]['swipes'][0] = chat[mesId]['mes']; //assign swipe array with last chat[mesId] from chat
8686- chat[chat.length - 1]['swipes'] = []; // empty the array
9527+ chat[mesId]['swipe_info'][0] = {
8687- chat[chat.length - 1]['swipe_info'] = [];
9528+ 'send_date': chat[mesId]['send_date'],
8688- chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat
9529+ 'gen_started': chat[mesId]['gen_started'],
8689- chat[chat.length - 1]['swipe_info'][0] = {
9530+ 'gen_finished': chat[mesId]['gen_finished'],
86909531 'send_dateextra': structuredClone(chat[chat.length - 1mesId]['send_dateextra']),
8691- 'gen_started': chat[chat.length - 1]['gen_started'],
9532+ };
8692- 'gen_finished': chat[chat.length - 1]['gen_finished'],
9533+ }
8693- 'extra': structuredClone(chat[chat.length - 1]['extra']),
9534+ // If the user is holding down the key and we're at the last or first swipe, don't do anything.
8694- };
9535+ let isLastSwipe = (direction === SWIPE_DIRECTION.RIGHT) ? (chat[mesId].swipe_id === Math.max(0, chat[mesId]['swipes'].length - 1)) : chat[mesId].swipe_id === 0;
8695- //assign swipe info array with last message from chat
9536+ if (source === 'keyboard' && repeated && isLastSwipe) {
9537+ await endSwipe();
9538+ return;
9539+ }
9540+ } else if (source == 'delete') {
9541+ //If the swipe is being deleted.
9542+ await standardSwipe();
9543+ return;
86969544 }
8697- // if swipe_right is called on the last alternate greeting in pristine chats, loop back around
9545+
8698- if (chat.length === 1 && chat[0]['swipe_id'] !== undefined && chat[0]['swipe_id'] === chat[0]['swipes'].length - 1 && isPristine) {
9546+ //If swiping left.
8699- chat[0]['swipe_id'] = 0;
9547+ if (direction === SWIPE_DIRECTION.LEFT) {
8700- } else {
9548+ newSwipeId--;
8701- // If the user is holding down the key and we're at the last swipe, don't do anything
9549+ //Loop to last swipe if negative.
8702- if (source === 'keyboard' && repeated && chat[chat.length - 1].swipe_id === chat[chat.length - 1].swipes.length - 1) {
9550+ if (newSwipeId < 0) {
9551+ newSwipeId = Math.max(0, chat[mesId]['swipes'].length - 1);
9552+ }
9553+ //Limit swipe_id to swipes.
9554+ if (newSwipeId > chat[mesId]['swipes'].length - 1) {
9555+ toastr.warning(`The swipe_id for message #${mesId} was ${newSwipeId}. It has been reset to ${chat[mesId]['swipes'].length - 1}.`);
9556+ chat[mesId]['swipe_id'] = chat[mesId]['swipes'].length - 1;
9557+ await endSwipe();
87039558 return;
87049559 }
9560+ await standardSwipe();
9561+ return;
9562+ }
9563+ //If swiping right.
9564+ else if (direction === SWIPE_DIRECTION.RIGHT) {
87059565 // make new slot in array
8706- chat[chat.length - 1]['swipe_id']++;
9566+ newSwipeId++;
8707- }
8708- if (chat[chat.length - 1].extra) {
8709- // if message has memory attached - remove it to allow regen
8710- if (chat[chat.length - 1].extra.memory) {
8711- delete chat[chat.length - 1].extra.memory;
8712- }
8713- // ditto for display text
8714- if (chat[chat.length - 1].extra.display_text) {
8715- delete chat[chat.length - 1].extra.display_text;
8716- }
8717-
8718- delete chat[chat.length - 1].extra.image;
8719- delete chat[chat.length - 1].extra.image_swipes;
8720- delete chat[chat.length - 1].extra.video;
8721- delete chat[chat.length - 1].extra.inline_image;
8722- }
8723- if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) {
8724- chat[chat.length - 1]['swipe_info'] = [];
8725- }
8726- //if swipe id of last message is the same as the length of the 'swipes' array and not the greeting
8727- if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length && (chat.length !== 1 || !isPristine)) {
8728- delete chat[chat.length - 1].gen_started;
8729- delete chat[chat.length - 1].gen_finished;
8730- run_generate = true;
8731- } else if (parseInt(chat[chat.length - 1]['swipe_id']) < chat[chat.length - 1]['swipes'].length) { //otherwise, if the id is less than the number of swipes
8732- chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; //load the last mes box with the latest generation
8733- chat[chat.length - 1]['send_date'] = chat[chat.length - 1]?.swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1]['send_date']; //update send date
8734- chat[chat.length - 1]['extra'] = structuredClone(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra || []);
8735- run_swipe_right = true; //then prepare to do normal right swipe to show next message
8736- }
8737-
8738- const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`);
8739- let this_div = currentMessage.find('.swipe_right');
8740- let this_mes_div = this_div.parent().parent();
8741-
8742- if (chat[chat.length - 1]['swipe_id'] > chat[chat.length - 1]['swipes'].length) { //if we swipe right while generating (the swipe ID is greater than what we are viewing now)
8743- chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length; //show that message slot (will be '...' while generating)
8744- }
8745- if (run_generate) { //hide swipe arrows while generating
8746- this_div.css('display', 'none');
8747- }
8748- // handles animated transitions when swipe right, specifically height transitions between messages
8749- if (run_generate || run_swipe_right) {
8750- let this_mes_block = this_mes_div.find('.mes_block .mes_text');
8751- const this_mes_div_height = this_mes_div[0].scrollHeight;
8752- const this_mes_block_height = this_mes_block[0].scrollHeight;
8753-
8754- this_mes_div.children('.swipe_left').css('display', 'flex');
8755- this_mes_div.children('.mes_block').transition({ // this moves the div back and forth
8756- x: '-' + swipe_range,
8757- duration: animation_duration > 0 ? swipe_duration : 0,
8758- easing: animation_easing,
8759- queue: false,
8760- complete: async function () {
8761- const is_animation_scroll = (chatElement.scrollTop() >= (chatElement.prop('scrollHeight') - chatElement.outerHeight()) - 10);
8762- //console.log(parseInt(chat[chat.length-1]['swipe_id']));
8763- //console.log(chat[chat.length-1]['swipes'].length);
8764- const swipeMessage = chatElement.find('[mesid="' + (chat.length - 1) + '"]');
8765- if (run_generate && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
8766- //shows "..." while generating
8767- swipeMessage.find('.mes_text').html('...');
8768- // resets the timer
8769- swipeMessage.find('.mes_timer').html('');
8770- swipeMessage.find('.tokenCounterDisplay').text('');
8771- updateReasoningUI(swipeMessage, { reset: true });
8772- } else {
8773- //console.log('showing previously generated swipe candidate, or "..."');
8774- //console.log('onclick right swipe calling addOneMessage');
8775- addOneMessage(chat[chat.length - 1], { type: 'swipe' });
8776-
8777- if (power_user.message_token_count_enabled) {
8778- if (!chat[chat.length - 1].extra) {
8779- chat[chat.length - 1].extra = {};
8780- }
87819567
8782- const tokenCountText = (chat[chat.length - 1]?.extra?.reasoning || '') + chat[chat.length - 1].mes;
9568+ //Minimum of zero.
8783- const tokenCount = await getTokenCountAsync(tokenCountText, 0);
9569+ if (newSwipeId < 0) {
8784- chat[chat.length - 1]['extra']['token_count'] = tokenCount;
9570+ toastr.warning(`The swipe_id for message #${mesId} was ${newSwipeId}. It has been reset to zero.`);
8785- swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
9571+ chat[mesId]['swipe_id'] = 0;
8786- }
9572+ await endSwipe();
8787- }
9573+ return;
8788- let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
9574+ }
8789- if (new_height < 103) new_height = 103;
87909575
9576+ //if swipe id of last message is the same as the length of the 'swipes' array and not the greeting.
9577+ if (newSwipeId >= chat[mesId]['swipes'].length && ((chat.length !== 1 || !isPristine))) {
9578+ newSwipeId = chat[mesId]['swipes'].length;
87919579
8792- this_mes_div.animate({ height: new_height + 'px' }, {
9580+ //Update the swipe_id.
8793- duration: 0, //used to be 100
9581+ chat[mesId]['swipe_id'] = newSwipeId;
8794- queue: false,
8795- progress: function () {
8796- // Scroll the chat down as the message expands
8797- if (is_animation_scroll) chatElement.scrollTop(chatElement[0].scrollHeight);
8798- },
8799- complete: function () {
8800- this_mes_div.css('height', 'auto');
8801- // Scroll the chat down to the bottom once the animation is complete
8802- if (is_animation_scroll) chatElement.scrollTop(chatElement[0].scrollHeight);
8803- },
8804- });
8805- this_mes_div.children('.mes_block').transition({
8806- x: swipe_range,
8807- duration: 0,
8808- easing: animation_easing,
8809- queue: false,
8810- complete: function () {
8811- this_mes_div.children('.mes_block').transition({
8812- x: '0px',
8813- duration: animation_duration > 0 ? swipe_duration : 0,
8814- easing: animation_easing,
8815- queue: false,
8816- complete: async function () {
8817- appendMediaToMessage(chat[chat.length - 1], swipeMessage);
8818- await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1));
8819- if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
8820- console.debug('caught here 2');
8821- is_send_press = true;
8822- await Generate('swipe');
8823- } else {
8824- if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) {
8825- saveChatDebounced();
8826- }
8827- }
8828- },
8829- });
8830- },
8831- });
8832- },
8833- });
8834- this_mes_div.children('.avatar').transition({ // moves avatar along with swipe
8835- x: '-' + swipe_range,
8836- duration: animation_duration > 0 ? swipe_duration : 0,
8837- easing: animation_easing,
8838- queue: false,
8839- complete: function () {
8840- this_mes_div.children('.avatar').transition({
8841- x: swipe_range,
8842- duration: 0,
8843- easing: animation_easing,
8844- queue: false,
8845- complete: function () {
8846- this_mes_div.children('.avatar').transition({
8847- x: '0px',
8848- duration: animation_duration > 0 ? swipe_duration : 0,
8849- easing: animation_easing,
8850- queue: false,
8851- complete: function () {
88529582
8853- },
9583+ //Cancel the generation if it's a user message or the first message in a pristine chat.
8854- });
9584+ if (chat[mesId].is_user || (mesId === 0 && isPristine)) {
8855- },
9585+ //Cancel swipe.
8856- });
9586+ chat[mesId]['swipe_id'] = originalSwipeId;
8857- },
9587+ await endSwipe();
8858- });
9588+ return;
9589+ } else {
9590+ //Generate.
9591+ await loadFromSwipeId(mesId, newSwipeId);
9592+ let run_generate = true;
9593+ await animateSwipe(run_generate);
9594+ await endSwipe();
9595+ return;
9596+ }
9597+ }
9598+ else {
9599+ // if swipe_right is called on the last alternate greeting in pristine chats, loop back around
9600+ if (chat.length === 1 && newSwipeId !== undefined && newSwipeId === chat[0]['swipes'].length && isPristine) {
9601+ newSwipeId = 0;
9602+ }
9603+ }
9604+ await standardSwipe();
9605+ return;
88599606 }
88609607}
88619608
88629609/**
9610+ * @deprecated Use `swipe` instead.
9611+ * Handles the swipe to the left event.
9612+ * @param {JQuery.Event} _event Event.
9613+ * @param {object} params Additional parameters.
9614+ * @param {string} [params.source] The source of the swipe event.
9615+ * @param {boolean} [params.repeated] Is the swipe event repeated.
9616+ * @param {object} [params.message] The chat message to swipe.
9617+ */
9618+export async function swipe_left(_event, { source, repeated, message } = {}) {
9619+ await swipe.call(this, _event, SWIPE_DIRECTION.LEFT, { source: source, repeated: repeated, message: message });
9620+}
9621+
9622+/**
9623+ * @deprecated Use `swipe` instead.
9624+ * Handles the swipe to the right event.
9625+ * @param {JQuery.Event} [_event] Event.
9626+ * @param {object} params Additional parameters.
9627+ * @param {string} [params.source] The source of the swipe event.
9628+ * @param {boolean} [params.repeated] Is the swipe event repeated.
9629+ * @param {object} [params.message] The chat message to swipe.
9630+ */
9631+//MARK: swipe_right
9632+export async function swipe_right(_event = null, { source, repeated, message } = {}) {
9633+ await swipe.call(this, _event, SWIPE_DIRECTION.RIGHT, { source: source, repeated: repeated, message: message });
9634+}
9635+
9636+/**
88639637 * Imports supported files dropped into the app window.
88649638 * @param {File[]} files Array of files to process
88659639 * @param {Map<File, string>} [data] Extra data to pass to the import function
@@ -8952,6 +9726,7 @@ async function importCharacter(file, { preserveFileName = '', importTags = false
89529726 const formData = new FormData();
89539727 formData.append('avatar', file);
89549728 formData.append('file_type', format);
9729+ formData.append('user_name', name1);
89559730 if (preserveFileName) formData.append('preserved_name', preserveFileName);
89569731
89579732 try {
@@ -9543,8 +10318,8 @@ jQuery(async function () {
954310318 ///// SWIPE BUTTON CLICKS ///////
954410319
954510320 //limit swiping to only last message clicks
954610321 $(document).on('click', '.last_mes .swipe_right', swipe_rightasync (e, data) => await swipe(e, SWIPE_DIRECTION.RIGHT, data));
954710322 $(document).on('click', '.last_mes .swipe_left', swipe_leftasync (e, data) => await swipe(e, SWIPE_DIRECTION.LEFT, data));
954810323
954910324 initCharacterSearch();
955010325
@@ -9556,8 +10331,9 @@ jQuery(async function () {
955610331 $('#option_continue').trigger('click');
955710332 });
955810333
9559- $('#send_but').on('click', function () {
10334+ const userInputGenerateMutex = new SimpleMutex(sendTextareaMessage);
9560- sendTextareaMessage();
10335+ $('#send_but').on('click', async function () {
10336+ await userInputGenerateMutex.update();
956110337 });
956210338
956310339 //menu buttons setup
@@ -9970,7 +10746,7 @@ jQuery(async function () {
997010746 if (!isMouseOverButtonOrMenu()) { hideMenu(); }
997110747 });
997210748
997310749 /* $('#set_chat_scenarioset_chat_character_settings').on('click', setScenarioOverride); */
997410750
997510751 ///////////// OPTIMIZED LISTENERS FOR LEFT SIDE OPTIONS POPUP MENU //////////////////////
997610752 $('#options [id]').on('click', async function (event, customData) {
@@ -10046,7 +10822,7 @@ jQuery(async function () {
1004610822 }
1004710823
1004810824 else if (id == 'option_continue') {
1004910825 if (this_edit_mes_id >= 0) return; // don't proceed if editing a message
1005010826
1005110827 if (is_send_press == false || fromSlashCommand) {
1005210828 is_send_press = true;
@@ -10236,8 +11012,7 @@ jQuery(async function () {
1023611012 return;
1023711013 }*/
1023811014
10239- let chatScrollPosition = chatElement.scrollTop();
11015+ if (this_edit_mes_id >= 0) {
10240- if (this_edit_mes_id !== undefined) {
1024111016 let mes_edited = chatElement.find(`[mesid="${this_edit_mes_id}"]`).find('.mes_edit_done');
1024211017 if (Number(edit_mes_id) == chat.length - 1) { //if the generating swipe (...)
1024311018 let run_edit = true;
@@ -10252,55 +11027,9 @@ jQuery(async function () {
1025211027 }
1025311028 await messageEditDone(mes_edited);
1025411029 }
1025511030 var edit_mes_id = Number($(this).closest('.mes_blockmes').findattr('.mes_textmesid').empty();
10256- $(this).closest('.mes_block').find('.mes_buttons').css('display', 'none');
10257- $(this).closest('.mes_block').find('.mes_edit_buttons').css('display', 'inline-flex');
10258- var edit_mes_id = $(this).closest('.mes').attr('mesid');
10259- this_edit_mes_id = edit_mes_id;
10260-
10261- // Also edit reasoning, if it exists
10262- const reasoningEdit = $(this).closest('.mes_block').find('.mes_reasoning_edit:visible');
10263- if (reasoningEdit.length > 0) {
10264- reasoningEdit.trigger('click');
10265- }
10266-
10267- var text = chat[edit_mes_id]['mes'];
10268- if (chat[edit_mes_id]['is_user']) {
10269- this_edit_mes_chname = name1;
10270- } else if (chat[edit_mes_id]['force_avatar']) {
10271- this_edit_mes_chname = chat[edit_mes_id]['name'];
10272- } else {
10273- this_edit_mes_chname = name2;
10274- }
10275- if (power_user.trim_spaces) {
10276- text = text.trim();
10277- }
10278- $(this)
10279- .closest('.mes_block')
10280- .find('.mes_text')
10281- .append(
10282- '<textarea id=\'curEditTextarea\' class=\'edit_textarea mdHotkeys\'></textarea>',
10283- );
10284- $('#curEditTextarea').val(text);
10285- let edit_textarea = $(this)
10286- .closest('.mes_block')
10287- .find('.edit_textarea');
10288- if (!cssAutofit) {
10289- edit_textarea.height(0);
10290- edit_textarea.height(edit_textarea[0].scrollHeight);
10291- }
10292- edit_textarea.trigger('focus');
10293- const textAreaElement = /** @type {HTMLTextAreaElement} */ (edit_textarea[0]);
10294- // Sets the cursor at the end of the text
10295- textAreaElement.setSelectionRange(
10296- String(edit_textarea.val()).length,
10297- String(edit_textarea.val()).length,
10298- );
10299- if (Number(this_edit_mes_id) === chat.length - 1) {
10300- chatElement.scrollTop(chatScrollPosition);
10301- }
1030211031
1030311032 updateEditArrowClassesawait messageEdit(edit_mes_id);
1030411033 }
1030511034 });
1030611035
@@ -10379,91 +11108,24 @@ jQuery(async function () {
1037911108 });
1038011109
1038111110 $(document).on('click', '.mes_edit_cancel', async function () {
10382- let text = chat[this_edit_mes_id]['mes'];
11111+ await messageEditCancel.call(this, this_edit_mes_id);
10383-
10384- $(this).closest('.mes_block').find('.mes_text').empty();
10385- $(this).closest('.mes_edit_buttons').css('display', 'none');
10386- $(this).closest('.mes_block').find('.mes_buttons').css('display', '');
10387- $(this)
10388- .closest('.mes_block')
10389- .find('.mes_text')
10390- .append(messageFormatting(
10391- text,
10392- this_edit_mes_chname,
10393- chat[this_edit_mes_id].is_system,
10394- chat[this_edit_mes_id].is_user,
10395- this_edit_mes_id,
10396- {},
10397- false,
10398- ));
10399- appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
10400- addCopyToCodeBlocks($(this).closest('.mes'));
10401-
10402- const reasoningEditDone = $(this).closest('.mes_block').find('.mes_reasoning_edit_cancel:visible');
10403- if (reasoningEditDone.length > 0) {
10404- reasoningEditDone.trigger('click');
10405- }
10406-
10407- await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
10408- this_edit_mes_id = undefined;
1040911112 });
1041011113
1041111114 $(document).on('click', '.mes_edit_up', async function () {
1041211115 if (is_send_press || this_edit_mes_id <= 0) {
1041311116 return;
1041411117 }
10415-
10416- hideSwipeButtons();
1041711118 const targetId = Number(this_edit_mes_id) - 1;
10418- const target = chatElement.find(`.mes[mesid="${targetId}"]`);
11119+ await messageEditMove(this_edit_mes_id, targetId);
10419- const root = $(this).closest('.mes');
10420-
10421- if (root.length === 0 || target.length === 0) {
10422- return;
10423- }
10424-
10425- root.insertBefore(target);
10426-
10427- target.attr('mesid', this_edit_mes_id);
10428- root.attr('mesid', targetId);
10429-
10430- const temp = chat[targetId];
10431- chat[targetId] = chat[this_edit_mes_id];
10432- chat[this_edit_mes_id] = temp;
10433-
10434- this_edit_mes_id = targetId;
10435- updateViewMessageIds();
10436- await saveChatConditional();
10437- showSwipeButtons();
1043811120 });
1043911121
1044011122 $(document).on('click', '.mes_edit_down', async function () {
1044111123 if (is_send_press || this_edit_mes_id >= chat.length - 1) {
1044211124 return;
1044311125 }
1044411126
10445- hideSwipeButtons();
1044611127 const targetId = Number(this_edit_mes_id) + 1;
10447- const target = chatElement.find(`.mes[mesid="${targetId}"]`);
11128+ await messageEditMove(this_edit_mes_id, targetId);
10448- const root = $(this).closest('.mes');
10449-
10450- if (root.length === 0 || target.length === 0) {
10451- return;
10452- }
10453-
10454- root.insertAfter(target);
10455-
10456- target.attr('mesid', this_edit_mes_id);
10457- root.attr('mesid', targetId);
10458-
10459- const temp = chat[targetId];
10460- chat[targetId] = chat[this_edit_mes_id];
10461- chat[this_edit_mes_id] = temp;
10462-
10463- this_edit_mes_id = targetId;
10464- updateViewMessageIds();
10465- await saveChatConditional();
10466- showSwipeButtons();
1046711129 });
1046811130
1046911131 $(document).on('click', '.mes_edit_copy', async function () {
@@ -10476,7 +11138,7 @@ jQuery(async function () {
1047611138 const oldScroll = chatElement[0].scrollTop;
1047711139 const clone = structuredClone(chat[this_edit_mes_id]);
1047811140 clone.send_date = Date.now();
1047911141 clone.mes = $(this).closest('.mes').find('.edit_textarea').val().toString();
1048011142
1048111143 if (power_user.trim_spaces) {
1048211144 clone.mes = clone.mes.trim();
@@ -10493,48 +11155,11 @@ jQuery(async function () {
1049311155
1049411156 $(document).on('click', '.mes_edit_delete', async function (event, customData) {
1049511157 const fromSlashCommand = customData?.fromSlashCommand || false;
10496- const canDeleteSwipe = (Array.isArray(chat[this_edit_mes_id].swipes) && chat[this_edit_mes_id].swipes.length > 1 && !chat[this_edit_mes_id].is_user && parseInt(this_edit_mes_id) === chat.length - 1);
11158+ const message = chat[this_edit_mes_id];
10497-
11159+ const selectedSwipe = message['swipe_id'] ?? undefined;
10498- let deleteOnlySwipe = false;
11160+ const swipesArray = Array.isArray(message['swipes']) ? message['swipes'] : [];
10499- if (power_user.confirm_message_delete && fromSlashCommand !== true) {
11161+ const canDeleteSwipe = !fromSlashCommand && !message.is_user && swipesArray.length > 1 && this_edit_mes_id === chat.length - 1 && selectedSwipe !== undefined;
10500- const result = await callGenericPopup(t`Are you sure you want to delete this message?`, POPUP_TYPE.CONFIRM, null, {
11162+ await deleteMessage(Number(this_edit_mes_id), canDeleteSwipe ? selectedSwipe : undefined, power_user.confirm_message_delete && fromSlashCommand !== true);
10501- okButton: canDeleteSwipe ? t`Delete Swipe` : t`Delete Message`,
10502- cancelButton: 'Cancel',
10503- customButtons: canDeleteSwipe ? [t`Delete Message`] : null,
10504- });
10505- if (!result) {
10506- return;
10507- }
10508- deleteOnlySwipe = canDeleteSwipe && result === 1; // Default button, not the custom one
10509- }
10510-
10511- const messageElement = $(this).closest('.mes');
10512- if (!messageElement) {
10513- return;
10514- }
10515-
10516- if (deleteOnlySwipe) {
10517- const message = chat[this_edit_mes_id];
10518- const swipe_id = message.swipe_id;
10519- await deleteSwipe(swipe_id, Number(this_edit_mes_id));
10520- return;
10521- }
10522-
10523- chat.splice(this_edit_mes_id, 1);
10524- messageElement.remove();
10525-
10526- let startFromZero = Number(this_edit_mes_id) === 0;
10527-
10528- this_edit_mes_id = undefined;
10529- chat_metadata['tainted'] = true;
10530-
10531- updateViewMessageIds(startFromZero);
10532- saveChatDebounced();
10533-
10534- hideSwipeButtons();
10535- showSwipeButtons();
10536-
10537- await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
1053811163 });
1053911164
1054011165 $(document).on('click', '.mes_edit_done', async function () {
@@ -10872,8 +11497,8 @@ jQuery(async function () {
1087211497 }
1087311498 });
1087411499
1087511500 $(document).on('keyupkeydown', function (e) {
1087611501 if (e.key === 'Escape' && !e.originalEvent.isComposing) {
1087711502 const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0;
1087811503 if (isEditVisible && power_user.auto_save_msg_edits === false) {
1087911504 closeMessageEditor('all');
@@ -10886,7 +11511,7 @@ jQuery(async function () {
1088611511 $('#send_textarea').trigger('focus');
1088711512 return;
1088811513 }
1088911514 if (!this_edit_mes_id === undefined && $('#mes_stop').is(':visible')) {
1089011515 $('#mes_stop').trigger('click');
1089111516 if (chat.length && Array.isArray(chat[chat.length - 1].swipes) && chat[chat.length - 1].swipe_id == chat[chat.length - 1].swipes.length) {
1089211517 $('.last_mes .swipe_left').trigger('click');
@@ -10902,8 +11527,8 @@ jQuery(async function () {
1090211527 case 'set_character_world':
1090311528 await openCharacterWorldPopup();
1090411529 break;
1090511530 case 'set_chat_scenarioset_chat_character_settings':
1090611531 await setScenarioOverridesetCharacterSettingsOverrides();
1090711532 break;
1090811533 case 'renameCharButton':
1090911534 await renameCharacter();
@@ -11134,7 +11759,7 @@ jQuery(async function () {
1113411759 await firstLoadInit();
1113511760
1113611761 window.addEventListener('beforeunload', (e) => {
1113711762 if (isChatSaving || this_edit_mes_id >= 0) {
1113811763 e.preventDefault();
1113911764 e.returnValue = true;
1114011765 }
public/scripts/RossAscends-mods.js+5 -2
@@ -19,6 +19,7 @@ import {
1919 substituteParams,
2020 sendTextareaMessage,
2121 doNavbarIconClick,
22+ isSwipingAllowed,
2223} from '../script.js';
2324
2425import {
@@ -402,6 +403,7 @@ function RA_autoconnect(PrevApi) {
402403 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
403404 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
404405 || (secret_state[SECRET_KEYS.GROQ] && oai_settings.chat_completion_source == chat_completion_sources.GROQ)
406+ || (secret_state[SECRET_KEYS.SILICONFLOW] && oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW)
405407 || (secret_state[SECRET_KEYS.ELECTRONHUB] && oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB)
406408 || (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
407409 || (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
@@ -410,6 +412,7 @@ function RA_autoconnect(PrevApi) {
410412 || (secret_state[SECRET_KEYS.MOONSHOT] && oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT)
411413 || (secret_state[SECRET_KEYS.FIREWORKS] && oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS)
412414 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)
415+ || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI)
413416 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
414417 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
415418 || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)
@@ -1109,8 +1112,8 @@ export function initRossMods() {
11091112
11101113 if (event.key == 'ArrowLeft') { //swipes left
11111114 if (
1115+ isSwipingAllowed &&
11121116 !isNanogallery2LightboxActive() && // Check if lightbox is NOT active
1113- $('.swipe_left:last').css('display') === 'flex' &&
11141117 $('#send_textarea').val() === '' &&
11151118 $('#character_popup').css('display') === 'none' &&
11161119 $('#shadow_select_chat_popup').css('display') === 'none' &&
@@ -1123,8 +1126,8 @@ export function initRossMods() {
11231126 }
11241127 if (event.key == 'ArrowRight') { //swipes right
11251128 if (
1129+ isSwipingAllowed &&
11261130 !isNanogallery2LightboxActive() && // Check if lightbox is NOT active
1127- $('.swipe_right:last').css('display') === 'flex' &&
11281131 $('#send_textarea').val() === '' &&
11291132 $('#character_popup').css('display') === 'none' &&
11301133 $('#shadow_select_chat_popup').css('display') === 'none' &&
public/scripts/audio-player.js+605 -0
@@ -0,0 +1,605 @@
1+import { formatTime } from './utils.js';
2+
3+export class AudioPlayer {
4+ /**
5+ * Creates an audio player instance
6+ * @param {HTMLElement} audioElement - The audio element to control
7+ * @param {HTMLElement} containerElement - The container element with player controls
8+ * @param {Object} options - Configuration options
9+ */
10+ constructor(audioElement, containerElement, options = {}) {
11+ if (!(audioElement instanceof HTMLAudioElement)) {
12+ throw new Error('First argument must be an HTMLAudioElement');
13+ }
14+ if (!(containerElement instanceof HTMLElement)) {
15+ throw new Error('Second argument must be an HTMLElement');
16+ }
17+
18+ this.audio = audioElement;
19+ this.container = containerElement;
20+ this.options = {
21+ title: '',
22+ autoplay: false,
23+ volume: 1.0,
24+ onPlay: null,
25+ onPause: null,
26+ onEnded: null,
27+ onTimeUpdate: null,
28+ onVolumeChange: null,
29+ ...options,
30+ };
31+
32+ this.isDragging = false;
33+ this.isDestroyed = false;
34+
35+ // Store bound event handlers for cleanup
36+ this.boundHandlers = {
37+ // Audio event handlers
38+ audioLoadedMetadata: this.onAudioLoadedMetadata.bind(this),
39+ audioTimeUpdate: this.onAudioTimeUpdate.bind(this),
40+ audioPlay: this.onAudioPlay.bind(this),
41+ audioPause: this.onAudioPause.bind(this),
42+ audioEnded: this.onAudioEnded.bind(this),
43+ audioVolumeChange: this.onAudioVolumeChange.bind(this),
44+ // Control event handlers
45+ playPauseClick: this.onPlayPauseClick.bind(this),
46+ volumeClick: this.onVolumeClick.bind(this),
47+ volumeInput: this.onVolumeInput.bind(this),
48+ progressMouseDown: this.onProgressMouseDown.bind(this),
49+ progressClick: this.onProgressClick.bind(this),
50+ progressMouseMove: this.onProgressMouseMove.bind(this),
51+ documentMouseMove: this.onDocumentMouseMove.bind(this),
52+ documentMouseUp: this.onDocumentMouseUp.bind(this),
53+ };
54+
55+ // MutationObserver for DOM cleanup detection
56+ this.observer = null;
57+
58+ this.init();
59+ }
60+
61+ /**
62+ * Initializes the audio player by setting up elements, events, and initial state
63+ * @returns {void}
64+ */
65+ init() {
66+ this.findElements();
67+ this.bindEvents();
68+ this.setupDOMObserver();
69+
70+ if (this.options.title) {
71+ this.setTitle(this.options.title);
72+ } else if (this.audio.title) {
73+ this.setTitle(this.audio.title);
74+ } else if (this.audio.src) {
75+ const srcParts = this.audio.src.split('/');
76+ this.setTitle(decodeURIComponent(srcParts[srcParts.length - 1]));
77+ }
78+
79+ if (this.options.autoplay) {
80+ this.play();
81+ }
82+
83+ this.setVolume(this.options.volume);
84+
85+ // Initialize time displays
86+ this.updateTimeDisplays();
87+ }
88+
89+ /**
90+ * Finds and caches all required DOM elements within the container
91+ * @returns {void}
92+ */
93+ findElements() {
94+ this.elements = {
95+ title: this.container.querySelector('.audio-player-title'),
96+ playPauseBtn: this.container.querySelector('.audio-player-play-pause'),
97+ currentTime: this.container.querySelector('.audio-player-current-time'),
98+ totalTime: this.container.querySelector('.audio-player-total-time'),
99+ progress: this.container.querySelector('.audio-player-progress'),
100+ progressBar: this.container.querySelector('.audio-player-progress-bar'),
101+ volumeBtn: this.container.querySelector('.audio-player-volume'),
102+ };
103+
104+ // Validate required elements
105+ const requiredElements = ['playPauseBtn', 'currentTime', 'totalTime', 'progress', 'progressBar', 'volumeBtn'];
106+ for (const key of requiredElements) {
107+ if (!this.elements[key]) {
108+ console.warn(`AudioPlayer: Required element .audio-player-${key.replace(/([A-Z])/g, '-$1').toLowerCase()} not found`);
109+ }
110+ }
111+ }
112+
113+ /**
114+ * Sets up a MutationObserver to detect when audio or container elements are removed from DOM
115+ * @returns {void}
116+ */
117+ setupDOMObserver() {
118+ // Watch for removal of audio or container from DOM
119+ this.observer = new MutationObserver((mutations) => {
120+ for (const mutation of mutations) {
121+ for (const node of mutation.removedNodes) {
122+ if (node === this.audio || node === this.container ||
123+ node.contains?.(this.audio) || node.contains?.(this.container)) {
124+ this.destroy();
125+ return;
126+ }
127+ }
128+ }
129+ });
130+
131+ // Observe the parent nodes
132+ const chatParent = this.audio.closest('#chat') ?? document.body;
133+
134+ if (chatParent) {
135+ this.observer.observe(chatParent, { childList: true, subtree: true });
136+ }
137+ }
138+
139+ /**
140+ * Binds all event listeners to audio and control elements
141+ * @returns {void}
142+ */
143+ bindEvents() {
144+ // Audio events
145+ this.audio.addEventListener('loadedmetadata', this.boundHandlers.audioLoadedMetadata);
146+ this.audio.addEventListener('timeupdate', this.boundHandlers.audioTimeUpdate);
147+ this.audio.addEventListener('play', this.boundHandlers.audioPlay);
148+ this.audio.addEventListener('pause', this.boundHandlers.audioPause);
149+ this.audio.addEventListener('ended', this.boundHandlers.audioEnded);
150+ this.audio.addEventListener('volumechange', this.boundHandlers.audioVolumeChange);
151+
152+ // Control events
153+ if (this.elements.playPauseBtn) {
154+ this.elements.playPauseBtn.addEventListener('click', this.boundHandlers.playPauseClick);
155+ }
156+ if (this.elements.volumeBtn) {
157+ this.elements.volumeBtn.addEventListener('click', this.boundHandlers.volumeClick);
158+ }
159+ if (this.elements.progress) {
160+ this.elements.progress.addEventListener('mousedown', this.boundHandlers.progressMouseDown);
161+ this.elements.progress.addEventListener('click', this.boundHandlers.progressClick);
162+ this.elements.progress.addEventListener('mousemove', this.boundHandlers.progressMouseMove);
163+ }
164+ }
165+
166+ /**
167+ * Removes all event listeners from audio and control elements
168+ * @returns {void}
169+ */
170+ unbindEvents() {
171+ // Audio events
172+ this.audio.removeEventListener('loadedmetadata', this.boundHandlers.audioLoadedMetadata);
173+ this.audio.removeEventListener('timeupdate', this.boundHandlers.audioTimeUpdate);
174+ this.audio.removeEventListener('play', this.boundHandlers.audioPlay);
175+ this.audio.removeEventListener('pause', this.boundHandlers.audioPause);
176+ this.audio.removeEventListener('ended', this.boundHandlers.audioEnded);
177+ this.audio.removeEventListener('volumechange', this.boundHandlers.audioVolumeChange);
178+
179+ // Control events
180+ if (this.elements.playPauseBtn) {
181+ this.elements.playPauseBtn.removeEventListener('click', this.boundHandlers.playPauseClick);
182+ }
183+ if (this.elements.volumeBtn) {
184+ this.elements.volumeBtn.removeEventListener('click', this.boundHandlers.volumeClick);
185+ }
186+ if (this.elements.progress) {
187+ this.elements.progress.removeEventListener('mousedown', this.boundHandlers.progressMouseDown);
188+ this.elements.progress.removeEventListener('click', this.boundHandlers.progressClick);
189+ this.elements.progress.removeEventListener('mousemove', this.boundHandlers.progressMouseMove);
190+ }
191+
192+ // Document events
193+ document.removeEventListener('mousemove', this.boundHandlers.documentMouseMove);
194+ document.removeEventListener('mouseup', this.boundHandlers.documentMouseUp);
195+ }
196+
197+ // Audio event handlers
198+ /**
199+ * Handles the audio element's loadedmetadata event
200+ * @returns {void}
201+ */
202+ onAudioLoadedMetadata() {
203+ if (this.isDestroyed) return;
204+ this.updateTimeDisplays();
205+ }
206+
207+ /**
208+ * Handles the audio element's timeupdate event
209+ * @returns {void}
210+ */
211+ onAudioTimeUpdate() {
212+ if (this.isDestroyed || this.isDragging) return;
213+
214+ const percent = (this.audio.currentTime / this.audio.duration) * 100 || 0;
215+ if (this.elements.progressBar) {
216+ /** @type {HTMLElement} */ (this.elements.progressBar).style.width = percent + '%';
217+ }
218+ if (this.elements.currentTime) {
219+ this.elements.currentTime.textContent = formatTime(this.audio.currentTime);
220+ }
221+
222+ if (typeof this.options.onTimeUpdate === 'function') {
223+ this.options.onTimeUpdate.call(this, this.audio.currentTime, this.audio.duration);
224+ }
225+ }
226+
227+ /**
228+ * Handles the audio element's play event
229+ * @returns {void}
230+ */
231+ onAudioPlay() {
232+ if (this.isDestroyed) return;
233+
234+ if (this.elements.playPauseBtn) {
235+ this.elements.playPauseBtn.classList.remove('fa-play');
236+ this.elements.playPauseBtn.classList.add('fa-pause');
237+ this.elements.playPauseBtn.setAttribute('title', 'Pause');
238+ }
239+
240+ if (typeof this.options.onPlay === 'function') {
241+ this.options.onPlay.call(this);
242+ }
243+ }
244+
245+ /**
246+ * Handles the audio element's pause event
247+ * @returns {void}
248+ */
249+ onAudioPause() {
250+ if (this.isDestroyed) return;
251+
252+ if (this.elements.playPauseBtn) {
253+ this.elements.playPauseBtn.classList.remove('fa-pause');
254+ this.elements.playPauseBtn.classList.add('fa-play');
255+ this.elements.playPauseBtn.setAttribute('title', 'Play');
256+ }
257+
258+ if (typeof this.options.onPause === 'function') {
259+ this.options.onPause.call(this);
260+ }
261+ }
262+
263+ /**
264+ * Handles the audio element's ended event
265+ * @returns {void}
266+ */
267+ onAudioEnded() {
268+ if (this.isDestroyed) return;
269+
270+ if (this.elements.playPauseBtn) {
271+ this.elements.playPauseBtn.classList.remove('fa-pause');
272+ this.elements.playPauseBtn.classList.add('fa-play');
273+ this.elements.playPauseBtn.setAttribute('title', 'Play');
274+ }
275+
276+ if (typeof this.options.onEnded === 'function') {
277+ this.options.onEnded.call(this);
278+ }
279+ }
280+
281+ /**
282+ * Handles the audio element's volumechange event
283+ * @returns {void}
284+ */
285+ onAudioVolumeChange() {
286+ if (this.isDestroyed) return;
287+
288+ this.updateVolumeIcon();
289+
290+ if (typeof this.options.onVolumeChange === 'function') {
291+ this.options.onVolumeChange.call(this, this.audio.volume, this.audio.muted);
292+ }
293+ }
294+
295+ // Control event handlers
296+ /**
297+ * Handles click events on the play/pause button
298+ * @param {MouseEvent} e - The click event
299+ * @returns {void}
300+ */
301+ onPlayPauseClick(e) {
302+ e.preventDefault();
303+ this.togglePlay();
304+ }
305+
306+ /**
307+ * Handles click events on the volume button
308+ * @param {MouseEvent} e - The click event
309+ * @returns {void}
310+ */
311+ onVolumeClick(e) {
312+ e.preventDefault();
313+ this.toggleMute();
314+ }
315+
316+ /**
317+ * Handles input events on the volume slider
318+ * @param {InputEvent} e - The input event
319+ * @returns {void}
320+ */
321+ onVolumeInput(e) {
322+ if (!(e.target instanceof HTMLInputElement)) return;
323+ const value = parseFloat(e.target.value);
324+ this.setVolume(value);
325+ }
326+
327+ /**
328+ * Handles mousedown events on the progress bar
329+ * @param {MouseEvent} e - The mousedown event
330+ * @returns {void}
331+ */
332+ onProgressMouseDown(e) {
333+ this.isDragging = true;
334+ this.updateProgress(e);
335+ document.addEventListener('mousemove', this.boundHandlers.documentMouseMove);
336+ document.addEventListener('mouseup', this.boundHandlers.documentMouseUp);
337+ }
338+
339+ /**
340+ * Handles click events on the progress bar
341+ * @param {MouseEvent} e - The click event
342+ * @returns {void}
343+ */
344+ onProgressClick(e) {
345+ if (!this.isDragging) {
346+ this.updateProgress(e);
347+ }
348+ }
349+
350+ /**
351+ * Handles mousemove on the progress bar (no-op if dragging)
352+ * @param {MouseEvent} e - The mousemove event
353+ * @returns {void}
354+ */
355+ onProgressMouseMove(e) {
356+ if (!this.isDragging) {
357+ this.updateProgressTitle(e);
358+ }
359+ }
360+
361+ /**
362+ * Handles document mousemove events during progress bar dragging
363+ * @param {MouseEvent} e - The mousemove event
364+ * @returns {void}
365+ */
366+ onDocumentMouseMove(e) {
367+ if (this.isDragging) {
368+ this.updateProgress(e);
369+ }
370+ }
371+
372+ /**
373+ * Handles document mouseup events to end progress bar dragging
374+ * @returns {void}
375+ */
376+ onDocumentMouseUp() {
377+ if (this.isDragging) {
378+ this.isDragging = false;
379+ document.removeEventListener('mousemove', this.boundHandlers.documentMouseMove);
380+ document.removeEventListener('mouseup', this.boundHandlers.documentMouseUp);
381+ }
382+ }
383+
384+ /**
385+ * Updates the progress bar position and seeks audio based on mouse position
386+ * @param {MouseEvent} e - The mouse event containing position information
387+ * @returns {void}
388+ */
389+ updateProgress(e) {
390+ if (!this.elements.progress) return;
391+
392+ const rect = this.elements.progress.getBoundingClientRect();
393+ const offsetX = e.clientX - rect.left;
394+ const width = rect.width;
395+ const percent = Math.max(0, Math.min(100, (offsetX / width) * 100));
396+
397+ if (this.elements.progressBar) {
398+ /** @type {HTMLElement} */ (this.elements.progressBar).style.width = percent + '%';
399+ }
400+
401+ const seekTime = (percent / 100) * this.audio.duration;
402+ if (isFinite(seekTime)) {
403+ this.audio.currentTime = seekTime;
404+ if (this.elements.currentTime) {
405+ this.elements.currentTime.textContent = formatTime(seekTime);
406+ }
407+ }
408+ }
409+
410+ /**
411+ * Updates the volume icon based on current volume and mute state
412+ * @returns {void}
413+ */
414+ updateVolumeIcon() {
415+ if (!this.elements.volumeBtn) return;
416+
417+ const volume = this.audio.volume;
418+ const isMuted = this.audio.muted;
419+
420+ this.elements.volumeBtn.classList.remove('fa-volume-high', 'fa-volume-low', 'fa-volume-off', 'fa-volume-xmark');
421+
422+ if (isMuted || volume === 0) {
423+ this.elements.volumeBtn.classList.add('fa-volume-xmark');
424+ } else if (volume < 0.5) {
425+ this.elements.volumeBtn.classList.add('fa-volume-low');
426+ } else {
427+ this.elements.volumeBtn.classList.add('fa-volume-high');
428+ }
429+ }
430+
431+ /**
432+ * Updates the current time and total time display elements
433+ * @returns {void}
434+ */
435+ updateTimeDisplays() {
436+ if (this.elements.currentTime) {
437+ this.elements.currentTime.textContent = formatTime(this.audio.currentTime || 0);
438+ }
439+ if (this.elements.totalTime) {
440+ this.elements.totalTime.textContent = formatTime(this.audio.duration || 0);
441+ }
442+ }
443+
444+ /**
445+ * Updates the mouseover title on the progress bar to show time at cursor position
446+ * @param {MouseEvent} e - The mouse event
447+ * @returns {void}
448+ */
449+ updateProgressTitle(e) {
450+ if (!this.elements.progress) return;
451+
452+ const rect = this.elements.progress.getBoundingClientRect();
453+ const offsetX = e.clientX - rect.left;
454+ const width = rect.width;
455+ const percent = Math.max(0, Math.min(100, (offsetX / width) * 100));
456+
457+ this.elements.progress.setAttribute('title', formatTime((percent / 100) * this.audio.duration));
458+ }
459+
460+ // Public methods
461+ /**
462+ * Starts audio playback
463+ * @returns {void}
464+ */
465+ play() {
466+ if (this.isDestroyed) return;
467+ if (this.audio.paused) {
468+ const playPromise = this.audio.play();
469+ if (playPromise !== undefined) {
470+ playPromise.catch(error => {
471+ console.error('Audio play failed:', error);
472+ });
473+ }
474+ }
475+ }
476+
477+ /**
478+ * Pauses audio playback
479+ * @returns {void}
480+ */
481+ pause() {
482+ if (this.isDestroyed) return;
483+ if (!this.audio.paused) {
484+ this.audio.pause();
485+ }
486+ }
487+
488+ /**
489+ * Toggles between play and pause states
490+ * @returns {void}
491+ */
492+ togglePlay() {
493+ if (this.audio.paused) {
494+ this.play();
495+ } else {
496+ this.pause();
497+ }
498+ }
499+
500+ /**
501+ * Seeks to a specific time in the audio
502+ * @param {number} time - The time in seconds to seek to
503+ * @returns {void}
504+ */
505+ seek(time) {
506+ if (this.isDestroyed) return;
507+ if (isFinite(time) && time >= 0 && time <= this.audio.duration) {
508+ this.audio.currentTime = time;
509+ }
510+ }
511+
512+ /**
513+ * Sets the volume level
514+ * @param {number} volume - Volume level between 0.0 and 1.0
515+ * @returns {void}
516+ */
517+ setVolume(volume) {
518+ if (this.isDestroyed) return;
519+ volume = Math.max(0, Math.min(1, volume));
520+ this.audio.volume = volume;
521+
522+ if (volume > 0 && this.audio.muted) {
523+ this.audio.muted = false;
524+ }
525+ }
526+
527+ /**
528+ * Mutes the audio
529+ * @returns {void}
530+ */
531+ mute() {
532+ if (this.isDestroyed) return;
533+ this.audio.muted = true;
534+ }
535+
536+ /**
537+ * Unmutes the audio
538+ * @returns {void}
539+ */
540+ unmute() {
541+ if (this.isDestroyed) return;
542+ this.audio.muted = false;
543+ }
544+
545+ /**
546+ * Toggles the mute state
547+ * @returns {void}
548+ */
549+ toggleMute() {
550+ if (this.isDestroyed) return;
551+ this.audio.muted = !this.audio.muted;
552+ }
553+
554+ /**
555+ * Sets the audio source URL
556+ * @param {string} src - The URL of the audio file
557+ * @returns {void}
558+ */
559+ setSrc(src) {
560+ if (this.isDestroyed) return;
561+ this.audio.src = src;
562+ }
563+
564+ /**
565+ * Sets the title displayed in the player
566+ * @param {string} title - The title text to display
567+ * @returns {void}
568+ */
569+ setTitle(title) {
570+ if (this.isDestroyed) return;
571+ this.options.title = title;
572+ if (this.elements.title) {
573+ this.elements.title.textContent = title;
574+ }
575+ }
576+
577+ /**
578+ * Cleans up the player by removing event listeners and clearing references
579+ * @returns {void}
580+ */
581+ destroy() {
582+ if (this.isDestroyed) return;
583+ this.isDestroyed = true;
584+
585+ // Stop observing DOM changes
586+ if (this.observer) {
587+ this.observer.disconnect();
588+ this.observer = null;
589+ }
590+
591+ // Pause and clear audio
592+ this.pause();
593+ this.audio.src = '';
594+
595+ // Remove all event listeners
596+ this.unbindEvents();
597+
598+ // Clear references to prevent memory leaks
599+ this.audio = null;
600+ this.container = null;
601+ this.elements = null;
602+ this.options = null;
603+ this.boundHandlers = null;
604+ }
605+}
public/scripts/bookmarks.js+1 -0
@@ -344,6 +344,7 @@ export async function convertSoloToGroupChat() {
344344
345345 // Save group-chat marker
346346 if (index == 0) {
347+ // @ts-ignore
347348 message.is_group = true;
348349 }
349350
public/scripts/chats.js+250 -124
@@ -10,12 +10,10 @@ import {
1010 event_types,
1111 getCurrentChatId,
1212 getRequestHeaders,
13- hideSwipeButtons,
1413 name1,
1514 name2,
1615 reloadCurrentChat,
1716 saveSettingsDebounced,
18- showSwipeButtons,
1917 this_chid,
2018 saveChatConditional,
2119 chat_metadata,
@@ -27,6 +25,10 @@ import {
2725 getSystemMessageByType,
2826 printMessages,
2927 clearChat,
28+ refreshSwipeButtons,
29+ getMediaIndex,
30+ getMediaDisplay,
31+ chatElement,
3032} from '../script.js';
3133import { selected_group } from './group-chats.js';
3234import { power_user } from './power-user.js';
@@ -44,6 +46,8 @@ import {
4446 getFileText,
4547 getFileExtension,
4648 convertTextToBase64,
49+ isSameFile,
50+ clamp,
4751} from './utils.js';
4852import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
4953import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
@@ -53,6 +57,7 @@ import { renderTemplateAsync } from './templates.js';
5357import { t } from './i18n.js';
5458import { humanizedDateTime } from './RossAscends-mods.js';
5559import { accountStorage } from './util/AccountStorage.js';
60+import { MEDIA_DISPLAY, MEDIA_SOURCE, MEDIA_TYPE, SCROLL_BEHAVIOR, SWIPE_DIRECTION } from './constants.js';
5661
5762/**
5863 * @typedef {Object} FileAttachment
@@ -159,8 +164,7 @@ export async function hideChatMessageRange(start, end, unhide, nameFitler = null
159164 }
160165
161166 // Reload swipes. Useful when a last message is hidden.
162167 hideSwipeButtonsrefreshSwipeButtons();
163- showSwipeButtons();
164168
165169 await saveChatConditional();
166170}
@@ -189,62 +193,71 @@ export async function unhideChatMessage(messageId, _messageBlock) {
189193
190194/**
191195 * Adds a file attachment to the message.
192196 * @param {objectChatMessage} message Message object
193197 * @returns {Promise<void>} A promise that resolves when file is uploaded.
194198 */
195199export async function populateFileAttachment(message, inputId = 'file_form_input') {
196200 try {
197201 if (!message) return;
198202 if (!message.extra || typeof message.extra !== 'object') message.extra = {};
199203 const fileInput = document.getElementById(inputId);
200204 if (!(fileInput instanceof HTMLInputElement)) return;
201- const file = fileInput.files[0];
205+
202- if (!file) return;
206+ for (const file of fileInput.files) {
203-
207+ const slug = getStringHash(file.name);
204- const slug = getStringHash(file.name);
208+ const fileNamePrefix = `${Date.now()}_${slug}`;
205- const fileNamePrefix = `${Date.now()}_${slug}`;
209+ const fileBase64 = await getBase64Async(file);
206- const fileBase64 = await getBase64Async(file);
210+ let base64Data = fileBase64.split(',')[1];
207- let base64Data = fileBase64.split(',')[1];
211+ const extension = getFileExtension(file);
208- const extension = getFileExtension(file);
212+
209-
213+ const mediaType = MEDIA_TYPE.getFromMime(file.type);
210- // If file is image
214+ if (mediaType) {
211- if (file.type.startsWith('image/')) {
215+ const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
212- const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
216+ if (!Array.isArray(message.extra.media)) {
213217 message.extra.imagemedia = imageUrl[];
214- message.extra.inline_image = true;
218+ }
215- }
219+ /** @type {MediaAttachment} */
216- // If file is video
220+ const mediaAttachment = {
217- else if (file.type.startsWith('video/')) {
221+ url: imageUrl,
218- const videoUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension);
222+ type: mediaType,
219- message.extra.video = videoUrl;
223+ title: file.name,
220- } else {
224+ source: MEDIA_SOURCE.UPLOAD,
221- const uniqueFileName = `${fileNamePrefix}.txt`;
225+ };
222-
226+ message.extra.media.push(mediaAttachment);
223- if (isConvertible(file.type)) {
227+ message.extra.media_index = message.extra.media.length - 1;
224- try {
228+ message.extra.inline_image = true;
225- const converter = getConverter(file.type);
229+ } else {
226- const fileText = await converter(file);
230+ const uniqueFileName = `${fileNamePrefix}.txt`;
227- base64Data = convertTextToBase64(fileText);
231+
228232 } catchif (errorisConvertible(file.type)) {
229- toastr.error(String(error), t`Could not convert file`);
233+ try {
230- console.error('Could not convert file', error);
234+ const converter = getConverter(file.type);
235+ const fileText = await converter(file);
236+ base64Data = convertTextToBase64(fileText);
237+ } catch (error) {
238+ toastr.error(String(error), t`Could not convert file`);
239+ console.error('Could not convert file', error);
240+ }
231241 }
232- }
233242
234243 const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data);
235244
236245 if (!fileUrl) {
237- return;
246+ continue;
238247 }
239248
240- message.extra.file = {
249+ if (!Array.isArray(message.extra.files)) {
241- url: fileUrl,
250+ message.extra.files = [];
242- size: file.size,
251+ }
243- name: file.name,
244- created: Date.now(),
245- };
246- }
247252
253+ message.extra.files.push({
254+ url: fileUrl,
255+ size: file.size,
256+ name: file.name,
257+ created: Date.now(),
258+ });
259+ }
260+ }
248261 } catch (error) {
249262 console.error('Could not upload file', error);
250263 toastr.error(t`Either the file is corrupted or its format is not supported.`, t`Could not upload the file`);
@@ -316,16 +329,16 @@ export async function getFileAttachment(url) {
316329 */
317330async function validateFile(file) {
318331 const fileText = await file.text();
319332 const isImageisMedia = file.type.startsWith('image/') || file.type.startsWith('video/') || file.type.startsWith('audio/');
320333 const isBinary = /^[\x00-\x08\x0E-\x1F\x7F-\xFF]*$/.test(fileText);
321334
322335 if (!isImageisMedia && file.size > fileSizeLimit) {
323336 toastr.error(t`File is too big. Maximum size is ${humanFileSize(fileSizeLimit)}.`);
324337 return false;
325338 }
326339
327340 // If file is binary
328341 if (isBinary && !isImageisMedia && !isConvertible(file.type)) {
329342 toastr.error(t`Binary files are not supported. Select a text file or image.`);
330343 return false;
331344 }
@@ -342,22 +355,28 @@ export function hasPendingFileAttachment() {
342355
343356/**
344357 * Displays file information in the message sending form.
345358 * @param {FileFileList} filefileList File object
346359 * @returns {Promise<void>}
347360 */
348361async function onFileAttach(filefileList) {
349362 if (!filefileList || fileList.length === 0) return;
350363
351- const isValid = await validateFile(file);
364+ for (const file of fileList) {
365+ const isValid = await validateFile(file);
352366
353367 // If file is binary
354368 if (!isValid) {
355- $('#file_form').trigger('reset');
369+ toastr.warning(t`File ${file.name} is not supported.`);
356- return;
370+ $('#file_form').trigger('reset');
371+ return;
372+ }
357373 }
358374
359- $('#file_form .file_name').text(file.name);
375+ const name = fileList.length === 1 ? fileList[0].name : t`${fileList.length} files selected`;
360- $('#file_form .file_size').text(humanFileSize(file.size));
376+ const size = [...fileList].reduce((acc, file) => acc + file.size, 0);
377+ const title = [...fileList].map(x => x.name).join('\n');
378+ $('#file_form .file_name').text(name).attr('title', title);
379+ $('#file_form .file_size').text(humanFileSize(size)).attr('title', size);
361380 $('#file_form').removeClass('displayNone');
362381
363382 // Reset form on chat change (if not on a welcome screen)
@@ -370,10 +389,17 @@ async function onFileAttach(file) {
370389}
371390
372391/**
373392 * Deletes file from a message.
393+ * @param {JQuery<HTMLElement>} messageBlock Message block element
374394 * @param {number} messageId Message ID
395+ * @param {number} fileIndex File index
375396 */
376397async function deleteMessageFile(messageBlock, messageId, fileIndex) {
398+ if (isNaN(messageId) || isNaN(fileIndex)) {
399+ console.warn('Invalid message ID or file index');
400+ return;
401+ }
402+
377403 const confirm = await callGenericPopup('Are you sure you want to delete this file?', POPUP_TYPE.CONFIRM);
378404
379405 if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
@@ -383,26 +409,49 @@ async function deleteMessageFile(messageId) {
383409
384410 const message = chat[messageId];
385411
386412 if (!Array.isArray(message?.extra?.filefiles)) {
387413 console.debug('Message has no filefiles');
414+ return;
415+ }
416+
417+ if (fileIndex < 0 || fileIndex >= message.extra.files.length) {
418+ console.warn('Invalid file index for message');
388419 return;
389420 }
390421
391422 const url = message.extra.filefiles[fileIndex]?.url;
423+ message.extra.files.splice(fileIndex, 1);
392424
393- delete message.extra.file;
394- $(`.mes[mesid="${messageId}"] .mes_file_container`).remove();
395425 await saveChatConditional();
396426 await deleteFileFromServer(url);
397-}
398427
428+ appendMediaToMessage(message, messageBlock, SCROLL_BEHAVIOR.KEEP);
429+}
399430
400431/**
401432 * Opens file from message in a modal.
402433 * @param {number} messageId Message ID
434+ * @param {number} fileIndex File index
403435 */
404436async function viewMessageFile(messageId, fileIndex) {
405- const messageFile = chat[messageId]?.extra?.file;
437+ if (isNaN(messageId) || isNaN(fileIndex)) {
438+ console.warn('Invalid message ID or file index');
439+ return;
440+ }
441+
442+ const message = chat[messageId];
443+
444+ if (!Array.isArray(message?.extra?.files)) {
445+ console.debug('Message has no files');
446+ return;
447+ }
448+
449+ if (fileIndex < 0 || fileIndex >= message.extra.files.length) {
450+ console.warn('Invalid file index for message');
451+ return;
452+ }
453+
454+ const messageFile = message.extra.files[fileIndex];
406455
407456 if (!messageFile) {
408457 console.debug('Message has no file or it is empty');
@@ -431,39 +480,51 @@ function embedMessageFile(messageId, messageBlock) {
431480 .on('change', parseAndUploadEmbed)
432481 .trigger('click');
433482
434483 async function parseAndUploadEmbed(/** @type {JQuery.ChangeEvent} */ e) {
435- const file = e.target.files[0];
484+ if (!(e.target instanceof HTMLInputElement)) return;
436485 if (!filee.target.files.length) return;
437486
438- const isValid = await validateFile(file);
487+ for (const file of e.target.files) {
488+ const isValid = await validateFile(file);
439489
440490 if (!isValid) {
441- $('#file_form').trigger('reset');
491+ toastr.warning(t`File ${file.name} is not supported.`);
442- return;
492+ $('#file_form').trigger('reset');
493+ return;
494+ }
443495 }
444496
445497 await populateFileAttachment(message, 'embed_file_input');
446498 await eventSource.emit(event_types.MESSAGE_FILE_EMBEDDED, messageId);
447499 appendMediaToMessage(message, messageBlock, SCROLL_BEHAVIOR.KEEP);
448500 await saveChatConditional();
449501 }
450502}
451503
452504/**
453505 * Appends file content to the message text.
454506 * @param {objectChatMessage} message Message object
455507 * @param {string} messageText Message text
456508 * @returns {Promise<string>} Message text with file content appended.
457509 */
458510export async function appendFileContent(message, messageText) {
459- if (message.extra?.file) {
511+ if (!message || !message.extra || typeof message.extra !== 'object') {
460- const fileText = message.extra.file.text || (await getFileAttachment(message.extra.file.url));
512+ return messageText;
461-
513+ }
462- if (fileText) {
514+ if (message.extra.fileLength >= 0) {
463- const fileWrapped = `${fileText}\n\n`;
515+ delete message.extra.fileLength;
464- message.extra.fileLength = fileWrapped.length;
516+ }
465- messageText = fileWrapped + messageText;
517+ if (Array.isArray(message.extra?.files) && message.extra.files.length > 0) {
518+ const fileTexts = [];
519+ for (const file of message.extra.files) {
520+ const fileText = file.text || (await getFileAttachment(file.url));
521+ if (fileText) {
522+ fileTexts.push(fileText);
523+ }
466524 }
525+ const mergedFileTexts = fileTexts.join('\n\n') + '\n\n';
526+ message.extra.fileLength = mergedFileTexts.length;
527+ return mergedFileTexts + messageText;
467528 }
468529 return messageText;
469530}
@@ -809,62 +870,128 @@ export function isExternalMediaAllowed() {
809870 return !power_user.forbid_external_media;
810871}
811872
812-function expandMessageImage(event) {
873+/**
813- const mesBlock = $(event.currentTarget).closest('.mes');
874+ * Expands the message media attachment.
814- const mesId = mesBlock.attr('mesid');
875+ * @param {number} messageId Message ID
815- const message = chat[mesId];
876+ * @param {number} mediaIndex Media index
816- const imgSrc = message?.extra?.image;
877+ * @returns {HTMLElement} Enlarged media element
817- const title = message?.extra?.title;
878+ */
879+function expandMessageMedia(messageId, mediaIndex) {
880+ if (isNaN(messageId) || isNaN(mediaIndex)) {
881+ console.warn('Invalid message ID or media index');
882+ return;
883+ }
884+
885+ /** @type {ChatMessage} */
886+ const message = chat[messageId];
818887
819- if (!imgSrc) {
888+ if (!Array.isArray(message?.extra?.media) || message.extra.media.length === 0) {
889+ console.warn('Message has no media to expand');
820890 return;
821891 }
822892
823893 const imgmediaAttachment = documentmessage.createElement('img')extra.media[mediaIndex];
824- img.classList.add('img_enlarged');
894+ const title = mediaAttachment.title || message.extra.title || '';
825- img.src = imgSrc;
826- const imgHolder = document.createElement('div');
827- imgHolder.classList.add('img_enlarged_holder');
828- imgHolder.append(img);
829- const imgContainer = $('<div><pre><code class="img_enlarged_title"></code></pre></div>');
830- imgContainer.prepend(imgHolder);
831- imgContainer.addClass('img_enlarged_container');
832895
833- const codeTitle = imgContainer.find('.img_enlarged_title');
896+ if (!mediaAttachment) {
834- codeTitle.addClass('txt').text(title);
897+ return;
835- const titleEmpty = !title || title.trim().length === 0;
898+ }
836- imgContainer.find('pre').toggle(!titleEmpty);
837- addCopyToCodeBlocks(imgContainer);
838899
839- const popup = new Popup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true, transparent: true });
900+ if (mediaAttachment.type === MEDIA_TYPE.AUDIO) {
901+ console.warn('Audio media cannot be expanded');
902+ return;
903+ }
840904
841- popup.dlg.style.width = 'unset';
905+ /**
842- popup.dlg.style.height = 'unset';
906+ * Gets the media element based on its type.
907+ * @returns {HTMLElement} Media element
908+ */
909+ function getMediaElement() {
910+ function getImageElement() {
911+ const img = document.createElement('img');
912+ img.src = mediaAttachment.url;
913+ img.classList.add('img_enlarged');
914+ return img;
915+ }
843916
844- img.addEventListener('click', event => {
917+ function getVideoElement() {
845918 const shouldZoomvideo = !img.classListdocument.containscreateElement('zoomedvideo');
846- img.classList.toggle('zoomed', shouldZoom);
919+ video.src = mediaAttachment.url;
847- event.stopPropagation();
920+ video.classList.add('img_enlarged');
848- });
921+ video.controls = true;
849- codeTitle[0]?.addEventListener('click', event => {
922+ video.autoplay = true;
923+ return video;
924+ }
925+
926+ switch (mediaAttachment.type) {
927+ case MEDIA_TYPE.IMAGE:
928+ return getImageElement();
929+ case MEDIA_TYPE.VIDEO:
930+ return getVideoElement();
931+ }
932+
933+ console.warn('Unsupported media type for enlargement:', mediaAttachment.type);
934+ return getImageElement();
935+ }
936+
937+ const mediaElement = getMediaElement();
938+ const mediaHolder = document.createElement('div');
939+ mediaHolder.classList.add('img_enlarged_holder');
940+ mediaHolder.append(mediaElement);
941+ const mediaContainer = document.createElement('div');
942+ mediaContainer.classList.add('img_enlarged_container');
943+ mediaContainer.append(mediaHolder);
944+
945+ mediaElement.addEventListener('click', event => {
946+ const shouldZoom = !mediaElement.classList.contains('zoomed') && mediaElement.nodeName === 'IMG';
947+ mediaElement.classList.toggle('zoomed', shouldZoom);
850948 event.stopPropagation();
851949 });
852950
853- popup.dlg.addEventListener('click', event => {
951+ if (title.trim().length > 0) {
952+ const mediaTitlePre = document.createElement('pre');
953+ const mediaTitleCode = document.createElement('code');
954+ mediaTitleCode.classList.add('img_enlarged_title', 'txt');
955+ mediaTitleCode.textContent = title;
956+ mediaTitlePre.append(mediaTitleCode);
957+ mediaTitleCode.addEventListener('click', event => {
958+ event.stopPropagation();
959+ });
960+ mediaContainer.append(mediaTitlePre);
961+ addCopyToCodeBlocks(mediaContainer);
962+ }
963+
964+ const popup = new Popup(mediaContainer, POPUP_TYPE.DISPLAY, '', { large: true, transparent: true });
965+
966+ popup.dlg.style.width = 'unset';
967+ popup.dlg.style.height = 'unset';
968+ popup.dlg.addEventListener('click', () => {
854969 popup.completeCancelled();
855970 });
856971
857972 popup.show();
858973 return imgmediaElement;
859974}
860975
861-function expandAndZoomMessageImage(event) {
976+/**
862- expandMessageImage(event).click();
977+ * Deletes an image from a message.
863-}
978+ * @param {number} messageId Message ID
979+ * @param {number} mediaIndex Image index
980+ * @param {JQuery<HTMLElement>} messageBlock Message block element
981+ */
982+async function deleteMessageMedia(messageId, mediaIndex, messageBlock) {
983+ if (isNaN(messageId) || isNaN(mediaIndex)) {
984+ console.warn('Invalid message ID or media index');
985+ return;
986+ }
987+
988+ const deleteUrls = [];
989+ const deleteFromServerId = 'delete_media_files_checkbox';
990+ let deleteFromServer = true;
864991
865-async function deleteMessageImage() {
992+ const value = await Popup.show.confirm(t`Delete media from message?`, t`This action can't be undone.`, {
866- const value = await callGenericPopup('<h3>Delete image from message?<br>This action can\'t be undone.</h3>', POPUP_TYPE.TEXT, '', {
public/scripts/constants.js+0 -0
public/scripts/events.js+0 -0
public/scripts/extensions.js+0 -0
public/scripts/extensions/caption/index.js+0 -0
public/scripts/extensions/caption/settings.html+0 -0
public/scripts/extensions/connection-manager/index.js+0 -0
public/scripts/extensions/gallery/index.js+0 -0
public/scripts/extensions/quick-reply/index.js+0 -0
public/scripts/extensions/regex/dropdown.html+0 -0
public/scripts/extensions/regex/engine.js+0 -0
public/scripts/extensions/regex/importTarget.html+0 -0
public/scripts/extensions/regex/index.js+0 -0
public/scripts/extensions/regex/scriptTemplate.html+0 -0
public/scripts/extensions/shared.js+0 -0
public/scripts/extensions/stable-diffusion/index.js+0 -0
public/scripts/extensions/stable-diffusion/settings.html+0 -0
public/scripts/extensions/tts/cosyvoice.js+0 -0
public/scripts/extensions/vectors/index.js+0 -0
public/scripts/extensions/vectors/settings.html+0 -0
public/scripts/group-chats.js+0 -0
public/scripts/horde.js+0 -0
public/scripts/openai.js+0 -0
public/scripts/popup.js+0 -0
public/scripts/power-user.js+0 -0
public/scripts/preset-manager.js+0 -0
public/scripts/reasoning.js+0 -0
public/scripts/samplerSelect.js+0 -0
public/scripts/scrapers.js+0 -0
public/scripts/secrets.js+0 -0
public/scripts/slash-commands.js+0 -0
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+0 -0
public/scripts/st-context.js+0 -0
public/scripts/system-messages.js+0 -0
public/scripts/tags.js+0 -0
public/scripts/templates/samplerSelector.html+0 -0
public/scripts/templates/scenarioOverride.html+0 -0
public/scripts/templates/tagManagement.html+0 -0
public/scripts/textgen-models.js+0 -0
public/scripts/textgen-settings.js+0 -0
public/scripts/tool-calling.js+0 -0
public/scripts/util/SimpleMutex.js+0 -0
public/scripts/utils.js+0 -0
public/scripts/welcome-screen.js+0 -0
public/scripts/world-info.js+0 -0
public/style.css+0 -0
src/byaf.js+0 -0
src/constants.js+0 -0
src/endpoints/backends/chat-completions.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/data-maid.js+0 -0
src/endpoints/extensions.js+0 -0
src/endpoints/google.js+0 -0
src/endpoints/horde.js+0 -0
src/endpoints/openai.js+0 -0
src/endpoints/openrouter.js+0 -0
src/endpoints/secrets.js+0 -0
src/endpoints/stable-diffusion.js+0 -0
src/endpoints/thumbnails.js+0 -0
src/endpoints/vectors.js+0 -0
src/express-common.js+0 -0
src/middleware/whitelist.js+0 -0
src/prompt-converters.js+0 -0
src/types/byaf.d.ts+0 -0
src/users.js+0 -0
src/util.js+0 -0
src/vectors/ollama-vectors.js+0 -0
src/vectors/openai-vectors.js+0 -0
tests/.eslintrc.js → tests/.eslintrc.cjs+0 -0
tests/jest-e2e.config.json+0 -0
tests/jest.config.json+0 -0
tests/package-lock.json+0 -0
tests/package.json+0 -0
tests/sample.test.js → tests/sample.e2e.js+0 -0
tests/util.test.js+0 -0
Diff truncated