Merge branch 'staging' into 202411-backend-maxctx

c930a66d819b27ea8a29e6600cc727334eda8da8

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

27 files changed, +484 -422Showing whitespace changes
default/content/index.json+8 -0
@@ -628,6 +628,14 @@
628 "type": "instruct"628 "type": "instruct"
629 },629 },
630 {630 {
631 "filename": "presets/instruct/Tulu.json",
632 "type": "instruct"
633 },
634 {
635 "filename": "presets/context/Tulu.json",
636 "type": "context"
637 },
638 {
631 "filename": "presets/instruct/Vicuna 1.0.json",639 "filename": "presets/instruct/Vicuna 1.0.json",
632 "type": "instruct"640 "type": "instruct"
633 },641 },
default/content/presets/context/Tulu.json+11 -0
@@ -0,0 +1,11 @@
1{
2 "story_string": "<|system|>\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}\n",
3 "example_separator": "",
4 "chat_start": "",
5 "use_stop_strings": false,
6 "allow_jailbreak": false,
7 "always_force_name2": true,
8 "trim_sentences": false,
9 "single_line": false,
10 "name": "Tulu"
11}
default/content/presets/instruct/Tulu.json+22 -0
@@ -0,0 +1,22 @@
1{
2 "input_sequence": "<|user|>\n",
3 "output_sequence": "<|assistant|>\n",
4 "first_output_sequence": "",
5 "last_output_sequence": "",
6 "system_sequence_prefix": "",
7 "system_sequence_suffix": "",
8 "stop_sequence": "<|end_of_text|>",
9 "wrap": false,
10 "macro": true,
11 "names_behavior": "always",
12 "activation_regex": "",
13 "skip_examples": false,
14 "output_suffix": "<|end_of_text|>\n",
15 "input_suffix": "\n",
16 "system_sequence": "<|system|>\n",
17 "system_suffix": "\n",
18 "user_alignment_message": "",
19 "last_system_sequence": "",
20 "system_same_as_user": false,
21 "name": "Tulu"
22}
default/content/presets/openai/Default.json+0 -1
@@ -230,7 +230,6 @@
230 "show_external_models": false,230 "show_external_models": false,
231 "assistant_prefill": "",231 "assistant_prefill": "",
232 "assistant_impersonation": "",232 "assistant_impersonation": "",
233 "human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
234 "claude_use_sysprompt": false,233 "claude_use_sysprompt": false,
235 "use_alt_scale": false,234 "use_alt_scale": false,
236 "squash_system_messages": false,235 "squash_system_messages": false,
package-lock.json+2 -255
@@ -1,12 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.12.7",3 "version": "1.12.8",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.12.7",9 "version": "1.12.8",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
@@ -67,7 +67,6 @@
67 "vectra": "^0.2.2",67 "vectra": "^0.2.2",
68 "wavefile": "^11.0.0",68 "wavefile": "^11.0.0",
69 "webpack": "^5.95.0",69 "webpack": "^5.95.0",
70 "webpack-dev-middleware": "^7.4.2",
71 "write-file-atomic": "^5.0.1",70 "write-file-atomic": "^5.0.1",
72 "ws": "^8.17.1",71 "ws": "^8.17.1",
73 "yaml": "^2.3.4",72 "yaml": "^2.3.4",
@@ -866,60 +865,6 @@
866 "@jridgewell/sourcemap-codec": "^1.4.14"865 "@jridgewell/sourcemap-codec": "^1.4.14"
867 }866 }
868 },867 },
869 "node_modules/@jsonjoy.com/base64": {
870 "version": "1.1.2",
871 "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz",
872 "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==",
873 "license": "Apache-2.0",
874 "engines": {
875 "node": ">=10.0"
876 },
877 "funding": {
878 "type": "github",
879 "url": "https://github.com/sponsors/streamich"
880 },
881 "peerDependencies": {
882 "tslib": "2"
883 }
884 },
885 "node_modules/@jsonjoy.com/json-pack": {
886 "version": "1.1.0",
887 "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz",
888 "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==",
889 "license": "Apache-2.0",
890 "dependencies": {
891 "@jsonjoy.com/base64": "^1.1.1",
892 "@jsonjoy.com/util": "^1.1.2",
893 "hyperdyperid": "^1.2.0",
894 "thingies": "^1.20.0"
895 },
896 "engines": {
897 "node": ">=10.0"
898 },
899 "funding": {
900 "type": "github",
901 "url": "https://github.com/sponsors/streamich"
902 },
903 "peerDependencies": {
904 "tslib": "2"
905 }
906 },
907 "node_modules/@jsonjoy.com/util": {
908 "version": "1.5.0",
909 "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz",
910 "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==",
911 "license": "Apache-2.0",
912 "engines": {
913 "node": ">=10.0"
914 },
915 "funding": {
916 "type": "github",
917 "url": "https://github.com/sponsors/streamich"
918 },
919 "peerDependencies": {
920 "tslib": "2"
921 }
922 },
923 "node_modules/@kwsites/file-exists": {868 "node_modules/@kwsites/file-exists": {
924 "version": "1.1.1",869 "version": "1.1.1",
925 "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",870 "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@@ -1803,45 +1748,6 @@
1803 "url": "https://github.com/sponsors/epoberezkin"1748 "url": "https://github.com/sponsors/epoberezkin"
1804 }1749 }
1805 },1750 },
1806 "node_modules/ajv-formats": {
1807 "version": "2.1.1",
1808 "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
1809 "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
1810 "license": "MIT",
1811 "dependencies": {
1812 "ajv": "^8.0.0"
1813 },
1814 "peerDependencies": {
1815 "ajv": "^8.0.0"
1816 },
1817 "peerDependenciesMeta": {
1818 "ajv": {
1819 "optional": true
1820 }
1821 }
1822 },
1823 "node_modules/ajv-formats/node_modules/ajv": {
1824 "version": "8.17.1",
1825 "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
1826 "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
1827 "license": "MIT",
1828 "dependencies": {
1829 "fast-deep-equal": "^3.1.3",
1830 "fast-uri": "^3.0.1",
1831 "json-schema-traverse": "^1.0.0",
1832 "require-from-string": "^2.0.2"
1833 },
1834 "funding": {
1835 "type": "github",
1836 "url": "https://github.com/sponsors/epoberezkin"
1837 }
1838 },
1839 "node_modules/ajv-formats/node_modules/json-schema-traverse": {
1840 "version": "1.0.0",
1841 "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
1842 "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
1843 "license": "MIT"
1844 },
1845 "node_modules/ajv-keywords": {1751 "node_modules/ajv-keywords": {
1846 "version": "3.5.2",1752 "version": "3.5.2",
1847 "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",1753 "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -2602,12 +2508,6 @@
2602 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",2508 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
2603 "license": "MIT"2509 "license": "MIT"
2604 },2510 },
2605 "node_modules/colorette": {
2606 "version": "2.0.20",
2607 "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
2608 "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
2609 "license": "MIT"
2610 },
2611 "node_modules/combined-stream": {2511 "node_modules/combined-stream": {
2612 "version": "1.0.8",2512 "version": "1.0.8",
2613 "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",2513 "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3785,12 +3685,6 @@
3785 "dev": true,3685 "dev": true,
3786 "license": "MIT"3686 "license": "MIT"
3787 },3687 },
3788 "node_modules/fast-uri": {
3789 "version": "3.0.3",
3790 "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz",
3791 "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==",
3792 "license": "BSD-3-Clause"
3793 },
3794 "node_modules/fastq": {3688 "node_modules/fastq": {
3795 "version": "1.15.0",3689 "version": "1.15.0",
3796 "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",3690 "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -4541,15 +4435,6 @@
4541 "ms": "^2.0.0"4435 "ms": "^2.0.0"
4542 }4436 }
4543 },4437 },
4544 "node_modules/hyperdyperid": {
4545 "version": "1.2.0",
4546 "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
4547 "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
4548 "license": "MIT",
4549 "engines": {
4550 "node": ">=10.18"
4551 }
4552 },
4553 "node_modules/iconv-lite": {4438 "node_modules/iconv-lite": {
4554 "version": "0.6.3",4439 "version": "0.6.3",
4555 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",4440 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -5176,25 +5061,6 @@
5176 "node": ">= 0.6"5061 "node": ">= 0.6"
5177 }5062 }
5178 },5063 },
5179 "node_modules/memfs": {
5180 "version": "4.14.0",
5181 "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz",
5182 "integrity": "sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==",
5183 "license": "Apache-2.0",
5184 "dependencies": {
5185 "@jsonjoy.com/json-pack": "^1.0.3",
5186 "@jsonjoy.com/util": "^1.3.0",
5187 "tree-dump": "^1.0.1",
5188 "tslib": "^2.0.0"
5189 },
5190 "engines": {
5191 "node": ">= 4.0.0"
5192 },
5193 "funding": {
5194 "type": "github",
5195 "url": "https://github.com/sponsors/streamich"
5196 }
5197 },
5198 "node_modules/merge-descriptors": {5064 "node_modules/merge-descriptors": {
5199 "version": "1.0.3",5065 "version": "1.0.3",
5200 "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",5066 "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -6310,15 +6176,6 @@
6310 "node": ">=0.10.0"6176 "node": ">=0.10.0"
6311 }6177 }
6312 },6178 },
6313 "node_modules/require-from-string": {
6314 "version": "2.0.2",
6315 "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
6316 "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
6317 "license": "MIT",
6318 "engines": {
6319 "node": ">=0.10.0"
6320 }
6321 },
6322 "node_modules/resolve-alpn": {6179 "node_modules/resolve-alpn": {
6323 "version": "1.2.1",6180 "version": "1.2.1",
6324 "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",6181 "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
@@ -6447,59 +6304,6 @@
6447 "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",6304 "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
6448 "license": "ISC"6305 "license": "ISC"
6449 },6306 },
6450 "node_modules/schema-utils": {
6451 "version": "4.2.0",
6452 "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
6453 "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
6454 "license": "MIT",
6455 "dependencies": {
6456 "@types/json-schema": "^7.0.9",
6457 "ajv": "^8.9.0",
6458 "ajv-formats": "^2.1.1",
6459 "ajv-keywords": "^5.1.0"
6460 },
6461 "engines": {
6462 "node": ">= 12.13.0"
6463 },
6464 "funding": {
6465 "type": "opencollective",
6466 "url": "https://opencollective.com/webpack"
6467 }
6468 },
6469 "node_modules/schema-utils/node_modules/ajv": {
6470 "version": "8.17.1",
6471 "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
6472 "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
6473 "license": "MIT",
6474 "dependencies": {
6475 "fast-deep-equal": "^3.1.3",
6476 "fast-uri": "^3.0.1",
6477 "json-schema-traverse": "^1.0.0",
6478 "require-from-string": "^2.0.2"
6479 },
6480 "funding": {
6481 "type": "github",
6482 "url": "https://github.com/sponsors/epoberezkin"
6483 }
6484 },
6485 "node_modules/schema-utils/node_modules/ajv-keywords": {
6486 "version": "5.1.0",
6487 "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
6488 "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
6489 "license": "MIT",
6490 "dependencies": {
6491 "fast-deep-equal": "^3.1.3"
6492 },
6493 "peerDependencies": {
6494 "ajv": "^8.8.2"
6495 }
6496 },
6497 "node_modules/schema-utils/node_modules/json-schema-traverse": {
6498 "version": "1.0.0",
6499 "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
6500 "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
6501 "license": "MIT"
6502 },
6503 "node_modules/seedrandom": {6307 "node_modules/seedrandom": {
6504 "version": "3.0.5",6308 "version": "3.0.5",
6505 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",6309 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
@@ -7038,18 +6842,6 @@
7038 "dev": true,6842 "dev": true,
7039 "license": "MIT"6843 "license": "MIT"
7040 },6844 },
7041 "node_modules/thingies": {
7042 "version": "1.21.0",
7043 "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
7044 "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==",
7045 "license": "Unlicense",
7046 "engines": {
7047 "node": ">=10.18"
7048 },
7049 "peerDependencies": {
7050 "tslib": "^2"
7051 }
7052 },
7053 "node_modules/tiktoken": {6845 "node_modules/tiktoken": {
7054 "version": "1.0.16",6846 "version": "1.0.16",
7055 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.16.tgz",6847 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.16.tgz",
@@ -7106,22 +6898,6 @@
7106 "node": ">=18"6898 "node": ">=18"
7107 }6899 }
7108 },6900 },
7109 "node_modules/tree-dump": {
7110 "version": "1.0.2",
7111 "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
7112 "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==",
7113 "license": "Apache-2.0",
7114 "engines": {
7115 "node": ">=10.0"
7116 },
7117 "funding": {
7118 "type": "github",
7119 "url": "https://github.com/sponsors/streamich"
7120 },
7121 "peerDependencies": {
7122 "tslib": "2"
7123 }
7124 },
7125 "node_modules/truncate-utf8-bytes": {6901 "node_modules/truncate-utf8-bytes": {
7126 "version": "1.0.2",6902 "version": "1.0.2",
7127 "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",6903 "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -7425,35 +7201,6 @@
7425 }7201 }
7426 }7202 }
7427 },7203 },
7428 "node_modules/webpack-dev-middleware": {
7429 "version": "7.4.2",
7430 "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz",
7431 "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==",
7432 "license": "MIT",
7433 "dependencies": {
7434 "colorette": "^2.0.10",
7435 "memfs": "^4.6.0",
7436 "mime-types": "^2.1.31",
7437 "on-finished": "^2.4.1",
7438 "range-parser": "^1.2.1",
7439 "schema-utils": "^4.0.0"
7440 },
7441 "engines": {
7442 "node": ">= 18.12.0"
7443 },
7444 "funding": {
7445 "type": "opencollective",
7446 "url": "https://opencollective.com/webpack"
7447 },
7448 "peerDependencies": {
7449 "webpack": "^5.0.0"
7450 },
7451 "peerDependenciesMeta": {
7452 "webpack": {
7453 "optional": true
7454 }
7455 }
7456 },
7457 "node_modules/webpack-sources": {7204 "node_modules/webpack-sources": {
7458 "version": "3.2.3",7205 "version": "3.2.3",
7459 "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",7206 "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
package.json+1 -2
@@ -57,7 +57,6 @@
57 "vectra": "^0.2.2",57 "vectra": "^0.2.2",
58 "wavefile": "^11.0.0",58 "wavefile": "^11.0.0",
59 "webpack": "^5.95.0",59 "webpack": "^5.95.0",
60 "webpack-dev-middleware": "^7.4.2",
61 "write-file-atomic": "^5.0.1",60 "write-file-atomic": "^5.0.1",
62 "ws": "^8.17.1",61 "ws": "^8.17.1",
63 "yaml": "^2.3.4",62 "yaml": "^2.3.4",
@@ -85,7 +84,7 @@
85 "type": "git",84 "type": "git",
86 "url": "https://github.com/SillyTavern/SillyTavern.git"85 "url": "https://github.com/SillyTavern/SillyTavern.git"
87 },86 },
88 "version": "1.12.7",87 "version": "1.12.8",
89 "scripts": {88 "scripts": {
90 "start": "node server.js",89 "start": "node server.js",
91 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",90 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
public/css/login.css+6 -0
@@ -42,3 +42,9 @@ body.login .userSelect .userHandle {
42body.login .userSelect:hover {42body.login .userSelect:hover {
43 background-color: var(--black30a);43 background-color: var(--black30a);
44}44}
45
46body.login #handleEntryBlock,
47body.login #passwordEntryBlock,
48body.login #passwordRecoveryBlock {
49 margin: 2px;
50}
public/index.html+12 -18
@@ -1342,12 +1342,12 @@
1342 <input class="neo-range-slider" type="range" id="presence_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" />1342 <input class="neo-range-slider" type="range" id="presence_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" />
1343 <input class="neo-range-input" type="number" min="-2" max="2" step="0.01" data-for="presence_pen_textgenerationwebui" id="presence_pen_counter_textgenerationwebui">1343 <input class="neo-range-input" type="number" min="-2" max="2" step="0.01" data-for="presence_pen_textgenerationwebui" id="presence_pen_counter_textgenerationwebui">
1344 </div>1344 </div>
1345 <div data-tg-type="ooba" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">1345 <div data-tg-type="aphrodite, ooba" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
1346 <small data-i18n="No Repeat Ngram Size">No Repeat Ngram Size</small>1346 <small data-i18n="No Repeat Ngram Size">No Repeat Ngram Size</small>
1347 <input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">1347 <input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">
1348 <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">1348 <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">
1349 </div>1349 </div>
1350 <div data-tg-type="tabby" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">1350 <div data-tg-type="tabby, aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
1351 <small data-i18n="Skew">Skew</small>1351 <small data-i18n="Skew">Skew</small>
1352 <input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />1352 <input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />
1353 <input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui">1353 <input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui">
@@ -1402,7 +1402,7 @@
1402 </div>1402 </div>
1403 </div>1403 </div>
14041404
1405 <div data-tg-type="ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p">1405 <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p">
1406 <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">1406 <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">
1407 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>1407 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
1408 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">1408 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -1959,15 +1959,6 @@
1959 Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.1959 Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.
1960 </span>1960 </span>
1961 </div>1961 </div>
1962 <div id="claude_human_sysprompt_message_block" class="wide100p">
1963 <div class="range-block-title openai_restorable">
1964 <span data-i18n="User first message">User first message</span>
1965 <div id="claude_human_sysprompt_message_restore" title="Restore User first message" data-i18n="[title]Restore User first message" class="right_menu_button">
1966 <div class="fa-solid fa-clock-rotate-left"></div>
1967 </div>
1968 </div>
1969 <textarea id="claude_human_sysprompt_textarea" class="text_pole textarea_compact autoSetHeight" rows="2" data-i18n="[placeholder]Human message" placeholder="Human message, instruction, etc.&#10;Adds nothing when empty, i.e. requires a new prompt with the role 'user'."></textarea>
1970 </div>
1971 </div>1962 </div>
1972 </div>1963 </div>
1973 <div class="range-block m-t-1" data-source="openai,openrouter,scale,custom">1964 <div class="range-block m-t-1" data-source="openai,openrouter,scale,custom">
@@ -3287,7 +3278,7 @@
3287 Advanced Formatting3278 Advanced Formatting
3288 </span>3279 </span>
32893280
3290 <a href="https://docs.sillytavern.app/usage/prompts/" class="notes-link" target="_blank">3281 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/" class="notes-link" target="_blank">
3291 <span class="fa-solid fa-circle-question note-link-span"></span>3282 <span class="fa-solid fa-circle-question note-link-span"></span>
3292 </a>3283 </a>
3293 </h3>3284 </h3>
@@ -3749,7 +3740,7 @@
3749 </div>3740 </div>
3750 <h3 class="margin0">3741 <h3 class="margin0">
3751 <span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>3742 <span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>
3752 <a href="https://docs.sillytavern.app/usage/worldinfo/" class="notes-link" target="_blank">3743 <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/" class="notes-link" target="_blank">
3753 <span class="fa-solid fa-circle-question note-link-span"></span>3744 <span class="fa-solid fa-circle-question note-link-span"></span>
3754 </a>3745 </a>
3755 </h3>3746 </h3>
@@ -3943,7 +3934,13 @@
3943 <div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">3934 <div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">
3944 <div class="flex-container">3935 <div class="flex-container">
3945 <div class="flex-container flexnowrap alignItemsBaseline">3936 <div class="flex-container flexnowrap alignItemsBaseline">
3946 <h3 class="margin0"><span data-i18n="User Settings">User Settings</span></h3>3937 <h3 class="margin0">
3938 <span data-i18n="User Settings">User Settings</span>
3939
3940 <a href="https://docs.sillytavern.app/usage/user_settings/" class="notes-link" target="_blank">
3941 <span class="fa-solid fa-circle-question note-link-span"></span>
3942 </a>
3943 </h3>
3947 </div>3944 </div>
3948 </div>3945 </div>
3949 <div id="UI-language-block" class="flex-container alignItemsBaseline">3946 <div id="UI-language-block" class="flex-container alignItemsBaseline">
@@ -4276,9 +4273,6 @@
4276 <audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>4273 <audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>
4277 <span>4274 <span>
4278 <small data-i18n="Message Sound">Message Sound</small>4275 <small data-i18n="Message Sound">Message Sound</small>
4279 <a href="https://docs.sillytavern.app/usage/user_settings/uicustomization/#message-sound" class="notes-link" target="_blank">
4280 <span class="fa-solid fa-circle-question note-link-span"></span>
4281 </a>
4282 </span>4276 </span>
4283 </label>4277 </label>
4284 <label for="play_sound_unfocused" class="checkbox_label" title="Only play a sound when ST's browser tab is unfocused." data-i18n="[title]Only play a sound when ST's browser tab is unfocused">4278 <label for="play_sound_unfocused" class="checkbox_label" title="Only play a sound when ST's browser tab is unfocused." data-i18n="[title]Only play a sound when ST's browser tab is unfocused">
public/script.js+4 -3
@@ -1633,7 +1633,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
1633 subEntities = filterByTagState(entities, { subForEntity: entity });1633 subEntities = filterByTagState(entities, { subForEntity: entity });
1634 if (doFilter) {1634 if (doFilter) {
1635 // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up1635 // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up
1636 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } });1636 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
1637 }1637 }
1638 if (doSort) {1638 if (doSort) {
1639 sortEntitiesList(subEntities);1639 sortEntitiesList(subEntities);
@@ -1646,11 +1646,11 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
1646 // Second run filters, hiding whatever should be filtered later1646 // Second run filters, hiding whatever should be filtered later
1647 if (doFilter) {1647 if (doFilter) {
1648 const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true });1648 const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true });
1649 entities = entitiesFilter.applyFilters(beforeFinalEntities);1649 entities = entitiesFilter.applyFilters(beforeFinalEntities, { clearFuzzySearchCaches: false });
16501650
1651 // Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters.1651 // Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters.
1652 if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) {1652 if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) {
1653 entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } });1653 entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
1654 }1654 }
1655 }1655 }
16561656
@@ -1666,6 +1666,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
1666 if (doSort) {1666 if (doSort) {
1667 sortEntitiesList(entities);1667 sortEntitiesList(entities);
1668 }1668 }
1669 entitiesFilter.clearFuzzySearchCaches();
1669 return entities;1670 return entities;
1670}1671}
16711672
public/scripts/chat-templates.js+6 -0
@@ -53,6 +53,12 @@ const hash_derivations = {
53 // command-r-08-202453 // command-r-08-2024
54 'Command R'54 'Command R'
55 ,55 ,
56
57 // Tulu
58 'ac7498a36a719da630e99d48e6ebc4409de85a77556c2b6159eeb735bcbd11df':
59 // Tulu-3-8B
60 // Tulu-3-70B
61 'Tulu'
56};62};
5763
58const substr_derivations = {64const substr_derivations = {
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+28 -15
@@ -419,30 +419,35 @@ export class SlashCommandHandler {
419 namedArgumentList: [419 namedArgumentList: [
420 SlashCommandNamedArgument.fromProps({420 SlashCommandNamedArgument.fromProps({
421 name: 'set',421 name: 'set',
422 description: 'QR set name',422 description: 'Name of QR set to add the context menu to',
423 typeList: [ARGUMENT_TYPE.STRING],423 typeList: [ARGUMENT_TYPE.STRING],
424 isRequired: true,424 isRequired: true,
425 enumProvider: localEnumProviders.qrSets,425 enumProvider: localEnumProviders.qrSets,
426 }),426 }),
427 SlashCommandNamedArgument.fromProps({427 SlashCommandNamedArgument.fromProps({
428 name: 'label',428 name: 'label',
429 description: 'Quick Reply label',429 description: 'Label of Quick Reply to add the context menu to',
430 typeList: [ARGUMENT_TYPE.STRING],430 typeList: [ARGUMENT_TYPE.STRING],
431 enumProvider: localEnumProviders.qrEntries,431 enumProvider: localEnumProviders.qrEntries,
432 }),432 }),
433 SlashCommandNamedArgument.fromProps({433 SlashCommandNamedArgument.fromProps({
434 name: 'id',434 name: 'id',
435 description: 'numeric ID of the QR, e.g., id=42',435 description: 'Numeric ID of Quick Reply to add the context menu to, e.g. id=42',
436 typeList: [ARGUMENT_TYPE.NUMBER],436 typeList: [ARGUMENT_TYPE.NUMBER],
437 enumProvider: localEnumProviders.qrIds,437 enumProvider: localEnumProviders.qrIds,
438 }),438 }),
439 new SlashCommandNamedArgument(439 new SlashCommandNamedArgument(
440 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',440 'chain',
441 'If true, button QR is sent together with (before) the clicked QR from the context menu',
442 [ARGUMENT_TYPE.BOOLEAN],
443 false,
444 false,
445 'false',
441 ),446 ),
442 ],447 ],
443 unnamedArgumentList: [448 unnamedArgumentList: [
444 SlashCommandArgument.fromProps({449 SlashCommandArgument.fromProps({
445 description: 'QR set name',450 description: 'Name of QR set to add as a context menu',
446 typeList: [ARGUMENT_TYPE.STRING],451 typeList: [ARGUMENT_TYPE.STRING],
447 isRequired: true,452 isRequired: true,
448 enumProvider: localEnumProviders.qrSets,453 enumProvider: localEnumProviders.qrSets,
@@ -450,13 +455,16 @@ export class SlashCommandHandler {
450 ],455 ],
451 helpString: `456 helpString: `
452 <div>457 <div>
453 Add context menu preset to a QR.458 Add a context menu preset to a QR.
459 </div>
460 <div>
461 If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
454 </div>462 </div>
455 <div>463 <div>
456 <strong>Example:</strong>464 <strong>Example:</strong>
457 <ul>465 <ul>
458 <li>466 <li>
459 <pre><code>/qr-contextadd set=MyPreset label=MyButton chain=true MyOtherPreset</code></pre>467 <pre><code>/qr-contextadd set=MyQRSetWithTheButton label=MyButton chain=true MyQRSetWithContextItems</code></pre>
460 </li>468 </li>
461 </ul>469 </ul>
462 </div>470 </div>
@@ -470,27 +478,27 @@ export class SlashCommandHandler {
470 namedArgumentList: [478 namedArgumentList: [
471 SlashCommandNamedArgument.fromProps({479 SlashCommandNamedArgument.fromProps({
472 name: 'set',480 name: 'set',
473 description: 'QR set name',481 description: 'Name of QR set to remove the context menu from',
474 typeList: [ARGUMENT_TYPE.STRING],482 typeList: [ARGUMENT_TYPE.STRING],
475 isRequired: true,483 isRequired: true,
476 enumProvider: localEnumProviders.qrSets,484 enumProvider: localEnumProviders.qrSets,
477 }),485 }),
478 SlashCommandNamedArgument.fromProps({486 SlashCommandNamedArgument.fromProps({
479 name: 'label',487 name: 'label',
480 description: 'Quick Reply label',488 description: 'Label of Quick Reply to remove the context menu from',
481 typeList: [ARGUMENT_TYPE.STRING],489 typeList: [ARGUMENT_TYPE.STRING],
482 enumProvider: localEnumProviders.qrEntries,490 enumProvider: localEnumProviders.qrEntries,
483 }),491 }),
484 SlashCommandNamedArgument.fromProps({492 SlashCommandNamedArgument.fromProps({
485 name: 'id',493 name: 'id',
486 description: 'numeric ID of the QR, e.g., id=42',494 description: 'Numeric ID of Quick Reply to remove the context menu from, e.g. id=42',
487 typeList: [ARGUMENT_TYPE.NUMBER],495 typeList: [ARGUMENT_TYPE.NUMBER],
488 enumProvider: localEnumProviders.qrIds,496 enumProvider: localEnumProviders.qrIds,
489 }),497 }),
490 ],498 ],
491 unnamedArgumentList: [499 unnamedArgumentList: [
492 SlashCommandArgument.fromProps({500 SlashCommandArgument.fromProps({
493 description: 'QR set name',501 description: 'Name of QR set to remove',
494 typeList: [ARGUMENT_TYPE.STRING],502 typeList: [ARGUMENT_TYPE.STRING],
495 isRequired: true,503 isRequired: true,
496 enumProvider: localEnumProviders.qrSets,504 enumProvider: localEnumProviders.qrSets,
@@ -501,6 +509,9 @@ export class SlashCommandHandler {
501 Remove context menu preset from a QR.509 Remove context menu preset from a QR.
502 </div>510 </div>
503 <div>511 <div>
512 If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
513 </div>
514 <div>
504 <strong>Example:</strong>515 <strong>Example:</strong>
505 <ul>516 <ul>
506 <li>517 <li>
@@ -542,6 +553,9 @@ export class SlashCommandHandler {
542 Remove all context menu presets from a QR.553 Remove all context menu presets from a QR.
543 </div>554 </div>
544 <div>555 <div>
556 If <code>id</code> and a label are both provided, <code>id</code> will be used.
557 </div>
558 <div>
545 <strong>Example:</strong>559 <strong>Example:</strong>
546 <ul>560 <ul>
547 <li>561 <li>
@@ -908,12 +922,11 @@ export class SlashCommandHandler {
908 }922 }
909 }923 }
910924
911
912 createContextItem(args, name) {925 createContextItem(args, name) {
913 try {926 try {
914 this.api.createContextItem(927 this.api.createContextItem(
915 args.set,928 args.set,
916 args.label,929 args.id !== undefined ? Number(args.id) : args.label,
917 name,930 name,
918 isTrueBoolean(args.chain),931 isTrueBoolean(args.chain),
919 );932 );
@@ -923,14 +936,14 @@ export class SlashCommandHandler {
923 }936 }
924 deleteContextItem(args, name) {937 deleteContextItem(args, name) {
925 try {938 try {
926 this.api.deleteContextItem(args.set, args.label, name);939 this.api.deleteContextItem(args.set, args.id !== undefined ? Number(args.id) : args.label, name);
927 } catch (ex) {940 } catch (ex) {
928 toastr.error(ex.message);941 toastr.error(ex.message);
929 }942 }
930 }943 }
931 clearContextMenu(args, label) {944 clearContextMenu(args, label) {
932 try {945 try {
933 this.api.clearContextMenu(args.set, args.label ?? label);946 this.api.clearContextMenu(args.set, args.id !== undefined ? Number(args.id) : args.label ?? label);
934 } catch (ex) {947 } catch (ex) {
935 toastr.error(ex.message);948 toastr.error(ex.message);
936 }949 }
public/scripts/extensions/quick-reply/src/ui/ctx/ContextMenu.js+20 -2
@@ -19,7 +19,7 @@ export class ContextMenu {
19 this.itemList = this.build(qr).children;19 this.itemList = this.build(qr).children;
20 this.itemList.forEach(item => {20 this.itemList.forEach(item => {
21 item.onExpand = () => {21 item.onExpand = () => {
22 this.itemList.filter(it => it != item)22 this.itemList.filter(it => it !== item)
23 .forEach(it => it.collapse());23 .forEach(it => it.collapse());
24 };24 };
25 });25 });
@@ -36,6 +36,7 @@ export class ContextMenu {
36 icon: qr.icon,36 icon: qr.icon,
37 showLabel: qr.showLabel,37 showLabel: qr.showLabel,
38 label: qr.label,38 label: qr.label,
39 title: qr.title,
39 message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,40 message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
40 children: [],41 children: [],
41 };42 };
@@ -45,12 +46,29 @@ export class ContextMenu {
45 const nextHierarchy = [...hierarchy, cl.set];46 const nextHierarchy = [...hierarchy, cl.set];
46 const nextLabelHierarchy = [...labelHierarchy, tree.label];47 const nextLabelHierarchy = [...labelHierarchy, tree.label];
47 tree.children.push(new MenuHeader(cl.set.name));48 tree.children.push(new MenuHeader(cl.set.name));
48 cl.set.qrList.forEach(subQr => {49
50 // If the Quick Reply's own set is added as a context menu,
51 // show only the sub-QRs that are Invisible but have an icon
52 // intent: allow a QR set to be assigned to one of its own QR buttons for a "burger" menu
53 // with "UI" QRs either in the bar or in the menu, and "library function" QRs still hidden.
54 // - QRs already visible on the bar are filtered out,
55 // - hidden QRs without an icon are filtered out,
56 // - hidden QRs **with an icon** are shown in the menu
57 // so everybody is happy
58 const qrsOwnSetAddedAsContextMenu = cl.set.qrList.includes(qr);
59 const visible = (subQr) => {
60 return qrsOwnSetAddedAsContextMenu
61 ? subQr.isHidden && !!subQr.icon // yes .isHidden gets inverted here
62 : !subQr.isHidden;
63 };
64
65 cl.set.qrList.filter(visible).forEach(subQr => {
49 const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);66 const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
50 tree.children.push(new MenuItem(67 tree.children.push(new MenuItem(
51 subTree.icon,68 subTree.icon,
52 subTree.showLabel,69 subTree.showLabel,
53 subTree.label,70 subTree.label,
71 subTree.title,
54 subTree.message,72 subTree.message,
55 (evt) => {73 (evt) => {
56 evt.stopPropagation();74 evt.stopPropagation();
public/scripts/extensions/quick-reply/src/ui/ctx/MenuHeader.js+1 -1
@@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
22
3export class MenuHeader extends MenuItem {3export class MenuHeader extends MenuItem {
4 constructor(/**@type {String}*/label) {4 constructor(/**@type {String}*/label) {
5 super(null, null, label, null, null);5 super(null, null, label, null, null, null, []);
6 }6 }
77
88
public/scripts/extensions/quick-reply/src/ui/ctx/MenuItem.js+11 -7
@@ -4,11 +4,11 @@ export class MenuItem {
4 /**@type {string}*/ icon;4 /**@type {string}*/ icon;
5 /**@type {boolean}*/ showLabel;5 /**@type {boolean}*/ showLabel;
6 /**@type {string}*/ label;6 /**@type {string}*/ label;
7 /**@type {string}*/ title;
7 /**@type {object}*/ value;8 /**@type {object}*/ value;
8 /**@type {function}*/ callback;9 /**@type {function}*/ callback;
9 /**@type {MenuItem[]}*/ childList = [];10 /**@type {MenuItem[]}*/ childList = [];
10 /**@type {SubMenu}*/ subMenu;11 /**@type {SubMenu}*/ subMenu;
11 /**@type {boolean}*/ isForceExpanded = false;
1212
13 /**@type {HTMLElement}*/ root;13 /**@type {HTMLElement}*/ root;
1414
@@ -19,17 +19,19 @@ export class MenuItem {
1919
20 /**20 /**
21 *21 *
22 * @param {string} icon22 * @param {?string} icon
23 * @param {boolean} showLabel23 * @param {?boolean} showLabel
24 * @param {string} label24 * @param {string} label
25 * @param {?string} title Tooltip
25 * @param {object} value26 * @param {object} value
26 * @param {function} callback27 * @param {function} callback
27 * @param {MenuItem[]} children28 * @param {MenuItem[]} children
28 */29 */
29 constructor(icon, showLabel, label, value, callback, children = []) {30 constructor(icon, showLabel, label, title, value, callback, children = []) {
30 this.icon = icon;31 this.icon = icon;
31 this.showLabel = showLabel;32 this.showLabel = showLabel;
32 this.label = label;33 this.label = label;
34 this.title = title;
33 this.value = value;35 this.value = value;
34 this.callback = callback;36 this.callback = callback;
35 this.childList = children;37 this.childList = children;
@@ -42,12 +44,15 @@ export class MenuItem {
42 this.root = item;44 this.root = item;
43 item.classList.add('list-group-item');45 item.classList.add('list-group-item');
44 item.classList.add('ctx-item');46 item.classList.add('ctx-item');
45 item.title = this.value;47
48 // if a title/tooltip is set, add it, otherwise use the QR content
49 // same as for the main QR list
50 item.title = this.title || this.value;
51
46 if (this.callback) {52 if (this.callback) {
47 item.addEventListener('click', (evt) => this.callback(evt, this));53 item.addEventListener('click', (evt) => this.callback(evt, this));
48 }54 }
49 const icon = document.createElement('div'); {55 const icon = document.createElement('div'); {
50 this.domIcon = icon;
51 icon.classList.add('qr--button-icon');56 icon.classList.add('qr--button-icon');
52 icon.classList.add('fa-solid');57 icon.classList.add('fa-solid');
53 if (!this.icon) icon.classList.add('qr--hidden');58 if (!this.icon) icon.classList.add('qr--hidden');
@@ -55,7 +60,6 @@ export class MenuItem {
55 item.append(icon);60 item.append(icon);
56 }61 }
57 const lbl = document.createElement('div'); {62 const lbl = document.createElement('div'); {
58 this.domLabel = lbl;
59 lbl.classList.add('qr--button-label');63 lbl.classList.add('qr--button-label');
60 if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');64 if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');
61 lbl.textContent = this.label;65 lbl.textContent = this.label;
public/scripts/extensions/quick-reply/style.css+3 -0
@@ -174,6 +174,9 @@
174 position: absolute;174 position: absolute;
175 overflow: visible;175 overflow: visible;
176}176}
177.ctx-menu .ctx-item .qr--hidden {
178 display: none;
179}
177.list-group .list-group-item.ctx-header {180.list-group .list-group-item.ctx-header {
178 font-weight: bold;181 font-weight: bold;
179 cursor: default;182 cursor: default;
public/scripts/extensions/quick-reply/style.less+4 -0
@@ -176,6 +176,10 @@
176 overflow: visible;176 overflow: visible;
177}177}
178178
179.ctx-menu .ctx-item .qr--hidden {
180 display: none;
181}
182
179.list-group .list-group-item.ctx-header {183.list-group .list-group-item.ctx-header {
180 font-weight: bold;184 font-weight: bold;
181 cursor: default;185 cursor: default;
public/scripts/filters.js+45 -6
@@ -56,6 +56,19 @@ export function isFilterState(a, b) {
56}56}
5757
58/**58/**
59 * The fuzzy search categories
60 * @type {{ characters: string, worldInfo: string, personas: string, tags: string, groups: string }}
61 */
62export const fuzzySearchCategories = Object.freeze({
63 characters: 'characters',
64 worldInfo: 'worldInfo',
65 personas: 'personas',
66 tags: 'tags',
67 groups: 'groups',
68});
69
70
71/**
59 * Helper class for filtering data.72 * Helper class for filtering data.
60 * @example73 * @example
61 * const filterHelper = new FilterHelper(() => console.log('data changed'));74 * const filterHelper = new FilterHelper(() => console.log('data changed'));
@@ -73,12 +86,25 @@ export class FilterHelper {
73 scoreCache;86 scoreCache;
7487
75 /**88 /**
89 * Cache for fuzzy search results per category.
90 * @type {Object.<string, { resultMap: Map<string, any> }>}
91 */
92 fuzzySearchCaches;
93
94 /**
76 * Creates a new FilterHelper95 * Creates a new FilterHelper
77 * @param {Function} onDataChanged Callback to trigger when the filter data changes96 * @param {Function} onDataChanged Callback to trigger when the filter data changes
78 */97 */
79 constructor(onDataChanged) {98 constructor(onDataChanged) {
80 this.onDataChanged = onDataChanged;99 this.onDataChanged = onDataChanged;
81 this.scoreCache = new Map();100 this.scoreCache = new Map();
101 this.fuzzySearchCaches = {
102 [fuzzySearchCategories.characters]: { resultMap: new Map() },
103 [fuzzySearchCategories.worldInfo]: { resultMap: new Map() },
104 [fuzzySearchCategories.personas]: { resultMap: new Map() },
105 [fuzzySearchCategories.tags]: { resultMap: new Map() },
106 [fuzzySearchCategories.groups]: { resultMap: new Map() },
107 };
82 }108 }
83109
84 /**110 /**
@@ -151,7 +177,7 @@ export class FilterHelper {
151 return data;177 return data;
152 }178 }
153179
154 const fuzzySearchResults = fuzzySearchWorldInfo(data, term);180 const fuzzySearchResults = fuzzySearchWorldInfo(data, term, this.fuzzySearchCaches);
155 this.cacheScores(FILTER_TYPES.WORLD_INFO_SEARCH, new Map(fuzzySearchResults.map(i => [i.item?.uid, i.score])));181 this.cacheScores(FILTER_TYPES.WORLD_INFO_SEARCH, new Map(fuzzySearchResults.map(i => [i.item?.uid, i.score])));
156182
157 const filteredData = data.filter(entity => fuzzySearchResults.find(x => x.item === entity));183 const filteredData = data.filter(entity => fuzzySearchResults.find(x => x.item === entity));
@@ -170,7 +196,7 @@ export class FilterHelper {
170 return data;196 return data;
171 }197 }
172198
173 const fuzzySearchResults = fuzzySearchPersonas(data, term);199 const fuzzySearchResults = fuzzySearchPersonas(data, term, this.fuzzySearchCaches);
174 this.cacheScores(FILTER_TYPES.PERSONA_SEARCH, new Map(fuzzySearchResults.map(i => [i.item.key, i.score])));200 this.cacheScores(FILTER_TYPES.PERSONA_SEARCH, new Map(fuzzySearchResults.map(i => [i.item.key, i.score])));
175201
176 const filteredData = data.filter(name => fuzzySearchResults.find(x => x.item.key === name));202 const filteredData = data.filter(name => fuzzySearchResults.find(x => x.item.key === name));
@@ -289,9 +315,9 @@ export class FilterHelper {
289315
290 // Save fuzzy search results and scores if enabled316 // Save fuzzy search results and scores if enabled
291 if (power_user.fuzzy_search) {317 if (power_user.fuzzy_search) {
292 const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue);318 const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue, this.fuzzySearchCaches);
293 const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue);319 const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue, this.fuzzySearchCaches);
294 const fuzzySearchTagsResult = fuzzySearchTags(searchValue);320 const fuzzySearchTagsResult = fuzzySearchTags(searchValue, this.fuzzySearchCaches);
295 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchCharactersResults.map(i => [`character.${i.refIndex}`, i.score])));321 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchCharactersResults.map(i => [`character.${i.refIndex}`, i.score])));
296 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchGroupsResults.map(i => [`group.${i.item.id}`, i.score])));322 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchGroupsResults.map(i => [`group.${i.item.id}`, i.score])));
297 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchTagsResult.map(i => [`tag.${i.item.id}`, i.score])));323 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchTagsResult.map(i => [`tag.${i.item.id}`, i.score])));
@@ -343,11 +369,14 @@ export class FilterHelper {
343 * @param {object} options - Optional call parameters369 * @param {object} options - Optional call parameters
344 * @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared.370 * @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared.
345 * @param {Object.<FilterType, any>} [options.tempOverrides={}] - Temporarily override specific filters for this filter application371 * @param {Object.<FilterType, any>} [options.tempOverrides={}] - Temporarily override specific filters for this filter application
372 * @param {boolean} [options.clearFuzzySearchCaches=true] - Whether the fuzzy search caches should be cleared.
346 * @returns {any[]} The filtered data.373 * @returns {any[]} The filtered data.
347 */374 */
348 applyFilters(data, { clearScoreCache = true, tempOverrides = {} } = {}) {375 applyFilters(data, { clearScoreCache = true, tempOverrides = {}, clearFuzzySearchCaches = true } = {}) {
349 if (clearScoreCache) this.clearScoreCache();376 if (clearScoreCache) this.clearScoreCache();
350377
378 if (clearFuzzySearchCaches) this.clearFuzzySearchCaches();
379
351 // Save original filter states380 // Save original filter states
352 const originalStates = {};381 const originalStates = {};
353 for (const key in tempOverrides) {382 for (const key in tempOverrides) {
@@ -411,4 +440,14 @@ export class FilterHelper {
411 this.scoreCache = new Map();440 this.scoreCache = new Map();
412 }441 }
413 }442 }
443
444 /**
445 * Clears fuzzy search caches
446 */
447 clearFuzzySearchCaches() {
448 for (const cache of Object.values(this.fuzzySearchCaches)) {
449 cache.resultMap.clear();
450 }
451 console.log('All fuzzy search caches cleared');
452 }
414}453}
public/scripts/openai.js+0 -24
@@ -99,7 +99,6 @@ const default_wi_format = '{0}';
99const default_new_chat_prompt = '[Start a new Chat]';99const default_new_chat_prompt = '[Start a new Chat]';
100const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]';100const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]';
101const default_new_example_chat_prompt = '[Example Chat]';101const default_new_example_chat_prompt = '[Example Chat]';
102const default_claude_human_sysprompt_message = 'Let\'s get started. Please generate your response based on the information and instructions provided above.';
103const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]';102const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]';
104const default_bias = 'Default (none)';103const default_bias = 'Default (none)';
105const default_personality_format = '[{{char}}\'s personality: {{personality}}]';104const default_personality_format = '[{{char}}\'s personality: {{personality}}]';
@@ -276,7 +275,6 @@ const default_settings = {
276 proxy_password: '',275 proxy_password: '',
277 assistant_prefill: '',276 assistant_prefill: '',
278 assistant_impersonation: '',277 assistant_impersonation: '',
279 human_sysprompt_message: default_claude_human_sysprompt_message,
280 claude_use_sysprompt: false,278 claude_use_sysprompt: false,
281 use_makersuite_sysprompt: true,279 use_makersuite_sysprompt: true,
282 use_alt_scale: false,280 use_alt_scale: false,
@@ -353,7 +351,6 @@ const oai_settings = {
353 proxy_password: '',351 proxy_password: '',
354 assistant_prefill: '',352 assistant_prefill: '',
355 assistant_impersonation: '',353 assistant_impersonation: '',
356 human_sysprompt_message: default_claude_human_sysprompt_message,
357 claude_use_sysprompt: false,354 claude_use_sysprompt: false,
358 use_makersuite_sysprompt: true,355 use_makersuite_sysprompt: true,
359 use_alt_scale: false,356 use_alt_scale: false,
@@ -1892,7 +1889,6 @@ async function sendOpenAIRequest(type, messages, signal) {
1892 generate_data['top_k'] = Number(oai_settings.top_k_openai);1889 generate_data['top_k'] = Number(oai_settings.top_k_openai);
1893 generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;1890 generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;
1894 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.1891 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
1895 generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
1896 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.1892 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
1897 if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {1893 if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {
1898 generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);1894 generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
@@ -3030,7 +3026,6 @@ function loadOpenAISettings(data, settings) {
3030 oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;3026 oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
3031 oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;3027 oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
3032 oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation;3028 oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation;
3033 oai_settings.human_sysprompt_message = settings.human_sysprompt_message ?? default_settings.human_sysprompt_message;
3034 oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;3029 oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
3035 oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;3030 oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
3036 oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;3031 oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;
@@ -3070,7 +3065,6 @@ function loadOpenAISettings(data, settings) {
3070 $('#openai_proxy_password').val(oai_settings.proxy_password);3065 $('#openai_proxy_password').val(oai_settings.proxy_password);
3071 $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);3066 $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
3072 $('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);3067 $('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);
3073 $('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
3074 $('#openai_image_inlining').prop('checked', oai_settings.image_inlining);3068 $('#openai_image_inlining').prop('checked', oai_settings.image_inlining);
3075 $('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);3069 $('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);
30763070
@@ -3400,7 +3394,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
3400 show_external_models: settings.show_external_models,3394 show_external_models: settings.show_external_models,
3401 assistant_prefill: settings.assistant_prefill,3395 assistant_prefill: settings.assistant_prefill,
3402 assistant_impersonation: settings.assistant_impersonation,3396 assistant_impersonation: settings.assistant_impersonation,
3403 human_sysprompt_message: settings.human_sysprompt_message,
3404 claude_use_sysprompt: settings.claude_use_sysprompt,3397 claude_use_sysprompt: settings.claude_use_sysprompt,
3405 use_makersuite_sysprompt: settings.use_makersuite_sysprompt,3398 use_makersuite_sysprompt: settings.use_makersuite_sysprompt,
3406 use_alt_scale: settings.use_alt_scale,3399 use_alt_scale: settings.use_alt_scale,
@@ -3825,7 +3818,6 @@ function onSettingsPresetChange() {
3825 proxy_password: ['#openai_proxy_password', 'proxy_password', false],3818 proxy_password: ['#openai_proxy_password', 'proxy_password', false],
3826 assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],3819 assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
3827 assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],3820 assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],
3828 human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false],
3829 claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true],3821 claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true],
3830 use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true],3822 use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true],
3831 use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true],3823 use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true],
@@ -4677,10 +4669,6 @@ function toggleChatCompletionForms() {
4677 const validSources = $(this).data('source').split(',');4669 const validSources = $(this).data('source').split(',');
4678 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));4670 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
4679 });4671 });
4680
4681 if (chat_completion_sources.CLAUDE == oai_settings.chat_completion_source) {
4682 $('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
4683 }
4684}4672}
46854673
4686async function testApiConnection() {4674async function testApiConnection() {
@@ -5036,7 +5024,6 @@ export function initOpenAI() {
50365024
5037 $('#claude_use_sysprompt').on('change', function () {5025 $('#claude_use_sysprompt').on('change', function () {
5038 oai_settings.claude_use_sysprompt = !!$('#claude_use_sysprompt').prop('checked');5026 oai_settings.claude_use_sysprompt = !!$('#claude_use_sysprompt').prop('checked');
5039 $('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
5040 saveSettingsDebounced();5027 saveSettingsDebounced();
5041 });5028 });
50425029
@@ -5113,12 +5100,6 @@ export function initOpenAI() {
5113 saveSettingsDebounced();5100 saveSettingsDebounced();
5114 });5101 });
51155102
5116 $('#claude_human_sysprompt_message_restore').on('click', function () {
5117 oai_settings.human_sysprompt_message = default_claude_human_sysprompt_message;
5118 $('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
5119 saveSettingsDebounced();
5120 });
5121
5122 $('#newgroupchat_prompt_restore').on('click', function () {5103 $('#newgroupchat_prompt_restore').on('click', function () {
5123 oai_settings.new_group_chat_prompt = default_new_group_chat_prompt;5104 oai_settings.new_group_chat_prompt = default_new_group_chat_prompt;
5124 $('#newgroupchat_prompt_textarea').val(oai_settings.new_group_chat_prompt);5105 $('#newgroupchat_prompt_textarea').val(oai_settings.new_group_chat_prompt);
@@ -5210,11 +5191,6 @@ export function initOpenAI() {
5210 saveSettingsDebounced();5191 saveSettingsDebounced();
5211 });5192 });
52125193
5213 $('#claude_human_sysprompt_textarea').on('input', function () {
5214 oai_settings.human_sysprompt_message = String($('#claude_human_sysprompt_textarea').val());
5215 saveSettingsDebounced();
5216 });
5217
5218 $('#openrouter_use_fallback').on('input', function () {5194 $('#openrouter_use_fallback').on('input', function () {
5219 oai_settings.openrouter_use_fallback = !!$(this).prop('checked');5195 oai_settings.openrouter_use_fallback = !!$(this).prop('checked');
5220 saveSettingsDebounced();5196 saveSettingsDebounced();
public/scripts/power-user.js+69 -66
@@ -53,6 +53,7 @@ import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandE
53import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';53import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
54import { POPUP_TYPE, callGenericPopup } from './popup.js';54import { POPUP_TYPE, callGenericPopup } from './popup.js';
55import { loadSystemPrompts } from './sysprompt.js';55import { loadSystemPrompts } from './sysprompt.js';
56import { fuzzySearchCategories } from './filters.js';
5657
57export {58export {
58 loadPowerUserSettings,59 loadPowerUserSettings,
@@ -1831,15 +1832,51 @@ async function loadContextSettings() {
1831 });1832 });
1832}1833}
18331834
1835
1834/**1836/**
1835 * Fuzzy search characters by a search term1837 * Common function to perform fuzzy search with optional caching
1838 * @param {string} type - Type of search from fuzzySearchCategories
1839 * @param {any[]} data - Data array to search in
1840 * @param {Array<{name: string, weight: number, getFn?: Function}>} keys - Fuse.js keys configuration
1836 * @param {string} searchValue - The search term1841 * @param {string} searchValue - The search term
1842 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1837 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1843 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
1838 */1844 */
1839export function fuzzySearchCharacters(searchValue) {1845function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) {
1846 // Check cache if provided
1847 if (fuzzySearchCaches) {
1848 const cache = fuzzySearchCaches[type];
1849 if (cache?.resultMap.has(searchValue)) {
1850 return cache.resultMap.get(searchValue);
1851 }
1852 }
1853
1840 // @ts-ignore1854 // @ts-ignore
1841 const fuse = new Fuse(characters, {1855 const fuse = new Fuse(data, {
1842 keys: [1856 keys: keys,
1857 includeScore: true,
1858 ignoreLocation: true,
1859 useExtendedSearch: true,
1860 threshold: 0.2,
1861 });
1862
1863 const results = fuse.search(searchValue);
1864
1865 // Store in cache if provided
1866 if (fuzzySearchCaches) {
1867 fuzzySearchCaches[type].resultMap.set(searchValue, results);
1868 }
1869 return results;
1870}
1871
1872/**
1873 * Fuzzy search characters by a search term
1874 * @param {string} searchValue - The search term
1875 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1876 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
1877 */
1878export function fuzzySearchCharacters(searchValue, fuzzySearchCaches = null) {
1879 const keys = [
1843 { name: 'data.name', weight: 20 },1880 { name: 'data.name', weight: 20 },
1844 { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },1881 { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },
1845 { name: 'data.description', weight: 3 },1882 { name: 'data.description', weight: 3 },
@@ -1851,28 +1888,20 @@ export function fuzzySearchCharacters(searchValue) {
1851 { name: 'data.creator', weight: 1 },1888 { name: 'data.creator', weight: 1 },
1852 { name: 'data.tags', weight: 1 },1889 { name: 'data.tags', weight: 1 },
1853 { name: 'data.alternate_greetings', weight: 1 },1890 { name: 'data.alternate_greetings', weight: 1 },
1854 ],1891 ];
1855 includeScore: true,
1856 ignoreLocation: true,
1857 useExtendedSearch: true,
1858 threshold: 0.2,
1859 });
18601892
1861 const results = fuse.search(searchValue);1893 return performFuzzySearch(fuzzySearchCategories.characters, characters, keys, searchValue, fuzzySearchCaches);
1862 console.debug('Characters fuzzy search results for ' + searchValue, results);
1863 return results;
1864}1894}
18651895
1866/**1896/**
1867 * Fuzzy search world info entries by a search term1897 * Fuzzy search world info entries by a search term
1868 * @param {*[]} data - WI items data array1898 * @param {*[]} data - WI items data array
1869 * @param {string} searchValue - The search term1899 * @param {string} searchValue - The search term
1900 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1870 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1901 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
1871 */1902 */
1872export function fuzzySearchWorldInfo(data, searchValue) {1903export function fuzzySearchWorldInfo(data, searchValue, fuzzySearchCaches = null) {
1873 // @ts-ignore1904 const keys = [
1874 const fuse = new Fuse(data, {
1875 keys: [
1876 { name: 'key', weight: 20 },1905 { name: 'key', weight: 20 },
1877 { name: 'group', weight: 15 },1906 { name: 'group', weight: 15 },
1878 { name: 'comment', weight: 10 },1907 { name: 'comment', weight: 10 },
@@ -1880,88 +1909,62 @@ export function fuzzySearchWorldInfo(data, searchValue) {
1880 { name: 'content', weight: 3 },1909 { name: 'content', weight: 3 },
1881 { name: 'uid', weight: 1 },1910 { name: 'uid', weight: 1 },
1882 { name: 'automationId', weight: 1 },1911 { name: 'automationId', weight: 1 },
1883 ],1912 ];
1884 includeScore: true,
1885 ignoreLocation: true,
1886 useExtendedSearch: true,
1887 threshold: 0.2,
1888 });
18891913
1890 const results = fuse.search(searchValue);1914 return performFuzzySearch(fuzzySearchCategories.worldInfo, data, keys, searchValue, fuzzySearchCaches);
1891 console.debug('World Info fuzzy search results for ' + searchValue, results);
1892 return results;
1893}1915}
18941916
1895/**1917/**
1896 * Fuzzy search persona entries by a search term1918 * Fuzzy search persona entries by a search term
1897 * @param {*[]} data - persona data array1919 * @param {*[]} data - persona data array
1898 * @param {string} searchValue - The search term1920 * @param {string} searchValue - The search term
1921 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1899 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1922 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
1900 */1923 */
1901export function fuzzySearchPersonas(data, searchValue) {1924export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) {
1902 data = data.map(x => ({ key: x, name: power_user.personas[x] ?? '', description: power_user.persona_descriptions[x]?.description ?? '' }));1925 const mappedData = data.map(x => ({
1903 // @ts-ignore1926 key: x,
1904 const fuse = new Fuse(data, {1927 name: power_user.personas[x] ?? '',
1905 keys: [1928 description: power_user.persona_descriptions[x]?.description ?? ''
1929 }));
1930
1931 const keys = [
1906 { name: 'name', weight: 20 },1932 { name: 'name', weight: 20 },
1907 { name: 'description', weight: 3 },1933 { name: 'description', weight: 3 },
1908 ],1934 ];
1909 includeScore: true,
1910 ignoreLocation: true,
1911 useExtendedSearch: true,
1912 threshold: 0.2,
1913 });
19141935
1915 const results = fuse.search(searchValue);1936 return performFuzzySearch(fuzzySearchCategories.personas, mappedData, keys, searchValue, fuzzySearchCaches);
1916 console.debug('Personas fuzzy search results for ' + searchValue, results);
1917 return results;
1918}1937}
19191938
1920/**1939/**
1921 * Fuzzy search tags by a search term1940 * Fuzzy search tags by a search term
1922 * @param {string} searchValue - The search term1941 * @param {string} searchValue - The search term
1942 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1923 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1943 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
1924 */1944 */
1925export function fuzzySearchTags(searchValue) {1945export function fuzzySearchTags(searchValue, fuzzySearchCaches = null) {
1926 // @ts-ignore1946 const keys = [
1927 const fuse = new Fuse(tags, {
1928 keys: [
1929 { name: 'name', weight: 1 },1947 { name: 'name', weight: 1 },
1930 ],1948 ];
1931 includeScore: true,
1932 ignoreLocation: true,
1933 useExtendedSearch: true,
1934 threshold: 0.2,
1935 });
19361949
1937 const results = fuse.search(searchValue);1950 return performFuzzySearch(fuzzySearchCategories.tags, tags, keys, searchValue, fuzzySearchCaches);
1938 console.debug('Tags fuzzy search results for ' + searchValue, results);
1939 return results;
1940}1951}
19411952
1942/**1953/**
1943 * Fuzzy search groups by a search term1954 * Fuzzy search groups by a search term
1944 * @param {string} searchValue - The search term1955 * @param {string} searchValue - The search term
1956 * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
1945 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score1957 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
1946 */1958 */
1947export function fuzzySearchGroups(searchValue) {1959export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
1948 // @ts-ignore1960 const keys = [
1949 const fuse = new Fuse(groups, {
1950 keys: [
1951 { name: 'name', weight: 20 },1961 { name: 'name', weight: 20 },
1952 { name: 'members', weight: 15 },1962 { name: 'members', weight: 15 },
1953 { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },1963 { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },
1954 { name: 'id', weight: 1 },1964 { name: 'id', weight: 1 },
1955 ],1965 ];
1956 includeScore: true,
1957 ignoreLocation: true,
1958 useExtendedSearch: true,
1959 threshold: 0.2,
1960 });
19611966
1962 const results = fuse.search(searchValue);1967 return performFuzzySearch(fuzzySearchCategories.groups, groups, keys, searchValue, fuzzySearchCaches);
1963 console.debug('Groups fuzzy search results for ' + searchValue, results);
1964 return results;
1965}1968}
19661969
1967/**1970/**
public/scripts/textgen-settings.js+1 -0
@@ -1258,6 +1258,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
1258 'xtc_probability': settings.xtc_probability,1258 'xtc_probability': settings.xtc_probability,
1259 'nsigma': settings.nsigma,1259 'nsigma': settings.nsigma,
1260 'custom_token_bans': toIntArray(banned_tokens),1260 'custom_token_bans': toIntArray(banned_tokens),
1261 'no_repeat_ngram_size': settings.no_repeat_ngram_size,
1261 };1262 };
12621263
1263 if (settings.type === OPENROUTER) {1264 if (settings.type === OPENROUTER) {
server.js+1 -5
@@ -627,10 +627,6 @@ const tavernUrl = new URL(
627 (':' + server_port),627 (':' + server_port),
628);628);
629629
630function prepareFrontendBundle() {
631 return new Promise((resolve) => webpackMiddleware.waitUntilValid(resolve));
632}
633
634/**630/**
635 * Tasks that need to be run before the server starts listening.631 * Tasks that need to be run before the server starts listening.
636 */632 */
@@ -682,7 +678,7 @@ const preSetupTasks = async function () {
682 initRequestProxy({ enabled: proxyEnabled, url: proxyUrl, bypass: proxyBypass });678 initRequestProxy({ enabled: proxyEnabled, url: proxyUrl, bypass: proxyBypass });
683679
684 // Wait for frontend libs to compile680 // Wait for frontend libs to compile
685 await prepareFrontendBundle();681 await webpackMiddleware.runWebpackCompiler();
686};682};
687683
688/**684/**
src/endpoints/backends/chat-completions.js+1 -1
@@ -102,7 +102,7 @@ async function sendClaudeRequest(request, response) {
102 const additionalHeaders = {};102 const additionalHeaders = {};
103 const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;103 const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;
104 const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;104 const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;
105 const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, request.body.human_sysprompt_message, request.body.char_name, request.body.user_name);105 const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, request.body.char_name, request.body.user_name);
106 // Add custom stop sequences106 // Add custom stop sequences
107 const stopSequences = [];107 const stopSequences = [];
108 if (Array.isArray(request.body.stop)) {108 if (Array.isArray(request.body.stop)) {
src/endpoints/characters.js+6 -2
@@ -14,7 +14,7 @@ import jimp from 'jimp';
1414
15import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';15import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
16import { jsonParser, urlencodedParser } from '../express-common.js';16import { jsonParser, urlencodedParser } from '../express-common.js';
17import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer } from '../util.js';17import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap } from '../util.js';
18import { TavernCardValidator } from '../validator/TavernCardValidator.js';18import { TavernCardValidator } from '../validator/TavernCardValidator.js';
19import { parse, write } from '../character-card-parser.js';19import { parse, write } from '../character-card-parser.js';
20import { readWorldInfoFile } from './worldinfo.js';20import { readWorldInfoFile } from './worldinfo.js';
@@ -23,7 +23,8 @@ import { importRisuSprites } from './sprites.js';
23const defaultAvatarPath = './public/img/ai4.png';23const defaultAvatarPath = './public/img/ai4.png';
2424
25// KV-store for parsed character data25// KV-store for parsed character data
26const characterDataCache = new Map();26// 100 MB limit. Would take roughly 3000 characters to reach this limit
27const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);
27// Some Android devices require tighter memory management28// Some Android devices require tighter memory management
28const isAndroid = process.platform === 'android';29const isAndroid = process.platform === 'android';
2930
@@ -58,6 +59,9 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
58 try {59 try {
59 // Reset the cache60 // Reset the cache
60 for (const key of characterDataCache.keys()) {61 for (const key of characterDataCache.keys()) {
62 if (Buffer.isBuffer(inputFile)) {
63 break;
64 }
61 if (key.startsWith(inputFile)) {65 if (key.startsWith(inputFile)) {
62 characterDataCache.delete(key);66 characterDataCache.delete(key);
63 break;67 break;
src/middleware/webpack-serve.js+35 -10
@@ -1,20 +1,45 @@
1import process from 'node:process';1import path from 'node:path';
2import webpack from 'webpack';2import webpack from 'webpack';
3import middleware from 'webpack-dev-middleware';
4import { publicLibConfig } from '../../webpack.config.js';3import { publicLibConfig } from '../../webpack.config.js';
54
6export default function getWebpackServeMiddleware() {5export default function getWebpackServeMiddleware() {
6 const outputPath = publicLibConfig.output?.path;
7 const outputFile = publicLibConfig.output?.filename;
7 const compiler = webpack(publicLibConfig);8 const compiler = webpack(publicLibConfig);
89
9 if (process.env.NODE_ENV === 'production' || process.platform === 'android') {10 /**
10 compiler.hooks.done.tap('serve', () => {11 * A very spartan recreation of webpack-dev-middleware.
11 if (compiler.watching) {12 * @param {import('express').Request} req Request object.
12 compiler.watching.close(() => { });13 * @param {import('express').Response} res Response object.
14 * @param {import('express').NextFunction} next Next function.
15 * @type {import('express').RequestHandler}
16 */
17 function devMiddleware(req, res, next) {
18 if (req.method === 'GET' && path.parse(req.path).base === outputFile) {
19 return res.sendFile(outputFile, { root: outputPath });
13 }20 }
14 compiler.watchFileSystem = null;21
15 compiler.watchMode = false;22 next();
16 });23 }
24
25 /**
26 * Wait until Webpack is done compiling.
27 * @returns {Promise<void>}
28 */
29 devMiddleware.runWebpackCompiler = () => {
30 return new Promise((resolve) => {
31 console.log();
32 console.log('Compiling frontend libraries...');
33 compiler.run((_error, stats) => {
34 const output = stats?.toString(publicLibConfig.stats);
35 if (output) {
36 console.log(output);
37 console.log();
17 }38 }
39 resolve();
40 });
41 });
42 };
1843
19 return middleware(compiler, {});44 return devMiddleware;
20}45}
src/prompt-converters.js+3 -4
@@ -91,11 +91,10 @@ export function convertClaudePrompt(messages, addAssistantPostfix, addAssistantP
91 * @param {string} prefillString User determined prefill string91 * @param {string} prefillString User determined prefill string
92 * @param {boolean} useSysPrompt See if we want to use a system prompt92 * @param {boolean} useSysPrompt See if we want to use a system prompt
93 * @param {boolean} useTools See if we want to use tools93 * @param {boolean} useTools See if we want to use tools
94 * @param {string} humanMsgFix Add Human message between system prompt and assistant.
95 * @param {string} charName Character name94 * @param {string} charName Character name
96 * @param {string} userName User name95 * @param {string} userName User name
97 */96 */
98export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') {97export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, charName, userName) {
99 let systemPrompt = [];98 let systemPrompt = [];
100 if (useSysPrompt) {99 if (useSysPrompt) {
101 // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.100 // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.
@@ -122,10 +121,10 @@ export function convertClaudeMessages(messages, prefillString, useSysPrompt, use
122121
123 // Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.122 // Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.
124 // Also prevents erroring out if the messages array is empty.123 // Also prevents erroring out if the messages array is empty.
125 if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) {124 if (messages.length === 0) {
126 messages.unshift({125 messages.unshift({
127 role: 'user',126 role: 'user',
128 content: humanMsgFix || PROMPT_PLACEHOLDER,127 content: PROMPT_PLACEHOLDER,
129 });128 });
130 }129 }
131 }130 }
src/util.js+179 -0
@@ -670,3 +670,182 @@ export function isValidUrl(url) {
670 return false;670 return false;
671 }671 }
672}672}
673
674/**
675 * MemoryLimitedMap class that limits the memory usage of string values.
676 */
677export class MemoryLimitedMap {
678 /**
679 * Creates an instance of MemoryLimitedMap.
680 * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
681 */
682 constructor(maxMemoryInBytes) {
683 if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0) {
684 throw new Error('maxMemoryInBytes must be a positive number');
685 }
686 this.maxMemory = maxMemoryInBytes;
687 this.currentMemory = 0;
688 this.map = new Map();
689 this.queue = [];
690 }
691
692 /**
693 * Estimates the memory usage of a string in bytes.
694 * Assumes each character occupies 2 bytes (UTF-16).
695 * @param {string} str
696 * @returns {number}
697 */
698 static estimateStringSize(str) {
699 return str ? str.length * 2 : 0;
700 }
701
702 /**
703 * Adds or updates a key-value pair in the map.
704 * If adding the new value exceeds the memory limit, evicts oldest entries.
705 * @param {string} key
706 * @param {string} value
707 */
708 set(key, value) {
709 if (typeof key !== 'string' || typeof value !== 'string') {
710 return;
711 }
712
713 const newValueSize = MemoryLimitedMap.estimateStringSize(value);
714
715 // If the new value itself exceeds the max memory, reject it
716 if (newValueSize > this.maxMemory) {
717 return;
718 }
719
720 // Check if the key already exists to adjust memory accordingly
721 if (this.map.has(key)) {
722 const oldValue = this.map.get(key);
723 const oldValueSize = MemoryLimitedMap.estimateStringSize(oldValue);
724 this.currentMemory -= oldValueSize;
725 // Remove the key from its current position in the queue
726 const index = this.queue.indexOf(key);
727 if (index > -1) {
728 this.queue.splice(index, 1);
729 }
730 }
731
732 // Evict oldest entries until there's enough space
733 while (this.currentMemory + newValueSize > this.maxMemory && this.queue.length > 0) {
734 const oldestKey = this.queue.shift();
735 const oldestValue = this.map.get(oldestKey);
736 const oldestValueSize = MemoryLimitedMap.estimateStringSize(oldestValue);
737 this.map.delete(oldestKey);
738 this.currentMemory -= oldestValueSize;
739 }
740
741 // After eviction, check again if there's enough space
742 if (this.currentMemory + newValueSize > this.maxMemory) {
743 return;
744 }
745
746 // Add the new key-value pair
747 this.map.set(key, value);
748 this.queue.push(key);
749 this.currentMemory += newValueSize;
750 }
751
752 /**
753 * Retrieves the value associated with the given key.
754 * @param {string} key
755 * @returns {string | undefined}
756 */
757 get(key) {
758 return this.map.get(key);
759 }
760
761 /**
762 * Checks if the map contains the given key.
763 * @param {string} key
764 * @returns {boolean}
765 */
766 has(key) {
767 return this.map.has(key);
768 }
769
770 /**
771 * Deletes the key-value pair associated with the given key.
772 * @param {string} key
773 * @returns {boolean} - Returns true if the key was found and deleted, else false.
774 */
775 delete(key) {
776 if (!this.map.has(key)) {
777 return false;
778 }
779 const value = this.map.get(key);
780 const valueSize = MemoryLimitedMap.estimateStringSize(value);
781 this.map.delete(key);
782 this.currentMemory -= valueSize;
783
784 // Remove the key from the queue
785 const index = this.queue.indexOf(key);
786 if (index > -1) {
787 this.queue.splice(index, 1);
788 }
789
790 return true;
791 }
792
793 /**
794 * Clears all entries from the map.
795 */
796 clear() {
797 this.map.clear();
798 this.queue = [];
799 this.currentMemory = 0;
800 }
801
802 /**
803 * Returns the number of key-value pairs in the map.
804 * @returns {number}
805 */
806 size() {
807 return this.map.size;
808 }
809
810 /**
811 * Returns the current memory usage in bytes.
812 * @returns {number}
813 */
814 totalMemory() {
815 return this.currentMemory;
816 }
817
818 /**
819 * Returns an iterator over the keys in the map.
820 * @returns {IterableIterator<string>}
821 */
822 keys() {
823 return this.map.keys();
824 }
825
826 /**
827 * Returns an iterator over the values in the map.
828 * @returns {IterableIterator<string>}
829 */
830 values() {
831 return this.map.values();
832 }
833
834 /**
835 * Iterates over the map in insertion order.
836 * @param {Function} callback - Function to execute for each element.
837 */
838 forEach(callback) {
839 this.map.forEach((value, key) => {
840 callback(value, key, this);
841 });
842 }
843
844 /**
845 * Makes the MemoryLimitedMap iterable.
846 * @returns {Iterator} - Iterator over [key, value] pairs.
847 */
848 [Symbol.iterator]() {
849 return this.map[Symbol.iterator]();
850 }
851}
webpack.config.js+5 -0
@@ -8,6 +8,8 @@ export const publicLibConfig = {
8 cache: {8 cache: {
9 type: 'filesystem',9 type: 'filesystem',
10 cacheDirectory: path.resolve(process.cwd(), 'dist/webpack'),10 cacheDirectory: path.resolve(process.cwd(), 'dist/webpack'),
11 store: 'pack',
12 compression: 'gzip',
11 },13 },
12 devtool: false,14 devtool: false,
13 watch: false,15 watch: false,
@@ -16,6 +18,8 @@ export const publicLibConfig = {
16 preset: 'minimal',18 preset: 'minimal',
17 assets: false,19 assets: false,
18 modules: false,20 modules: false,
21 colors: true,
22 timings: true,
19 },23 },
20 experiments: {24 experiments: {
21 outputModule: true,25 outputModule: true,
@@ -24,6 +28,7 @@ export const publicLibConfig = {
24 hints: false,28 hints: false,
25 },29 },
26 output: {30 output: {
31 path: path.resolve(process.cwd(), 'dist'),
27 filename: 'lib.js',32 filename: 'lib.js',
28 libraryTarget: 'module',33 libraryTarget: 'module',
29 },34 },