Compare commits

..

56 Commits

Author SHA1 Message Date
Cohee
de3f340a55 Merge pull request #3135 from SillyTavern/staging
Staging
2024-12-01 17:31:30 +02:00
Cohee
39c3924b3f Merge pull request #3112 from kallewoof/202411-backend-maxctx
feature: allow auto-use of max context size given by backend
2024-12-01 15:15:15 +02:00
Cohee
8e94589f83 Revert keepContextLock flag in setGenerationParamsFromPreset 2024-12-01 15:13:17 +02:00
Cohee
66a862f797 Lint the use of data-i18n attributes 2024-12-01 15:10:22 +02:00
Cohee
c930a66d81 Merge branch 'staging' into 202411-backend-maxctx 2024-12-01 14:39:01 +02:00
Cohee
89ec8fd23a Bump package version 2024-12-01 14:38:17 +02:00
Cohee
a86735d743 Fix sendless statuses 2024-11-30 19:30:14 +02:00
Cohee
56c99000c4 Split auto-switch toast 2024-11-30 18:21:15 +02:00
Cohee
4a7a11dfd5 Add JSdoc for function 2024-11-30 18:19:31 +02:00
Cohee
8e20ebb534 Run eslint 2024-11-30 18:14:46 +02:00
Cohee
04b68d2cce Move derive context size to TC settings 2024-11-30 18:08:12 +02:00
Cohee
c3c16ea0d6 Merge pull request #3106 from joenunezb/optimize/improve-search
perf(search): improve fuzzy character search performance by ~13x (4.5s → 350ms)
2024-11-30 17:50:40 +02:00
Cohee
8b7a14f895 Merge pull request #3124 from ceruleandeep/feature/actuallyCompactContextMenus
Filter out hidden items in context menus
2024-11-30 17:39:50 +02:00
ceruleandeep
2f7bc7ca8d Filter out hidden items in context menus
Add CSS to apply label show/hide settings to QRs in context menus

Add provision for QR set applied to one of its own buttons as "burger" menu
2024-11-30 00:46:04 +11:00
Cohee
a444a782e2 Fix input outline on discreet login page 2024-11-29 13:32:53 +00:00
Cohee
0bebf02c97 Merge pull request #3119 from SillyTavern/webpack-memory
Replace webpack-dev-middleware with a statically compiled file
2024-11-29 15:24:53 +02:00
Cohee
7fbff41329 Unasync route handler 2024-11-29 13:14:40 +00:00
Cohee
53514b5e1a Prettify compilation console logs 2024-11-29 13:12:46 +00:00
Cohee
c0b37631bc Merge branch 'staging' into webpack-memory 2024-11-29 13:07:17 +00:00
Cohee
e124a22ffd Merge pull request #3120 from SillyTavern/char-cache-limit
(perf) Add 100MB limit to parsed characters cache
2024-11-29 15:04:03 +02:00
Cohee
095d19cda7 Rename LimitedMap => MemoryLimitedMap 2024-11-29 12:33:48 +00:00
Cohee
176ef77624 Merge branch 'staging' into char-cache-limit 2024-11-29 12:30:26 +00:00
Cohee
2384031d09 Merge pull request #3121 from ceruleandeep/fix/handleIdForQRMenuAdd
Wire up id= parameter for /qr-context*
2024-11-29 14:27:57 +02:00
Cohee
710a4ee867 Merge pull request #3122 from ceruleandeep/feature/compactererQRContextMenus
Make QR context menu display options more consistent with QR bar
2024-11-29 14:17:24 +02:00
ceruleandeep
3a1a955164 Make QR context menu display options more consistent with QR bar
Use QR title as tooltip if set on the QR

Add qr--hidden class to "invisible" context items to allow hiding with CSS

Add title and isHidden props to MenuItem

Remove domIcon and domLabel props: not needed for ctx menu rendering; isForceExpanded: unimplemented
2024-11-29 17:44:51 +11:00
ceruleandeep
e8004b5b56 Wire up id= parameter for /qr-context*
Parameter is in the named arguments but was not handled in the handler. Added `args.id !== undefined ? Number(args.id) : args.label` etc, as used elsewhere.
2024-11-29 11:52:57 +11:00
Cohee
c873362d01 Safer estimation of possibly undefined values 2024-11-29 01:28:10 +02:00
Cohee
52606616c4 Add 100MB limit to parsed characters cache 2024-11-29 01:06:10 +02:00
Cohee
768b3e48f7 Remove webpack watch unhook 2024-11-29 00:25:31 +02:00
Cohee
afccb8517a Ditch webpack-dev-middleware 2024-11-29 00:13:43 +02:00
Cohee
b5c2ecdfcc Webpack: cache lib.js to disk to prevent occasional OOM 2024-11-28 22:47:27 +02:00
Cohee
afb4acc19b Revert "Webpack: cache lib.js to disk to prevent occasional OOM"
This reverts commit f630c8892a.
2024-11-28 22:45:45 +02:00
Cohee
f630c8892a Webpack: cache lib.js to disk to prevent occasional OOM 2024-11-28 22:39:20 +02:00
Cohee
4f24f8078d Merge pull request #3118 from kallewoof/202411-tulu-nl
tulu template: added \n to input/system_suffix to match model card
2024-11-28 14:38:45 +02:00
Cohee
8bfb695536 Adjust documentation links 2024-11-28 11:38:10 +00:00
Karl-Johan Alm
ef35adb9e4 tulu template: added \n to input_suffix and system_suffix to match model card 2024-11-28 13:04:40 +09:00
Cohee
76c2789587 Merge pull request #3117 from kallewoof/202411-tulu
Add Tulu templates
2024-11-27 12:31:44 +02:00
Joe
f4ef9697e9 Account for optional cache and remove timing code 2024-11-26 20:07:30 -08:00
Joe
309157dd89 Remove unnecesary key only clearing for cache clearing 2024-11-26 19:52:21 -08:00
Joe
1395c0b8c6 Encapsulate logic into filters instead of spreading around 2024-11-26 19:47:09 -08:00
Karl-Johan Alm
8d67bdee84 Add Tulu templates 2024-11-27 11:58:12 +09:00
Joe
78c55558af Merge branch 'staging' of https://github.com/joenunezb/SillyTavern into optimize/improve-search 2024-11-26 18:00:28 -08:00
Joe
dd4a1bf072 Attempt to allow preview for github 2024-11-26 13:32:40 -08:00
Joe
16ba8331b5 Document Fuzzy Search process 2024-11-26 13:27:27 -08:00
Cohee
8dbd78f560 Merge pull request #3108 from SillyTavern/claude-no-filler
Claude: remove user filler from prompt converter
2024-11-26 21:41:33 +02:00
Cohee
f730e2179b Merge branch 'staging' into claude-no-filler 2024-11-26 21:25:47 +02:00
Karl-Johan Alm
faf80d1b62 UI: move the derived context size flag into connection pane 2024-11-26 10:36:40 +09:00
Karl-Johan Alm
e02f57b7b0 respect context unlocked flag when auto-setting context size 2024-11-26 10:00:52 +09:00
Karl-Johan Alm
4988f22e94 feature: allow auto-use of max context size given by backend 2024-11-25 23:08:11 +09:00
Joe
e56faaaed5 Remove hash function 2024-11-24 14:55:22 -08:00
Joe
e1d6a47048 Remove key hashing and explicitly clear the cache after printing characters 2024-11-24 14:54:12 -08:00
Cohee
9382845dee Claude: remove user filler from prompt converter 2024-11-24 19:05:41 +02:00
Joe
d1cac298c6 Coment out log statements 2024-11-23 22:51:48 -08:00
Joe
e2c083ba31 Remove duplicated code 2024-11-23 22:34:56 -08:00
Joe
2661881bc4 Handle stale cache 2024-11-23 22:13:20 -08:00
Joe
eb29f03ab0 Sped up character search by 93% 2024-11-23 20:48:36 -08:00
29 changed files with 624 additions and 496 deletions

View File

@@ -627,6 +627,14 @@
"filename": "presets/instruct/Synthia.json",
"type": "instruct"
},
{
"filename": "presets/instruct/Tulu.json",
"type": "instruct"
},
{
"filename": "presets/context/Tulu.json",
"type": "context"
},
{
"filename": "presets/instruct/Vicuna 1.0.json",
"type": "instruct"

View File

@@ -0,0 +1,11 @@
{
"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",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
"allow_jailbreak": false,
"always_force_name2": true,
"trim_sentences": false,
"single_line": false,
"name": "Tulu"
}

View File

@@ -0,0 +1,22 @@
{
"input_sequence": "<|user|>\n",
"output_sequence": "<|assistant|>\n",
"first_output_sequence": "",
"last_output_sequence": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"stop_sequence": "<|end_of_text|>",
"wrap": false,
"macro": true,
"names_behavior": "always",
"activation_regex": "",
"skip_examples": false,
"output_suffix": "<|end_of_text|>\n",
"input_suffix": "\n",
"system_sequence": "<|system|>\n",
"system_suffix": "\n",
"user_alignment_message": "",
"last_system_sequence": "",
"system_same_as_user": false,
"name": "Tulu"
}

View File

@@ -230,7 +230,6 @@
"show_external_models": false,
"assistant_prefill": "",
"assistant_impersonation": "",
"human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
"claude_use_sysprompt": false,
"use_alt_scale": false,
"squash_system_messages": false,

257
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.12.7",
"version": "1.12.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.12.7",
"version": "1.12.8",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@@ -67,7 +67,6 @@
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"webpack": "^5.95.0",
"webpack-dev-middleware": "^7.4.2",
"write-file-atomic": "^5.0.1",
"ws": "^8.17.1",
"yaml": "^2.3.4",
@@ -866,60 +865,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@jsonjoy.com/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/@jsonjoy.com/json-pack": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz",
"integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==",
"license": "Apache-2.0",
"dependencies": {
"@jsonjoy.com/base64": "^1.1.1",
"@jsonjoy.com/util": "^1.1.2",
"hyperdyperid": "^1.2.0",
"thingies": "^1.20.0"
},
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/@jsonjoy.com/util": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz",
"integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
@@ -1803,45 +1748,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
@@ -2602,12 +2508,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3785,12 +3685,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz",
"integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==",
"license": "BSD-3-Clause"
},
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -4541,15 +4435,6 @@
"ms": "^2.0.0"
}
},
"node_modules/hyperdyperid": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
"integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
"license": "MIT",
"engines": {
"node": ">=10.18"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -5176,25 +5061,6 @@
"node": ">= 0.6"
}
},
"node_modules/memfs": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz",
"integrity": "sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==",
"license": "Apache-2.0",
"dependencies": {
"@jsonjoy.com/json-pack": "^1.0.3",
"@jsonjoy.com/util": "^1.3.0",
"tree-dump": "^1.0.1",
"tslib": "^2.0.0"
},
"engines": {
"node": ">= 4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -6310,15 +6176,6 @@
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
@@ -6447,59 +6304,6 @@
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/schema-utils": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 12.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/schema-utils/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/schema-utils/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/schema-utils/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
@@ -7038,18 +6842,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/thingies": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
"integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==",
"license": "Unlicense",
"engines": {
"node": ">=10.18"
},
"peerDependencies": {
"tslib": "^2"
}
},
"node_modules/tiktoken": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.16.tgz",
@@ -7106,22 +6898,6 @@
"node": ">=18"
}
},
"node_modules/tree-dump": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
"integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
"tslib": "2"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
@@ -7425,35 +7201,6 @@
}
}
},
"node_modules/webpack-dev-middleware": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz",
"integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==",
"license": "MIT",
"dependencies": {
"colorette": "^2.0.10",
"memfs": "^4.6.0",
"mime-types": "^2.1.31",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"webpack": {
"optional": true
}
}
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",

View File

@@ -57,7 +57,6 @@
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"webpack": "^5.95.0",
"webpack-dev-middleware": "^7.4.2",
"write-file-atomic": "^5.0.1",
"ws": "^8.17.1",
"yaml": "^2.3.4",
@@ -85,7 +84,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.12.7",
"version": "1.12.8",
"scripts": {
"start": "node server.js",
"start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",

View File

@@ -42,3 +42,9 @@ body.login .userSelect .userHandle {
body.login .userSelect:hover {
background-color: var(--black30a);
}
body.login #handleEntryBlock,
body.login #passwordEntryBlock,
body.login #passwordRecoveryBlock {
margin: 2px;
}

View File

@@ -820,8 +820,8 @@
</div>
</div>
<div class="range-block m-t-1">
<div class="range-block-title openai_restorable" data-i18n="World Info Format Template">
<span>World Info format template</span>
<div class="range-block-title openai_restorable">
<span data-i18n="World Info Format Template">World Info format template</span>
<div id="wi_format_restore" data-i18n="[title]Restore default format" title="Restore default format" class="right_menu_button">
<div class="fa-solid fa-clock-rotate-left"></div>
</div>
@@ -1524,15 +1524,15 @@
<div class="">
<label class="checkbox_label" for="early_stopping_textgenerationwebui">
<input type="checkbox" id="early_stopping_textgenerationwebui" />
<small data-i18n="Early Stopping">Early Stopping
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates." title="Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates."></div>
</small>
<small data-i18n="Early Stopping">Early Stopping</small>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates." title="Controls the stopping condition for beam search. If checked, the generation stops as soon as there are '# of Beams' sequences. If not checked, a heuristic is applied and the generation is stopped when it's very unlikely to find better candidates."></div>
</label>
</div>
</div>
</div>
<div data-tg-type="ooba" id="contrastiveSearchBlock" name="contrastiveSearchBlock" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<h4 class="textAlignCenter" data-i18n="Contrastive search">Contrastive Search
<h4 class="textAlignCenter">
<span data-i18n="Contrastive search">Contrastive Search</span>
<div class=" fa-solid fa-circle-info opacity50p " data-i18n="Contrastive_search_txt" title="A sampler that encourages diversity while maintaining coherence, by exploiting the isotropicity of the representation space of most LLMs. For details, see the paper A Contrastive Framework for Neural Text Generation by Su et al. (2022)."></div>
</h4>
<div class="alignitemscenter flex-container flexFlowColumn wide100p gap0">
@@ -1566,9 +1566,8 @@
</label>
<label data-tg-type="vllm, aphrodite, infermaticai" class="checkbox_label" for="ignore_eos_token_textgenerationwebui">
<input type="checkbox" id="ignore_eos_token_textgenerationwebui" />
<small data-i18n="Ignore EOS Token">Ignore EOS Token
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ignore the EOS Token even if it generates." title="Ignore the EOS Token even if it generates."></div>
</small>
<small data-i18n="Ignore EOS Token">Ignore EOS Token</small>
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Ignore the EOS Token even if it generates." title="Ignore the EOS Token even if it generates."></div>
</label>
<label class="checkbox_label flexGrow flexShrink" for="skip_special_tokens_textgenerationwebui">
<input type="checkbox" id="skip_special_tokens_textgenerationwebui" />
@@ -1629,7 +1628,8 @@
</div>
<div id="cfg_block_ooba" data-tg-type="ooba, tabby" class="wide100p">
<hr class="width100p">
<h4 data-i18n="CFG" class="textAlignCenter">CFG
<h4 class="textAlignCenter">
<span data-i18n="CFG">CFG</span>
<div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]Classifier Free Guidance. More helpful tip coming soon" title="Classifier Free Guidance. More helpful tip coming soon."></div>
</h4>
<div class="alignitemscenter flex-container flexFlowColumn flexShrink gap0">
@@ -1959,23 +1959,16 @@
Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.
</span>
</div>
<div id="claude_human_sysprompt_message_block" class="wide100p">
<div class="range-block-title openai_restorable">
<span data-i18n="User first message">User first message</span>
<div id="claude_human_sysprompt_message_restore" title="Restore User first message" data-i18n="[title]Restore User first message" class="right_menu_button">
<div class="fa-solid fa-clock-rotate-left"></div>
</div>
</div>
<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>
</div>
</div>
</div>
<div class="range-block m-t-1" data-source="openai,openrouter,scale,custom">
<div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">
Logit Bias
</div>
<div class="toggle-description justifyLeft" data-i18n="Helps to ban or reenforce the usage of certain words">
Helps to ban or reinforce the usage of certain tokens. Confirm token parsing with <a target="_blank" href="https://platform.openai.com/tokenizer/">Tokenizer</a>.
<div class="toggle-description justifyLeft">
<span data-i18n="Helps to ban or reenforce the usage of certain words">Helps to ban or reinforce the usage of certain tokens.</span>
<span data-i18n="Confirm token parsing with">Confirm token parsing with</span>
<a target="_blank" href="https://platform.openai.com/tokenizer/" data-i18n="Tokenizer">Tokenizer</a>.
</div>
<div class="openai_logit_bias_preset_form">
<select id="openai_logit_bias_preset">
@@ -2276,8 +2269,8 @@
<div data-tg-type="mancer" class="flex-container flexFlowColumn">
<div class="flex-container flexFlowColumn">
</div>
<h4 data-i18n="Mancer API key">
Mancer API key
<h4>
<span data-i18n="Mancer API key">Mancer API key</span>
<a href="https://mancer.tech/" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
@@ -2573,15 +2566,21 @@
<input id="koboldcpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="koboldcpp">
</div>
</div>
<div class="flex-container flexFlowColumn marginTopBot5">
<label data-tg-type="ooba" class="checkbox_label" for="bypass_status_check_textgenerationwebui">
<input type="checkbox" id="bypass_status_check_textgenerationwebui" />
<span data-i18n="Bypass status check">Bypass status check</span>
</label>
<label data-tg-type="koboldcpp, llamacpp" class="checkbox_label" for="context_size_derived">
<input type="checkbox" id="context_size_derived" />
<span data-i18n="Derive context size from backend">Derive context size from backend</span>
</label>
</div>
<div class="flex-container">
<div id="api_button_textgenerationwebui" class="api_button menu_button menu_button_icon" type="submit" data-i18n="Connect" data-server-connect="ooba_blocking,vllm,aphrodite,tabby,koboldcpp,ollama,llamacpp,huggingface">Connect</div>
<div data-tg-type="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div>
<div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div>
</div>
<label data-tg-type="ooba" class="checkbox_label margin-bot-10px" for="bypass_status_check_textgenerationwebui">
<input type="checkbox" id="bypass_status_check_textgenerationwebui" />
<span data-i18n="Bypass status check">Bypass status check</span>
</label>
</form>
<div class="online_status">
<div class="online_status_indicator"></div>
@@ -2624,8 +2623,9 @@
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Saved addresses and passwords.">
Saved addresses and passwords.<br>
Saved addresses and passwords.
</span>
<br>
</div>
<div class="openai_logit_bias_preset_form">
<select id="openai_proxy_preset">
@@ -2638,8 +2638,9 @@
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="This will show up as your saved preset.">
This will show up as your saved preset.<br>
This will show up as your saved preset.
</span>
<br>
</div>
<div class="wide100p">
<input id="openai_reverse_proxy_name" type="text" class="text_pole" placeholder="..." />
@@ -2649,8 +2650,9 @@
</div>
<div class="toggle-description justifyLeft wide100p">
<span data-i18n="Alternative server URL (leave empty to use the default value).">
Alternative server URL (leave empty to use the default value).<br>
Alternative server URL (leave empty to use the default value).
</span>
<br>
</div>
<div class="wide100p">
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" />
@@ -2663,8 +2665,9 @@
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Will be used as a password for the proxy instead of API key.">
Will be used as a password for the proxy instead of API key.<br>
Will be used as a password for the proxy instead of API key.
</span>
<br>
</div>
<div class="flex-container width100p">
<input id="openai_proxy_password" type="password" class="text_pole flex1" placeholder="" form="openai_form" autocomplete="off" />
@@ -3281,7 +3284,7 @@
Advanced Formatting
</span>
<a href="https://docs.sillytavern.app/usage/prompts/" class="notes-link" target="_blank">
<a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h3>
@@ -3743,7 +3746,7 @@
</div>
<h3 class="margin0">
<span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>
<a href="https://docs.sillytavern.app/usage/worldinfo/" class="notes-link" target="_blank">
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h3>
@@ -3752,7 +3755,11 @@
<div id="wiTopBlock" class="flex-container">
<div id="WIMultiSelector" class="flex1 flex alignSelfStart range-block">
<div class="range-block-title justifyLeft">
<span data-i18n="Active World(s) for all chats"><small>Active World(s) for all chats</small></span>
<span>
<small data-i18n="Active World(s) for all chats">
Active World(s) for all chats
</small>
</span>
</div>
<div class="range-block-range">
<select id="world_info" class="select2_multi_sameline" multiple>
@@ -3937,7 +3944,13 @@
<div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">
<div class="flex-container">
<div class="flex-container flexnowrap alignItemsBaseline">
<h3 class="margin0"><span data-i18n="User Settings">User Settings</span></h3>
<h3 class="margin0">
<span data-i18n="User Settings">User Settings</span>
<a href="https://docs.sillytavern.app/usage/user_settings/" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h3>
</div>
</div>
<div id="UI-language-block" class="flex-container alignItemsBaseline">
@@ -4071,7 +4084,10 @@
<div class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small>
<span data-i18n="Chat Width">Chat Width <i class="fa-solid fa-desktop"></i></span>
<span>
<span data-i18n="Chat Width">Chat Width</span>
<i class="fa-solid fa-desktop"></i>
</span>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Width of the main chat window in % of screen width" title="Width of the main chat window in % of screen width"></div>
</small>
<input class="neo-range-slider" type="range" id="chat_width_slider" name="chat_width_slider" min="25" max="100" step="1">
@@ -4243,7 +4259,7 @@
<div id="reload_chat" class="menu_button whitespacenowrap" data-i18n="[title]Reload and redraw the currently open chat" title="Reload and redraw the currently open chat.">
<small data-i18n="Reload Chat">Reload Chat</small>
</div>
<div id="debug_menu" class="menu_button whitespacenowrap" data-i18n="Debug Menu">
<div id="debug_menu" class="menu_button whitespacenowrap">
<small data-i18n="Debug Menu">Debug Menu</small>
</div>
</div>
@@ -4270,9 +4286,6 @@
<audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>
<span>
<small data-i18n="Message Sound">Message Sound</small>
<a href="https://docs.sillytavern.app/usage/user_settings/uicustomization/#message-sound" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</span>
</label>
<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">
@@ -4365,8 +4378,10 @@
</div>
</div>
<div id="examples-behavior-block">
<label data-i18n="Example Messages Behavior">
<small>Example Messages Behavior:</small>
<label>
<small data-i18n="Example Messages Behavior">
Example Messages Behavior:
</small>
</label>
<select id="example_messages_behavior">
<option value="normal" data-i18n="Gradual push-out">Gradual push-out</option>
@@ -4530,7 +4545,9 @@
</label>
<div class="flex-container">
<div class="flex1" title="Determines how entries are found for autocomplete." data-i18n="[title]Determines how entries are found for autocomplete.">
<label for="stscript_matching" data-i18n="Autocomplete Matching"><small>Matching</small></label>
<label for="stscript_matching">
<small data-i18n="Autocomplete Matching">Matching</small>
</label>
<select id="stscript_matching">
<option data-i18n="Starts with" value="strict">Starts with</option>
<option data-i18n="Includes" value="includes">Includes</option>
@@ -4538,7 +4555,9 @@
</select>
</div>
<div class="flex1" title="Sets the style of the autocomplete." data-i18n="[title]Sets the style of the autocomplete.">
<label for="stscript_autocomplete_style" data-i18n="Autocomplete Style"><small>Style</small></label>
<label for="stscript_autocomplete_style">
<small data-i18n="Autocomplete Style">Style</small>
</label>
<div class="flex-container flexFlowRow alignItemsBaseline">
<select id="stscript_autocomplete_style">
<option data-i18n="Follow Theme" value="theme">Follow Theme</option>
@@ -4550,8 +4569,8 @@
</div>
</div>
<div title="Determines which keys select an item from the AutoComplete suggestions">
<label data-i18n="Keyboard">
<small>Keyboard:</small>
<label>
<small data-i18n="Keyboard">Keyboard:</small>
</label>
<select id="stscript_autocomplete_select">
<option value="3" data-i18n="Select with Tab or Enter">Select with Tab or Enter</option>
@@ -4565,7 +4584,9 @@
<input class="neo-range-input" type="number" min="0.5" max="2" step="0.01" data-for="stscript_autocomplete_font_scale" id="stscript_autocomplete_font_scale_counter">
</div>
<div title="Sets the width of the autocomplete." data-i18n="[title]Sets the width of the autocomplete.">
<label for="stscript_autocomplete_width" data-i18n="Autocomplete Width"><small>Width</small></label>
<label for="stscript_autocomplete_width">
<small data-i18n="Autocomplete Width">Width</small>
</label>
<div class="doubleRangeContainer">
<div class="doubleRangeInputContainer">
<input type="range" id="stscript_autocomplete_width_left" min="0" max="2" step="1">
@@ -4593,14 +4614,18 @@
<label><small data-i18n="Parser Flags">Parser Flags</small></label>
<label class="checkbox_label" title="Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well." data-i18n="[title]Switch to stricter escaping, allowing all delimiting characters to be escaped with a backslash, and backslashes to be escaped as well.">
<input id="stscript_parser_flag_strict_escaping" type="checkbox" />
<span data-i18n="STRICT_ESCAPING"><small>STRICT_ESCAPING</small></span>
<span>
<small data-i18n="STRICT_ESCAPING">STRICT_ESCAPING</small>
</span>
<a href="https://docs.sillytavern.app/usage/st-script/#strict-escaping" target="_blank" class="notes-link">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</label>
<label class="checkbox_label" title="Prevents {{getvar::}} {{getglobalvar::}} macros from having literal macro-like values auto-evaluated.&NewLine;e.g. &quot;{{newline}}&quot; remains as literal string &quot;{{newline}}&quot;&NewLine;&NewLine;(This is done by internally replacing {{getvar::}} {{getglobalvar::}} macros with scoped variables.)" data-i18n="[title]stscript_parser_flag_replace_getvar_label">
<input id="stscript_parser_flag_replace_getvar" type="checkbox" />
<span data-i18n="REPLACE_GETVAR"><small>REPLACE_GETVAR</small></span>
<span>
<small data-i18n="REPLACE_GETVAR">REPLACE_GETVAR</small>
</span>
<a href="https://docs.sillytavern.app/usage/st-script/#replace-variable-macros" target="_blank" class="notes-link">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
@@ -4731,10 +4756,12 @@
<div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
<div class="flex-container alignItemsBaseline wide100p">
<div class="flex1 flex-container alignItemsBaseline">
<h3 class="margin0" data-i18n="Persona Management">Persona Management</h3>
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank" data-i18n="How do I use this?">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
<h3 class="margin0" >
<span data-i18n="Persona Management">Persona Management</span>
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h3>
</div>
<div class="flex-container">
<div class="menu_button menu_button_icon user_stats_button" data-i18n="[title]Click for stats!" title="Click for stats!">
@@ -5455,7 +5482,12 @@
<span data-i18n="Examples of dialogue" class="mdhotkey_location">Examples of dialogue</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="mes_example_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
</h4>
<h5 data-i18n="Important to set the character's writing style.">Important to set the character's writing style. <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#examples-of-dialogue" class="notes-link" target="_blank"><span class="fa-solid fa-circle-question note-link-span"></span></a></h5>
<h5>
<span data-i18n="Important to set the character's writing style.">Important to set the character's writing style.</span>
<a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#examples-of-dialogue" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h5>
</div>
<textarea id="mes_example_textarea" class="flexGrow mdHotkeys" name="mes_example" data-i18n="[placeholder](Examples of chat dialog. Begin each example with START on a new line.)" placeholder="(Examples of chat dialog. Begin each example with &lt;START&gt; on a new line.)" form="form_create" rows="6"></textarea>
<div class="extension_token_counter">
@@ -5531,7 +5563,8 @@
<div class="character_world range-block flexFlowColumn flex-container">
<div class="range-block-title">
<h3>
<span data-i18n="Select a World Info file for"> Select a World Info file for <span class="character_name"></span></span>:
<span data-i18n="Select a World Info file for">Select a World Info file for</span>
<span class="character_name"></span>:
</h3>
</div>
<h4 data-i18n="Primary Lorebook">Primary Lorebook</h4>
@@ -5832,7 +5865,7 @@
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Sticky entries will stay active for N messages after being triggered." title="Sticky entries will stay active for N messages after being triggered.">
<div class="flex-container justifySpaceBetween marginBot5">
<small class="flex-container alignItemsBaseline" for="sticky" data-i18n="Sticky">
<small class="flex-container alignItemsBaseline" for="sticky">
<span data-i18n="Sticky">
Sticky
</span>
@@ -5845,7 +5878,7 @@
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a cooldown can't be activated N messages after being triggered." title="Entries with a cooldown can't be activated N messages after being triggered.">
<div class="flex-container justifySpaceBetween marginBot5">
<small class="flex-container alignItemsBaseline" for="cooldown" data-i18n="Cooldown">
<small class="flex-container alignItemsBaseline" for="cooldown">
<span data-i18n="Cooldown">
Cooldown
</span>
@@ -5858,7 +5891,7 @@
</div>
<div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a delay can't be activated until there are N messages present in the chat." title="Entries with a delay can't be activated until there are N messages present in the chat.">
<div class="flex-container justifySpaceBetween marginBot5">
<small class="flex-container alignItemsBaseline" for="delay" data-i18n="Delay">
<small class="flex-container alignItemsBaseline" for="delay">
<span data-i18n="Delay">
Delay
</span>

View File

@@ -1238,8 +1238,9 @@ async function getStatusTextgen() {
const wantsInstructDerivation = (power_user.instruct.enabled && power_user.instruct.derived);
const wantsContextDerivation = power_user.context_derived;
const wantsContextSize = power_user.context_size_derived;
const supportsChatTemplate = [textgen_types.KOBOLDCPP, textgen_types.LLAMACPP].includes(textgen_settings.type);
if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation)) {
if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation || wantsContextSize)) {
const response = await fetch('/api/backends/text-completions/props', {
method: 'POST',
headers: getRequestHeaders(),
@@ -1253,6 +1254,17 @@ async function getStatusTextgen() {
const data = await response.json();
if (data) {
const { chat_template, chat_template_hash } = data;
if (wantsContextSize && 'default_generation_settings' in data) {
const backend_max_context = data['default_generation_settings']['n_ctx'];
const old_value = max_context;
if (max_context !== backend_max_context) {
setGenerationParamsFromPreset({ max_length: backend_max_context });
}
if (old_value !== max_context) {
console.log(`Auto-switched max context from ${old_value} to ${max_context}`);
toastr.info(`${old_value}${max_context}`, 'Context Size Changed');
}
}
console.log(`We have chat template ${chat_template.split('\n')[0]}...`);
const templates = await deriveTemplatesFromChatTemplate(chat_template, chat_template_hash);
if (templates) {
@@ -1621,7 +1633,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
subEntities = filterByTagState(entities, { subForEntity: entity });
if (doFilter) {
// sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up
subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } });
subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
}
if (doSort) {
sortEntitiesList(subEntities);
@@ -1634,11 +1646,11 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
// Second run filters, hiding whatever should be filtered later
if (doFilter) {
const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true });
entities = entitiesFilter.applyFilters(beforeFinalEntities);
entities = entitiesFilter.applyFilters(beforeFinalEntities, { clearFuzzySearchCaches: false });
// Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters.
if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) {
entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } });
entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false });
}
}
@@ -1654,6 +1666,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
if (doSort) {
sortEntitiesList(entities);
}
entitiesFilter.clearFuzzySearchCaches();
return entities;
}
@@ -6821,6 +6834,10 @@ export async function saveSettings(type) {
});
}
/**
* Sets the generation parameters from a preset object.
* @param {{ genamt?: number, max_length?: number }} preset Preset object
*/
export function setGenerationParamsFromPreset(preset) {
const needsUnlock = (preset.max_length ?? max_context) > MAX_CONTEXT_DEFAULT || (preset.genamt ?? amount_gen) > MAX_RESPONSE_DEFAULT;
$('#max_context_unlocked').prop('checked', needsUnlock).trigger('change');

View File

@@ -53,6 +53,12 @@ const hash_derivations = {
// command-r-08-2024
'Command R'
,
// Tulu
'ac7498a36a719da630e99d48e6ebc4409de85a77556c2b6159eeb735bcbd11df':
// Tulu-3-8B
// Tulu-3-70B
'Tulu'
};
const substr_derivations = {

View File

@@ -40,7 +40,7 @@
<div class="expression_fallback_block m-b-1 m-t-1">
<label for="expression_fallback" data-i18n="Default / Fallback Expression">Default / Fallback Expression</label>
<small data-i18n="Set the default and fallback expression being used when no matching expression is found.">Set the default and fallback expression being used when no matching expression is found.</small>
<select id="expression_fallback" class="flex1 margin0" data-i18n="Fallback Expression" placeholder="Fallback Expression"></select>
<select id="expression_fallback" class="flex1 margin0"></select>
</div>
<div class="expression_custom_block m-b-1 m-t-1">
<label for="expression_custom" data-i18n="Custom Expressions">Custom Expressions</label>

View File

@@ -419,30 +419,35 @@ export class SlashCommandHandler {
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'QR set name',
description: 'Name of QR set to add the context menu to',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
SlashCommandNamedArgument.fromProps({
name: 'label',
description: 'Quick Reply label',
description: 'Label of Quick Reply to add the context menu to',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries,
}),
SlashCommandNamedArgument.fromProps({
name: 'id',
description: 'numeric ID of the QR, e.g., id=42',
description: 'Numeric ID of Quick Reply to add the context menu to, e.g. id=42',
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: localEnumProviders.qrIds,
}),
new SlashCommandNamedArgument(
'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
'chain',
'If true, button QR is sent together with (before) the clicked QR from the context menu',
[ARGUMENT_TYPE.BOOLEAN],
false,
false,
'false',
),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'QR set name',
description: 'Name of QR set to add as a context menu',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
@@ -450,13 +455,16 @@ export class SlashCommandHandler {
],
helpString: `
<div>
Add context menu preset to a QR.
Add a context menu preset to a QR.
</div>
<div>
If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/qr-contextadd set=MyPreset label=MyButton chain=true MyOtherPreset</code></pre>
<pre><code>/qr-contextadd set=MyQRSetWithTheButton label=MyButton chain=true MyQRSetWithContextItems</code></pre>
</li>
</ul>
</div>
@@ -470,27 +478,27 @@ export class SlashCommandHandler {
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'set',
description: 'QR set name',
description: 'Name of QR set to remove the context menu from',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
}),
SlashCommandNamedArgument.fromProps({
name: 'label',
description: 'Quick Reply label',
description: 'Label of Quick Reply to remove the context menu from',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: localEnumProviders.qrEntries,
}),
SlashCommandNamedArgument.fromProps({
name: 'id',
description: 'numeric ID of the QR, e.g., id=42',
description: 'Numeric ID of Quick Reply to remove the context menu from, e.g. id=42',
typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: localEnumProviders.qrIds,
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'QR set name',
description: 'Name of QR set to remove',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.qrSets,
@@ -500,6 +508,9 @@ export class SlashCommandHandler {
<div>
Remove context menu preset from a QR.
</div>
<div>
If <code>id</code> and <code>label</code> are both provided, <code>id</code> will be used.
</div>
<div>
<strong>Example:</strong>
<ul>
@@ -541,6 +552,9 @@ export class SlashCommandHandler {
<div>
Remove all context menu presets from a QR.
</div>
<div>
If <code>id</code> and a label are both provided, <code>id</code> will be used.
</div>
<div>
<strong>Example:</strong>
<ul>
@@ -908,12 +922,11 @@ export class SlashCommandHandler {
}
}
createContextItem(args, name) {
try {
this.api.createContextItem(
args.set,
args.label,
args.id !== undefined ? Number(args.id) : args.label,
name,
isTrueBoolean(args.chain),
);
@@ -923,14 +936,14 @@ export class SlashCommandHandler {
}
deleteContextItem(args, name) {
try {
this.api.deleteContextItem(args.set, args.label, name);
this.api.deleteContextItem(args.set, args.id !== undefined ? Number(args.id) : args.label, name);
} catch (ex) {
toastr.error(ex.message);
}
}
clearContextMenu(args, label) {
try {
this.api.clearContextMenu(args.set, args.label ?? label);
this.api.clearContextMenu(args.set, args.id !== undefined ? Number(args.id) : args.label ?? label);
} catch (ex) {
toastr.error(ex.message);
}

View File

@@ -19,7 +19,7 @@ export class ContextMenu {
this.itemList = this.build(qr).children;
this.itemList.forEach(item => {
item.onExpand = () => {
this.itemList.filter(it => it != item)
this.itemList.filter(it => it !== item)
.forEach(it => it.collapse());
};
});
@@ -36,6 +36,7 @@ export class ContextMenu {
icon: qr.icon,
showLabel: qr.showLabel,
label: qr.label,
title: qr.title,
message: (chainedMessage && qr.message ? `${chainedMessage} | ` : '') + qr.message,
children: [],
};
@@ -45,12 +46,29 @@ export class ContextMenu {
const nextHierarchy = [...hierarchy, cl.set];
const nextLabelHierarchy = [...labelHierarchy, tree.label];
tree.children.push(new MenuHeader(cl.set.name));
cl.set.qrList.forEach(subQr => {
// If the Quick Reply's own set is added as a context menu,
// show only the sub-QRs that are Invisible but have an icon
// intent: allow a QR set to be assigned to one of its own QR buttons for a "burger" menu
// with "UI" QRs either in the bar or in the menu, and "library function" QRs still hidden.
// - QRs already visible on the bar are filtered out,
// - hidden QRs without an icon are filtered out,
// - hidden QRs **with an icon** are shown in the menu
// so everybody is happy
const qrsOwnSetAddedAsContextMenu = cl.set.qrList.includes(qr);
const visible = (subQr) => {
return qrsOwnSetAddedAsContextMenu
? subQr.isHidden && !!subQr.icon // yes .isHidden gets inverted here
: !subQr.isHidden;
};
cl.set.qrList.filter(visible).forEach(subQr => {
const subTree = this.build(subQr, cl.isChained ? tree.message : null, nextHierarchy, nextLabelHierarchy);
tree.children.push(new MenuItem(
subTree.icon,
subTree.showLabel,
subTree.label,
subTree.title,
subTree.message,
(evt) => {
evt.stopPropagation();

View File

@@ -2,7 +2,7 @@ import { MenuItem } from './MenuItem.js';
export class MenuHeader extends MenuItem {
constructor(/**@type {String}*/label) {
super(null, null, label, null, null);
super(null, null, label, null, null, null, []);
}

View File

@@ -4,11 +4,11 @@ export class MenuItem {
/**@type {string}*/ icon;
/**@type {boolean}*/ showLabel;
/**@type {string}*/ label;
/**@type {string}*/ title;
/**@type {object}*/ value;
/**@type {function}*/ callback;
/**@type {MenuItem[]}*/ childList = [];
/**@type {SubMenu}*/ subMenu;
/**@type {boolean}*/ isForceExpanded = false;
/**@type {HTMLElement}*/ root;
@@ -19,17 +19,19 @@ export class MenuItem {
/**
*
* @param {string} icon
* @param {boolean} showLabel
* @param {?string} icon
* @param {?boolean} showLabel
* @param {string} label
* @param {?string} title Tooltip
* @param {object} value
* @param {function} callback
* @param {MenuItem[]} children
*/
constructor(icon, showLabel, label, value, callback, children = []) {
constructor(icon, showLabel, label, title, value, callback, children = []) {
this.icon = icon;
this.showLabel = showLabel;
this.label = label;
this.title = title;
this.value = value;
this.callback = callback;
this.childList = children;
@@ -42,12 +44,15 @@ export class MenuItem {
this.root = item;
item.classList.add('list-group-item');
item.classList.add('ctx-item');
item.title = this.value;
// if a title/tooltip is set, add it, otherwise use the QR content
// same as for the main QR list
item.title = this.title || this.value;
if (this.callback) {
item.addEventListener('click', (evt) => this.callback(evt, this));
}
const icon = document.createElement('div'); {
this.domIcon = icon;
icon.classList.add('qr--button-icon');
icon.classList.add('fa-solid');
if (!this.icon) icon.classList.add('qr--hidden');
@@ -55,7 +60,6 @@ export class MenuItem {
item.append(icon);
}
const lbl = document.createElement('div'); {
this.domLabel = lbl;
lbl.classList.add('qr--button-label');
if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden');
lbl.textContent = this.label;

View File

@@ -174,6 +174,9 @@
position: absolute;
overflow: visible;
}
.ctx-menu .ctx-item .qr--hidden {
display: none;
}
.list-group .list-group-item.ctx-header {
font-weight: bold;
cursor: default;

View File

@@ -176,6 +176,10 @@
overflow: visible;
}
.ctx-menu .ctx-item .qr--hidden {
display: none;
}
.list-group .list-group-item.ctx-header {
font-weight: bold;
cursor: default;

View File

@@ -57,7 +57,7 @@
</div>
<div id="playback_rate_block" class="range-block">
<hr>
<div class="range-block-title justifyLeft" data-i18n="Audio Playback Speed">
<div class="range-block-title justifyLeft">
<small data-i18n="Audio Playback Speed">Audio Playback Speed</small>
</div>
<div class="range-block-range-and-counter">
@@ -80,4 +80,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@@ -55,6 +55,19 @@ export function isFilterState(a, b) {
return aKey === bKey;
}
/**
* The fuzzy search categories
* @type {{ characters: string, worldInfo: string, personas: string, tags: string, groups: string }}
*/
export const fuzzySearchCategories = Object.freeze({
characters: 'characters',
worldInfo: 'worldInfo',
personas: 'personas',
tags: 'tags',
groups: 'groups',
});
/**
* Helper class for filtering data.
* @example
@@ -72,6 +85,12 @@ export class FilterHelper {
*/
scoreCache;
/**
* Cache for fuzzy search results per category.
* @type {Object.<string, { resultMap: Map<string, any> }>}
*/
fuzzySearchCaches;
/**
* Creates a new FilterHelper
* @param {Function} onDataChanged Callback to trigger when the filter data changes
@@ -79,6 +98,13 @@ export class FilterHelper {
constructor(onDataChanged) {
this.onDataChanged = onDataChanged;
this.scoreCache = new Map();
this.fuzzySearchCaches = {
[fuzzySearchCategories.characters]: { resultMap: new Map() },
[fuzzySearchCategories.worldInfo]: { resultMap: new Map() },
[fuzzySearchCategories.personas]: { resultMap: new Map() },
[fuzzySearchCategories.tags]: { resultMap: new Map() },
[fuzzySearchCategories.groups]: { resultMap: new Map() },
};
}
/**
@@ -151,7 +177,7 @@ export class FilterHelper {
return data;
}
const fuzzySearchResults = fuzzySearchWorldInfo(data, term);
const fuzzySearchResults = fuzzySearchWorldInfo(data, term, this.fuzzySearchCaches);
this.cacheScores(FILTER_TYPES.WORLD_INFO_SEARCH, new Map(fuzzySearchResults.map(i => [i.item?.uid, i.score])));
const filteredData = data.filter(entity => fuzzySearchResults.find(x => x.item === entity));
@@ -170,7 +196,7 @@ export class FilterHelper {
return data;
}
const fuzzySearchResults = fuzzySearchPersonas(data, term);
const fuzzySearchResults = fuzzySearchPersonas(data, term, this.fuzzySearchCaches);
this.cacheScores(FILTER_TYPES.PERSONA_SEARCH, new Map(fuzzySearchResults.map(i => [i.item.key, i.score])));
const filteredData = data.filter(name => fuzzySearchResults.find(x => x.item.key === name));
@@ -289,9 +315,9 @@ export class FilterHelper {
// Save fuzzy search results and scores if enabled
if (power_user.fuzzy_search) {
const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue);
const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue);
const fuzzySearchTagsResult = fuzzySearchTags(searchValue);
const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue, this.fuzzySearchCaches);
const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue, this.fuzzySearchCaches);
const fuzzySearchTagsResult = fuzzySearchTags(searchValue, this.fuzzySearchCaches);
this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchCharactersResults.map(i => [`character.${i.refIndex}`, i.score])));
this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchGroupsResults.map(i => [`group.${i.item.id}`, i.score])));
this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchTagsResult.map(i => [`tag.${i.item.id}`, i.score])));
@@ -343,11 +369,14 @@ export class FilterHelper {
* @param {object} options - Optional call parameters
* @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared.
* @param {Object.<FilterType, any>} [options.tempOverrides={}] - Temporarily override specific filters for this filter application
* @param {boolean} [options.clearFuzzySearchCaches=true] - Whether the fuzzy search caches should be cleared.
* @returns {any[]} The filtered data.
*/
applyFilters(data, { clearScoreCache = true, tempOverrides = {} } = {}) {
applyFilters(data, { clearScoreCache = true, tempOverrides = {}, clearFuzzySearchCaches = true } = {}) {
if (clearScoreCache) this.clearScoreCache();
if (clearFuzzySearchCaches) this.clearFuzzySearchCaches();
// Save original filter states
const originalStates = {};
for (const key in tempOverrides) {
@@ -411,4 +440,14 @@ export class FilterHelper {
this.scoreCache = new Map();
}
}
/**
* Clears fuzzy search caches
*/
clearFuzzySearchCaches() {
for (const cache of Object.values(this.fuzzySearchCaches)) {
cache.resultMap.clear();
}
console.log('All fuzzy search caches cleared');
}
}

View File

@@ -99,7 +99,6 @@ const default_wi_format = '{0}';
const default_new_chat_prompt = '[Start a new Chat]';
const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]';
const default_new_example_chat_prompt = '[Example Chat]';
const default_claude_human_sysprompt_message = 'Let\'s get started. Please generate your response based on the information and instructions provided above.';
const 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}}]';
const default_bias = 'Default (none)';
const default_personality_format = '[{{char}}\'s personality: {{personality}}]';
@@ -276,7 +275,6 @@ const default_settings = {
proxy_password: '',
assistant_prefill: '',
assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message,
claude_use_sysprompt: false,
use_makersuite_sysprompt: true,
use_alt_scale: false,
@@ -353,7 +351,6 @@ const oai_settings = {
proxy_password: '',
assistant_prefill: '',
assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message,
claude_use_sysprompt: false,
use_makersuite_sysprompt: true,
use_alt_scale: false,
@@ -1892,7 +1889,6 @@ async function sendOpenAIRequest(type, messages, signal) {
generate_data['top_k'] = Number(oai_settings.top_k_openai);
generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;
generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
// Don't add a prefill on quiet gens (summarization) and when using continue prefill.
if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {
generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
@@ -3030,7 +3026,6 @@ function loadOpenAISettings(data, settings) {
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation;
oai_settings.human_sysprompt_message = settings.human_sysprompt_message ?? default_settings.human_sysprompt_message;
oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;
@@ -3070,7 +3065,6 @@ function loadOpenAISettings(data, settings) {
$('#openai_proxy_password').val(oai_settings.proxy_password);
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);
$('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
$('#openai_image_inlining').prop('checked', oai_settings.image_inlining);
$('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);
@@ -3400,7 +3394,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
show_external_models: settings.show_external_models,
assistant_prefill: settings.assistant_prefill,
assistant_impersonation: settings.assistant_impersonation,
human_sysprompt_message: settings.human_sysprompt_message,
claude_use_sysprompt: settings.claude_use_sysprompt,
use_makersuite_sysprompt: settings.use_makersuite_sysprompt,
use_alt_scale: settings.use_alt_scale,
@@ -3825,7 +3818,6 @@ function onSettingsPresetChange() {
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],
human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false],
claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true],
use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true],
use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true],
@@ -4677,10 +4669,6 @@ function toggleChatCompletionForms() {
const validSources = $(this).data('source').split(',');
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
});
if (chat_completion_sources.CLAUDE == oai_settings.chat_completion_source) {
$('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
}
}
async function testApiConnection() {
@@ -5036,7 +5024,6 @@ export function initOpenAI() {
$('#claude_use_sysprompt').on('change', function () {
oai_settings.claude_use_sysprompt = !!$('#claude_use_sysprompt').prop('checked');
$('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
saveSettingsDebounced();
});
@@ -5113,12 +5100,6 @@ export function initOpenAI() {
saveSettingsDebounced();
});
$('#claude_human_sysprompt_message_restore').on('click', function () {
oai_settings.human_sysprompt_message = default_claude_human_sysprompt_message;
$('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
saveSettingsDebounced();
});
$('#newgroupchat_prompt_restore').on('click', function () {
oai_settings.new_group_chat_prompt = default_new_group_chat_prompt;
$('#newgroupchat_prompt_textarea').val(oai_settings.new_group_chat_prompt);
@@ -5210,11 +5191,6 @@ export function initOpenAI() {
saveSettingsDebounced();
});
$('#claude_human_sysprompt_textarea').on('input', function () {
oai_settings.human_sysprompt_message = String($('#claude_human_sysprompt_textarea').val());
saveSettingsDebounced();
});
$('#openrouter_use_fallback').on('input', function () {
oai_settings.openrouter_use_fallback = !!$(this).prop('checked');
saveSettingsDebounced();

View File

@@ -53,6 +53,7 @@ import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandE
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
export {
loadPowerUserSettings,
@@ -245,6 +246,7 @@ let power_user = {
},
context_derived: false,
context_size_derived: false,
sysprompt: {
enabled: true,
@@ -1481,6 +1483,7 @@ async function loadPowerUserSettings(settings, data) {
$('#example_messages_behavior').val(getExampleMessagesBehavior());
$(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);
$('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived);
$('#context_size_derived').prop('checked', !!power_user.context_size_derived);
$('#console_log_prompts').prop('checked', power_user.console_log_prompts);
$('#request_token_probabilities').prop('checked', power_user.request_token_probabilities);
@@ -1829,27 +1832,28 @@ async function loadContextSettings() {
});
}
/**
* Fuzzy search characters by a search term
* Common function to perform fuzzy search with optional caching
* @param {string} type - Type of search from fuzzySearchCategories
* @param {any[]} data - Data array to search in
* @param {Array<{name: string, weight: number, getFn?: Function}>} keys - Fuse.js keys configuration
* @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
*/
export function fuzzySearchCharacters(searchValue) {
function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) {
// Check cache if provided
if (fuzzySearchCaches) {
const cache = fuzzySearchCaches[type];
if (cache?.resultMap.has(searchValue)) {
return cache.resultMap.get(searchValue);
}
}
// @ts-ignore
const fuse = new Fuse(characters, {
keys: [
{ name: 'data.name', weight: 20 },
{ name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },
{ name: 'data.description', weight: 3 },
{ name: 'data.mes_example', weight: 3 },
{ name: 'data.scenario', weight: 2 },
{ name: 'data.personality', weight: 2 },
{ name: 'data.first_mes', weight: 2 },
{ name: 'data.creator_notes', weight: 2 },
{ name: 'data.creator', weight: 1 },
{ name: 'data.tags', weight: 1 },
{ name: 'data.alternate_greetings', weight: 1 },
],
const fuse = new Fuse(data, {
keys: keys,
includeScore: true,
ignoreLocation: true,
useExtendedSearch: true,
@@ -1857,109 +1861,110 @@ export function fuzzySearchCharacters(searchValue) {
});
const results = fuse.search(searchValue);
console.debug('Characters fuzzy search results for ' + searchValue, results);
// Store in cache if provided
if (fuzzySearchCaches) {
fuzzySearchCaches[type].resultMap.set(searchValue, results);
}
return results;
}
/**
* Fuzzy search characters by a search term
* @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
*/
export function fuzzySearchCharacters(searchValue, fuzzySearchCaches = null) {
const keys = [
{ name: 'data.name', weight: 20 },
{ name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },
{ name: 'data.description', weight: 3 },
{ name: 'data.mes_example', weight: 3 },
{ name: 'data.scenario', weight: 2 },
{ name: 'data.personality', weight: 2 },
{ name: 'data.first_mes', weight: 2 },
{ name: 'data.creator_notes', weight: 2 },
{ name: 'data.creator', weight: 1 },
{ name: 'data.tags', weight: 1 },
{ name: 'data.alternate_greetings', weight: 1 },
];
return performFuzzySearch(fuzzySearchCategories.characters, characters, keys, searchValue, fuzzySearchCaches);
}
/**
* Fuzzy search world info entries by a search term
* @param {*[]} data - WI items data array
* @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
*/
export function fuzzySearchWorldInfo(data, searchValue) {
// @ts-ignore
const fuse = new Fuse(data, {
keys: [
{ name: 'key', weight: 20 },
{ name: 'group', weight: 15 },
{ name: 'comment', weight: 10 },
{ name: 'keysecondary', weight: 10 },
{ name: 'content', weight: 3 },
{ name: 'uid', weight: 1 },
{ name: 'automationId', weight: 1 },
],
includeScore: true,
ignoreLocation: true,
useExtendedSearch: true,
threshold: 0.2,
});
export function fuzzySearchWorldInfo(data, searchValue, fuzzySearchCaches = null) {
const keys = [
{ name: 'key', weight: 20 },
{ name: 'group', weight: 15 },
{ name: 'comment', weight: 10 },
{ name: 'keysecondary', weight: 10 },
{ name: 'content', weight: 3 },
{ name: 'uid', weight: 1 },
{ name: 'automationId', weight: 1 },
];
const results = fuse.search(searchValue);
console.debug('World Info fuzzy search results for ' + searchValue, results);
return results;
return performFuzzySearch(fuzzySearchCategories.worldInfo, data, keys, searchValue, fuzzySearchCaches);
}
/**
* Fuzzy search persona entries by a search term
* @param {*[]} data - persona data array
* @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
*/
export function fuzzySearchPersonas(data, searchValue) {
data = data.map(x => ({ key: x, name: power_user.personas[x] ?? '', description: power_user.persona_descriptions[x]?.description ?? '' }));
// @ts-ignore
const fuse = new Fuse(data, {
keys: [
{ name: 'name', weight: 20 },
{ name: 'description', weight: 3 },
],
includeScore: true,
ignoreLocation: true,
useExtendedSearch: true,
threshold: 0.2,
});
export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) {
const mappedData = data.map(x => ({
key: x,
name: power_user.personas[x] ?? '',
description: power_user.persona_descriptions[x]?.description ?? ''
}));
const results = fuse.search(searchValue);
console.debug('Personas fuzzy search results for ' + searchValue, results);
return results;
const keys = [
{ name: 'name', weight: 20 },
{ name: 'description', weight: 3 },
];
return performFuzzySearch(fuzzySearchCategories.personas, mappedData, keys, searchValue, fuzzySearchCaches);
}
/**
* Fuzzy search tags by a search term
* @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
*/
export function fuzzySearchTags(searchValue) {
// @ts-ignore
const fuse = new Fuse(tags, {
keys: [
{ name: 'name', weight: 1 },
],
includeScore: true,
ignoreLocation: true,
useExtendedSearch: true,
threshold: 0.2,
});
export function fuzzySearchTags(searchValue, fuzzySearchCaches = null) {
const keys = [
{ name: 'name', weight: 1 },
];
const results = fuse.search(searchValue);
console.debug('Tags fuzzy search results for ' + searchValue, results);
return results;
return performFuzzySearch(fuzzySearchCategories.tags, tags, keys, searchValue, fuzzySearchCaches);
}
/**
* Fuzzy search groups by a search term
* @param {string} searchValue - The search term
* @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
* @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score
*/
export function fuzzySearchGroups(searchValue) {
// @ts-ignore
const fuse = new Fuse(groups, {
keys: [
{ name: 'name', weight: 20 },
{ name: 'members', weight: 15 },
{ name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },
{ name: 'id', weight: 1 },
],
includeScore: true,
ignoreLocation: true,
useExtendedSearch: true,
threshold: 0.2,
});
export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
const keys = [
{ name: 'name', weight: 20 },
{ name: 'members', weight: 15 },
{ name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },
{ name: 'id', weight: 1 },
];
const results = fuse.search(searchValue);
console.debug('Groups fuzzy search results for ' + searchValue, results);
return results;
return performFuzzySearch(fuzzySearchCategories.groups, groups, keys, searchValue, fuzzySearchCaches);
}
/**
@@ -3076,6 +3081,16 @@ $(document).ready(() => {
$('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived);
});
$('#context_size_derived').on('input', function () {
const value = !!$(this).prop('checked');
power_user.context_size_derived = value;
saveSettingsDebounced();
});
$('#context_size_derived').on('change', function () {
$('#context_size_derived').prop('checked', !!power_user.context_size_derived);
});
$('#always-force-name2-checkbox').change(function () {
power_user.always_force_name2 = !!$(this).prop('checked');
saveSettingsDebounced();

View File

@@ -627,10 +627,6 @@ const tavernUrl = new URL(
(':' + server_port),
);
function prepareFrontendBundle() {
return new Promise((resolve) => webpackMiddleware.waitUntilValid(resolve));
}
/**
* Tasks that need to be run before the server starts listening.
*/
@@ -682,7 +678,7 @@ const preSetupTasks = async function () {
initRequestProxy({ enabled: proxyEnabled, url: proxyUrl, bypass: proxyBypass });
// Wait for frontend libs to compile
await prepareFrontendBundle();
await webpackMiddleware.runWebpackCompiler();
};
/**

View File

@@ -102,7 +102,7 @@ async function sendClaudeRequest(request, response) {
const additionalHeaders = {};
const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;
const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;
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);
const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, request.body.char_name, request.body.user_name);
// Add custom stop sequences
const stopSequences = [];
if (Array.isArray(request.body.stop)) {

View File

@@ -152,7 +152,7 @@ router.post('/status', jsonParser, async function (request, response) {
if (!modelsReply.ok) {
console.log('Models endpoint is offline.');
return response.status(400);
return response.sendStatus(400);
}
/** @type {any} */
@@ -173,7 +173,7 @@ router.post('/status', jsonParser, async function (request, response) {
if (!Array.isArray(data.data)) {
console.log('Models response is not an array.');
return response.status(400);
return response.sendStatus(400);
}
const modelIds = data.data.map(x => x.id);
@@ -224,7 +224,7 @@ router.post('/status', jsonParser, async function (request, response) {
return response.send({ result, data: data.data });
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -244,7 +244,7 @@ router.post('/props', jsonParser, async function (request, response) {
const propsReply = await fetch(propsUrl, args);
if (!propsReply.ok) {
return response.status(400);
return response.sendStatus(400);
}
/** @type {any} */
@@ -258,7 +258,7 @@ router.post('/props', jsonParser, async function (request, response) {
return response.send(props);
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -450,7 +450,7 @@ ollama.post('/download', jsonParser, async function (request, response) {
return response.send({ ok: true });
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -493,7 +493,7 @@ ollama.post('/caption-image', jsonParser, async function (request, response) {
return response.send({ caption });
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -540,7 +540,7 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) {
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -569,7 +569,7 @@ llamacpp.post('/props', jsonParser, async function (request, response) {
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -619,7 +619,7 @@ llamacpp.post('/slots', jsonParser, async function (request, response) {
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});
@@ -665,7 +665,7 @@ tabby.post('/download', jsonParser, async function (request, response) {
return response.send({ ok: true });
} catch (error) {
console.error(error);
return response.status(500);
return response.sendStatus(500);
}
});

View File

@@ -14,7 +14,7 @@ import jimp from 'jimp';
import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
import { jsonParser, urlencodedParser } from '../express-common.js';
import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer } from '../util.js';
import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap } from '../util.js';
import { TavernCardValidator } from '../validator/TavernCardValidator.js';
import { parse, write } from '../character-card-parser.js';
import { readWorldInfoFile } from './worldinfo.js';
@@ -23,7 +23,8 @@ import { importRisuSprites } from './sprites.js';
const defaultAvatarPath = './public/img/ai4.png';
// KV-store for parsed character data
const characterDataCache = new Map();
// 100 MB limit. Would take roughly 3000 characters to reach this limit
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * 100);
// Some Android devices require tighter memory management
const isAndroid = process.platform === 'android';
@@ -58,6 +59,9 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
try {
// Reset the cache
for (const key of characterDataCache.keys()) {
if (Buffer.isBuffer(inputFile)) {
break;
}
if (key.startsWith(inputFile)) {
characterDataCache.delete(key);
break;

View File

@@ -1,20 +1,45 @@
import process from 'node:process';
import path from 'node:path';
import webpack from 'webpack';
import middleware from 'webpack-dev-middleware';
import { publicLibConfig } from '../../webpack.config.js';
export default function getWebpackServeMiddleware() {
const outputPath = publicLibConfig.output?.path;
const outputFile = publicLibConfig.output?.filename;
const compiler = webpack(publicLibConfig);
if (process.env.NODE_ENV === 'production' || process.platform === 'android') {
compiler.hooks.done.tap('serve', () => {
if (compiler.watching) {
compiler.watching.close(() => { });
}
compiler.watchFileSystem = null;
compiler.watchMode = false;
});
/**
* A very spartan recreation of webpack-dev-middleware.
* @param {import('express').Request} req Request object.
* @param {import('express').Response} res Response object.
* @param {import('express').NextFunction} next Next function.
* @type {import('express').RequestHandler}
*/
function devMiddleware(req, res, next) {
if (req.method === 'GET' && path.parse(req.path).base === outputFile) {
return res.sendFile(outputFile, { root: outputPath });
}
next();
}
return middleware(compiler, {});
/**
* Wait until Webpack is done compiling.
* @returns {Promise<void>}
*/
devMiddleware.runWebpackCompiler = () => {
return new Promise((resolve) => {
console.log();
console.log('Compiling frontend libraries...');
compiler.run((_error, stats) => {
const output = stats?.toString(publicLibConfig.stats);
if (output) {
console.log(output);
console.log();
}
resolve();
});
});
};
return devMiddleware;
}

View File

@@ -91,11 +91,10 @@ export function convertClaudePrompt(messages, addAssistantPostfix, addAssistantP
* @param {string} prefillString User determined prefill string
* @param {boolean} useSysPrompt See if we want to use a system prompt
* @param {boolean} useTools See if we want to use tools
* @param {string} humanMsgFix Add Human message between system prompt and assistant.
* @param {string} charName Character name
* @param {string} userName User name
*/
export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') {
export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, charName, userName) {
let systemPrompt = [];
if (useSysPrompt) {
// 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
// Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.
// Also prevents erroring out if the messages array is empty.
if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) {
if (messages.length === 0) {
messages.unshift({
role: 'user',
content: humanMsgFix || PROMPT_PLACEHOLDER,
content: PROMPT_PLACEHOLDER,
});
}
}

View File

@@ -670,3 +670,182 @@ export function isValidUrl(url) {
return false;
}
}
/**
* MemoryLimitedMap class that limits the memory usage of string values.
*/
export class MemoryLimitedMap {
/**
* Creates an instance of MemoryLimitedMap.
* @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values.
*/
constructor(maxMemoryInBytes) {
if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0) {
throw new Error('maxMemoryInBytes must be a positive number');
}
this.maxMemory = maxMemoryInBytes;
this.currentMemory = 0;
this.map = new Map();
this.queue = [];
}
/**
* Estimates the memory usage of a string in bytes.
* Assumes each character occupies 2 bytes (UTF-16).
* @param {string} str
* @returns {number}
*/
static estimateStringSize(str) {
return str ? str.length * 2 : 0;
}
/**
* Adds or updates a key-value pair in the map.
* If adding the new value exceeds the memory limit, evicts oldest entries.
* @param {string} key
* @param {string} value
*/
set(key, value) {
if (typeof key !== 'string' || typeof value !== 'string') {
return;
}
const newValueSize = MemoryLimitedMap.estimateStringSize(value);
// If the new value itself exceeds the max memory, reject it
if (newValueSize > this.maxMemory) {
return;
}
// Check if the key already exists to adjust memory accordingly
if (this.map.has(key)) {
const oldValue = this.map.get(key);
const oldValueSize = MemoryLimitedMap.estimateStringSize(oldValue);
this.currentMemory -= oldValueSize;
// Remove the key from its current position in the queue
const index = this.queue.indexOf(key);
if (index > -1) {
this.queue.splice(index, 1);
}
}
// Evict oldest entries until there's enough space
while (this.currentMemory + newValueSize > this.maxMemory && this.queue.length > 0) {
const oldestKey = this.queue.shift();
const oldestValue = this.map.get(oldestKey);
const oldestValueSize = MemoryLimitedMap.estimateStringSize(oldestValue);
this.map.delete(oldestKey);
this.currentMemory -= oldestValueSize;
}
// After eviction, check again if there's enough space
if (this.currentMemory + newValueSize > this.maxMemory) {
return;
}
// Add the new key-value pair
this.map.set(key, value);
this.queue.push(key);
this.currentMemory += newValueSize;
}
/**
* Retrieves the value associated with the given key.
* @param {string} key
* @returns {string | undefined}
*/
get(key) {
return this.map.get(key);
}
/**
* Checks if the map contains the given key.
* @param {string} key
* @returns {boolean}
*/
has(key) {
return this.map.has(key);
}
/**
* Deletes the key-value pair associated with the given key.
* @param {string} key
* @returns {boolean} - Returns true if the key was found and deleted, else false.
*/
delete(key) {
if (!this.map.has(key)) {
return false;
}
const value = this.map.get(key);
const valueSize = MemoryLimitedMap.estimateStringSize(value);
this.map.delete(key);
this.currentMemory -= valueSize;
// Remove the key from the queue
const index = this.queue.indexOf(key);
if (index > -1) {
this.queue.splice(index, 1);
}
return true;
}
/**
* Clears all entries from the map.
*/
clear() {
this.map.clear();
this.queue = [];
this.currentMemory = 0;
}
/**
* Returns the number of key-value pairs in the map.
* @returns {number}
*/
size() {
return this.map.size;
}
/**
* Returns the current memory usage in bytes.
* @returns {number}
*/
totalMemory() {
return this.currentMemory;
}
/**
* Returns an iterator over the keys in the map.
* @returns {IterableIterator<string>}
*/
keys() {
return this.map.keys();
}
/**
* Returns an iterator over the values in the map.
* @returns {IterableIterator<string>}
*/
values() {
return this.map.values();
}
/**
* Iterates over the map in insertion order.
* @param {Function} callback - Function to execute for each element.
*/
forEach(callback) {
this.map.forEach((value, key) => {
callback(value, key, this);
});
}
/**
* Makes the MemoryLimitedMap iterable.
* @returns {Iterator} - Iterator over [key, value] pairs.
*/
[Symbol.iterator]() {
return this.map[Symbol.iterator]();
}
}

View File

@@ -8,6 +8,8 @@ export const publicLibConfig = {
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(process.cwd(), 'dist/webpack'),
store: 'pack',
compression: 'gzip',
},
devtool: false,
watch: false,
@@ -16,6 +18,8 @@ export const publicLibConfig = {
preset: 'minimal',
assets: false,
modules: false,
colors: true,
timings: true,
},
experiments: {
outputModule: true,
@@ -24,6 +28,7 @@ export const publicLibConfig = {
hints: false,
},
output: {
path: path.resolve(process.cwd(), 'dist'),
filename: 'lib.js',
libraryTarget: 'module',
},