Merge branch 'staging' into 202411-backend-maxctx

c930a66d819b27ea8a29e6600cc727334eda8da8

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

27 files changed, +503 -441Ignore whitespace
default/content/index.json+8 -0
@@ -628,6 +628,14 @@
628628 "type": "instruct"
629629 },
630630 {
631+ "filename": "presets/instruct/Tulu.json",
632+ "type": "instruct"
633+ },
634+ {
635+ "filename": "presets/context/Tulu.json",
636+ "type": "context"
637+ },
638+ {
631639 "filename": "presets/instruct/Vicuna 1.0.json",
632640 "type": "instruct"
633641 },
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 @@
230230 "show_external_models": false,
231231 "assistant_prefill": "",
232232 "assistant_impersonation": "",
233- "human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
234233 "claude_use_sysprompt": false,
235234 "use_alt_scale": false,
236235 "squash_system_messages": false,
package-lock.json+2 -255
@@ -1,12 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.12.78",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.12.78",
1010 "hasInstallScript": true,
1111 "license": "AGPL-3.0",
1212 "dependencies": {
@@ -67,7 +67,6 @@
6767 "vectra": "^0.2.2",
6868 "wavefile": "^11.0.0",
6969 "webpack": "^5.95.0",
70- "webpack-dev-middleware": "^7.4.2",
7170 "write-file-atomic": "^5.0.1",
7271 "ws": "^8.17.1",
7372 "yaml": "^2.3.4",
@@ -866,60 +865,6 @@
866865 "@jridgewell/sourcemap-codec": "^1.4.14"
867866 }
868867 },
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- },
923868 "node_modules/@kwsites/file-exists": {
924869 "version": "1.1.1",
925870 "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@@ -1803,45 +1748,6 @@
18031748 "url": "https://github.com/sponsors/epoberezkin"
18041749 }
18051750 },
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- },
18451751 "node_modules/ajv-keywords": {
18461752 "version": "3.5.2",
18471753 "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -2602,12 +2508,6 @@
26022508 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
26032509 "license": "MIT"
26042510 },
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- },
26112511 "node_modules/combined-stream": {
26122512 "version": "1.0.8",
26132513 "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3785,12 +3685,6 @@
37853685 "dev": true,
37863686 "license": "MIT"
37873687 },
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- },
37943688 "node_modules/fastq": {
37953689 "version": "1.15.0",
37963690 "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -4541,15 +4435,6 @@
45414435 "ms": "^2.0.0"
45424436 }
45434437 },
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- },
45534438 "node_modules/iconv-lite": {
45544439 "version": "0.6.3",
45554440 "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -5176,25 +5061,6 @@
51765061 "node": ">= 0.6"
51775062 }
51785063 },
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- },
51985064 "node_modules/merge-descriptors": {
51995065 "version": "1.0.3",
52005066 "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -6310,15 +6176,6 @@
63106176 "node": ">=0.10.0"
63116177 }
63126178 },
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- },
63226179 "node_modules/resolve-alpn": {
63236180 "version": "1.2.1",
63246181 "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
@@ -6447,59 +6304,6 @@
64476304 "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
64486305 "license": "ISC"
64496306 },
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- },
65036307 "node_modules/seedrandom": {
65046308 "version": "3.0.5",
65056309 "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
@@ -7038,18 +6842,6 @@
70386842 "dev": true,
70396843 "license": "MIT"
70406844 },
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- },
70536845 "node_modules/tiktoken": {
70546846 "version": "1.0.16",
70556847 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.16.tgz",
@@ -7106,22 +6898,6 @@
71066898 "node": ">=18"
71076899 }
71086900 },
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- },
71256901 "node_modules/truncate-utf8-bytes": {
71266902 "version": "1.0.2",
71276903 "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -7425,35 +7201,6 @@
74257201 }
74267202 }
74277203 },
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- },
74577204 "node_modules/webpack-sources": {
74587205 "version": "3.2.3",
74597206 "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
package.json+1 -2
@@ -57,7 +57,6 @@
5757 "vectra": "^0.2.2",
5858 "wavefile": "^11.0.0",
5959 "webpack": "^5.95.0",
60- "webpack-dev-middleware": "^7.4.2",
6160 "write-file-atomic": "^5.0.1",
6261 "ws": "^8.17.1",
6362 "yaml": "^2.3.4",
@@ -85,7 +84,7 @@
8584 "type": "git",
8685 "url": "https://github.com/SillyTavern/SillyTavern.git"
8786 },
8887 "version": "1.12.78",
8988 "scripts": {
9089 "start": "node server.js",
9190 "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 {
4242body.login .userSelect:hover {
4343 background-color: var(--black30a);
4444}
45+
46+body.login #handleEntryBlock,
47+body.login #passwordEntryBlock,
48+body.login #passwordRecoveryBlock {
49+ margin: 2px;
50+}
public/index.html+12 -18
@@ -1342,12 +1342,12 @@
13421342 <input class="neo-range-slider" type="range" id="presence_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" />
13431343 <input class="neo-range-input" type="number" min="-2" max="2" step="0.01" data-for="presence_pen_textgenerationwebui" id="presence_pen_counter_textgenerationwebui">
13441344 </div>
13451345 <div data-tg-type="aphrodite, ooba" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13461346 <small data-i18n="No Repeat Ngram Size">No Repeat Ngram Size</small>
13471347 <input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">
13481348 <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">
13491349 </div>
13501350 <div data-tg-type="tabby, aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
13511351 <small data-i18n="Skew">Skew</small>
13521352 <input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />
13531353 <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 @@
14021402 </div>
14031403 </div>
14041404
14051405 <div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p">
14061406 <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">
14071407 <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
14081408 <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -1959,15 +1959,6 @@
19591959 Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.
19601960 </span>
19611961 </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>
19711962 </div>
19721963 </div>
19731964 <div class="range-block m-t-1" data-source="openai,openrouter,scale,custom">
@@ -3287,7 +3278,7 @@
32873278 Advanced Formatting
32883279 </span>
32893280
32903281 <a href="https://docs.sillytavern.app/usage/promptscore-concepts/advancedformatting/" class="notes-link" target="_blank">
32913282 <span class="fa-solid fa-circle-question note-link-span"></span>
32923283 </a>
32933284 </h3>
@@ -3749,7 +3740,7 @@
37493740 </div>
37503741 <h3 class="margin0">
37513742 <span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>
37523743 <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/" class="notes-link" target="_blank">
37533744 <span class="fa-solid fa-circle-question note-link-span"></span>
37543745 </a>
37553746 </h3>
@@ -3943,7 +3934,13 @@
39433934 <div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">
39443935 <div class="flex-container">
39453936 <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>
39473944 </div>
39483945 </div>
39493946 <div id="UI-language-block" class="flex-container alignItemsBaseline">
@@ -4276,9 +4273,6 @@
42764273 <audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>
42774274 <span>
42784275 <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>
42824276 </span>
42834277 </label>
42844278 <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 } = {}) {
16331633 subEntities = filterByTagState(entities, { subForEntity: entity });
16341634 if (doFilter) {
16351635 // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up
16361636 subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
16371637 }
16381638 if (doSort) {
16391639 sortEntitiesList(subEntities);
@@ -1646,11 +1646,11 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16461646 // Second run filters, hiding whatever should be filtered later
16471647 if (doFilter) {
16481648 const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true });
16491649 entities = entitiesFilter.applyFilters(beforeFinalEntities, { clearFuzzySearchCaches: false });
16501650
16511651 // Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters.
16521652 if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) {
16531653 entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
16541654 }
16551655 }
16561656
@@ -1666,6 +1666,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
16661666 if (doSort) {
16671667 sortEntitiesList(entities);
16681668 }
1669+ entitiesFilter.clearFuzzySearchCaches();
16691670 return entities;
16701671}
16711672
public/scripts/chat-templates.js+6 -0
@@ -53,6 +53,12 @@ const hash_derivations = {
5353 // command-r-08-2024
5454 'Command R'
5555 ,
56+
57+ // Tulu
58+ 'ac7498a36a719da630e99d48e6ebc4409de85a77556c2b6159eeb735bcbd11df':
59+ // Tulu-3-8B
60+ // Tulu-3-70B
61+ 'Tulu'
5662};
5763
5864const substr_derivations = {
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+28 -15
@@ -419,30 +419,35 @@ export class SlashCommandHandler {
419419 namedArgumentList: [
420420 SlashCommandNamedArgument.fromProps({
421421 name: 'set',
422422 description: 'Name of QR set nameto add the context menu to',
423423 typeList: [ARGUMENT_TYPE.STRING],
424424 isRequired: true,
425425 enumProvider: localEnumProviders.qrSets,
426426 }),
427427 SlashCommandNamedArgument.fromProps({
428428 name: 'label',
429429 description: 'Label of Quick Reply labelto add the context menu to',
430430 typeList: [ARGUMENT_TYPE.STRING],
431431 enumProvider: localEnumProviders.qrEntries,
432432 }),
433433 SlashCommandNamedArgument.fromProps({
434434 name: 'id',
435435 description: 'numericNumeric ID of Quick Reply to add the QRcontext menu to, e.g., id=42',
436436 typeList: [ARGUMENT_TYPE.NUMBER],
437437 enumProvider: localEnumProviders.qrIds,
438438 }),
439439 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',
441446 ),
442447 ],
443448 unnamedArgumentList: [
444449 SlashCommandArgument.fromProps({
445450 description: 'Name of QR set nameto add as a context menu',
446451 typeList: [ARGUMENT_TYPE.STRING],
447452 isRequired: true,
448453 enumProvider: localEnumProviders.qrSets,
@@ -450,13 +455,16 @@ export class SlashCommandHandler {
450455 ],
451456 helpString: `
452457 <div>
453458 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.
454462 </div>
455463 <div>
456464 <strong>Example:</strong>
457465 <ul>
458466 <li>
459467 <pre><code>/qr-contextadd set=MyPresetMyQRSetWithTheButton label=MyButton chain=true MyOtherPresetMyQRSetWithContextItems</code></pre>
460468 </li>
461469 </ul>
462470 </div>
@@ -470,27 +478,27 @@ export class SlashCommandHandler {
470478 namedArgumentList: [
471479 SlashCommandNamedArgument.fromProps({
472480 name: 'set',
473481 description: 'Name of QR set nameto remove the context menu from',
474482 typeList: [ARGUMENT_TYPE.STRING],
475483 isRequired: true,
476484 enumProvider: localEnumProviders.qrSets,
477485 }),
478486 SlashCommandNamedArgument.fromProps({
479487 name: 'label',
480488 description: 'Label of Quick Reply labelto remove the context menu from',
481489 typeList: [ARGUMENT_TYPE.STRING],
482490 enumProvider: localEnumProviders.qrEntries,
483491 }),
484492 SlashCommandNamedArgument.fromProps({
485493 name: 'id',
486494 description: 'numericNumeric ID of Quick Reply to remove the QRcontext menu from, e.g., id=42',
487495 typeList: [ARGUMENT_TYPE.NUMBER],
488496 enumProvider: localEnumProviders.qrIds,
489497 }),
490498 ],
491499 unnamedArgumentList: [
492500 SlashCommandArgument.fromProps({
493501 description: 'Name of QR set nameto remove',
494502 typeList: [ARGUMENT_TYPE.STRING],
495503 isRequired: true,
496504 enumProvider: localEnumProviders.qrSets,
@@ -501,6 +509,9 @@ export class SlashCommandHandler {
501509 Remove context menu preset from a QR.
502510 </div>
503511 <div>
512+ If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
513+ </div>
514+ <div>
504515 <strong>Example:</strong>
505516 <ul>
506517 <li>
@@ -542,6 +553,9 @@ export class SlashCommandHandler {
542553 Remove all context menu presets from a QR.
543554 </div>
544555 <div>
556+ If <code>id</code> and a label are both provided, <code>id</code> will be used.
557+ </div>
558+ <div>
545559 <strong>Example:</strong>
546560 <ul>
547561 <li>
@@ -908,12 +922,11 @@ export class SlashCommandHandler {
908922 }
909923 }
910924
911-
912925 createContextItem(args, name) {
913926 try {
914927 this.api.createContextItem(
915928 args.set,
916- args.label,
929+ args.id !== undefined ? Number(args.id) : args.label,
917930 name,
918931 isTrueBoolean(args.chain),
919932 );
@@ -923,14 +936,14 @@ export class SlashCommandHandler {
923936 }
924937 deleteContextItem(args, name) {
925938 try {
926939 this.api.deleteContextItem(args.set, args.id !== undefined ? Number(args.id) : args.label, name);
927940 } catch (ex) {
928941 toastr.error(ex.message);
929942 }
930943 }
931944 clearContextMenu(args, label) {
932945 try {
933946 this.api.clearContextMenu(args.set, args.id !== undefined ? Number(args.id) : args.label ?? label);
934947 } catch (ex) {
935948 toastr.error(ex.message);
936949 }
public/scripts/extensions/quick-reply/src/ui/ctx/ContextMenu.js+20 -2
@@ -19,7 +19,7 @@ export class ContextMenu {
1919 this.itemList = this.build(qr).children;
2020 this.itemList.forEach(item => {
2121 item.onExpand = () => {
2222 this.itemList.filter(it => it !== item)
2323 .forEach(it => it.collapse());
2424 };
2525 });
@@ -36,6 +36,7 @@ export class ContextMenu {
3636 icon: qr.icon,
3737 showLabel: qr.showLabel,
3838 label: qr.label,
39+ title: qr.title,
3940 message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
4041 children: [],
4142 };
@@ -45,12 +46,29 @@ export class ContextMenu {
4546 const nextHierarchy = [...hierarchy, cl.set];
4647 const nextLabelHierarchy = [...labelHierarchy, tree.label];
4748 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 => {
4966 const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
5067 tree.children.push(new MenuItem(
5168 subTree.icon,
5269 subTree.showLabel,
5370 subTree.label,
71+ subTree.title,
5472 subTree.message,
5573 (evt) => {
5674 evt.stopPropagation();
public/scripts/extensions/quick-reply/src/ui/ctx/MenuHeader.js+1 -1
@@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
22
33export class MenuHeader extends MenuItem {
44 constructor(/**@type {String}*/label) {
55 super(null, null, label, null, null, null, []);
66 }
77
88
public/scripts/extensions/quick-reply/src/ui/ctx/MenuItem.js+11 -7
@@ -4,11 +4,11 @@ export class MenuItem {
44 /**@type {string}*/ icon;
55 /**@type {boolean}*/ showLabel;
66 /**@type {string}*/ label;
7+ /**@type {string}*/ title;
78 /**@type {object}*/ value;
89 /**@type {function}*/ callback;
910 /**@type {MenuItem[]}*/ childList = [];
1011 /**@type {SubMenu}*/ subMenu;
11- /**@type {boolean}*/ isForceExpanded = false;
1212
1313 /**@type {HTMLElement}*/ root;
1414
@@ -19,17 +19,19 @@ export class MenuItem {
1919
2020 /**
2121 *
2222 * @param {?string} icon
2323 * @param {?boolean} showLabel
2424 * @param {string} label
25+ * @param {?string} title Tooltip
2526 * @param {object} value
2627 * @param {function} callback
2728 * @param {MenuItem[]} children
2829 */
2930 constructor(icon, showLabel, label, title, value, callback, children = []) {
3031 this.icon = icon;
3132 this.showLabel = showLabel;
3233 this.label = label;
34+ this.title = title;
3335 this.value = value;
3436 this.callback = callback;
3537 this.childList = children;
@@ -42,12 +44,15 @@ export class MenuItem {
4244 this.root = item;
4345 item.classList.add('list-group-item');
4446 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+
4652 if (this.callback) {
4753 item.addEventListener('click', (evt) => this.callback(evt, this));
4854 }
4955 const icon = document.createElement('div'); {
50- this.domIcon = icon;
5156 icon.classList.add('qr--button-icon');
5257 icon.classList.add('fa-solid');
5358 if (!this.icon) icon.classList.add('qr--hidden');
@@ -55,7 +60,6 @@ export class MenuItem {
5560 item.append(icon);
5661 }
5762 const lbl = document.createElement('div'); {
58- this.domLabel = lbl;
5963 lbl.classList.add('qr--button-label');
6064 if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');
6165 lbl.textContent = this.label;
public/scripts/extensions/quick-reply/style.css+3 -0
@@ -174,6 +174,9 @@
174174 position: absolute;
175175 overflow: visible;
176176}
177+.ctx-menu .ctx-item .qr--hidden {
178+ display: none;
179+}
177180.list-group .list-group-item.ctx-header {
178181 font-weight: bold;
179182 cursor: default;
public/scripts/extensions/quick-reply/style.less+4 -0
@@ -176,6 +176,10 @@
176176 overflow: visible;
177177}
178178
179+.ctx-menu .ctx-item .qr--hidden {
180+ display: none;
181+}
182+
179183.list-group .list-group-item.ctx-header {
180184 font-weight: bold;
181185 cursor: default;
public/scripts/filters.js+45 -6
@@ -56,6 +56,19 @@ export function isFilterState(a, b) {
5656}
5757
5858/**
59+ * The fuzzy search categories
60+ * @type {{ characters: string, worldInfo: string, personas: string, tags: string, groups: string }}
61+ */
62+export const fuzzySearchCategories = Object.freeze({
63+ characters: 'characters',
64+ worldInfo: 'worldInfo',
65+ personas: 'personas',
66+ tags: 'tags',
67+ groups: 'groups',
68+});
69+
70+
71+/**
5972 * Helper class for filtering data.
6073 * @example
6174 * const filterHelper = new FilterHelper(() => console.log('data changed'));
@@ -73,12 +86,25 @@ export class FilterHelper {
7386 scoreCache;
7487
7588 /**
89+ * Cache for fuzzy search results per category.
90+ * @type {Object.<string, { resultMap: Map<string, any> }>}
91+ */
92+ fuzzySearchCaches;
93+
94+ /**
7695 * Creates a new FilterHelper
7796 * @param {Function} onDataChanged Callback to trigger when the filter data changes
7897 */
7998 constructor(onDataChanged) {
8099 this.onDataChanged = onDataChanged;
81100 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+ };
82108 }
83109
84110 /**
@@ -151,7 +177,7 @@ export class FilterHelper {
151177 return data;
152178 }
153179
154180 const fuzzySearchResults = fuzzySearchWorldInfo(data, term, this.fuzzySearchCaches);
155181 this.cacheScores(FILTER_TYPES.WORLD_INFO_SEARCH, new Map(fuzzySearchResults.map(i => [i.item?.uid, i.score])));
156182
157183 const filteredData = data.filter(entity => fuzzySearchResults.find(x => x.item === entity));
@@ -170,7 +196,7 @@ export class FilterHelper {
170196 return data;
171197 }
172198
173199 const fuzzySearchResults = fuzzySearchPersonas(data, term, this.fuzzySearchCaches);
174200 this.cacheScores(FILTER_TYPES.PERSONA_SEARCH, new Map(fuzzySearchResults.map(i => [i.item.key, i.score])));
175201
176202 const filteredData = data.filter(name => fuzzySearchResults.find(x => x.item.key === name));
@@ -289,9 +315,9 @@ export class FilterHelper {
289315
290316 // Save fuzzy search results and scores if enabled
291317 if (power_user.fuzzy_search) {
292318 const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue, this.fuzzySearchCaches);
293319 const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue, this.fuzzySearchCaches);
294320 const fuzzySearchTagsResult = fuzzySearchTags(searchValue, this.fuzzySearchCaches);
295321 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchCharactersResults.map(i => [`character.${i.refIndex}`, i.score])));
296322 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchGroupsResults.map(i => [`group.${i.item.id}`, i.score])));
297323 this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchTagsResult.map(i => [`tag.${i.item.id}`, i.score])));
@@ -343,11 +369,14 @@ export class FilterHelper {
343369 * @param {object} options - Optional call parameters
344370 * @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared.
345371 * @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.
346373 * @returns {any[]} The filtered data.
347374 */
348375 applyFilters(data, { clearScoreCache = true, tempOverrides = {}, clearFuzzySearchCaches = true } = {}) {
349376 if (clearScoreCache) this.clearScoreCache();
350377
378+ if (clearFuzzySearchCaches) this.clearFuzzySearchCaches();
379+
351380 // Save original filter states
352381 const originalStates = {};
353382 for (const key in tempOverrides) {
@@ -411,4 +440,14 @@ export class FilterHelper {
411440 this.scoreCache = new Map();
412441 }
413442 }
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+ }
414453}
public/scripts/openai.js+0 -24
@@ -99,7 +99,6 @@ const default_wi_format = '{0}';
9999const default_new_chat_prompt = '[Start a new Chat]';
100100const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]';
101101const default_new_example_chat_prompt = '[Example Chat]';
102-const default_claude_human_sysprompt_message = 'Let\'s get started. Please generate your response based on the information and instructions provided above.';
103102const 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}}]';
104103const default_bias = 'Default (none)';
105104const default_personality_format = '[{{char}}\'s personality: {{personality}}]';
@@ -276,7 +275,6 @@ const default_settings = {
276275 proxy_password: '',
277276 assistant_prefill: '',
278277 assistant_impersonation: '',
279- human_sysprompt_message: default_claude_human_sysprompt_message,
280278 claude_use_sysprompt: false,
281279 use_makersuite_sysprompt: true,
282280 use_alt_scale: false,
@@ -353,7 +351,6 @@ const oai_settings = {
353351 proxy_password: '',
354352 assistant_prefill: '',
355353 assistant_impersonation: '',
356- human_sysprompt_message: default_claude_human_sysprompt_message,
357354 claude_use_sysprompt: false,
358355 use_makersuite_sysprompt: true,
359356 use_alt_scale: false,
@@ -1892,7 +1889,6 @@ async function sendOpenAIRequest(type, messages, signal) {
18921889 generate_data['top_k'] = Number(oai_settings.top_k_openai);
18931890 generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;
18941891 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
1895- generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
18961892 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
18971893 if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {
18981894 generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
@@ -3030,7 +3026,6 @@ function loadOpenAISettings(data, settings) {
30303026 oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
30313027 oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
30323028 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;
30343029 oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
30353030 oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
30363031 oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;
@@ -3070,7 +3065,6 @@ function loadOpenAISettings(data, settings) {
30703065 $('#openai_proxy_password').val(oai_settings.proxy_password);
30713066 $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
30723067 $('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);
3073- $('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
30743068 $('#openai_image_inlining').prop('checked', oai_settings.image_inlining);
30753069 $('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);
30763070
@@ -3400,7 +3394,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
34003394 show_external_models: settings.show_external_models,
34013395 assistant_prefill: settings.assistant_prefill,
34023396 assistant_impersonation: settings.assistant_impersonation,
3403- human_sysprompt_message: settings.human_sysprompt_message,
34043397 claude_use_sysprompt: settings.claude_use_sysprompt,
34053398 use_makersuite_sysprompt: settings.use_makersuite_sysprompt,
34063399 use_alt_scale: settings.use_alt_scale,
@@ -3825,7 +3818,6 @@ function onSettingsPresetChange() {
38253818 proxy_password: ['#openai_proxy_password', 'proxy_password', false],
38263819 assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
38273820 assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],
3828- human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false],
38293821 claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true],
38303822 use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true],
38313823 use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true],
@@ -4677,10 +4669,6 @@ function toggleChatCompletionForms() {
46774669 const validSources = $(this).data('source').split(',');
46784670 $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
46794671 });
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- }
46844672}
46854673
46864674async function testApiConnection() {
@@ -5036,7 +5024,6 @@ export function initOpenAI() {
50365024
50375025 $('#claude_use_sysprompt').on('change', function () {
50385026 oai_settings.claude_use_sysprompt = !!$('#claude_use_sysprompt').prop('checked');
5039- $('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
50405027 saveSettingsDebounced();
50415028 });
50425029
@@ -5113,12 +5100,6 @@ export function initOpenAI() {
51135100 saveSettingsDebounced();
51145101 });
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-
51225103 $('#newgroupchat_prompt_restore').on('click', function () {
51235104 oai_settings.new_group_chat_prompt = default_new_group_chat_prompt;
51245105 $('#newgroupchat_prompt_textarea').val(oai_settings.new_group_chat_prompt);
@@ -5210,11 +5191,6 @@ export function initOpenAI() {
52105191 saveSettingsDebounced();
52115192 });
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-
52185194 $('#openrouter_use_fallback').on('input', function () {
52195195 oai_settings.openrouter_use_fallback = !!$(this).prop('checked');
52205196 saveSettingsDebounced();
public/scripts/power-user.js+87 -84
@@ -53,6 +53,7 @@ import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandE
5353import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
5454import { POPUP_TYPE, callGenericPopup } from './popup.js';
5555import { loadSystemPrompts } from './sysprompt.js';
56+import { fuzzySearchCategories } from './filters.js';
5657
5758export {
5859 loadPowerUserSettings,
@@ -1831,27 +1832,28 @@ async function loadContextSettings() {
18311832 });
18321833}
18331834
1835+
18341836/**
18351837 * FuzzyCommon searchfunction charactersto byperform afuzzy search termwith 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
18361841 * @param {string} searchValue - The search term
1842+ * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
18371843 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
18381844 */
1839-export function fuzzySearchCharacters(searchValue) {
1845+function 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+
18401854 // @ts-ignore
18411855 const fuse = new Fuse(charactersdata, {
18421856 keys: [keys,
1843- { name: 'data.name', weight: 20 },
1844- { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },
1845- { name: 'data.description', weight: 3 },
1846- { name: 'data.mes_example', weight: 3 },
1847- { name: 'data.scenario', weight: 2 },
1848- { name: 'data.personality', weight: 2 },
1849- { name: 'data.first_mes', weight: 2 },
1850- { name: 'data.creator_notes', weight: 2 },
1851- { name: 'data.creator', weight: 1 },
1852- { name: 'data.tags', weight: 1 },
1853- { name: 'data.alternate_greetings', weight: 1 },
1854- ],
18551857 includeScore: true,
18561858 ignoreLocation: true,
18571859 useExtendedSearch: true,
@@ -1859,109 +1861,110 @@ export function fuzzySearchCharacters(searchValue) {
18591861 });
18601862
18611863 const results = fuse.search(searchValue);
1862- console.debug('Characters fuzzy search results for ' + searchValue, results);
1864+
1865+ // Store in cache if provided
1866+ if (fuzzySearchCaches) {
1867+ fuzzySearchCaches[type].resultMap.set(searchValue, results);
1868+ }
18631869 return results;
18641870}
18651871
18661872/**
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+ */
1878+export function fuzzySearchCharacters(searchValue, fuzzySearchCaches = null) {
1879+ const keys = [
1880+ { name: 'data.name', weight: 20 },
1881+ { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },
1882+ { name: 'data.description', weight: 3 },
1883+ { name: 'data.mes_example', weight: 3 },
1884+ { name: 'data.scenario', weight: 2 },
1885+ { name: 'data.personality', weight: 2 },
1886+ { name: 'data.first_mes', weight: 2 },
1887+ { name: 'data.creator_notes', weight: 2 },
1888+ { name: 'data.creator', weight: 1 },
1889+ { name: 'data.tags', weight: 1 },
1890+ { name: 'data.alternate_greetings', weight: 1 },
1891+ ];
1892+
1893+ return performFuzzySearch(fuzzySearchCategories.characters, characters, keys, searchValue, fuzzySearchCaches);
1894+}
1895+
1896+/**
18671897 * Fuzzy search world info entries by a search term
18681898 * @param {*[]} data - WI items data array
18691899 * @param {string} searchValue - The search term
1900+ * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
18701901 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
18711902 */
18721903export function fuzzySearchWorldInfo(data, searchValue, fuzzySearchCaches = null) {
1873- // @ts-ignore
1904+ const keys = [
1874- const fuse = new Fuse(data, {
1905+ { name: 'key', weight: 20 },
1875- keys: [
1906+ { name: 'group', weight: 15 },
18761907 { name: 'keycomment', weight: 2010 },
18771908 { name: 'groupkeysecondary', weight: 1510 },
18781909 { name: 'commentcontent', weight: 103 },
18791910 { name: 'keysecondaryuid', weight: 101 },
18801911 { name: 'contentautomationId', weight: 31 },
1881- { name: 'uid', weight: 1 },
1912+ ];
1882- { name: 'automationId', weight: 1 },
1883- ],
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;
18931915}
18941916
18951917/**
18961918 * Fuzzy search persona entries by a search term
18971919 * @param {*[]} data - persona data array
18981920 * @param {string} searchValue - The search term
1921+ * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
18991922 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
19001923 */
19011924export 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-ignore
1926+ key: x,
1904- const fuse = new Fuse(data, {
1927+ name: power_user.personas[x] ?? '',
1905- keys: [
1928+ description: power_user.persona_descriptions[x]?.description ?? ''
1906- { name: 'name', weight: 20 },
1929+ }));
1907- { name: 'description', weight: 3 },
1908- ],
1909- includeScore: true,
1910- ignoreLocation: true,
1911- useExtendedSearch: true,
1912- threshold: 0.2,
1913- });
19141930
19151931 const resultskeys = fuse.search(searchValue);[
1916- console.debug('Personas fuzzy search results for ' + searchValue, results);
1932+ { name: 'name', weight: 20 },
1917- return results;
1933+ { name: 'description', weight: 3 },
1934+ ];
1935+
1936+ return performFuzzySearch(fuzzySearchCategories.personas, mappedData, keys, searchValue, fuzzySearchCaches);
19181937}
19191938
19201939/**
19211940 * Fuzzy search tags by a search term
19221941 * @param {string} searchValue - The search term
1942+ * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
19231943 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
19241944 */
19251945export function fuzzySearchTags(searchValue, fuzzySearchCaches = null) {
1926- // @ts-ignore
1946+ const keys = [
1927- const fuse = new Fuse(tags, {
1947+ { name: 'name', weight: 1 },
1928- keys: [
1948+ ];
1929- { name: 'name', weight: 1 },
1930- ],
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;
19401951}
19411952
19421953/**
19431954 * Fuzzy search groups by a search term
19441955 * @param {string} searchValue - The search term
1956+ * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
19451957 * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
19461958 */
19471959export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
1948- // @ts-ignore
1960+ const keys = [
1949- const fuse = new Fuse(groups, {
1961+ { name: 'name', weight: 20 },
1950- keys: [
1962+ { name: 'members', weight: 15 },
1951- { name: 'name', weight: 20 },
1963+ { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },
19521964 { name: 'membersid', weight: 151 },
1953- { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },
1965+ ];
1954- { name: 'id', weight: 1 },
1955- ],
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;
19651968}
19661969
19671970/**
public/scripts/textgen-settings.js+1 -0
@@ -1258,6 +1258,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
12581258 'xtc_probability': settings.xtc_probability,
12591259 'nsigma': settings.nsigma,
12601260 'custom_token_bans': toIntArray(banned_tokens),
1261+ 'no_repeat_ngram_size': settings.no_repeat_ngram_size,
12611262 };
12621263
12631264 if (settings.type === OPENROUTER) {
server.js+1 -5
@@ -627,10 +627,6 @@ const tavernUrl = new URL(
627627 (':' + server_port),
628628);
629629
630-function prepareFrontendBundle() {
631- return new Promise((resolve) => webpackMiddleware.waitUntilValid(resolve));
632-}
633-
634630/**
635631 * Tasks that need to be run before the server starts listening.
636632 */
@@ -682,7 +678,7 @@ const preSetupTasks = async function () {
682678 initRequestProxy({ enabled: proxyEnabled, url: proxyUrl, bypass: proxyBypass });
683679
684680 // Wait for frontend libs to compile
685681 await prepareFrontendBundlewebpackMiddleware.runWebpackCompiler();
686682};
687683
688684/**
src/endpoints/backends/chat-completions.js+1 -1
@@ -102,7 +102,7 @@ async function sendClaudeRequest(request, response) {
102102 const additionalHeaders = {};
103103 const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;
104104 const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;
105105 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);
106106 // Add custom stop sequences
107107 const stopSequences = [];
108108 if (Array.isArray(request.body.stop)) {
src/endpoints/characters.js+6 -2
@@ -14,7 +14,7 @@ import jimp from 'jimp';
1414
1515import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
1616import { jsonParser, urlencodedParser } from '../express-common.js';
1717import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap } from '../util.js';
1818import { TavernCardValidator } from '../validator/TavernCardValidator.js';
1919import { parse, write } from '../character-card-parser.js';
2020import { readWorldInfoFile } from './worldinfo.js';
@@ -23,7 +23,8 @@ import { importRisuSprites } from './sprites.js';
2323const defaultAvatarPath = './public/img/ai4.png';
2424
2525// KV-store for parsed character data
26-const characterDataCache = new Map();
26+// 100 MB limit. Would take roughly 3000 characters to reach this limit
27+const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);
2728// Some Android devices require tighter memory management
2829const isAndroid = process.platform === 'android';
2930
@@ -58,6 +59,9 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
5859 try {
5960 // Reset the cache
6061 for (const key of characterDataCache.keys()) {
62+ if (Buffer.isBuffer(inputFile)) {
63+ break;
64+ }
6165 if (key.startsWith(inputFile)) {
6266 characterDataCache.delete(key);
6367 break;
src/middleware/webpack-serve.js+36 -11
@@ -1,20 +1,45 @@
11import processpath from 'node:processpath';
22import webpack from 'webpack';
3-import middleware from 'webpack-dev-middleware';
43import { publicLibConfig } from '../../webpack.config.js';
54
65export default function getWebpackServeMiddleware() {
6+ const outputPath = publicLibConfig.output?.path;
7+ const outputFile = publicLibConfig.output?.filename;
78 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.
13- }
14+ * @param {import('express').NextFunction} next Next function.
14- compiler.watchFileSystem = null;
15+ * @type {import('express').RequestHandler}
15- compiler.watchMode = false;
16+ */
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 });
20+ }
21+
22+ next();
1723 }
1824
19- return middleware(compiler, {});
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();
38+ }
39+ resolve();
40+ });
41+ });
42+ };
43+
44+ return devMiddleware;
2045}
src/prompt-converters.js+3 -4
@@ -91,11 +91,10 @@ export function convertClaudePrompt(messages, addAssistantPostfix, addAssistantP
9191 * @param {string} prefillString User determined prefill string
9292 * @param {boolean} useSysPrompt See if we want to use a system prompt
9393 * @param {boolean} useTools See if we want to use tools
94- * @param {string} humanMsgFix Add Human message between system prompt and assistant.
9594 * @param {string} charName Character name
9695 * @param {string} userName User name
9796 */
9897export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') {
9998 let systemPrompt = [];
10099 if (useSysPrompt) {
101100 // 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
123122 // Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.
124123 // Also prevents erroring out if the messages array is empty.
125124 if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) {
126125 messages.unshift({
127126 role: 'user',
128127 content: humanMsgFix || PROMPT_PLACEHOLDER,
129128 });
130129 }
131130 }
src/util.js+179 -0
@@ -670,3 +670,182 @@ export function isValidUrl(url) {
670670 return false;
671671 }
672672}
673+
674+/**
675+ * MemoryLimitedMap class that limits the memory usage of string values.
676+ */
677+export 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 = {
88 cache: {
99 type: 'filesystem',
1010 cacheDirectory: path.resolve(process.cwd(), 'dist/webpack'),
11+ store: 'pack',
12+ compression: 'gzip',
1113 },
1214 devtool: false,
1315 watch: false,
@@ -16,6 +18,8 @@ export const publicLibConfig = {
1618 preset: 'minimal',
1719 assets: false,
1820 modules: false,
21+ colors: true,
22+ timings: true,
1923 },
2024 experiments: {
2125 outputModule: true,
@@ -24,6 +28,7 @@ export const publicLibConfig = {
2428 hints: false,
2529 },
2630 output: {
31+ path: path.resolve(process.cwd(), 'dist'),
2732 filename: 'lib.js',
2833 libraryTarget: 'module',
2934 },