Merge pull request #5591 from SillyTavern/staging Staging

51ad27fb86d39a3daca3adaa970375c9670c12df

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

Signed
165 files changed, +4704 -1777Showing whitespace changes
.github/workflows/npm-publish.yml+2 -2
@@ -19,7 +19,7 @@ jobs:
1919 - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3
2020 with:
2121 node-version: 24
2222 - run: npm ci --omit=dev --ignore-scripts
2323
2424 publish-npm:
2525 needs: build
@@ -30,5 +30,5 @@ jobs:
3030 with:
3131 node-version: 24
3232 registry-url: https://registry.npmjs.org/
3333 - run: npm ci --omit=dev --ignore-scripts
3434 - run: npm publish
.npmrc+2 -0
@@ -0,0 +1,2 @@
1+ignore-scripts=true
2+min-release-age=7
.vscode/settings.json+4 -0
@@ -0,0 +1,4 @@
1+{
2+ "js/ts.tsdk.promptToUseWorkspaceVersion": true,
3+ "js/ts.tsdk.path": "./node_modules/typescript/lib"
4+}
Dockerfile+1 -1
@@ -19,7 +19,7 @@ COPY --chown=node:node . ./
1919
2020RUN \
2121 echo "*** Install npm packages ***" && \
2222 npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts && npm cache clean --force
2323
2424# Create config directory and link config.yaml. Added hardcoded dirs(constants.js?)
2525# that must be present for Non-Root Mode and volumeless docker runs.
Start.bat+1 -1
@@ -1,7 +1,7 @@
11@echo off
22pushd %~dp0
33set NODE_ENV=production
44call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts
55node server.js %*
66pause
77popd
UpdateAndStart.bat+1 -1
@@ -20,7 +20,7 @@ if %errorlevel% neq 0 (
2020 )
2121)
2222set NODE_ENV=production
2323call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts
2424node server.js %*
2525:end
2626pause
UpdateForkAndStart.bat+1 -1
@@ -102,7 +102,7 @@ if %errorlevel% neq 0 (
102102
103103echo Installing npm packages and starting server
104104set NODE_ENV=production
105105call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts
106106node server.js %*
107107
108108:end
default/config.yaml+48 -3
@@ -41,6 +41,9 @@ port: 8000
4141# Interval in seconds to write a heartbeat file. Set to 0 to disable.
4242# This is used primarily for Docker healthchecks.
4343heartbeatInterval: 0
44+# Enable HTTP/HTTPS keep-alive globally.
45+# Disabling restores old Node 18 behavior, can help if ECONNRESET and other network errors occur.
46+enableKeepAlive: false
4447# -- SSL options --
4548ssl:
4649 # Enable SSL/TLS encryption
@@ -55,7 +58,7 @@ ssl:
5558# -- SECURITY CONFIGURATION --
5659# Toggle whitelist mode
5760whitelistMode: true
5861# WhitelistWhen enabled, whitelist will also verify IP in X-Forwarded-Forheaders /enabled X-Real-IPin headers`forwardedHeaders` section.
5962enableForwardedWhitelist: true
6063# Whitelist of allowed IP addresses
6164whitelist:
@@ -127,6 +130,13 @@ sso:
127130 # as that used for authentik. (Ensure the username in authentik
128131 # is an exact match in lowercase with that in sillytavern).
129132 authentikAuth: false
133+ # List of trusted proxy IPs for SSO authentication.
134+ # Supports wildcards or CIDR notation for subnets.
135+ # Example: ['127.0.0.1', '192.168.1.1']
136+ # Set to ['*'] to trust all proxies (NOT RECOMMENDED unless you have other security measures in place)
137+ trustedProxies:
138+ - ::1
139+ - 127.0.0.1
130140
131141# Host whitelist configuration. Recommended if you're using a listen mode
132142hostWhitelist:
@@ -141,6 +151,26 @@ hostWhitelist:
141151 # - .trycloudflare.com
142152 hosts: []
143153
154+# Perform whitelist checks against server-side HTTP requests that resolve to private IP addresses.
155+# This is an additional layer of security to prevent Server-Side Request Forgery (SSRF) attacks.
156+# Recommended when listen mode is enabled, or if your server is accessible by untrusted users.
157+privateAddressWhitelist:
158+ # Enable private address whitelist to block requests to private IP ranges.
159+ enabled: false
160+ # If true, requests to hosts that cannot be resolved will be allowed instead of blocked.
161+ allowUnresolvedHosts: false
162+ # Log blocked and allowed requests to the console.
163+ log:
164+ # Log blocked requests to the console with a warning message
165+ blockedRequests: true
166+ # Log allowed requests to the console with an info message
167+ allowedRequests: false
168+ # List of allowed private IP ranges (in CIDR notation or wildcard format).
169+ # Allows loopback IP ranges by default, but you can customize this list to fit your needs.
170+ allowedRanges:
171+ - '127.0.0.0/8' # Loopback (IPv4)
172+ - '::1/128' # Loopback (IPv6)
173+
144174# User session timeout *in seconds* (defaults to 24 hours).
145175## Set to a positive number to expire session after a certain time of inactivity
146176## Set to 0 to expire session when the browser is closed
@@ -159,9 +189,24 @@ logging:
159189 minLogLevel: 0
160190# -- RATE LIMITING CONFIGURATION --
161191rateLimiting:
162192 # Use X-Real-IPany headerof insteadthe ofenabled socketheaders in the `forwardedHeaders` section to identify the client IP for rate limiting.
163- # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy)
193+ # If disabled, only the socket IP will be used, which may not work correctly if you are behind a reverse proxy.
164194 preferRealIpHeader: false
195+ # Set the maximum number of allowed failed basic authentication attempts before rate limiting is applied. Set to 0 to disable rate limiting for basic auth.
196+ basicAuthMaxAttempts: 5
197+ # Set the maximum number of allowed failed account login attempts before rate limiting is applied. Set to 0 to disable rate limiting for account logins.
198+ accountsLoginMaxAttempts: 5
199+ # Set the maximum number of allowed failed account recovery attempts before rate limiting is applied. Set to 0 to disable rate limiting for account recovery.
200+ accountsRecoverMaxAttempts: 5
201+# Set to true to enable support for real IPs in certain request headers for features like IP whitelisting, rate limiting and access logging.
202+# Only change if you are sure that you use a correctly configured reverse proxy, otherwise this may lead to IP spoofing.
203+forwardedHeaders:
204+ # X-Real-IP header (common with Nginx and Caddy)
205+ xRealIp: true
206+ # X-Forwarded-For header (common with many proxies, but may contain multiple IPs - only the first one will be used)
207+ xForwardedFor: true
208+ # CF-Connecting-IP header (used by Cloudflare Tunnels)
209+ cfConnectingIp: false
165210
166211## BACKUP CONFIGURATION
167212backups:
default/public/error/forbidden-by-whitelist.html → default/content/errors/forbidden-by-whitelist.html+0 -0
default/public/error/host-not-allowed.html → default/content/errors/host-not-allowed.html+0 -0
default/public/error/unauthorized.html → default/content/errors/unauthorized.html+0 -0
default/public/error/url-not-found.html → default/content/errors/url-not-found.html+2 -2
@@ -2,11 +2,11 @@
22<html>
33
44<head>
55 <title>Not foundFound</title>
66</head>
77
88<body>
99 <h1>Not foundFound</h1>
1010 <p>
1111 The requested URL was not found on this server.
1212 </p>
default/content/index.json+32 -0
@@ -642,5 +642,37 @@
642642 {
643643 "filename": "presets/reasoning/Think XML.json",
644644 "type": "reasoning"
645+ },
646+ {
647+ "filename": "presets/reasoning/Gemma 4.json",
648+ "type": "reasoning"
649+ },
650+ {
651+ "filename": "presets/instruct/Gemma 4.json",
652+ "type": "instruct"
653+ },
654+ {
655+ "filename": "presets/context/Gemma 4.json",
656+ "type": "context"
657+ },
658+ {
659+ "filename": "user.css",
660+ "type": "stylesheet"
661+ },
662+ {
663+ "filename": "errors/forbidden-by-whitelist.html",
664+ "type": "error_page"
665+ },
666+ {
667+ "filename": "errors/host-not-allowed.html",
668+ "type": "error_page"
669+ },
670+ {
671+ "filename": "errors/unauthorized.html",
672+ "type": "error_page"
673+ },
674+ {
675+ "filename": "errors/url-not-found.html",
676+ "type": "error_page"
645677 }
646678]
default/content/presets/context/Gemma 4.json+14 -0
@@ -0,0 +1,14 @@
1+{
2+ "story_string": "{{#if anchorBefore}}{{anchorBefore}}\n{{/if}}{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{#if anchorAfter}}{{anchorAfter}}\n{{/if}}{{trim}}",
3+ "example_separator": "",
4+ "chat_start": "",
5+ "use_stop_strings": false,
6+ "names_as_stop_strings": true,
7+ "story_string_position": 0,
8+ "story_string_depth": 1,
9+ "story_string_role": 0,
10+ "always_force_name2": true,
11+ "trim_sentences": false,
12+ "single_line": false,
13+ "name": "Gemma 4"
14+}
default/content/presets/instruct/Gemma 4.json+25 -0
@@ -0,0 +1,25 @@
1+{
2+ "input_sequence": "<|turn>user\n",
3+ "output_sequence": "<|turn>model\n",
4+ "last_output_sequence": "",
5+ "system_sequence": "<|turn>system\n",
6+ "stop_sequence": "<turn|>",
7+ "wrap": false,
8+ "macro": true,
9+ "names_behavior": "force",
10+ "activation_regex": "",
11+ "first_output_sequence": "",
12+ "skip_examples": false,
13+ "output_suffix": "<turn|>\n",
14+ "input_suffix": "<turn|>\n",
15+ "system_suffix": "<turn|>\n",
16+ "user_alignment_message": "",
17+ "system_same_as_user": false,
18+ "last_system_sequence": "",
19+ "first_input_sequence": "",
20+ "last_input_sequence": "",
21+ "sequences_as_stop_strings": true,
22+ "story_string_prefix": "<|turn>system\n",
23+ "story_string_suffix": "<turn|>\n",
24+ "name": "Gemma 4"
25+}
default/content/presets/openai/Default.json+2 -0
@@ -10,6 +10,8 @@
1010 "mistralai_model": "mistral-large-latest",
1111 "chutes_model": "deepseek-ai/DeepSeek-V3-0324",
1212 "chutes_sort_models": "alphabetically",
13+ "minimax_model": "MiniMax-M2.7",
14+ "minimax_endpoint": "global",
1315 "electronhub_model": "gpt-4o-mini",
1416 "electronhub_sort_models": "alphabetically",
1517 "electronhub_group_models": false,
default/content/presets/reasoning/Gemma 4.json+6 -0
@@ -0,0 +1,6 @@
1+{
2+ "name": "Gemma 4",
3+ "prefix": "<|channel>thought\n",
4+ "suffix": "<channel|>",
5+ "separator": "\n\n"
6+}
default/public/css/user.css → default/content/user.css+0 -0
docker/docker-entrypoint.sh+3 -3
@@ -1,6 +1,6 @@
11#!/bin/sh
22
33# Function to handle startup logic (Config check + Postinstallinit + Start)
44start_sillytavern() {
55 local PREFIX="$1"
66 shift # Remove the first argument (PREFIX) so $@ contains the rest
@@ -11,8 +11,8 @@ start_sillytavern() {
1111 $PREFIX cp "default/config.yaml" "config/config.yaml"
1212 fi
1313
1414 # Execute postinstallinit script to auto-populate config.yaml with missing values
1515 $PREFIX npm run postinstallinit
1616
1717 # Start the server
1818 exec $PREFIX node server.js --listen "$@"
index.d.ts+4 -0
@@ -42,6 +42,10 @@ declare global {
4242 */
4343 handle: string | null;
4444 /**
45+ * Account version tag: shake256 derivative of password hash and salt.
46+ */
47+ version: string | null;
48+ /**
4549 * Last time the session was extended.
4650 */
4751 touch: number;
package-lock.json+80 -80
@@ -1,13 +1,12 @@
11{
22 "name": "sillytavern",
33 "version": "1.1718.0",
44 "lockfileVersion": 3,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "sillytavern",
99 "version": "1.1718.0",
10- "hasInstallScript": true,
1110 "license": "AGPL-3.0",
1211 "dependencies": {
1312 "@adobe/css-tools": "^4.4.4",
@@ -39,13 +38,14 @@
3938 "@mozilla/readability": "^0.6.0",
4039 "@popperjs/core": "^2.11.8",
4140 "@zeldafan0225/ai_horde": "^5.2.0",
41+ "agent-base": "^7.1.3",
4242 "archiver": "^7.0.1",
4343 "bing-translate-api": "^4.1.0",
4444 "body-parser": "^1.20.2",
4545 "bowser": "^2.12.1",
4646 "bytes": "^3.1.2",
4747 "chalk": "^5.6.0",
4848 "chevrotain": "^11.12.10",
4949 "command-exists": "^1.2.9",
5050 "compression": "^1.8.1",
5151 "cookie-parser": "^1.4.6",
@@ -54,7 +54,7 @@
5454 "crc": "^4.3.2",
5555 "csrf-sync": "^4.2.1",
5656 "diff-match-patch": "^1.0.5",
5757 "dompurify": "^3.24.62",
5858 "droll": "^0.2.1",
5959 "env-paths": "^3.0.0",
6060 "express": "^4.21.0",
@@ -74,8 +74,9 @@
7474 "ipaddr.js": "^2.2.0",
7575 "is-docker": "^3.0.0",
7676 "isomorphic-git": "^1.36.3",
77+ "js-sha256": "^0.11.1",
7778 "localforage": "^1.10.0",
7879 "lodash": "^4.1718.211",
7980 "mime-types": "^3.0.2",
8081 "moment": "^2.30.1",
8182 "morphdom": "^2.7.7",
@@ -109,7 +110,7 @@
109110 "sillytavern": "src/server-global.js"
110111 },
111112 "devDependencies": {
112113 "@chevrotain/types": "^11.02.30",
113114 "@types/archiver": "^6.0.3",
114115 "@types/bytes": "^3.1.5",
115116 "@types/command-exists": "^1.2.3",
@@ -123,7 +124,7 @@
123124 "@types/jquery-cropper": "^1.0.4",
124125 "@types/jquery.transit": "^0.9.33",
125126 "@types/jqueryui": "^1.12.24",
126127 "@types/lodash": "^4.17.2024",
127128 "@types/mime-types": "^3.0.1",
128129 "@types/multer": "^2.1.0",
129130 "@types/node": "^18.19.84",
@@ -139,7 +140,8 @@
139140 "eslint": "^8.57.1",
140141 "eslint-plugin-jest": "^27.9.0",
141142 "eslint-plugin-jsdoc": "^48.10.0",
142143 "eslint-plugin-playwright": "^2.3.0",
144+ "typescript": "^5.9.3"
143145 },
144146 "engines": {
145147 "node": ">= 20"
@@ -177,42 +179,42 @@
177179 "license": "Apache-2.0"
178180 },
179181 "node_modules/@chevrotain/cst-dts-gen": {
180182 "version": "11.12.10",
181183 "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.12.10.tgz",
182184 "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1KssJFvn/i9gSd5NNO0ds03ek0CboUXhQQeICw3SR/4uVKmOBcwfZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==",
183185 "license": "Apache-2.0",
184186 "dependencies": {
185187 "@chevrotain/gast": "11.12.10",
186188 "@chevrotain/types": "11.12.10",
187189 "lodash-es": "4.17.23"
188190 }
189191 },
190192 "node_modules/@chevrotain/gast": {
191193 "version": "11.12.10",
192194 "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.12.10.tgz",
193195 "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGmc+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvgKoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==",
194196 "license": "Apache-2.0",
195197 "dependencies": {
196198 "@chevrotain/types": "11.12.10",
197199 "lodash-es": "4.17.23"
198200 }
199201 },
200202 "node_modules/@chevrotain/regexp-to-ast": {
201203 "version": "11.12.10",
202204 "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.12.10.tgz",
203205 "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKwlG73pBFqbXODTbXhdZwv0oyUaI+82C2uylg3Irm+uOv5/TEwFRgwLmbhlln4qkmDytegW79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==",
204206 "license": "Apache-2.0"
205207 },
206208 "node_modules/@chevrotain/types": {
207209 "version": "11.12.10",
208210 "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.12.10.tgz",
209211 "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5yvBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqwkzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==",
210212 "license": "Apache-2.0"
211213 },
212214 "node_modules/@chevrotain/utils": {
213215 "version": "11.12.10",
214216 "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.12.10.tgz",
215217 "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==",
216218 "license": "Apache-2.0"
217219 },
218220 "node_modules/@es-joy/jsdoccomment": {
@@ -549,7 +551,6 @@
549551 "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",
550552 "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",
551553 "license": "MIT",
552- "peer": true,
553554 "dependencies": {
554555 "@jimp/core": "^0.22.12"
555556 }
@@ -1011,7 +1012,6 @@
10111012 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",
10121013 "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",
10131014 "license": "MIT",
1014- "peer": true,
10151015 "dependencies": {
10161016 "@jimp/core": "1.6.0",
10171017 "@jimp/types": "1.6.0",
@@ -1043,7 +1043,6 @@
10431043 "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",
10441044 "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
10451045 "license": "MIT",
1046- "peer": true,
10471046 "dependencies": {
10481047 "@jimp/utils": "^0.22.12"
10491048 },
@@ -1139,7 +1138,6 @@
11391138 "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",
11401139 "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
11411140 "license": "MIT",
1142- "peer": true,
11431141 "dependencies": {
11441142 "@jimp/utils": "^0.22.12"
11451143 },
@@ -1176,7 +1174,6 @@
11761174 "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",
11771175 "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",
11781176 "license": "MIT",
1179- "peer": true,
11801177 "dependencies": {
11811178 "@jimp/utils": "^0.22.12",
11821179 "tinycolor2": "^1.6.0"
@@ -1220,7 +1217,6 @@
12201217 "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",
12211218 "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",
12221219 "license": "MIT",
1223- "peer": true,
12241220 "dependencies": {
12251221 "@jimp/utils": "^0.22.12"
12261222 },
@@ -1308,7 +1304,6 @@
13081304 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",
13091305 "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
13101306 "license": "MIT",
1311- "peer": true,
13121307 "dependencies": {
13131308 "@jimp/utils": "^0.22.12"
13141309 },
@@ -1321,7 +1316,6 @@
13211316 "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",
13221317 "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
13231318 "license": "MIT",
1324- "peer": true,
13251319 "dependencies": {
13261320 "@jimp/utils": "^0.22.12"
13271321 },
@@ -1922,7 +1916,6 @@
19221916 "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
19231917 "dev": true,
19241918 "license": "MIT",
1925- "peer": true,
19261919 "dependencies": {
19271920 "@types/body-parser": "*",
19281921 "@types/express-serve-static-core": "^4.17.33",
@@ -2018,9 +2011,9 @@
20182011 }
20192012 },
20202013 "node_modules/@types/lodash": {
20212014 "version": "4.17.2024",
20222015 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.2024.tgz",
20232016 "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXAgIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGAFizimb35b7k8atwsQ==",
20242017 "dev": true,
20252018 "license": "MIT"
20262019 },
@@ -2612,7 +2605,6 @@
26122605 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
26132606 "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
26142607 "license": "MIT",
2615- "peer": true,
26162608 "bin": {
26172609 "acorn": "bin/acorn"
26182610 },
@@ -2799,9 +2791,9 @@
27992791 }
28002792 },
28012793 "node_modules/archiver-utils/node_modules/brace-expansion": {
28022794 "version": "2.01.20",
28032795 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.01.20.tgz",
28042796 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQTN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
28052797 "license": "MIT",
28062798 "dependencies": {
28072799 "balanced-match": "^1.0.0"
@@ -3021,14 +3013,23 @@
30213013 }
30223014 },
30233015 "node_modules/axios": {
30243016 "version": "1.1315.52",
30253017 "resolved": "https://registry.npmjs.org/axios/-/axios-1.1315.52.tgz",
30263018 "integrity": "sha512-cz4ur7Vb0xS4wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/KUN0tPWe44eqxrIu31mexEo0gHN+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8QD5A==",
30273019 "license": "MIT",
30283020 "dependencies": {
30293021 "follow-redirects": "^1.15.11",
30303022 "form-data": "^4.0.5",
30313023 "proxy-from-env": "^12.1.0"
3024+ }
3025+ },
3026+ "node_modules/axios/node_modules/proxy-from-env": {
3027+ "version": "2.1.0",
3028+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
3029+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
3030+ "license": "MIT",
3031+ "engines": {
3032+ "node": ">=10"
30323033 }
30333034 },
30343035 "node_modules/b4a": {
@@ -3083,9 +3084,9 @@
30833084 }
30843085 },
30853086 "node_modules/basic-ftp": {
30863087 "version": "5.23.01",
30873088 "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.23.01.tgz",
30883089 "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXwGttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
30893090 "license": "MIT",
30903091 "engines": {
30913092 "node": ">=10.0.0"
@@ -3190,9 +3191,9 @@
31903191 "license": "MIT"
31913192 },
31923193 "node_modules/brace-expansion": {
31933194 "version": "1.1.1214",
31943195 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1214.tgz",
31953196 "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBPMWPGfDxnyzKU7rNOW9SP/W1ZEyZ1twGWom5c50vi3xrmrua/56TF4lPcqjnDHcg+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
31963197 "dev": true,
31973198 "license": "MIT",
31983199 "dependencies": {
@@ -3232,7 +3233,6 @@
32323233 }
32333234 ],
32343235 "license": "MIT",
3235- "peer": true,
32363236 "dependencies": {
32373237 "baseline-browser-mapping": "^2.9.0",
32383238 "caniuse-lite": "^1.0.30001759",
@@ -3503,16 +3503,16 @@
35033503 }
35043504 },
35053505 "node_modules/chevrotain": {
35063506 "version": "11.12.10",
35073507 "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.12.10.tgz",
35083508 "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1mHCHTxM51nCklUw9RzRVc0DLjAh/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQSAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==",
35093509 "license": "Apache-2.0",
35103510 "dependencies": {
35113511 "@chevrotain/cst-dts-gen": "11.12.10",
35123512 "@chevrotain/gast": "11.12.10",
35133513 "@chevrotain/regexp-to-ast": "11.12.10",
35143514 "@chevrotain/types": "11.12.10",
35153515 "@chevrotain/utils": "11.12.10",
35163516 "lodash-es": "4.17.23"
35173517 }
35183518 },
@@ -4322,13 +4322,10 @@
43224322 }
43234323 },
43244324 "node_modules/dompurify": {
43254325 "version": "3.34.2",
43264326 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.34.2.tgz",
43274327 "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSRlHeS9SA/UPU5fq3OXClEHLJNGvbxKpQIKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
43284328 "license": "(MPL-2.0 OR Apache-2.0)",
4329- "engines": {
4330- "node": ">=20"
4331- },
43324329 "optionalDependencies": {
43334330 "@types/trusted-types": "^2.0.7"
43344331 }
@@ -4567,7 +4564,6 @@
45674564 "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
45684565 "dev": true,
45694566 "license": "MIT",
4570- "peer": true,
45714567 "dependencies": {
45724568 "@eslint-community/eslint-utils": "^4.2.0",
45734569 "@eslint-community/regexpp": "^4.6.1",
@@ -5209,9 +5205,9 @@
52095205 "license": "ISC"
52105206 },
52115207 "node_modules/follow-redirects": {
52125208 "version": "1.1516.110",
52135209 "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.1516.110.tgz",
52145210 "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FOy5rN/0gfIES8CuOsadFT/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQJfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
52155211 "funding": [
52165212 {
52175213 "type": "individual",
@@ -6378,6 +6374,12 @@
63786374 "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
63796375 "license": "BSD-3-Clause"
63806376 },
6377+ "node_modules/js-sha256": {
6378+ "version": "0.11.1",
6379+ "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.1.tgz",
6380+ "integrity": "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==",
6381+ "license": "MIT"
6382+ },
63816383 "node_modules/js-yaml": {
63826384 "version": "4.1.1",
63836385 "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
@@ -6614,9 +6616,9 @@
66146616 }
66156617 },
66166618 "node_modules/lodash": {
66176619 "version": "4.1718.231",
66186620 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.1718.231.tgz",
66196621 "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEcdMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/LCaj/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnGgfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+w0Q==",
66206622 "license": "MIT"
66216623 },
66226624 "node_modules/lodash-es": {
@@ -7926,9 +7928,9 @@
79267928 }
79277929 },
79287930 "node_modules/readdir-glob/node_modules/brace-expansion": {
79297931 "version": "2.01.20",
79307932 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.01.20.tgz",
79317933 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQTN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
79327934 "license": "MIT",
79337935 "dependencies": {
79347936 "balanced-match": "^1.0.0"
@@ -8125,7 +8127,6 @@
81258127 "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
81268128 "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
81278129 "license": "MIT",
8128- "peer": true,
81298130 "dependencies": {
81308131 "fast-deep-equal": "^3.1.3",
81318132 "fast-uri": "^3.0.1",
@@ -9143,7 +9144,6 @@
91439144 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
91449145 "dev": true,
91459146 "license": "Apache-2.0",
9146- "peer": true,
91479147 "bin": {
91489148 "tsc": "bin/tsc",
91499149 "tsserver": "bin/tsserver"
package.json+11 -8
@@ -29,13 +29,14 @@
2929 "@mozilla/readability": "^0.6.0",
3030 "@popperjs/core": "^2.11.8",
3131 "@zeldafan0225/ai_horde": "^5.2.0",
32+ "agent-base": "^7.1.3",
3233 "archiver": "^7.0.1",
3334 "bing-translate-api": "^4.1.0",
3435 "body-parser": "^1.20.2",
3536 "bowser": "^2.12.1",
3637 "bytes": "^3.1.2",
3738 "chalk": "^5.6.0",
3839 "chevrotain": "^11.12.10",
3940 "command-exists": "^1.2.9",
4041 "compression": "^1.8.1",
4142 "cookie-parser": "^1.4.6",
@@ -44,7 +45,7 @@
4445 "crc": "^4.3.2",
4546 "csrf-sync": "^4.2.1",
4647 "diff-match-patch": "^1.0.5",
4748 "dompurify": "^3.24.62",
4849 "droll": "^0.2.1",
4950 "env-paths": "^3.0.0",
5051 "express": "^4.21.0",
@@ -64,8 +65,9 @@
6465 "ipaddr.js": "^2.2.0",
6566 "is-docker": "^3.0.0",
6667 "isomorphic-git": "^1.36.3",
68+ "js-sha256": "^0.11.1",
6769 "localforage": "^1.10.0",
6870 "lodash": "^4.1718.211",
6971 "mime-types": "^3.0.2",
7072 "moment": "^2.30.1",
7173 "morphdom": "^2.7.7",
@@ -113,8 +115,9 @@
113115 "type": "git",
114116 "url": "https://github.com/SillyTavern/SillyTavern.git"
115117 },
116118 "version": "1.1718.0",
117119 "scripts": {
120+ "init": "node src/server-init.js",
118121 "start": "node server.js",
119122 "debug": "node --inspect server.js",
120123 "start:global": "node server.js --global",
@@ -122,7 +125,6 @@
122125 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
123126 "start:bun": "bun server.js",
124127 "start:no-csrf": "node server.js --disableCsrf",
125- "postinstall": "node post-install.js",
126128 "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",
127129 "lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix",
128130 "plugins:update": "node plugins update",
@@ -137,7 +139,7 @@
137139 },
138140 "main": "server.js",
139141 "devDependencies": {
140142 "@chevrotain/types": "^11.02.30",
141143 "@types/archiver": "^6.0.3",
142144 "@types/bytes": "^3.1.5",
143145 "@types/command-exists": "^1.2.3",
@@ -151,7 +153,7 @@
151153 "@types/jquery-cropper": "^1.0.4",
152154 "@types/jquery.transit": "^0.9.33",
153155 "@types/jqueryui": "^1.12.24",
154156 "@types/lodash": "^4.17.2024",
155157 "@types/mime-types": "^3.0.1",
156158 "@types/multer": "^2.1.0",
157159 "@types/node": "^18.19.84",
@@ -167,6 +169,7 @@
167169 "eslint": "^8.57.1",
168170 "eslint-plugin-jest": "^27.9.0",
169171 "eslint-plugin-jsdoc": "^48.10.0",
170172 "eslint-plugin-playwright": "^2.3.0",
173+ "typescript": "^5.9.3"
171174 }
172175}
post-install.js+0 -114
@@ -1,114 +0,0 @@
1-/**
2- * Scripts to be done before starting the server for the first time.
3- */
4-import fs from 'node:fs';
5-import path from 'node:path';
6-import process from 'node:process';
7-import yaml from 'yaml';
8-import chalk from 'chalk';
9-import { createRequire } from 'node:module';
10-import { addMissingConfigValues } from './src/config-init.js';
11-
12-/**
13- * Colorizes console output.
14- */
15-const color = chalk;
16-
17-/**
18- * Converts the old config.conf file to the new config.yaml format.
19- */
20-function convertConfig() {
21- if (fs.existsSync('./config.conf')) {
22- if (fs.existsSync('./config.yaml')) {
23- console.log(color.yellow('Both config.conf and config.yaml exist. Please delete config.conf manually.'));
24- return;
25- }
26-
27- try {
28- console.log(color.blue('Converting config.conf to config.yaml. Your old config.conf will be renamed to config.conf.bak'));
29- fs.renameSync('./config.conf', './config.conf.cjs'); // Force loading as CommonJS
30- const require = createRequire(import.meta.url);
31- const config = require(path.join(process.cwd(), './config.conf.cjs'));
32- fs.copyFileSync('./config.conf.cjs', './config.conf.bak');
33- fs.rmSync('./config.conf.cjs');
34- fs.writeFileSync('./config.yaml', yaml.stringify(config));
35- console.log(color.green('Conversion successful. Please check your config.yaml and fix it if necessary.'));
36- } catch (error) {
37- console.error(color.red('FATAL: Config conversion failed. Please check your config.conf file and try again.'), error);
38- return;
39- }
40- }
41-}
42-
43-/**
44- * Creates the default config files if they don't exist yet.
45- */
46-function createDefaultFiles() {
47- /**
48- * @typedef DefaultItem
49- * @type {object}
50- * @property {'file' | 'directory'} type - Whether the item should be copied as a single file or merged into a directory structure.
51- * @property {string} defaultPath - The path to the default item (typically in `default/`).
52- * @property {string} productionPath - The path to the copied item for production use.
53- */
54-
55- /** @type {DefaultItem[]} */
56- const defaultItems = [
57- {
58- type: 'file',
59- defaultPath: './default/config.yaml',
60- productionPath: './config.yaml',
61- },
62- {
63- type: 'directory',
64- defaultPath: './default/public/',
65- productionPath: './public/',
66- },
67- ];
68-
69- for (const defaultItem of defaultItems) {
70- try {
71- if (defaultItem.type === 'file') {
72- if (!fs.existsSync(defaultItem.productionPath)) {
73- fs.copyFileSync(
74- defaultItem.defaultPath,
75- defaultItem.productionPath,
76- );
77- console.log(
78- color.green(`Created default file: ${defaultItem.productionPath}`),
79- );
80- }
81- } else if (defaultItem.type === 'directory') {
82- fs.cpSync(defaultItem.defaultPath, defaultItem.productionPath, {
83- force: false, // Don't overwrite existing files!
84- recursive: true,
85- });
86- console.log(
87- color.green(`Synchronized missing files: ${defaultItem.productionPath}`),
88- );
89- } else {
90- throw new Error(
91- 'FATAL: Unexpected default file format in `post-install.js#createDefaultFiles()`.',
92- );
93- }
94- } catch (error) {
95- console.error(
96- color.red(
97- `FATAL: Could not write default ${defaultItem.type}: ${defaultItem.productionPath}`,
98- ),
99- error,
100- );
101- }
102- }
103-}
104-
105-try {
106- // 0. Convert config.conf to config.yaml
107- convertConfig();
108- // 1. Create default config files
109- createDefaultFiles();
110- // 2. Add missing config values
111- addMissingConfigValues(path.join(process.cwd(), './config.yaml'));
112-} catch (error) {
113- console.error(error);
114-}
public/css/!USER-CSS-README.md+7 -0
@@ -0,0 +1,7 @@
1+# Looking for user.css?
2+
3+user.css is now located under your data root directory in the "_css" folder.
4+
5+Example for the default data root:
6+
7+/data/_css/user.css
public/css/extensions-panel.css+11 -1
@@ -97,13 +97,23 @@ label[for="extensions_autoconnect"] {
9797 font-size: 1.05em;
9898}
9999
100100.extensions_info :is(.extension_version, .extension_author) {
101101 opacity: 0.8;
102102 font-size: 0.8em;
103103 font-weight: normal;
104104 margin-left: 2px;
105105}
106106
107+.extensions_info :is(.extension_version, .extension_author):empty {
108+ display: none;
109+}
110+
111+.extensions_info .extension_author {
112+ display: inline-flex;
113+ gap: 2px;
114+ align-items: baseline;
115+}
116+
107117.extensions_info .extension_block a {
108118 color: var(--SmartThemeBodyColor);
109119}
public/css/mobile-styles.css+5 -0
@@ -500,6 +500,11 @@
500500 .horde_multiple_hint {
501501 display: none;
502502 }
503+
504+ select[multiple] {
505+ max-height: 50px;
506+ overflow-y: auto;
507+ }
503508}
504509
505510/*landscape mode phones and ipads*/
public/css/streaming-display.css+236 -0
@@ -0,0 +1,236 @@
1+/* ─────────────────────────────────────────────────────────────────────────────
2+ Streaming Display — floating toast panel for live LLM generation output.
3+ Shows reasoning (thinking) and content as they stream in.
4+ Used by extensions that leverage ConnectionManagerRequestService streaming.
5+ ───────────────────────────────────────────────────────────────────────────── */
6+
7+.streaming-display {
8+ position: fixed;
9+ bottom: max(calc(var(--bottomFormBlockSize) + 5px), 20px);
10+ right: 20px;
11+ width: min(550px, calc(100vw - 40px));
12+ max-height: 70vh;
13+ background: var(--SmartThemeBlurTintColor);
14+ border: 1px solid var(--SmartThemeBorderColor);
15+ border-radius: 10px;
16+ padding: 14px;
17+ z-index: 9000;
18+ display: flex;
19+ flex-direction: column;
20+ gap: 10px;
21+ opacity: 0;
22+ transform: translateY(20px);
23+ transition: opacity var(--animation-duration, 125ms) ease, transform var(--animation-duration, 125ms) ease;
24+ overflow: hidden;
25+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
26+ backdrop-filter: blur(12px);
27+}
28+
29+.streaming-display-visible {
30+ opacity: 1;
31+ transform: translateY(0);
32+}
33+
34+/* Header label with animated activity indicator */
35+.streaming-display-label {
36+ font-weight: 600;
37+ font-size: 0.95em;
38+ color: var(--SmartThemeBodyColor);
39+ display: flex;
40+ align-items: center;
41+ gap: 8px;
42+ user-select: none;
43+}
44+
45+/* LED status indicator - pulsing while streaming */
46+.streaming-display-led {
47+ display: inline-block;
48+ width: 8px;
49+ height: 8px;
50+ border-radius: 50%;
51+ background: rgb(225, 138, 36);
52+ animation: streaming-display-pulse 1.5s ease-in-out infinite;
53+ flex-shrink: 0;
54+}
55+
56+/* Completed state: solid green LED */
57+.streaming-display-complete .streaming-display-led {
58+ background: #4caf50;
59+ animation: none;
60+ opacity: 1;
61+ box-shadow: 0 0 8px rgba(76, 175, 80, 0.6);
62+}
63+
64+/* Stopped state: solid red LED */
65+.streaming-display-stopped .streaming-display-led {
66+ background: #f44336;
67+ animation: none;
68+ opacity: 1;
69+ box-shadow: 0 0 8px rgba(244, 67, 54, 0.6);
70+}
71+
72+@keyframes streaming-display-pulse {
73+ 0%, 100% { opacity: 0.4; transform: scale(0.9); }
74+ 50% { opacity: 1; transform: scale(1.1); }
75+}
76+
77+/* Label text takes available space */
78+.streaming-display-label-text {
79+ flex: 1;
80+ min-width: 0;
81+ overflow: hidden;
82+ text-overflow: ellipsis;
83+ white-space: nowrap;
84+}
85+
86+/* Window control buttons container */
87+.streaming-display-controls {
88+ display: flex;
89+ align-items: center;
90+ gap: 4px;
91+ margin-left: auto;
92+ flex-shrink: 0;
93+}
94+
95+/* Window control buttons */
96+.streaming-display-btn {
97+ width: 22px;
98+ height: 22px;
99+ border: none;
100+ border-radius: 4px;
101+ background: transparent;
102+ color: var(--SmartThemeBodyColor);
103+ font-size: 14px;
104+ line-height: 1;
105+ cursor: pointer;
106+ display: flex;
107+ align-items: center;
108+ justify-content: center;
109+ opacity: 0.6;
110+ transition: opacity 0.15s ease, background-color 0.15s ease;
111+}
112+
113+.streaming-display-btn:hover {
114+ opacity: 1;
115+ background-color: rgba(255, 255, 255, 0.1);
116+}
117+
118+.streaming-display-btn-close:hover {
119+ background-color: rgba(244, 67, 54, 0.2);
120+}
121+
122+.streaming-display-btn-stop {
123+ font-size: 10px;
124+}
125+
126+.streaming-display-btn-stop:hover {
127+ background-color: rgba(244, 150, 36, 0.2);
128+ color: rgb(225, 138, 36);
129+}
130+
131+/* Content container - collapsible for minimize */
132+.streaming-display-content {
133+ display: flex;
134+ flex-direction: column;
135+ gap: 10px;
136+ overflow: hidden;
137+ transition: max-height var(--animation-duration, 125ms) ease, opacity var(--animation-duration, 125ms) ease;
138+}
139+
140+/* Minimized state - hide content sections */
141+.streaming-display-minimized .streaming-display-content {
142+ max-height: 0;
143+ opacity: 0;
144+}
145+
146+.streaming-display-minimized {
147+ gap: 0;
148+}
149+
150+/* Model/API icon in the label */
151+.streaming-display-icon {
152+ width: 1.1em;
153+ height: 1.1em;
154+ flex-shrink: 0;
155+}
156+
157+/* Minimized state adjustments */
158+.streaming-display-minimized.streaming-display {
159+ padding: 10px 14px;
160+}
161+
162+/* Reasoning (thinking) section */
163+.streaming-display-reasoning {
164+ background: color-mix(in srgb, var(--SmartThemeBodyColor) 5%, transparent);
165+ border-radius: 6px;
166+ padding: 8px 10px;
167+ border-left: 3px solid color-mix(in srgb, var(--SmartThemeBodyColor) 25%, transparent);
168+}
169+
170+.streaming-display-reasoning-label {
171+ font-size: 0.8em;
172+ font-weight: 600;
173+ opacity: 0.5;
174+ margin-bottom: 4px;
175+ letter-spacing: 0.03em;
176+ text-transform: uppercase;
177+}
178+
179+.streaming-display-reasoning-content {
180+ font-size: 0.82em;
181+ opacity: 0.65;
182+ max-height: 25vh;
183+ overflow-y: auto;
184+ word-break: break-word;
185+ line-height: 1.45;
186+ scrollbar-width: thin;
187+}
188+
189+.streaming-display-reasoning-content p {
190+ margin: 0.3em 0;
191+}
192+
193+.streaming-display-reasoning-content p:first-child {
194+ margin-top: 0;
195+}
196+
197+.streaming-display-reasoning-content p:last-child {
198+ margin-bottom: 0;
199+}
200+
201+/* Main content section */
202+.streaming-display-text {
203+ border-top: 1px solid color-mix(in srgb, var(--SmartThemeBorderColor) 50%, transparent);
204+ padding-top: 8px;
205+ min-height: 1.5em;
206+}
207+
208+.streaming-display-text-content {
209+ font-size: 0.9em;
210+ color: var(--SmartThemeBodyColor);
211+ max-height: 40vh;
212+ overflow-y: auto;
213+ word-break: break-word;
214+ line-height: 1.5;
215+ scrollbar-width: thin;
216+}
217+
218+.streaming-display-text-content p {
219+ margin: 0.4em 0;
220+}
221+
222+.streaming-display-text-content p:first-child {
223+ margin-top: 0;
224+}
225+
226+.streaming-display-text-content p:last-child {
227+ margin-bottom: 0;
228+}
229+
230+/* Strip auto-added quotes from <q> tags, as message formatting adds them */
231+.streaming-display-reasoning-content q:before,
232+.streaming-display-reasoning-content q:after,
233+.streaming-display-text-content q:before,
234+.streaming-display-text-content q:after {
235+ content: '';
236+}
public/css/toggle-dependent.css+4 -0
@@ -565,6 +565,10 @@ label[for="bind_preset_to_connection"]:has(input:checked) {
565565 display: none;
566566}
567567
568+#openai_settings:has(#openai_function_calling:not(:checked)) #tool_call_recurse_limit_block {
569+ display: none;
570+}
571+
568572#adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) {
569573 display: none;
570574}
public/css/welcome.css+1 -0
@@ -17,6 +17,7 @@
1717.welcomePanel.recentHidden .welcomeRecent,
1818.welcomePanel.recentHidden .recentChatsTitle,
1919.welcomePanel.recentHidden .hideRecentChats,
20+.welcomePanel.recentHidden .recentChatsSettings,
2021.welcomePanel:not(.recentHidden) .showRecentChats {
2122 display: none;
2223}
public/img/kobold.svg+4 -76
@@ -1,78 +1,6 @@
11<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-<!-- Created with Inkscape (http://www.inkscape.org/) -->
2+<svg id="svg2" width="660.14" height="621.01" version="1.1" viewBox="0 0 660.14 621.01" xmlns="http://www.w3.org/2000/svg">
3-
3+ <g id="g8" transform="translate(-12.7,-12.6)">
4-<svg
4+ <path id="path972" d="m171.3 47.3c5.5 33.5 12.9 56.6-16 78-15.6 13.6-27.3 32.5-43.1 45.3-12.7 6.5-49.7 1.9-63.5-0.6-15.9 28-27.4 57-36 88 13.7 10.8 35 39.1 53.3 38.7 0 0-3.8 77.9-8.1 90.5-10.8 15-30.6 29.8-45.2 41.5l25.3 69.3c7.3 3.1 54.7 9.3 54.7 9.3 42.8 3.6 33.4 58.2 89.3 46.7-0.6 3.6-2 9.1 1.2 11.9 4.9 4.2 11.7-3.7 17.4-1.8 14.2 4.8 19.7 29 37.4 16.5 3.7 14.9 13.7 53.6 34.5 53 20.5-0.5 4.5-62.6 42.8-63.7 20.7-0.6 53.1-5.3 69 10.9 11.9 12.1 12.1 52 28.2 50.8 7.5-0.6 21.5-24 22.8-55.1 21.4 13.6 26.8-8.6 45.3-15 5.7-2 13.9 4.9 18.7 1.1 3.4-2.8 0-8.5-1.3-11.4 42.4 4.4 54.3-1.8 78.7-45.3 21.7 0 43.4 2.3 65.2 2 25.1-43.5 31.5-72.2 30.9-76.7-2.7-18-34.1-27.2-42.5-42.8 1-27.9-1.7-56.9-1.7-85.3 0.5-18.7 33.7-29.7 42.8-44.6 4-6.5-1.4-16.9-3.5-23.4-6.5-19.7-14.1-42.9-26.5-59.7-9.7-13.1-44.3 5.1-63-2.8-19.3-20.2-39.2-39.3-60-58.5-15.9-14.8 2.9-45.4-3.6-63.8-3.8-10.8-75.4-40.2-86.8-35-16.2 9.4-22.6 33.9-39.6 41.8-28.2-5.2-60.9 7-88.8 0.6-14.8-10.2-30.4-30.8-41.5-45.1-31.1 6.9-58 21.6-86.7 34.7m400 218.7c-58.8-77.3-132.9-36.3-148 72-5.7-38.5-2.8-80.8-2.7-120 0-12.8 0.2-30.4 10.8-39.4 27.2-23 139.8-3.8 139.8 87.4m-301.3 53.3c-17.7-99.9-99.2-127.2-144-54.7-19.7-65 99.7-95.5 123.9-85 24 10.4 30.5 86.9 21.4 139.8m-46.6-96.1c5 13.9 18 23.4 25.5 36 7.4 12.5 9.7 32.1 22.5 40-3.9-24.8-18.5-72.1-48-76m200 77.3c10.9-7.5 13.7-23.5 19.9-34.7 7.7-13.8 22.1-25 26.8-40-27.9 4.5-44.2 50.2-46.7 74.7m-265.3-45.3c5.2-20.6 30.4-14.3 45-8.2m340.4 21.6c-18.3-11-31.3-18.6-53.3-18.7 19-16.3 44.5-1 53.3 18.7m-358.9-1.6c65.8-8.8 89.9 90 78.5 104.8-12.5 16.3-80.6 23.8-103.4 2.9-13.3-12.2-18.5-33.7-24.2-50.2l-4 1.3-5.3-17.3c24.3-2.8 31-32.7 54.4-40.6m333.6 2.3c32.6 19 36.5 42.5 55 39.9l-4.3 25-5.3-9.3c-8.7 20.9-12.7 50.6-37.3 58.5-40.9 13.1-102.3 2-98-29.1 13.4-39 40.6-87.6 86-86.8m-86.6 302.8c0 17.8-2.3 32.1-10.7 48-9.4-5.8-23.3-33.8-13.4-43.9 5.5-5.7 16.9-4.1 24.1-4.1m-145.3 4-8 46.7c-16.3-9.2-22.8-30.3-24-48l32 1.3"/>
5- version="1.1"
6- id="svg2"
7- width="682.66669"
8- height="682.66669"
9- viewBox="0 0 682.66669 682.66669"
10- sodipodi:docname="kobold.svg"
11- inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
12- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
14- xmlns="http://www.w3.org/2000/svg"
15- xmlns:svg="http://www.w3.org/2000/svg">
16- <defs
17- id="defs6" />
18- <sodipodi:namedview
19- id="namedview4"
20- pagecolor="#ffffff"
21- bordercolor="#000000"
22- borderopacity="0.25"
23- inkscape:showpageshadow="2"
24- inkscape:pageopacity="0.0"
25- inkscape:pagecheckerboard="0"
26- inkscape:deskcolor="#d1d1d1"
27- showgrid="false"
28- inkscape:zoom="1.5043945"
29- inkscape:cx="460.98281"
30- inkscape:cy="311.08862"
31- inkscape:window-width="2560"
32- inkscape:window-height="1377"
33- inkscape:window-x="2552"
34- inkscape:window-y="-8"
35- inkscape:window-maximized="1"
36- inkscape:current-layer="g8" />
37- <g
38- inkscape:groupmode="layer"
39- inkscape:label="Image"
40- id="g8">
41- <path
42- style="stroke:none;stroke-width:1.33333"
43- d="m 171.32749,47.331392 v 4 c 12.46,9.0412 4.032,24.8744 4.91067,37.33333 0.60933,8.61773 4.48133,15.097198 3.08933,23.999998 -9.768,-1.0508 -17.05467,6.59294 -24,12.62774 -15.61067,13.5656 -27.25574,32.5056 -43.14654,45.2576 -6.2576,5.02133 -14.829587,3.284 -22.186787,2.55733 -13.6492,-1.34666 -27.88494,-0.35466 -41.33334,-3.10933 -15.8956,27.97867 -27.41493,57.04 -36,88 13.74,10.83867 35.03,39.1 53.33334,38.66667 l -1.33334,6.66667 -5.33333,-2.66667 2.66667,14.66667 -4,-2.66667 -1.33334,6.66667 h 6.66667 l -1.33333,25.33333 h -1.33334 l -5.33333,-8 c 1.2756,13.55867 6.9132,37.864 2.5692,50.54 -2.02707,5.91467 -8.7172,10.132 -13.23613,14.05067 -10.5992,9.19067 -20.984,18.72 -31.99974,27.40933 l 25.33334,69.33334 h 1.33333 l 1.33333,-10.66667 17.33334,28 -2.66667,-5.33333 30.66667,4 1.33333,-6.66667 8,5.33334 -2.66667,-5.33334 c 33.997197,-5.85866 39.588927,35.29201 66.666667,47.46401 6.416,2.88533 16.03067,0.56933 22.66667,-0.79734 -0.58267,3.59734 -2.02267,9.11867 1.24133,11.92667 4.892,4.20667 11.732,-3.67467 17.35467,-1.764 14.21733,4.83467 19.67333,28.956 37.404,16.504 3.70133,14.85067 13.68667,53.584 34.496,53.04533 8.328,-0.216 10.988,-14.944 12.73867,-21.04533 5.44933,-18.99333 5.108,-41.92533 30.09866,-42.62667 20.65067,-0.58 53.08801,-5.31333 69.00801,10.92534 10.052,10.25333 8.66666,27.47066 15.73333,39.69333 2.42933,4.20133 6.73733,11.54 12.48667,11.09867 5.58133,-0.42934 9.27466,-8.18267 11.60533,-12.424 7.32533,-13.32667 10.54,-27.56534 11.16667,-42.66667 21.444,13.56267 26.752,-8.56 45.33333,-14.99333 5.74,-1.98667 13.94134,4.92 18.68,1.088 3.448,-2.788 0.0213,-8.492 -1.34666,-11.428 11.49466,1.196 36.74933,2.12 46.66667,-3.89067 13.856,-8.39867 22.12,-24.84267 33.33333,-36.10934 l -1.3333,-5.33333 41.3333,1.33333 c 1.3253,7.79467 6.2707,7.924 13.3333,8 v -6.66666 l 5.3334,5.33333 h 2.6666 c 10.9174,-18.93867 20.5987,-39.24933 27.5454,-60 2.1786,-6.504 6.8373,-15.692 5.808,-22.64934 -2.6627,-18.00933 -34.1347,-27.24133 -42.4814,-42.756 -4.5173,-8.39333 0.028,-24.07866 0.8147,-33.26133 1.5,-17.472 -3,-34.648 -2.5173,-51.99734 0.5226,-18.74133 33.68,-29.71733 42.796,-44.60933 3.9746,-6.49467 -1.3827,-16.932 -3.5227,-23.39334 -6.516,-19.676 -14.056,-42.93333 -26.5373,-59.73733 -4.7547,-6.40133 -10.9067,-4.772 -17.9054,-4.38667 -12.3666,0.68134 -33.6346,6.424 -45.12,1.564 -7.93997,-3.36133 -13.2693,-13.912 -18.93463,-20.06666 -12.87067,-13.98401 -27.26667,-25.57214 -41.10534,-38.47241 -15.90933,-14.829598 2.85333,-45.371998 -3.62133,-63.770668 -3.06134,-8.700397 -20.756,-14.264397 -28.552,-17.694131 -13.49867,-5.9384 -27.244,-11.378134 -41.33334,-15.735334 -5.22,-1.6144 -11.668,-4.60664 -16.88933,-1.585333 -16.16,9.350267 -22.56667,33.947998 -39.556,41.756398 -9.47467,4.3548 -27.90934,-1.2064 -38.22134,-1.7456 -15.03333,-0.786 -36.49067,6.88534 -50.54,2.33574 -5.92267,-1.9176 -10.304,-8.63254 -14.20267,-13.12894 -9.148,-10.549598 -18.88133,-20.827998 -27.25733,-31.999732 -31.076,6.852134 -58.048,21.650001 -86.66667,34.666672 m 297.33334,37.33333 v 1.33333 h -4 l 4,-1.33333 m 4,0 -1.33333,1.33333 1.33333,-1.33333 m -253.33334,1.33333 -1.33333,1.33334 1.33333,-1.33334 m 192.00001,44.000008 -1.33334,1.33333 1.33334,-1.33333 m 159.99997,136 h -1.3333 c -6.54533,-9.72 -13.10133,-19.16666 -22.67333,-26.17733 -6.84667,-5.01334 -14.52267,-8.54934 -22.66001,-10.896 -64.19333,-18.52 -94.28267,58.57733 -101.33333,109.07333 -4.65867,-12.744 -2.66667,-27.976 -2.66667,-41.33333 0,-26.22133 -0.096,-52.44534 0.003,-78.66667 0.048,-12.81733 0.236,-30.37867 10.848,-39.352 11.536,-9.75467 35,-9.336 49.14933,-7.74 50.02934,5.64267 90.66531,43.30267 90.66671,95.092 M 271.32782,319.3314 h -1.33333 c -8.73067,-49.15734 -41.592,-109.81467 -100,-92.31201 -8.24267,2.46934 -15.92267,6.46267 -22.66401,11.82 -8.93866,7.10267 -15.3944,16.20534 -21.336,25.82534 -14.76986,-48.75467 53.38134,-80.68134 90.66667,-85.05067 10.04267,-1.176 23.616,-4.14533 33.25467,0.0387 11.71067,5.08533 16.88267,22.98533 19.47067,34.34533 7.42133,32.56134 7.61067,72.44534 1.94133,105.33334 m -46.66667,-96.00001 c 4.99867,13.94667 17.988,23.35334 25.46001,36.00001 7.37733,12.484 9.68933,32.056 22.54,40 -3.912,-24.796 -18.472,-72.13734 -48.00001,-76.00001 m 200.00001,77.33334 c 10.88133,-7.50267 13.66933,-23.524 19.86667,-34.66667 7.652,-13.75733 22.06133,-25.04533 26.8,-40 -27.912,4.53067 -44.212,50.15067 -46.66667,74.66667 m -220.00034,-54.6667 v 1.33334 l -45.33333,8 c 5.248,-20.64801 30.736,-15.41734 45.33333,-9.33334 m 340.00002,22.66667 c -18.28667,-11.04667 -31.34001,-18.64267 -53.33334,-18.66667 19.048,-16.336 44.54933,-0.956 53.33334,18.66667 m -62.66667,-21.33333 -5.33334,8 5.33334,-8 m -330.66668,9.33333 v 9.33333 h -2.66667 l 2.66667,-9.33333 m 33.33333,10.66667 c -10.14666,28.372 -20.33733,64.79466 -3.732,93.332 23.412,40.23467 69.13467,19.88133 79.73201,-17.332 l -37.33334,12 c 7.55333,-29.95734 6.71333,-51.172 -5.33333,-80 l -4,1.33333 2.66666,-9.33333 c 25.18534,13.21066 33.43067,41.44266 45.02534,65.33333 5.99867,12.36134 12.33733,26.51467 2.584,39.25867 -4.64,6.06267 -15.18,9.508 -22.276,11.536 -21.07067,6.024 -63.98134,7.04267 -81.088,-8.61333 -13.28934,-12.164 -18.45467,-33.68534 -24.24534,-50.18134 l -4,1.33333 -5.33333,-17.33333 c 24.27867,-2.78133 33.91467,-33.52133 57.33333,-41.33333 m 328.00001,1.33333 c 13.32267,7.76533 22.768,16.98133 33.40401,27.99867 6.94666,7.19466 13.224,14.832 23.92933,13.33466 l 1.3333,1.33334 -5.3333,24 h -1.33333 l -2.66667,-9.33334 h -1.33333 c -8.71867,20.86401 -12.74534,50.58534 -37.33334,58.46534 -21.72,6.96133 -56.508,7.996 -77.33333,-1.67467 -10.91334,-5.068 -22.63334,-13.23333 -20.69734,-27.45733 0.92267,-6.78667 6.69067,-14.096 10.03067,-20 h 1.33333 c 0,8.11867 -0.77733,16.57467 3.116,24 22.06534,42.08667 74.49067,26.58933 82.21734,-16 l -33.33334,8 c 2.796,-20.556 10.612,-60.17867 -10.66666,-73.33334 l -2.66667,6.66667 h -1.33333 c 3.22666,-14.35067 15.708,-19.72133 27.70266,-8.79467 6.95467,6.336 9.74534,15.98934 12.044,24.79334 0.59067,2.264 2.49067,13.96 7.14667,10.308 8.94933,-7.02 -8.35467,-33.472 -8.22667,-42.30667 M 215.32749,311.3314 c -13.772,4.168 -8.14267,19.588 -8,30.66666 -7.48933,-6.38666 -6.66267,-16.27333 -6.66667,-25.33333 -18.06266,-3.052 -5.47733,-24.708 8.75467,-13.59333 2.832,2.212 4.33867,5.13333 5.912,8.26 m 261.33334,1.33333 v 2.66667 c -12.91733,5.19066 -8.896,15.32533 -6.66666,26.66666 -5.38134,-3.632 -15.92667,-20.34133 -17.05334,-26.78266 -1.472,-8.40667 8.44,-10.26267 14.36134,-7.90534 3.31866,1.32267 6.33733,3.48134 9.35866,5.35467 m -413.333337,2.66667 -1.33333,1.33333 1.33333,-1.33333 m -6.66667,13.33333 4,2.66667 v -1.33334 l -4,-1.33333 m 18.66667,180.00001 v 4 h -1.33333 l 1.33333,-4 m 349.333337,62.66667 c 0,17.77733 -2.26,32.136 -10.66667,48 -9.4,-5.81067 -23.30933,-33.76267 -13.44266,-43.90267 5.54,-5.69333 16.94133,-4.096 24.10933,-4.09733 m -145.33334,4 -8,46.66666 c -16.29333,-9.21866 -22.78666,-30.32 -24,-48 l 32,1.33334 m 124.00001,17.33333 v 10.66667 h 1.33333 z"
44- id="path972" />
45- <path
46- style="fill:#ffffff"
47- id="path1031"
48- d="m 171.07018,314.24465 c 1.43533,2.55362 3.43647,4.86204 6.02366,6.31063 15.51628,8.6877 9.94595,7.11918 18.45758,9.09921 2.9676,0.68502 6.00726,0.33711 9.00575,0.4851 0.51777,0.0256 1.97943,0.5002 1.53847,0.22762 -12.3291,-7.62122 -8.46459,-9.40075 -13.47067,-6.74985 -0.81161,0.51959 -1.60672,1.06589 -2.43482,1.55876 -4.52165,2.69122 -9.22362,5.08101 -13.79908,7.67767 -5.00626,2.84116 -5.79872,3.37745 -10.6807,6.35696 -3.21994,2.00581 -5.34709,3.29487 -8.43672,5.34334 -0.93485,0.61982 -1.86152,1.25236 -2.77524,1.90291 -0.53177,0.3786 -1.12785,0.69454 -1.56007,1.18372 -0.23297,0.26368 -0.67179,0.8249 -0.36192,0.99157 4.80104,2.58231 9.91238,4.5407 14.86857,6.81105 12.64758,-5.66419 24.4458,-13.03247 36.15953,-20.38753 8.83007,-5.82628 17.49966,-11.90043 26.13449,-18.01368 0.12258,-0.10977 0.51254,-0.25112 0.36775,-0.32931 -4.67223,-2.52289 -9.13452,-5.66802 -14.23142,-7.15676 -1.20748,-0.35269 -2.10539,1.37742 -3.14865,2.08026 -4.89213,3.29582 -9.54755,6.82945 -14.1897,10.46637 -9.49744,7.36804 -17.9992,15.83825 -26.01509,24.76432 -6.30592,7.59525 -8.14218,8.23949 10.2995,13.47521 1.78331,0.50629 3.3167,-1.66017 4.9328,-2.56834 5.73793,-3.22441 9.53881,-5.73893 15.19815,-9.3143 13.24893,-8.54994 25.96694,-17.90399 37.94901,-28.15332 1.04824,-0.91395 2.1079,-1.81498 3.14472,-2.74187 0.79084,-0.70699 1.56569,-1.43176 2.33769,-2.15926 0.53118,-0.50056 1.12786,-0.94513 1.57156,-1.52464 0.198,-0.25861 0.59124,-0.77034 0.30618,-0.92788 -14.71815,-8.13451 -10.1663,-9.48815 -17.93001,-5.21701 -11.41265,6.6473 -22.20287,14.27124 -32.75212,22.19736 -10.66088,8.29117 -20.89797,17.10296 -30.98403,26.07851 -0.98028,0.86051 -2.03371,1.64422 -2.94083,2.58153 -5.39678,5.57637 8.13193,7.60867 14.30926,9.09646 2.00094,0.48192 3.79185,-1.6031 5.66556,-2.45469 5.07424,-2.30622 9.60823,-4.57573 14.62099,-7.03268 11.14435,-5.70338 22.44245,-11.13252 33.38489,-17.22113 5.46451,-0.58006 14.20537,-1.41277 -14.20952,-6.74516 -1.73723,-0.32601 -2.8735,2.0593 -4.29587,3.10863 -5.86769,4.32876 -11.61624,8.8152 -17.40775,13.24456 -0.88721,0.84917 -1.97162,1.53157 -2.66162,2.54751 -0.28753,0.42334 -0.56545,1.30065 -0.10868,1.53142 5.12688,2.59017 10.33852,5.33462 15.9671,6.48047 1.71551,0.34924 3.17757,-1.47245 4.73846,-2.26529 4.90381,-2.49085 6.99171,-3.77194 11.81268,-6.53639 1.94264,-1.1845 3.88822,-2.3642 5.82792,-3.55351 4.47851,-2.74595 8.99301,-5.47352 13.30943,-8.47306 0.65181,-0.45295 2.58101,-1.00805 1.90658,-1.4266 -4.79779,-2.9775 -10.29254,-4.64766 -15.43882,-6.97148 -11.72135,5.99277 -22.91179,12.94688 -34.23961,19.6353 -6.60264,3.95287 -13.30201,7.72362 -20.18434,11.16672 -9.33663,4.8581 -9.99423,3.7481 8.12157,11.42393 0.83219,0.3526 1.70531,-0.59973 2.5527,-0.91405 1.02537,-0.38033 2.04352,-0.77986 3.06527,-1.1698 6.88738,-3.16305 13.93717,-5.90731 21.04709,-8.52021 4.06204,-1.52457 8.09972,-3.2178 12.32549,-4.25606 1.68407,-0.41377 2.88582,-0.56931 4.57605,-0.83571 -1.4108,-2.79602 3.18988,0.90932 2.44908,0.52434 -4.45476,-2.31509 -8.49787,-5.5392 -13.24768,-7.16519 -0.90867,-0.31106 -1.4379,1.27434 -2.1802,1.8838 -2.94314,2.41647 -3.50484,2.75165 -6.63769,5.0276 -6.26617,4.40862 -12.714,8.5456 -19.11454,12.75349 -4.45769,4.93043 -2.50115,2.00475 11.93444,10.09798 0.63232,0.3545 1.41442,-0.31883 2.11844,-0.49163 2.98621,-0.73295 5.93525,-1.60178 8.89459,-2.4344 5.27098,-1.7298 10.76117,-2.53812 16.20623,-3.48887 4.60406,-0.82453 9.1585,-1.85176 13.62925,-3.22747 3.11796,-1.15071 6.24676,-2.27099 9.18927,-3.82602 1.66601,-0.96562 3.51589,-1.59353 5.16478,-2.58761 0.59316,-0.3576 1.14403,-0.78107 1.71659,-1.1708 1.74687,-1.35545 2.65746,-2.35985 3.62271,-4.37357 0.84126,-2.07386 1.06124,-4.30847 1.19133,-6.52137 0.0789,-1.7605 0.0646,-3.52332 0.0576,-5.28505 0.42359,-6.7192 3.03353,-1.77248 -13.2615,-9.57841 -0.44856,-0.21487 -0.88179,0.46044 -1.3207,0.69441 -0.54734,0.29178 -1.09201,0.58855 -1.63801,0.88282 -3.72432,2.34742 -7.18224,5.08426 -10.82303,7.54849 -3.13256,2.27465 -6.59511,3.9843 -10.07352,5.64923 -2.68913,1.31556 -5.50527,2.2938 -8.12552,3.76052 4.28068,2.88035 8.26686,6.25599 12.84202,8.64104 0.64153,0.33443 1.30296,-0.62985 1.9647,-0.92223 4.26541,-1.8846 1.57695,-0.53142 6.36978,-2.80944 1.13947,-0.54159 6.36523,-3.15514 7.52569,-3.73405 2.81628,-1.45312 5.68135,-2.8072 8.50641,-4.24271 0.33246,-0.16893 1.29943,-0.30491 0.99237,-0.51652 -12.71423,-8.76181 -8.74434,-10.10314 -15.04117,-6.92273 -6.79382,3.53204 -13.74745,6.74262 -20.52354,10.31127 -7.5434,3.95521 -15.43438,7.12357 -23.44161,9.9938 -4.93666,1.93963 -10.00951,3.44204 -15.13808,4.76283 -0.61961,0.37578 -4.11298,0.37957 -4.53103,0.75257 -0.23898,0.21323 -0.7467,0.65064 -0.48212,0.83114 4.26858,2.91201 8.88087,5.28514 13.32131,7.92771 3.06017,-0.26036 6.1004,-0.66469 9.16565,-0.88282 3.5349,-0.27157 7.08772,-0.15372 10.62077,-0.41487 2.34311,-0.20178 4.68416,-0.42837 7.03502,-0.51429 1.98063,0.0101 3.95881,-0.2504 5.90142,-0.62775 1.98537,-0.26974 3.8929,-0.84394 5.80038,-1.43236 1.70419,-0.58082 3.50678,-0.50524 5.26603,-0.74464 2.53961,-0.30944 5.00901,-1.04243 7.50092,-1.60707 1.79673,-0.39005 3.62652,-0.69197 5.40419,-1.16894 0.77169,-0.20705 0.73152,-0.48877 0.6584,0.0322 -15.02092,-10.25008 -8.42834,-9.62098 -17.23578,-6.65309 -1.91185,0.77525 -3.84198,1.48065 -5.76047,2.23372 -2.1782,0.94213 -4.52971,1.33577 -6.77528,2.06463 -1.92286,0.62993 -3.95096,0.6523 -5.85054,1.34594 -1.43592,0.6249 -2.95828,0.80537 -4.50244,0.90949 -1.69973,0.41292 -3.45038,0.54237 -5.19138,0.64517 -1.88828,0.13475 -3.82163,0.27962 -5.61684,0.9172 -0.0211,-0.0295 -0.0943,-0.10741 -0.0634,-0.0885 4.4075,2.68861 8.5688,5.82092 13.20029,8.10215 0.50696,0.2497 0.95543,-0.60426 1.44328,-0.88951 2.47696,-1.44829 4.88574,-2.95444 7.29325,-4.51881 4.43353,-2.70197 8.70382,-5.65959 12.75546,-8.90627 1.32906,-0.98398 2.49335,-2.14207 3.62575,-3.33846 0,0 -12.90598,-7.87335 -12.90598,-7.87335 v 0 c -1.10475,1.13152 -2.24531,2.21955 -3.51853,3.16535 -4.0487,3.17935 -8.28306,6.10197 -12.67783,8.78408 -1.64902,1.06545 -3.39232,2.21147 -5.07695,3.22299 -0.72249,0.43381 -1.46465,0.83405 -2.19248,1.25885 -0.47497,0.27721 -1.81706,0.47105 -1.41578,0.8471 10.42477,9.76936 7.5619,11.33669 12.0127,8.76713 1.78097,-0.40206 3.61469,-0.5307 5.43243,-0.65354 1.74871,-0.15261 3.47842,-0.41518 5.21287,-0.6753 1.60693,-0.20434 3.15791,-0.59502 4.6777,-1.16108 1.93936,-0.48218 3.95267,-0.655 5.84512,-1.338 2.30159,-0.64532 4.63,-1.21698 6.8368,-2.15547 1.91913,-0.72053 3.81715,-1.49554 5.74076,-2.20447 8.62643,-3.25519 5.68409,-1.80659 -7.41768,-10.83793 -0.57951,0.42283 -1.36317,0.47509 -2.05091,0.62728 -1.43747,0.31811 -2.87464,0.63797 -4.31681,0.93429 -2.44658,0.58032 -4.88995,1.23651 -7.40174,1.46555 -1.83742,0.16771 -3.67129,0.3449 -5.42332,0.97071 -1.86505,0.56113 -3.76192,0.9855 -5.68941,1.27195 -1.92476,0.2718 -3.85014,0.48183 -5.79717,0.48098 -2.35121,0.13789 -4.69492,0.37057 -7.04479,0.52789 -3.54525,0.15356 -7.09771,0.15487 -10.63737,0.43806 -3.05823,0.25769 -6.10045,0.66447 -9.16265,0.87861 4.10095,2.6678 8.35865,5.10888 12.30286,8.00339 0.22243,0.16323 -0.50266,0.42091 -0.45351,0.69239 0.0141,0.0781 4.37054,-0.75142 4.58956,-0.83694 5.16506,-1.33006 10.23089,-2.98806 15.21856,-4.88087 8.04727,-2.92727 15.97342,-6.16211 23.55564,-10.16528 6.79245,-3.54284 13.74759,-6.75919 20.5298,-10.32082 6.1694,-3.46088 3.5939,-1.2335 -10.25015,-9.64727 -0.31437,-0.19106 -0.65421,0.33667 -0.98284,0.50202 -3.77307,1.89844 -0.71516,0.34579 -5.12115,2.54028 -1.12963,0.56264 -2.25547,1.13287 -3.38321,1.69931 -3.64669,1.81381 -7.27715,3.65196 -11.01842,5.26635 -0.92249,0.39807 -1.84114,0.80968 -2.78853,1.14419 -0.5191,0.18329 -2.01711,0.0453 -1.60091,0.40566 3.71695,3.21784 7.99353,5.72576 11.99029,8.58864 2.63436,-1.37598 5.4429,-2.34498 8.08645,-3.70104 3.53151,-1.71393 7.01231,-3.53031 10.19911,-5.84269 3.562,-2.48006 7.00535,-5.14327 10.72295,-7.3938 0.5256,-0.26341 1.03573,-0.56029 1.57681,-0.79022 0.34553,-0.14683 1.37754,-0.11453 1.07482,-0.33657 -13.82664,-10.14164 -11.55582,-12.30727 -12.54762,-6.01864 0.009,1.73726 0.0275,3.47508 -0.0202,5.21196 -0.0821,2.02459 -0.20817,4.07984 -0.90984,6.00091 -0.81376,1.75352 -1.64737,2.5454 -3.20333,3.67747 -2.14295,1.42279 -4.50639,2.42113 -6.78341,3.60728 -2.91093,1.43792 -5.96998,2.53986 -9.03631,3.59764 -4.44558,1.29448 -8.96449,2.28607 -13.52651,3.07895 -5.48707,0.94315 -10.98138,1.90216 -16.29401,3.61552 -1.12137,0.3136 -4.98063,1.40492 -6.11919,1.67531 -0.86475,0.20537 -1.72806,0.45221 -2.61364,0.5278 -0.54137,0.0462 -2.04418,-0.50768 -1.62151,-0.16625 11.42704,9.23077 8.40436,11.68934 12.60187,7.50083 6.42684,-4.20992 12.88092,-8.38087 19.1554,-12.81757 2.49735,-1.83513 4.3837,-3.14306 6.72175,-5.16166 0.83724,-0.72285 3.09224,-1.40127 2.41497,-2.27578 -6.33848,-8.18436 -7.96984,-10.4425 -14.66541,-9.60946 -5.89378,1.07124 -11.51294,3.08172 -17.0598,5.32199 -7.10879,2.62566 -14.12733,5.44994 -21.05306,8.52907 -1.01221,0.37581 -2.01669,0.77314 -3.03664,1.12742 -0.81369,0.28264 -3.03652,0.14035 -2.46272,0.78279 11.80339,13.21539 6.99615,10.9296 11.92284,8.12062 1.31947,-0.7523 3.74865,-1.97655 5.01135,-2.62453 6.89121,-3.49042 13.61336,-7.28283 20.24024,-11.25236 11.27734,-6.67828 22.43811,-13.59569 34.1506,-19.49953 -3.31035,-3.15832 -6.16118,-6.88237 -9.93104,-9.47496 -0.63445,-0.43632 -1.23051,0.92684 -1.86559,1.36225 -1.15761,0.79367 -2.33243,1.56218 -3.51117,2.32412 -5.12389,3.31211 -10.35273,6.45766 -15.57115,9.61718 -4.38295,2.49441 -7.24864,4.21481 -11.74375,6.43131 -1.51803,0.74853 -5.44101,0.61334 -4.62479,2.09606 2.12153,3.85393 7.02265,5.3155 10.31252,8.23619 0.20792,0.18458 -0.98326,0.41742 -0.80971,0.20021 0.73463,-0.91941 1.71836,-1.60849 2.57754,-2.41273 5.78153,-4.42323 11.52997,-8.88866 17.38712,-13.21198 1.42972,-1.05531 5.0733,-1.52866 4.31223,-3.13445 -5.81826,-12.27593 -6.3744,-11.75565 -11.23511,-9.58703 -10.94552,6.03635 -22.20646,11.4862 -33.34269,17.15676 -4.22937,2.06502 -10.22531,5.0476 -14.57292,6.96022 -1.81778,0.79967 -6.27975,0.40599 -5.52019,2.24089 1.70084,4.10876 6.3992,6.24034 10.07003,8.75027 1.49441,1.02179 0.88047,-9.44405 3.83393,-3.84665 10.06538,-8.93869 20.2702,-17.72751 30.91727,-25.97198 10.50559,-7.86626 21.24325,-15.45041 32.62871,-22.00546 7.36761,-3.8532 3.96829,-0.86418 -8.13522,-10.03306 -0.061,-0.0462 0.28008,-0.0735 0.22901,-0.0165 -0.47916,0.5348 -1.02792,1.00305 -1.55236,1.49352 -0.76503,0.71545 -1.53338,1.4276 -2.31581,2.12398 -1.03519,0.92135 -2.09105,1.81921 -3.13658,2.72881 -11.95628,10.17483 -24.62464,19.47989 -37.83252,27.9695 -4.36546,2.74513 -10.44576,6.69692 -15.10959,9.17839 -1.49209,0.79389 -5.49167,0.65909 -4.61365,2.10329 2.38531,3.9234 6.89231,6.21587 10.96295,8.34018 1.93356,1.00905 0.87576,-8.92908 3.97258,-5.19905 7.97197,-8.85114 16.43325,-17.24046 25.87613,-24.53933 4.62049,-3.60275 9.25775,-7.09819 14.12437,-10.36353 1.0398,-0.69767 3.88038,-1.05567 3.13843,-2.06435 -8.3794,-11.39171 -7.52997,-11.87727 -11.60778,-8.80552 -8.62141,6.08752 -17.25943,12.16238 -26.08925,17.94303 -11.65263,7.29252 -23.38273,14.63386 -36.00522,20.15284 3.76123,2.73954 7.65708,5.30322 11.28369,8.21863 0.17317,0.13921 -0.64943,0.598 -0.45933,0.48299 0.54064,-0.3271 1.0066,-0.76466 1.52415,-1.12721 3.62633,-2.54031 7.40811,-4.8487 11.16951,-7.18018 4.99607,-3.03314 5.5246,-3.4057 10.64708,-6.30516 4.6037,-2.6058 9.38129,-4.97264 13.86718,-7.78308 0.88771,-0.55616 1.72092,-1.19491 2.58138,-1.79236 0.50182,-0.59773 2.09084,-1.27701 1.50547,-1.79319 -3.87871,-3.42027 -8.57482,-5.79813 -13.05103,-8.38775 -0.47089,-0.27242 -1.08498,-0.086 -1.62866,-0.10489 -2.94034,-0.10204 -5.89671,-0.0548 -8.77687,-0.79164 -6.84974,-2.01396 -3.16404,-1.19536 7.6358,5.56497 0.6805,0.42597 -1.44013,-0.72013 -2.10585,-1.16884 -1.12844,-0.76059 -2.34725,-1.94327 -3.22517,-2.98418 -0.37019,-0.43891 -0.67583,-0.92844 -1.01374,-1.39266 0,0 -13.77387,-6.23173 -13.77387,-6.23173 z" />
49- <path
50- style="fill:#ffffff"
51- id="path1033"
52- d="m 232.73967,343.98425 c -0.16239,-4.21792 -0.38636,-8.43294 -0.53272,-12.65164 -0.17653,-6.95973 -1.85631,-4.87751 -13.29234,-10.63359 -0.04,-0.0201 -3.53227,3.33793 -3.78645,3.58181 -1.99028,1.91133 -4.00381,3.86285 -6.2969,5.4174 -1.06135,0.71952 -1.93388,1.15314 -3.0558,1.75665 -1.95113,0.78708 -4.0813,1.77702 -6.2486,1.72206 -0.41992,-0.0106 -0.83194,-0.11686 -1.24792,-0.1753 -0.48134,-0.10515 -0.97296,-0.17113 -1.44404,-0.31546 -1.88325,-0.57698 -3.69696,-1.49191 -5.50071,-2.26758 -2.1798,-0.9374 -2.2769,-0.95062 -4.52683,-1.82148 -4.44489,-1.59935 -9.05198,-2.51641 -13.72931,-3.06968 -0.51119,-0.087 -1.04311,-0.0927 -1.53355,-0.26109 -0.28832,-0.099 -1.01299,-0.67747 -0.75025,-0.5229 4.24182,2.49551 8.37529,5.17918 12.68969,7.54702 0.17035,0.0935 0.32998,-0.20543 0.49213,-0.31253 0.47218,-0.31192 0.93292,-0.64129 1.41076,-0.94447 0.64704,-0.41054 1.30876,-0.79748 1.96314,-1.19621 5.32786,-2.88263 10.80956,-5.47437 16.22858,-8.18209 4.78718,-1.95989 9.10461,-4.78507 13.46258,-7.52548 2.62671,-1.56346 5.19283,-3.22119 7.80177,-4.81406 1.1227,-0.93327 3.56593,-1.49092 4.53824,-2.47123 0.16716,-0.16853 0.24952,-0.40389 0.37428,-0.60583 -4.23667,-2.89754 -8.32988,-6.01688 -12.71002,-8.69261 -0.29187,-0.1783 -0.64392,0.2312 -0.96946,0.33612 -2.15826,0.69562 -4.30644,1.33446 -6.42473,2.15203 -4.29152,1.63988 -8.41307,3.66717 -12.6077,5.53018 -4.82936,2.0624 -9.56932,4.30241 -14.24575,6.68873 -3.90778,2.11644 -7.73683,4.3642 -11.5033,6.72178 -1.61315,0.98308 -3.112,2.13675 -4.70891,3.14424 -0.37871,0.23893 -0.78785,0.42969 -1.15472,0.68643 -0.77674,0.54356 -1.56314,1.30623 -2.2582,1.94248 -1.3674,1.53824 -3.49987,3.52541 -3.14491,5.83895 0.0542,0.35357 0.23681,0.67509 0.35521,1.01264 4.611,2.65737 9.10124,5.53626 13.833,7.9721 0.39531,0.2035 0.88276,-0.1075 1.32296,-0.17003 2.35331,-0.33431 4.41661,-1.16882 6.57287,-2.14245 4.66944,-2.08131 9.10602,-4.67138 13.44997,-7.35827 4.57957,-2.99451 9.14835,-6.0171 13.558,-9.25944 2.82993,-2.279 5.77344,-4.40642 8.73187,-6.51307 1.93406,-1.45357 3.91458,-2.83753 5.84743,-4.29188 1.50721,-1.13725 2.80699,-2.51181 4.20715,-3.77294 0.53578,-0.55497 1.54467,-0.85008 1.93694,-1.55022 2.9445,-5.25545 -8.98814,-6.22301 -12.95355,-8.48667 -2.46378,1.36153 -4.50197,3.20247 -6.39761,5.26251 -0.39857,0.48953 -5.58042,6.12839 -4.84005,6.67322 4.2012,3.09163 8.7864,5.62424 13.1796,8.43636 3.68207,-1.12239 6.2577,-3.8927 8.89656,-6.52432 0.0606,-0.0662 1.62195,-1.787 1.8513,-1.9885 0.12384,-0.10881 0.28737,-0.16161 0.43105,-0.24242 -4.39526,-2.2892 -8.63135,-4.91417 -13.18578,-6.86759 -0.34145,-0.14645 -0.2515,0.70012 -0.40601,1.03799 -0.22397,0.48975 -0.48546,0.96145 -0.72819,1.44218 -0.86092,1.6686 -2.0156,3.14343 -3.23225,4.56183 -0.24595,0.28673 -0.47983,0.58649 -0.7558,0.84446 -0.0265,0.0247 -0.071,-0.11872 -0.0392,-0.10134 4.40101,2.40648 8.77951,4.85392 13.16926,7.28089 1.46759,-2.4309 3.48191,-4.44214 4.9583,-6.86589 1.06137,-2.32718 0.93692,-4.83266 0.0823,-7.19189 -2.80578,-4.83762 -12.6773,-7.80282 -14.85026,-8.84928 -0.31655,-0.15245 -3.53109,1.16694 -3.87595,1.30947 -4.47781,1.99153 -8.79044,4.33462 -13.22011,6.43083 -4.21998,2.0963 -8.51473,4.03792 -12.87256,5.82964 -2.77042,1.16583 -5.6843,1.88722 -8.58772,2.62102 -2.80453,0.92069 -5.70723,1.44571 -8.56832,2.15075 -2.29694,0.62103 -4.58347,1.319 -6.66594,2.49128 -0.12684,0.0518 -0.49398,0.0785 -0.38052,0.15532 4.23064,2.86275 8.3752,5.89525 12.88037,8.30297 0.43136,0.23053 0.84219,-0.49762 1.26527,-0.74304 2.97907,-1.72813 5.83126,-3.60518 8.70066,-5.51101 5.75823,-3.98884 11.70446,-7.70199 17.74697,-11.24239 2.80788,-1.4419 5.6684,-2.82242 8.61115,-3.96542 2.70305,-1.02336 5.36367,-2.15739 8.10939,-3.06346 1.86988,-0.63702 3.74282,-1.3128 5.66466,-1.77909 1.44074,-0.18382 2.82028,-0.62291 4.25689,-0.83578 0.96481,-0.11847 1.939,-0.1301 2.9096,-0.15324 0.20012,-0.005 0.40765,-0.0685 0.60035,-0.0143 0.0912,0.0256 0.11119,0.15329 0.16679,0.22994 -12.44464,-7.68644 -11.33159,-11.89752 -15.32577,-3.63822 -0.92044,2.08126 -1.91653,4.06144 -3.30388,5.86997 -1.67673,1.99068 -4.14201,2.87169 -6.44358,3.91089 -5.85047,2.07862 -6.41927,-0.30481 11.67311,7.81678 0.39119,0.1756 0.56205,-0.64848 0.86246,-0.95446 0.8056,-0.82054 1.65794,-1.59387 2.48089,-2.39702 2.13255,-1.90308 3.73829,-3.81663 4.58765,-6.56018 0.51997,-2.17666 0.50849,-4.42627 0.51403,-6.65016 -0.0365,-1.98098 0.0282,-3.96596 -0.0874,-5.94446 -0.0469,-1.31854 -0.54398,-2.4502 -1.18941,-3.56455 -11.86794,-7.43097 -10.44561,-9.61528 -19.54019,-6.64195 -2.90504,1.06197 -5.64842,2.5092 -8.47323,3.76296 -2.82972,1.2514 -5.55757,2.71314 -8.35105,4.03951 -2.73802,1.46585 -5.62337,2.6416 -8.46532,3.89092 -2.93432,1.44932 -5.95304,2.71572 -8.93136,4.07021 -2.61697,1.18126 -5.17726,2.46451 -7.68626,3.85548 -0.43106,0.29155 -1.18643,0.74935 -1.55054,1.17364 -3.52989,4.11327 7.88695,6.33505 13.16143,8.67144 0.33506,0.14842 0.66895,-0.29947 1.00343,-0.44921 1.5692,-0.85195 3.27529,-1.39841 4.82461,-2.29156 0.80789,-0.46573 1.57825,-0.99384 2.37385,-1.48028 2.34583,-1.58221 4.30424,-3.63646 6.41685,-5.49806 1.46344,-1.30423 2.65707,-2.81893 3.34364,-4.6575 0.25683,-1.25844 0.81728,-2.81572 0.29801,-4.09133 -0.10581,-0.25993 -0.31332,-0.46569 -0.46998,-0.69854 -4.55348,-2.53758 -9.0634,-5.15497 -13.66045,-7.61274 -0.17399,-0.093 -0.39472,-0.015 -0.59186,-0.007 -0.12411,0.005 -0.47422,-0.0219 -0.36984,0.0455 4.24259,2.73583 8.50906,5.4383 12.85552,8.0059 0.073,0.0431 0.4994,-0.37586 0.75985,-0.61595 1.05025,-1.01942 1.86431,-2.28026 2.42797,-3.62806 0.31654,-0.52272 0.40322,-1.30266 0.96533,-1.64806 0.0979,-0.0602 0.21352,-0.0853 0.32028,-0.12794 0.13562,-0.0966 0.27123,-0.19324 0.40684,-0.28986 5.19693,-2.57523 10.31369,3.11931 -12.05308,-8.24825 -0.31398,-0.15958 -0.57926,0.4008 -0.86889,0.60121 -2.16543,1.33756 -4.16339,2.92389 -6.13481,4.52914 -1.66623,1.5478 -3.16695,3.26179 -4.80545,4.83609 -4.54417,4.78544 2.02375,3.79484 12.61783,9.20028 0.0449,0.0229 3.73072,-1.71529 3.99793,-1.84236 2.68136,-1.48749 5.35558,-2.96967 8.08898,-4.35972 1.77149,-0.97442 3.61795,-1.83865 5.50693,-2.55921 1.83273,-0.61603 3.6719,-1.20408 5.51169,-1.79859 1.74554,-0.54089 3.55893,-0.73243 5.34083,-1.11531 1.46277,-0.3253 2.95799,-0.3803 4.44952,-0.41387 1.62889,-0.0231 3.25805,-0.0161 4.88705,-0.0128 0.7882,0.19692 0.44532,0.0265 1.04462,0.47783 0,0 -11.91856,-9.30063 -11.91856,-9.30063 v 0 c -0.87451,-0.0778 -0.42265,-0.0541 -1.35592,-0.0645 -1.64103,8e-5 -3.28289,-0.005 -4.92286,0.0623 -1.54681,0.0866 -3.08424,0.26499 -4.59518,0.61963 -1.81956,0.37014 -3.67292,0.59739 -5.42732,1.241 -1.87635,0.58373 -3.73576,1.22084 -5.57977,1.8995 -1.8899,0.82 -3.77812,1.64933 -5.57518,2.66212 -2.72347,1.37718 -5.34241,2.93712 -8.06232,4.31931 -0.6843,0.3143 -2.00098,0.94025 -2.73657,1.19988 -0.32907,0.11614 -1.28367,0.058 -1.01018,0.27479 13.7453,10.89314 8.53254,11.61904 12.61923,7.42493 1.58097,-1.56706 3.0709,-3.22812 4.73039,-4.71521 1.96252,-1.57136 3.95718,-3.10421 6.08414,-4.44895 0.30082,-0.21807 1.18637,-0.41454 0.90245,-0.65421 -7.7916,-6.57739 -8.31092,-11.32876 -13.4836,-7.94085 -1.05349,0.71525 -1.69754,1.6303 -2.14331,2.82068 -0.43797,1.15738 -1.16584,2.19887 -2.07857,3.03463 -0.0545,0.0481 -0.92528,0.80376 -0.89054,0.83075 3.96643,3.08211 8.18217,5.82945 12.29744,8.70984 0.21262,0.14882 0.55613,0.0335 0.73193,0.26553 -4.11791,-2.3947 -8.23583,-4.78939 -12.35375,-7.18409 0.11825,0.0189 1.04835,0.11235 1.20019,0.31114 0.361,0.47262 -0.13437,2.13833 -0.19015,2.6962 -0.51158,1.63225 -1.70596,2.86555 -2.95481,3.99408 -2.02917,1.82599 -3.95843,3.79249 -6.27205,5.26938 -2.2578,1.37928 -4.6055,2.54372 -7.05381,3.5521 -5.58612,1.85709 -0.0223,-0.24329 11.68112,7.64689 0.40851,0.27541 -1.63103,1.30748 0.076,0.15975 0.2753,-0.18511 0.5646,-0.3485 0.84691,-0.52274 2.4821,-1.36587 5.04373,-2.57855 7.62031,-3.75523 2.97736,-1.34282 5.97456,-2.64164 8.92032,-4.05358 2.86232,-1.24381 5.72956,-2.48833 8.49579,-3.93692 2.76914,-1.35042 5.50943,-2.76014 8.32879,-4.00587 2.76516,-1.25474 5.47188,-2.67247 8.36843,-3.60722 1.26633,-0.36215 2.56448,-0.78342 3.89756,-0.80012 0.85192,-0.0107 0.82765,0.0752 1.53593,0.42768 -4.10255,-2.46371 -8.2051,-4.92742 -12.30765,-7.39113 0.87022,0.63506 1.41465,1.66666 1.57425,2.72657 0.29538,1.91362 0.0477,3.87804 0.15627,5.8075 0.009,2.09402 0.0596,4.20901 -0.31768,6.27765 -0.62923,2.46598 -2.34965,4.1958 -4.19368,5.88001 -0.32766,0.3196 -3.65762,3.49777 -3.50955,3.62853 10.25275,9.05415 7.97742,10.62804 13.96992,8.18094 2.5228,-1.13814 5.07757,-2.32734 6.8435,-4.54726 1.43052,-1.90044 2.50357,-3.95606 3.45643,-6.13263 3.80252,-7.93496 6.00519,-2.82532 -9.84222,-12.99277 -1.31575,0.30131 -2.7269,0.0687 -4.0426,0.43813 -1.44785,0.29793 -2.89095,0.62174 -4.3327,0.94508 -1.92223,0.5696 -3.83482,1.18028 -5.7206,1.8611 -2.74705,0.97129 -5.44361,2.07764 -8.158,3.13566 -2.96416,1.26301 -5.91996,2.56526 -8.73087,4.14719 -6.05698,3.565 -12.00387,7.32161 -17.79115,11.31094 -2.0647,1.36328 -4.22082,2.82155 -6.34676,4.10232 -0.74734,0.45023 -1.50867,0.8775 -2.2773,1.29035 -0.36773,0.19752 -1.45346,0.27595 -1.12997,0.53975 11.25958,9.18223 7.85353,10.77408 12.66198,8.30613 2.05871,-0.98645 4.25118,-1.63592 6.4617,-2.18772 2.86526,-0.69453 5.75463,-1.28469 8.57389,-2.15883 2.96319,-0.74043 5.89479,-1.61773 8.71335,-2.8061 4.36341,-1.83564 8.67479,-3.78933 12.91544,-5.89422 4.38121,-2.11024 8.66513,-4.43844 13.14946,-6.32484 0.79723,-0.27088 1.6527,-0.63379 2.51458,-0.67988 0.2792,-0.0149 1.07008,0.25495 0.83172,0.10879 -18.76731,-11.50759 -15.88476,-8.71313 -10.93879,-6.55684 1.1779,1.68282 1.33452,3.92655 0.61867,5.83 -1.51283,2.47276 -3.62336,4.50648 -5.0562,7.03708 4.23053,2.78584 8.38879,5.68466 12.6916,8.35752 0.11663,0.0725 0.19532,-0.19325 0.28736,-0.29514 0.26485,-0.29315 0.5285,-0.58756 0.78305,-0.88969 1.27543,-1.51383 2.4844,-3.08757 3.40139,-4.851 0.26271,-0.51611 0.54183,-1.02418 0.78813,-1.54831 0.2028,-0.43159 0.93409,-1.0235 0.55835,-1.31712 -12.01227,-9.38683 -10.01885,-11.64406 -15.33077,-5.90598 -1.79042,1.77947 -4.35818,4.82031 -7.00038,5.20786 -0.29162,0.0428 -0.58062,-0.10179 -0.87093,-0.15268 4.02282,2.65442 7.9953,5.38677 12.06847,7.96326 0.11339,0.0717 -0.15575,-0.27006 -0.0969,-0.39064 0.21338,-0.43739 0.56456,-0.79318 0.86224,-1.17819 1.14105,-1.47578 2.35791,-2.89629 3.60821,-4.2798 1.58596,-1.68233 3.44224,-3.73251 5.76525,-4.30599 -4.2131,-2.54932 -8.56527,-4.88182 -12.6393,-7.64798 -0.21311,-0.1447 0.33388,-0.3983 0.45002,-0.62823 0.0471,-0.0933 -0.15595,0.13926 -0.23661,0.20573 -0.55561,0.45787 -1.15351,0.8601 -1.69495,1.33579 -1.35883,1.24037 -2.64174,2.57235 -4.13464,3.65708 -1.919,1.45056 -3.90613,2.80573 -5.82514,4.25672 -2.96436,2.10935 -5.88464,4.27737 -8.74246,6.52975 -4.39397,3.19928 -8.92679,6.20958 -13.49223,9.15664 -4.30697,2.6285 -8.69454,5.17349 -13.33203,7.17839 -1.61461,0.66836 -3.19119,1.33514 -4.94353,1.54626 -5.13769,0.61898 -13.74732,-7.46153 9.72713,7.05026 -0.23995,-0.0713 -0.52011,-0.0631 -0.71985,-0.21402 -1.18523,-0.89541 1.85209,-3.97341 2.42794,-4.54589 2.44199,-2.14394 5.31075,-3.71203 7.98142,-5.55112 3.74114,-2.32298 7.54254,-4.54338 11.43148,-6.61142 4.66388,-2.36268 9.40496,-4.55199 14.20446,-6.62622 4.15951,-1.87582 8.27828,-3.84601 12.55452,-5.45065 1.64953,-0.59544 3.32117,-1.23265 5.04263,-1.59795 0.36175,-0.0768 0.73109,-0.18172 1.09941,-0.14867 0.18958,0.017 0.32738,0.19427 0.49107,0.29141 -4.04564,-2.7953 -8.09128,-5.59059 -12.13692,-8.38589 -1.49052,0.86633 -3.08899,1.54686 -4.54602,2.47941 -2.60577,1.60644 -5.19029,3.24617 -7.81092,4.82826 -4.32192,2.71934 -8.65468,5.41931 -13.38153,7.40097 -5.45576,2.72376 -10.99132,5.30639 -16.30537,8.29999 -0.95554,0.60573 -2.68974,1.62849 -3.56714,2.44843 -0.384,0.35884 -1.40022,0.87251 -1.00295,1.21659 7.44833,6.45106 8.6187,8.38054 15.11935,9.26037 4.61007,0.64895 9.14931,1.68107 13.50224,3.3662 3.83153,1.54046 7.61457,3.58519 11.77229,4.0085 2.81893,-0.10125 5.47716,-0.90361 7.97168,-2.23334 3.55204,-2.01968 6.71321,-4.56746 9.56112,-7.50291 0.0581,-0.0554 2.15763,-2.09521 2.47518,-2.27343 0.0932,-0.0523 0.3483,0.24732 0.25797,0.19025 -4.10953,-2.59651 -8.21085,-5.206 -12.31627,-7.80901 0.0837,0.25546 0.21511,0.49997 0.251,0.76638 0.0637,0.47319 0.0301,0.95446 0.0451,1.4317 0.13767,4.2041 0.32794,8.40601 0.55578,12.6062 0,0 13.47024,6.86343 13.47024,6.86343 z" />
53- <path
54- style="fill:#ffffff"
55- id="path1035"
56- d="m 185.8578,280.25304 c 2.1647,-1.03493 4.30367,-2.1219 6.44766,-3.19929 1.27197,-0.62913 2.53847,-1.12777 3.94755,-1.32128 1.43773,-0.12953 2.88474,-0.10256 4.32689,-0.10046 1.74211,0.005 3.48422,0.0109 5.22634,0.0137 1.74919,0.003 3.49839,0.003 5.24759,0.003 1.44315,9e-5 2.8863,0 4.32945,-3e-5 1.33089,-4e-5 2.66179,-5e-5 3.99269,-5e-5 1.32776,0 2.65551,0 3.98326,0 1.32931,0 2.65862,0 3.98793,0 0.17489,0.0539 0.34978,0.10779 0.52467,0.16169 0,0 -12.10961,-9.05049 -12.10961,-9.05049 v 0 c -0.21526,8.9e-4 -0.43052,0.002 -0.64578,0.003 -1.32931,0 -2.65861,0 -3.98792,0 -1.32776,0 -2.65552,0 -3.98327,10e-6 -1.33091,1e-5 -2.66181,3e-5 -3.99271,7e-5 -1.44308,3e-5 -2.88616,9e-5 -4.32924,-2.5e-4 -1.74869,-5.5e-4 -3.49737,-10e-4 -5.24605,-0.005 -1.74413,-0.004 -3.48827,-0.0127 -5.23239,-0.005 -1.50245,0.0167 -3.01946,0.0159 -4.49505,0.33689 -1.46312,0.37423 -2.82923,0.98965 -4.17862,1.66268 -2.15021,1.10101 -4.33465,2.13332 -6.47881,3.24688 0,0 12.66542,8.25477 12.66542,8.25477 z" />
57- <path
58- style="fill:#ffffff"
59- id="path1037"
60- d="m 517.46462,337.23029 c -2.56265,4.0164 -4.88261,8.17784 -7.31927,12.26999 -1.7766,3.09546 -4.32727,5.482 -7.18992,7.54762 -2.68734,1.95262 -5.15996,4.16998 -7.5556,6.46553 -1.38721,1.40056 -2.95482,2.59485 -4.53481,3.76485 -1.22963,0.91055 -2.3367,1.76135 -3.77132,2.29409 -5.09097,-1.74318 -1.08156,-0.40133 12.37559,6.98305 0.0258,0.0142 -0.004,-0.0636 0.0139,-0.0874 0.1837,-0.24854 0.37344,-0.49383 0.58333,-0.72068 0.75786,-0.81912 1.45438,-1.37948 2.30908,-2.12616 4.70831,-3.64505 10.04801,-6.34425 15.17505,-9.33458 2.03876,-1.27739 4.2988,-2.28601 6.19949,-3.78479 0.32219,-0.25406 0.62927,-0.53694 0.87598,-0.8648 0.15744,-0.20924 0.5329,-0.57258 0.31596,-0.71924 -4.29597,-2.90429 -8.86989,-5.37458 -13.30483,-8.06188 -3.57739,1.68493 -6.85132,3.90699 -10.27248,5.87217 -2.74604,1.62213 -5.6826,2.87125 -8.61663,4.10562 -2.65401,1.21151 -5.20027,2.67454 -7.58553,4.35282 -2.14447,1.40823 -4.28086,2.81275 -6.3615,4.31081 -1.40627,0.71348 -2.30929,1.77998 -2.62065,3.32068 0.81016,7.36832 12.11254,9.3282 18.52914,9.04592 2.50936,-0.31243 5.04454,-0.61344 7.40166,-1.56914 1.97441,-0.61026 2.76446,-1.84839 3.76012,-3.5379 1.07739,-2.05927 1.9625,-4.20328 2.99711,-6.28434 1.3492,-2.74011 3.16209,-5.22591 4.86933,-7.75101 1.18101,-1.65339 2.46708,-3.2623 3.46898,-5.03427 1.7824,-6.11494 -7.84664,-6.6416 -13.30612,-9.6205 -0.2292,-0.12506 -3.29355,0.22879 -3.63397,0.27387 -2.89195,0.30971 -5.62076,1.31686 -8.29068,2.41542 -2.82065,1.13197 -5.46628,2.58099 -8.06971,4.13143 -2.53638,1.22945 -4.83578,2.84226 -7.17486,4.3939 -1.3709,0.9908 -2.65439,2.08728 -3.75772,3.36967 -1.68219,1.5319 -3.2942,3.1344 -4.9593,4.68411 -1.45269,1.52497 -2.83742,3.11478 -4.17917,4.73738 -3.63798,5.37456 3.79556,4.99992 12.33474,9.71531 0.36277,0.20032 0.74858,-0.35639 1.13043,-0.51739 2.83931,-1.19711 0.0772,0.11931 3.2847,-1.4869 2.05827,-1.08132 4.03317,-2.30736 5.99544,-3.55117 0.47837,-0.30323 2.68062,-1.74462 3.32498,-2.03614 0.35437,-0.16032 0.74954,-0.2081 1.1243,-0.31215 5.21693,1.88088 0.36376,0.16898 -12.03101,-6.67223 -0.15964,-0.0881 0.50681,0.0961 0.47276,0.27524 -0.0452,0.23791 -0.50356,0.73864 -0.59892,0.8559 -1.01692,1.01725 -2.09668,1.99757 -3.23289,2.88304 -1.13366,0.77062 -1.78521,1.99288 -2.66487,3.00144 -0.4999,0.5181 -1.82578,2.76169 -0.85978,3.33392 4.5024,2.66709 9.1647,5.0545 13.74706,7.58175 2.23462,0.21341 4.48944,-0.29226 6.57794,-1.06304 0.183,-0.12081 0.39287,-0.20847 0.549,-0.36244 0.24654,-0.24315 0.89597,-1.50801 0.63823,-1.67783 -10.79379,-7.11175 -8.78038,-7.42141 -15.10327,-7.57337 -2.15751,-0.16966 -4.25743,0.16954 -6.33775,0.69945 -1.47207,0.22532 -2.86569,0.7409 -4.32815,0.98616 -1.4266,0.0627 -2.75764,0.56693 -4.1574,0.79381 -1.34956,0.0761 -2.77182,0.34036 -4.09815,0.0146 -0.19362,-0.0476 -0.39408,-0.0731 -0.58087,-0.14283 -0.44722,-0.167 -1.67781,-0.9967 -1.05789,-0.6456 20.98616,11.88561 15.60495,9.05661 11.16471,6.32842 -1.5196,-0.99201 -1.64758,-2.44567 -1.34593,-4.11665 0.81182,-3.10827 3.25991,-5.22887 5.61134,-7.23229 3.34059,-2.85841 7.1928,-4.9601 11.03557,-7.04621 4.20603,-2.0403 8.36157,-4.17644 12.43734,-6.46651 2.83986,-1.81873 5.72411,-3.57291 8.44219,-5.57327 1.02334,-0.93747 3.59337,-2.12394 4.07012,-3.46338 0.1339,-0.37621 0.1713,-0.78006 0.25695,-1.17009 -4.58492,-2.39842 -9.04567,-5.05089 -13.75477,-7.19526 -0.3497,-0.15925 -0.7063,0.30296 -1.06207,0.44814 -2.01467,0.82213 -3.9665,1.70254 -5.91589,2.67225 -3.65467,2.01385 -7.46814,3.79404 -10.77826,6.35973 -0.21249,0.34647 -0.5386,0.64517 -0.63747,1.03939 -1.30502,5.20313 13.83595,8.79595 14.32339,9.01748 0.61437,0.27922 1.34531,-0.12016 2.0121,-0.22411 1.77354,-0.2765 3.17125,-0.64859 4.92228,-1.06644 4.5451,-1.30218 8.81106,-3.4264 12.94588,-5.69043 1.97421,-0.92375 3.81889,-2.13756 5.873,-2.87661 1.77393,-1.01584 3.70407,-1.74284 5.64601,-2.36653 0.2903,-0.0996 0.5646,-0.27948 0.8709,-0.29874 0.14174,-0.009 0.48783,0.29097 0.36421,0.22107 -4.36595,-2.4686 -8.555,-5.27784 -13.09134,-7.41726 -0.35331,-0.16663 -0.51614,0.58655 -0.77843,0.87603 -1.08393,1.19626 -1.25521,1.36145 -2.40611,2.57297 -3.24235,3.30458 -6.48143,6.60383 -9.86178,9.77025 -3.28404,3.17757 -6.24306,6.66774 -9.44409,9.92313 -2.01291,2.02617 -4.18264,3.87968 -6.23082,5.8679 -1.9223,1.70425 -3.86185,3.39422 -5.88858,4.9743 -0.96722,0.65082 -1.83726,1.617 -2.96371,2.01994 -0.16514,0.0591 -0.37401,0.12202 -0.52514,0.033 -0.1206,-0.071 -0.22959,-0.47479 -0.10781,-0.4058 4.37015,2.47548 8.64001,5.12397 12.96002,7.68596 0.53794,-0.73321 1.39731,-1.28776 1.82599,-2.1042 2.47982,-4.72287 -10.9966,-8.27065 -13.75481,-9.82443 -3.01935,-0.16368 -6.00541,0.25868 -8.9999,0.5565 -3.33136,0.25062 -6.66491,0.53234 -9.97631,0.97952 -1.49207,0.0622 -2.91511,0.57342 -4.40233,0.66511 -0.31252,0.0193 -0.63635,0.0538 -0.93899,-0.0266 -0.22732,-0.0604 -0.79953,-0.49519 -0.59419,-0.3805 4.19573,2.34367 8.37764,4.712 12.56647,7.06801 -0.12644,-0.0812 -0.29432,-0.11966 -0.37932,-0.24357 -0.73362,-1.06945 0.52294,-2.95045 0.98506,-3.83506 1.54566,-2.92927 3.75683,-5.3607 6.04032,-7.72501 3.25479,-3.46003 6.89274,-6.53596 10.49318,-9.62639 3.01741,-2.51586 6.22504,-4.77774 9.2122,-7.33076 2.43349,-2.03466 5.14772,-3.71373 7.78144,-5.47881 7.74668,-5.34821 5.26549,-2.15496 -8.93837,-11.02194 -3.67679,2.35575 -7.28617,4.81658 -10.95286,7.1879 -3.60708,2.14166 -6.78641,4.82897 -9.89312,7.62168 -1.86144,1.77073 -3.57985,3.68242 -5.41099,5.4821 -1.46318,1.5278 -2.96981,3.07905 -4.72685,4.26969 -1.60606,1.0531 -3.23293,2.05513 -4.91002,2.98816 -1.48842,0.87657 -2.93256,1.85966 -4.62833,2.26668 -2.29749,0.73424 -4.56561,1.54956 -6.89065,2.19388 -0.21725,0.033 -1.44035,0.30317 -1.78598,0.0997 -0.1635,-0.0962 -0.41263,-0.60608 -0.24764,-0.51244 4.26109,2.41814 8.34115,5.14836 12.62053,7.53399 0.13307,0.0742 0.031,-0.30774 0.0903,-0.44808 0.14456,-0.342 0.34422,-0.658 0.51633,-0.987 2.37142,-3.19148 5.06218,-6.13906 7.75268,-9.063 2.78598,-3.28539 5.95741,-6.19831 9.12416,-9.10549 2.81779,-2.65813 5.52878,-5.42524 8.32563,-8.10749 2.35912,-2.2275 4.9302,-4.20401 7.51479,-6.15787 2.21682,-1.57455 4.70767,-2.64853 7.23257,-3.61266 2.59322,-1.05976 5.31693,-1.74942 7.95483,-2.67784 6.95804,-2.23836 5.26274,-0.70784 -10.5794,-8.877 -0.38723,-0.19968 -0.77405,0.40021 -1.15813,0.6059 -0.57211,0.30639 -1.14022,0.62019 -1.71034,0.93029 -5.10523,2.95137 -10.1952,5.93597 -15.4318,8.64977 -3.75271,2.05164 -7.48656,4.12742 -11.25821,6.14133 -2.71694,1.28113 -5.35381,2.71843 -8.01141,4.11619 -1.86867,0.9152 -3.66552,1.95908 -5.47922,2.98047 -1.72455,1.10921 -3.54098,2.13062 -5.05664,3.51931 -0.90463,0.82401 -2.04789,2.27228 -3.2307,2.66682 1.2635,-5.34205 -0.16936,0.0679 12.96906,7.26428 0.17418,0.0954 0.22262,-0.32904 0.32813,-0.49729 0.11736,-0.18717 0.22653,-0.37935 0.34031,-0.56871 0.0591,-0.0985 0.27684,-0.23544 0.17847,-0.2947 -4.39251,-2.6466 -8.90136,-5.09514 -13.35205,-7.6427 -1.49531,0.85332 -2.7992,1.97088 -4.22888,2.92082 -4.9037,3.89088 -8.1625,-1.2946 11.33893,8.0153 0.21258,0.10149 0.0789,-0.46569 0.14649,-0.69135 0.0927,-0.30958 0.22227,-0.60693 0.33341,-0.91039 0.9684,-2.27791 2.17116,-4.41805 3.44801,-6.53272 1.83613,-2.66105 4.2652,-4.84603 6.6056,-7.05325 2.98662,-2.55678 6.31032,-4.67369 9.50952,-6.94733 2.50895,-1.86606 5.26592,-3.33396 7.90531,-4.98838 2.47853,-1.36936 5.0954,-2.41932 7.72677,-3.44606 3.54649,-1.27142 7.2593,-1.95785 10.88689,-2.95161 3.44504,-1.09078 6.90807,-2.11655 10.36057,-3.18415 1.57896,-0.50775 3.15215,-1.01407 4.66702,-1.69465 0.22148,-0.0995 0.40862,-0.34535 0.65075,-0.32727 0.13052,0.01 -0.0964,0.24337 -0.14462,0.36505 -4.28721,-2.811 -8.39676,-5.91369 -12.86162,-8.433 -0.33959,-0.19162 -0.65762,0.4197 -0.9957,0.61396 -2.34749,1.34885 -4.80653,2.53706 -7.25277,3.6926 -4.36193,1.75711 -8.68282,3.60481 -12.99058,5.49322 -4.10098,1.72429 -7.9938,3.8697 -11.98214,5.82283 -2.74984,1.24947 -5.53582,2.41306 -8.32154,3.58056 -2.33776,0.90325 -4.64698,1.81777 -6.82899,3.06087 -1.6298,0.95465 -2.93181,2.35203 -4.53805,3.32872 -1.22767,0.5776 -2.9154,2.1317 -4.32374,1.80128 -2.79977,-4.49808 -0.18699,-0.17007 12.40537,6.86221 0.13592,0.0759 -0.24331,-0.20851 -0.31037,-0.34901 -0.3676,-0.7701 -0.29072,-1.79017 -0.29405,-2.60894 0.27412,-1.91104 1.29442,-3.60878 2.49962,-5.08165 1.12269,-1.14938 2.65266,-1.66837 3.92767,-2.62115 1.45993,-0.82313 2.77198,-1.84586 4.11828,-2.83995 1.71524,-1.19027 3.37924,-2.44494 5.19567,-3.48489 2.614,-1.40322 5.09913,-3.01589 7.68692,-4.46278 2.75467,-1.40031 5.54523,-2.81309 8.46488,-3.83629 3.572,-1.39268 7.35073,-2.15702 11.06617,-3.06688 2.1504,-0.43081 4.24068,-1.17674 6.24799,-2.0536 1.84007,-0.61386 3.57214,-1.48886 5.38903,-2.15968 2.39709,-0.73828 4.7544,-1.60292 7.1525,-2.33873 2.13542,-0.69731 4.39264,-0.70615 6.57979,-1.09077 1.97808,-0.16946 3.86686,-0.73435 5.77371,-1.23803 2.24307,-0.87141 4.58412,-1.44007 6.8694,-2.18694 2.22625,-0.79063 3.47755,-2.45154 4.24552,-4.59789 0.49708,-1.74364 0.66891,-3.12814 -0.47933,-4.63119 -3.93217,0.9071 -9.47789,-8.04187 -15.22808,-8.02582 -0.90173,0.003 -1.78593,0.33746 -2.64717,0.56315 -2.16621,0.70967 -3.8994,2.23159 -5.77133,3.47988 -1.28133,0.95004 -2.63044,1.80516 -3.90936,2.7585 -0.35558,0.26506 -0.71563,0.52642 -1.04581,0.82251 -0.19353,0.17354 -0.73737,0.43927 -0.51972,0.58138 4.38614,2.86373 9.04269,5.29022 13.56403,7.93533 2.86923,-1.07989 5.45916,-2.74922 7.90565,-4.5716 1.30728,-1.18837 2.73972,-2.28872 3.7411,-3.76068 0.69028,-1.09515 0.57253,-2.17863 0.22624,-3.35306 0,0 -13.75609,-6.27088 -13.75609,-6.27088 v 0 c 0.43127,0.58847 0.90259,1.25446 0.5011,1.95547 -0.93702,1.30814 -2.28597,2.27716 -3.47973,3.33299 -2.35429,1.69155 -4.84173,3.25548 -7.6231,4.14233 3.94379,2.81864 7.78552,5.78584 11.83137,8.45591 0.15408,0.10169 0.25521,-0.2681 0.39804,-0.38505 1.55286,-1.27142 3.28779,-2.30435 4.90031,-3.49462 1.73096,-1.16737 3.36424,-2.58064 5.46172,-3.01518 0.62992,-0.11694 0.95322,-0.2188 1.59062,-0.18113 4.62466,0.27329 16.94781,10.99054 -10.17189,-6.59562 1.74126,0.70663 1.64617,1.19544 1.27065,2.96111 -0.59489,1.92717 -1.5473,2.7829 -3.50056,3.38484 -2.28542,0.69029 -4.56126,1.40392 -6.83314,2.13394 -1.86368,0.50835 -3.75489,0.86129 -5.67334,1.09378 -2.26937,0.26543 -4.53808,0.57399 -6.71882,1.29155 -2.40682,0.79019 -4.81647,1.57331 -7.20528,2.41681 -1.80044,0.7305 -3.58032,1.50925 -5.40406,2.1821 -2.00043,0.75722 -4.01242,1.47936 -6.12371,1.87071 -3.75896,0.93132 -7.56641,1.75112 -11.16582,3.21306 -2.93909,1.16982 -5.82135,2.51337 -8.59563,4.03386 -2.57918,1.47872 -5.10173,3.05203 -7.70477,4.49029 -1.81837,1.12195 -3.53915,2.37807 -5.2793,3.6155 -1.32898,0.99565 -2.69698,1.91207 -4.09599,2.80484 -1.43419,0.93394 -3.04899,1.61757 -4.15253,2.98549 -1.3792,1.75296 -2.51107,3.72858 -2.9716,5.9322 -0.42607,6.72358 2.58546,13.89204 13.95641,11.7794 1.70484,-0.52484 3.23516,-1.48185 4.74126,-2.41844 1.50416,-1.05667 2.85238,-2.33425 4.47183,-3.22635 2.17907,-1.14349 4.47115,-1.99876 6.74447,-2.93278 2.80281,-1.1927 5.62687,-2.33761 8.37308,-3.65834 3.94777,-1.99116 7.87216,-4.0231 11.94217,-5.76243 4.31945,-1.89631 8.68307,-3.6801 13.01855,-5.53542 1.95914,-0.9522 3.90805,-1.926 5.82546,-2.96058 0.50397,-0.27193 0.99884,-0.56038 1.49829,-0.84052 0.31277,-0.17543 1.20979,-0.29179 0.93838,-0.52618 -3.74847,-3.2371 -7.92031,-5.94879 -11.88046,-8.92318 -1.22719,1.18526 -4.01355,1.49621 -5.55291,2.18899 -3.43466,1.07096 -6.88468,2.09121 -10.32725,3.13547 -3.67765,0.9652 -7.42888,1.70351 -10.98125,3.09056 -2.69832,1.05992 -5.36095,2.21186 -7.87351,3.66898 -2.70171,1.61585 -5.40577,3.22217 -7.95915,5.07237 -3.26329,2.2977 -6.63032,4.47577 -9.62334,7.12899 -2.43197,2.30605 -4.93949,4.60371 -6.81524,7.40375 -1.324,2.1933 -2.56344,4.41585 -3.58314,6.77064 -0.28262,0.72486 -0.78535,1.96378 -0.70523,2.0225 11.11095,8.14252 9.2911,11.16909 14.84179,7.04588 1.36792,-0.96609 2.68778,-2.00505 4.16026,-2.81433 -4.13289,-2.7284 -8.2024,-5.55535 -12.39869,-8.18521 -0.0147,-0.009 -0.14428,0.25714 -0.49677,0.8256 -0.14461,0.23321 -0.65593,0.52359 -0.4415,0.6948 11.86492,9.47378 8.42352,11.94948 12.84847,8.4416 1.25222,-0.84217 2.34972,-1.89283 3.41529,-2.95596 1.52098,-1.23338 3.20569,-2.25517 4.88718,-3.25333 1.81755,-0.98999 3.586,-2.06616 5.45951,-2.95033 2.66572,-1.38772 5.30731,-2.8214 8.0166,-4.1241 3.77769,-1.99676 7.47864,-4.12937 11.25287,-6.13319 5.2221,-2.75618 10.32081,-5.73451 15.44415,-8.66863 0.5681,-0.30701 1.13636,-0.61371 1.70429,-0.92104 0.39362,-0.213 1.51892,-0.34725 1.18035,-0.63995 -10.78271,-9.32163 -7.87059,-10.07916 -14.60754,-7.84877 -2.67927,0.86748 -5.3831,1.67329 -7.99425,2.73628 -2.62213,1.04815 -5.18008,2.27437 -7.45988,3.96169 -2.62589,1.99186 -5.21853,4.03187 -7.60812,6.30749 -2.7874,2.6763 -5.48264,5.44509 -8.30843,8.07963 -3.19517,2.93828 -6.36993,5.90573 -9.19109,9.21472 -2.77132,3.02267 -5.56044,6.06177 -7.9508,9.39909 -0.21109,0.3984 -0.44372,0.7861 -0.63328,1.19518 -0.17102,0.3691 -0.76758,0.88555 -0.44676,1.13567 8.73674,6.81144 8.79946,9.68501 15.48409,8.02983 2.31854,-0.69686 4.60328,-1.49915 6.91256,-2.22598 1.69473,-0.6526 3.23281,-1.61021 4.7984,-2.5221 1.71744,-0.96571 3.37745,-2.03192 5.0069,-3.13882 1.74325,-1.34381 3.35258,-2.85474 4.85028,-4.46755 1.79202,-1.80288 3.519,-3.67001 5.3695,-5.41483 3.06276,-2.73341 6.23067,-5.31534 9.76881,-7.42471 3.56377,-2.32805 7.04548,-4.81206 10.75925,-6.89388 -13.9328,-9.40918 -8.69904,-10.77058 -16.32658,-5.58395 -2.66559,1.80994 -5.43462,3.49608 -7.86903,5.61743 -3.01558,2.52213 -6.21582,4.80304 -9.21838,7.34065 -3.63424,3.12382 -7.29004,6.24902 -10.5699,9.75083 -2.36345,2.48192 -4.64404,5.04026 -6.25701,8.09181 -0.98025,1.86575 -1.87764,3.74138 -1.49605,5.91216 4.47978,2.60314 8.89349,5.32345 13.43933,7.80943 0.21105,0.11542 1.77486,-0.0717 1.89732,-0.0878 1.46508,-0.19215 2.91168,-0.51867 4.38649,-0.64271 3.31234,-0.38091 6.63266,-0.6784 9.95549,-0.95017 2.84468,-0.29162 5.80106,-0.75883 8.61293,-0.0364 -4.01164,-2.39741 -8.13559,-4.6162 -12.03493,-7.19225 -0.57156,-0.37759 1.09357,-0.4585 0.87991,-0.0213 -0.43108,0.88221 -1.47212,1.41354 -1.93076,2.28225 11.59732,8.66545 9.22916,11.9968 16.51576,6.29329 2.04341,-1.63486 4.03926,-3.33374 5.96812,-5.10217 2.06707,-1.98767 4.24691,-3.85399 6.25376,-5.90532 3.16428,-3.26173 6.1361,-6.70905 9.40961,-9.867 3.39505,-3.19207 6.64747,-6.51931 9.90836,-9.84623 0.25916,-0.27451 3.50217,-3.69601 3.39004,-3.79676 -9.96672,-8.9553 -7.81314,-10.57163 -13.77855,-8.49854 -1.94142,0.70676 -3.84608,1.49186 -5.68861,2.42977 -2.04138,0.89469 -3.9767,2.02298 -5.96838,3.02078 -4.06108,2.16276 -8.23356,4.22156 -12.71629,5.34929 -1.62184,0.34891 -3.11936,0.73218 -4.77701,0.8605 -0.61269,0.0474 -2.33845,-0.37035 -1.84358,-0.006 3.71143,2.73254 7.973,4.64748 11.74493,7.29587 0.25756,0.18084 -0.59442,0.45068 -0.8848,0.32936 -0.20091,-0.0839 0.22928,-0.37022 0.34392,-0.55533 3.31339,-2.409 7.02441,-4.18858 10.62438,-6.12136 1.49899,-0.72828 2.99183,-1.4498 4.55192,-2.04062 0.40737,-0.15427 0.8126,-0.31832 1.23226,-0.43509 0.27664,-0.077 1.09197,0.009 0.84932,-0.1441 -4.08456,-2.58476 -8.36799,-4.841 -12.55199,-7.26149 0.23631,-0.26817 0.46018,-0.54785 0.70893,-0.80451 0.0633,-0.0653 -0.085,0.16444 -0.15015,0.22795 -0.20966,0.20434 -0.43979,0.38689 -0.66838,0.5698 -0.49416,0.39542 -1.39427,1.05108 -1.89772,1.41656 -0.35135,0.25507 -0.70466,0.50742 -1.05699,0.76112 -2.69606,1.9333 -5.52364,3.67906 -8.3446,5.4215 -4.08556,2.2643 -8.25282,4.36573 -12.4297,6.4548 -3.91946,2.15218 -7.81879,4.36688 -11.20893,7.31806 -2.56409,2.24927 -5.13013,4.66762 -6.08973,8.05732 -0.12254,0.45699 -0.29555,0.90336 -0.36763,1.37098 -0.21991,1.42675 0.1583,3.14531 0.98221,4.31256 0.6152,0.57294 0.40284,0.43897 1.13778,0.86491 4.4268,2.56565 9.66767,7.64816 15.51758,8.2066 1.45832,-0.0239 2.92651,-0.0724 4.35816,-0.37589 1.38534,-0.3112 2.76572,-0.60664 4.17151,-0.81405 1.44323,-0.36329 2.89038,-0.69179 4.34497,-1.01042 2.00379,-0.416 4.03855,-0.52536 6.07966,-0.33812 5.72181,0.63545 5.81025,4.57384 -11.08516,-7.05781 -0.29961,-0.20627 0.46661,-0.55834 0.69003,-0.84538 0.0199,-0.0256 0.0697,-0.0944 0.0376,-0.0895 -0.14156,0.0215 -0.2695,0.0969 -0.40425,0.14532 -1.96016,0.55559 -4.04613,0.95541 -6.03614,0.31552 4.10167,2.38174 8.28145,4.63379 12.30502,7.14522 0.18644,0.11637 -0.43218,0.0811 -0.64597,0.1321 -0.13909,0.0332 -0.29574,0.20363 -0.41181,0.12012 -0.14738,-0.10602 0.68335,-1.16831 0.78671,-1.28219 0.79287,-0.96803 1.45595,-2.04923 2.51794,-2.76208 1.18285,-1.00593 2.39933,-1.99452 3.41218,-3.17648 0.54333,-0.72825 1.11914,-1.48461 1.24315,-2.42177 0.72239,-5.45934 -9.27368,-9.76863 -14.60467,-8.23205 -3.72115,1.5986 -6.82861,4.35869 -10.52914,6.02585 -0.79037,0.35684 -2.17456,1.06117 -3.10237,1.21728 -0.22721,0.0382 -0.8739,-0.22497 -0.68489,-0.0932 19.74632,13.76521 9.39646,11.90022 12.61258,7.28013 1.31409,-1.56503 2.64049,-3.12515 4.08106,-4.57625 1.69193,-1.58653 3.36292,-3.19567 5.0329,-4.80455 1.06326,-1.15314 2.25451,-2.16265 3.55446,-3.04299 2.30645,-1.56012 4.65827,-3.0362 7.13059,-4.32514 2.54667,-1.50998 5.18658,-2.83633 7.93981,-3.93423 2.59206,-1.0032 5.24439,-1.8682 8.03951,-2.05215 0.75935,-0.0307 1.59431,-0.11006 2.35351,0.0289 0.2913,0.0533 1.09313,0.43888 0.84307,0.28023 -4.01992,-2.55051 -8.1716,-4.89054 -12.1736,-7.46908 -0.12926,-0.0833 0.30923,-0.13404 0.45394,-0.082 0.0848,0.0305 0.0364,0.17647 0.0545,0.26471 -0.97645,1.73866 -2.26935,3.29571 -3.40716,4.92792 -1.74567,2.58014 -3.58934,5.12616 -4.97015,7.92547 -1.01904,2.04947 -1.90463,4.1522 -2.94347,6.19086 -0.97594,1.65484 -1.31084,2.04841 -3.20104,2.64489 -2.30959,0.69141 -4.70391,1.0518 -7.11217,1.15573 -1.14872,-0.0364 -1.5685,-0.0119 -2.68256,-0.18722 -0.36652,-0.0577 -0.73474,-0.11952 -1.08936,-0.22867 -0.27354,-0.0842 -1.02827,-0.49867 -0.78483,-0.34817 12.1521,7.51284 16.98049,7.11315 11.74075,7.44753 -0.64524,-0.69029 1.54481,-1.79198 1.90769,-2.06977 2.0632,-1.5008 4.23889,-2.83145 6.34049,-4.2778 2.35593,-1.56803 4.81573,-2.99305 7.42314,-4.10393 2.96865,-1.28918 5.94312,-2.576 8.71726,-4.2586 2.65138,-1.54923 0.9872,-0.56128 3.718,-2.22274 1.63477,-0.99462 3.31862,-2.11829 5.12482,-2.79026 0.3178,-0.11823 0.66178,-0.14818 0.99267,-0.22226 -4.06444,-2.69254 -8.12888,-5.38507 -12.19331,-8.07762 -0.0247,-0.0164 0.0948,0.0277 0.0743,0.0492 -0.22712,0.23843 -0.47605,0.45804 -0.74216,0.65199 -1.93532,1.41053 -4.11286,2.46482 -6.15034,3.70855 -5.2118,3.0227 -10.63123,5.78809 -15.33673,9.58923 -1.13626,1.02541 -2.40527,2.04636 -3.20845,3.3799 -0.23462,0.38955 -0.96384,0.95945 -0.59618,1.22707 11.84803,8.6244 8.4217,9.58039 13.95895,7.68437 3.19911,-1.63845 5.94733,-4.08316 8.58378,-6.49278 2.36984,-2.23814 4.80586,-4.40541 7.4576,-6.30866 2.96125,-2.25307 5.63225,-4.7928 7.49983,-8.05914 2.42312,-4.07031 4.73001,-8.20967 7.28048,-12.20339 0,0 -13.04362,-7.64316 -13.04362,-7.64316 z" />
61- <path
62- style="fill:#ffffff"
63- id="path1039"
64- d="m 432.49953,344.24829 c -0.74992,1.88333 -1.06676,4.9053 -2.27069,6.52441 17.7842,9.09169 10.87195,10.15007 14.63577,2.53805 1.10268,-1.69406 2.3453,-3.29642 3.58552,-4.89165 1.1227,-1.38745 2.25262,-2.77369 3.29055,-4.22698 1.32978,-2.11295 2.84636,-4.09666 4.29583,-6.1268 1.30305,-1.75714 2.50915,-3.58428 3.74208,-5.39123 1.56571,-1.86607 2.88779,-3.92042 4.5192,-5.73014 1.88665,-1.74545 3.98152,-3.25993 6.12942,-4.66512 2.9896,-2.09562 6.07519,-4.03418 8.97079,-6.26338 2.78,-1.954 5.25025,-4.28817 8.0846,-6.16612 2.11189,-1.2452 4.15986,-2.59054 6.24117,-3.88429 3.02352,-1.90978 5.76591,-4.21725 8.62645,-6.35471 1.48877,-1.16753 3.03884,-2.25756 4.59377,-3.33481 0.2987,-0.20694 0.56223,-0.49503 0.91007,-0.60017 0.10854,-0.0328 0.32024,0.31389 0.22153,0.25812 -4.34332,-2.4539 -8.64841,-4.97486 -12.97261,-7.46229 -2.62513,4.04578 -5.83026,7.70019 -8.9005,11.41297 -3.66383,4.80372 -8.12475,8.87004 -12.73052,12.74179 -5.91272,4.63036 -8.29371,2.67995 10.5926,8.76614 0.72744,0.23442 0.95585,-1.19331 1.45579,-1.7714 1.41805,-1.63974 2.66603,-2.92715 4.16048,-4.51228 2.07947,-2.31538 4.40674,-4.3784 6.66784,-6.50762 1.84986,-1.74198 3.62603,-3.51822 5.55816,-5.17079 2.44558,-1.96438 4.85201,-4.07114 7.7316,-5.37452 1.77224,-0.39347 3.46115,-1.03221 5.19071,-1.59061 2.31711,-0.81022 4.51877,-2.02167 6.60839,-3.31065 1.39458,-0.88305 2.74941,-1.91401 4.27263,-2.56811 0.19353,-0.1003 0.38287,-0.20912 0.58059,-0.3009 0.59457,-0.27597 0.58438,-0.16429 1.12628,-0.58611 0.45898,-0.35728 0.79797,-0.75022 1.30061,-1.05309 5.14148,-4.96325 -7.95067,-9.73777 -13.28583,-9.85314 -2.48876,0.35892 -4.69207,1.55828 -6.90425,2.67053 -2.3624,0.89905 -3.73647,2.37469 -4.94941,4.5448 -1.13905,2.59111 -2.308,5.17539 -3.35541,7.80576 -1.96526,6.34677 5.52532,5.98779 13.1748,9.82243 0.28349,0.14211 3.63407,-1.76581 4.01264,-1.97846 2.35819,-1.33273 4.68655,-2.71263 6.98133,-4.15154 1.37572,-0.7433 2.66223,-1.6029 3.75923,-2.71805 1.17204,-1.09559 1.77792,-2.49721 2.12557,-4.03109 0.0998,-1.18885 -0.16945,-2.73124 -1.30979,-3.41737 -13.34308,-8.02843 -8.61169,-6.96412 -14.89582,-8.20796 -2.60759,-0.54437 -5.18738,-0.18878 -7.72525,0.49502 -0.65085,0.12605 -1.04631,0.33591 -1.66437,0.0414 -0.177,-0.0843 -0.61222,-0.48509 -0.44157,-0.38856 4.20664,2.42581 8.41328,4.85163 12.61992,7.27744 -0.0556,-0.0879 -0.16461,-0.15979 -0.1668,-0.2638 -0.005,-0.23223 0.098,-0.45412 0.14694,-0.68117 0.55462,-1.72933 1.86737,-2.87785 3.2017,-4.01207 0.89484,-1.03596 2.11945,-1.6709 3.12761,-2.57143 0.86027,-0.47119 1.53234,-0.96433 1.98432,-1.83939 -10.36867,-6.55283 -10.56003,-10.30222 -16.32628,-5.43659 -1.3875,0.80231 -2.64757,1.80472 -3.95489,2.73368 -1.67535,0.85692 -3.39189,1.62876 -5.04528,2.53289 -1.98355,0.85518 -3.9245,1.76609 -5.9385,2.55617 -2.6027,1.25889 -5.19179,2.53692 -7.73292,3.91622 -3.16884,1.42333 -6.13113,3.23939 -9.20069,4.85676 -3.01773,1.63467 -5.94816,3.43762 -8.7288,5.45006 -1.8161,1.09651 -3.20033,2.68211 -4.6919,4.14933 -2.56301,2.52117 0.62871,-0.80843 -1.82713,1.78993 -1.29257,1.17689 -2.33137,2.57077 -3.45106,3.89804 -1.30688,1.49913 -2.76578,2.85213 -4.2452,4.17729 -1.67526,1.40297 -3.27521,2.85848 -4.81531,4.40468 -1.11976,1.32692 -1.82601,2.96903 -2.59642,4.51708 -0.89521,1.49446 -1.60042,3.07987 -2.33327,4.65731 -0.55628,0.99643 -1.16583,1.96813 -1.65121,3.00157 -0.53699,0.94305 -0.94412,1.93177 -1.55569,2.83258 -0.8921,1.16304 -1.39503,2.55479 -2.18277,3.77523 -0.75855,1.14369 -1.22847,2.45654 -1.66572,3.75099 -0.47382,1.2505 -0.77063,2.5524 -1.27452,3.79267 0.0176,-0.0459 0.01,-0.16167 0.0529,-0.13767 15.20424,8.50293 10.5337,11.06607 15.80147,3.6489 2.08695,-2.75768 3.99725,-5.64466 5.97647,-8.48077 1.92629,-2.76638 3.80319,-5.56446 5.65316,-8.38206 1.26769,-2.14829 2.54516,-4.29976 3.72202,-6.4994 0.99127,-1.696 1.86944,-3.43898 2.60941,-5.25851 0.6295,-1.85568 1.18095,-3.73524 1.94295,-5.54209 0.89682,-2.17472 2.06346,-4.22311 3.20854,-6.27422 1.00575,-1.47324 1.84774,-3.08799 2.93752,-4.50437 0.10494,-0.13639 1.21585,-1.33809 1.34628,-1.47981 0.9082,-1.31448 2.09267,-2.36626 3.17242,-3.52376 0.95213,-0.94011 1.9487,-1.7963 2.92104,-2.71484 1.11996,-1.04095 2.26946,-2.03134 3.34625,-3.11929 1.36036,-1.04378 2.73501,-2.1089 4.26504,-2.89286 1.62924,-1.00996 3.42299,-1.69537 5.08542,-2.63746 1.4904,-0.68881 3.08098,-1.11573 4.60632,-1.70803 1.61959,-0.626 3.03985,-1.70627 4.57116,-2.53507 1.26489,-0.46675 2.54065,-0.95411 3.84876,-1.29477 1.16488,-0.23001 2.35876,-0.2251 3.54126,-0.23621 0.64204,0.066 1.31618,-0.0895 1.95561,4.1e-4 0.38998,0.0548 0.54234,0.17649 0.85356,0.35797 -4.23079,-2.43099 -8.46158,-4.86198 -12.69237,-7.29297 -0.23596,0.40922 0.32876,-0.0608 0.27332,0.0117 -0.23121,0.30203 -1.16696,0.73465 -1.48098,0.91793 -1.89828,0.86389 -3.80655,1.66682 -5.80597,2.2709 -2.38683,0.81515 -4.90251,1.25962 -7.18253,2.35472 -1.59845,0.71242 -3.19309,1.38261 -4.74426,2.19516 -2.17518,1.09322 -4.14076,2.57229 -6.05109,4.07209 -1.04757,0.75941 -1.86421,1.78884 -2.84381,2.62126 -0.47353,0.40237 -1.00615,0.72951 -1.50758,1.09652 -2.60957,1.82293 -5.07268,3.85707 -7.44319,5.9788 -2.38247,2.05357 -4.89087,3.94947 -7.26141,6.01746 -1.45747,1.27654 -2.72386,2.74505 -3.98576,4.21007 -1.17894,1.2966 -2.3584,2.58174 -3.34479,4.03392 -0.80492,1.04949 -1.52848,2.15792 -2.2549,3.2618 -0.56841,1.04014 -0.90533,2.19537 -1.26485,3.32241 -0.20666,1.14317 -0.70444,2.17858 -1.1385,3.24297 -0.37682,1.24532 -0.9082,2.43344 -1.34007,3.65859 -0.59614,1.23685 -1.07713,2.51619 -1.55984,3.79939 -0.60561,1.64317 -1.22029,3.28304 -1.72453,4.96068 -0.49888,1.22534 -0.83747,2.50404 -1.31075,3.73744 -0.40444,1.06336 -0.97991,2.04175 -1.55375,3.01889 0,0 13.08691,7.56878 13.08691,7.56878 v 0 c 0.61569,-1.04805 1.22928,-2.09904 1.67579,-3.23392 0.49152,-1.27241 0.84263,-2.58551 1.37098,-3.84669 0.46931,-1.6472 1.08207,-3.24556 1.66603,-4.85454 0.50216,-1.29407 0.94652,-2.60845 1.58598,-3.84657 0.4,-1.21115 0.96582,-2.36112 1.30393,-3.59348 0.42896,-1.10903 1.01951,-2.16153 1.20003,-3.35429 0.28527,-0.99865 0.6179,-2.00144 1.04898,-2.94116 0.72768,-1.09373 1.44377,-2.19601 2.24241,-3.24048 0.96693,-1.3969 2.13628,-2.61995 3.25929,-3.89028 1.2158,-1.40982 2.45249,-2.8098 3.87076,-4.02235 2.38823,-2.05378 4.89907,-3.95769 7.27043,-6.03197 2.36178,-2.07152 4.79443,-4.06893 7.37306,-5.86698 1.54036,-1.15559 2.87935,-2.51821 4.3653,-3.73979 1.87881,-1.41018 3.76354,-2.83918 5.90154,-3.83977 1.53809,-0.76523 3.13304,-1.40357 4.69438,-2.11969 2.31022,-0.90327 4.75639,-1.41584 7.09887,-2.23031 2.062,-0.71132 4.09072,-1.52659 6.00735,-2.575 1.15677,-0.75487 2.31614,-1.57824 2.36727,-3.09574 -11.84239,-7.15983 -8.79952,-8.19681 -16.23653,-7.94552 -1.27385,0.0591 -2.55519,0.15442 -3.77266,0.56575 -1.36356,0.46811 -2.76869,0.85036 -4.03011,1.56676 -1.44448,0.83593 -2.83281,1.8206 -4.43538,2.33025 -1.60897,0.54133 -3.21588,1.11756 -4.72908,1.89291 -1.7058,0.87233 -3.44756,1.67232 -5.09656,2.65452 -1.58254,0.9473 -3.11251,2.01939 -4.48952,3.24809 -1.07479,1.06783 -2.23755,2.0338 -3.32634,3.0869 -0.97659,0.93843 -2.02953,1.78709 -2.95848,2.77592 -1.11653,1.14012 -2.228,2.27157 -3.18959,3.55198 -1.73615,1.91381 -3.12213,4.07803 -4.46152,6.28494 -1.17125,2.09783 -2.36412,4.19304 -3.28797,6.41396 -0.77999,1.81247 -1.36826,3.69143 -1.96005,5.57265 -0.69501,1.77895 -1.57036,3.46126 -2.52724,5.11323 -1.14995,2.16186 -2.41205,4.27023 -3.65327,6.38065 -1.84142,2.80594 -3.71367,5.58986 -5.63123,8.34468 -1.97876,2.83242 -3.8863,5.71663 -5.9713,8.47202 -5.09974,7.21931 -3.61585,3.58041 10.14902,11.86734 0.10559,0.0636 0.13753,-0.20454 0.2063,-0.30682 0.54562,-1.27233 0.85593,-2.60811 1.33519,-3.90196 0.366,-1.18393 0.79847,-2.37723 1.47685,-3.41749 0.76962,-1.22092 1.29148,-2.58159 2.16466,-3.74426 0.64823,-0.97619 1.10877,-2.02653 1.6781,-3.04528 0.45874,-1.04165 1.10992,-1.99296 1.6475,-2.99498 0.7231,-1.56766 1.42248,-3.13956 2.31322,-4.62198 0.69376,-1.40359 1.32111,-2.91229 2.35041,-4.10035 1.51226,-1.5356 3.15699,-2.90965 4.77261,-4.33535 1.51521,-1.38933 3.03002,-2.786 4.35719,-4.36096 1.07589,-1.31874 2.15268,-2.63092 3.39427,-3.80416 1.98924,-2.04024 3.98674,-4.10902 6.36889,-5.69823 2.76588,-1.9319 5.63882,-3.7109 8.62332,-5.28656 3.05605,-1.64619 6.06258,-3.38186 9.20471,-4.86291 2.50272,-1.33987 5.04539,-2.60674 7.63563,-3.76923 2.04517,-0.82102 4.03382,-1.76494 6.03055,-2.69514 1.72296,-0.88646 3.50857,-1.66371 5.15022,-2.6955 1.33179,-0.93952 2.63362,-1.91845 4.00061,-2.80598 5.75132,-4.27305 6.41889,-0.18499 -9.91136,-9.53506 0.0988,-0.15669 0.22735,-0.29822 0.29653,-0.47009 0.0309,-0.0768 -0.11478,0.11966 -0.17731,0.17392 -0.45656,0.39618 -1.06226,0.60574 -1.48578,1.04994 -1.07663,0.82836 -2.19514,1.6074 -3.14048,2.59206 -1.53745,1.34942 -2.95155,2.80683 -3.65012,4.78163 -0.0188,0.0593 -0.65643,1.94397 -0.51994,2.04078 7.75805,5.50234 9.22853,9.3143 15.73862,7.79935 2.42682,-0.47812 4.85872,-0.64526 7.24894,0.15555 5.24405,2.34611 1.12226,0.54529 -11.09364,-6.66691 -0.24934,-0.14721 0.51196,0.27175 0.75737,0.42541 0.62106,0.38887 1.13479,0.73189 1.24929,1.49492 -0.19447,1.28451 -0.67305,2.36229 -1.67066,3.23761 -1.0601,0.99375 -2.34252,1.69237 -3.58241,2.43713 -2.28455,1.40297 -4.58146,2.78316 -6.93152,4.07482 -0.6401,0.33075 -1.7557,0.97424 -2.50893,1.12733 -0.29072,0.0591 -1.13609,-0.18506 -0.88979,-0.0197 3.99496,2.68242 8.18171,5.06786 12.24564,7.64459 0.11606,0.0736 -0.36688,0.007 -0.3917,-0.1286 -0.0517,-0.28183 0.14352,-0.55483 0.21528,-0.83225 1.0086,-2.57511 2.17547,-5.08853 3.25754,-7.633 1.14069,-2.06393 2.33352,-2.87066 4.47439,-3.77341 1.99682,-0.99435 4.09811,-2.12487 6.39726,-1.94691 5.54739,1.31252 -1.16486,-0.19796 -11.70509,-7.15668 -0.22944,-0.15148 1.03266,-0.87795 0.47926,-0.42008 -0.1142,0.0945 -0.23753,0.17734 -0.3563,0.26601 -0.91035,0.69602 -1.797,1.34558 -2.88727,1.75467 -1.48835,0.81922 -2.91672,1.74573 -4.3402,2.6716 -2.04124,1.18747 -4.14532,2.33613 -6.41512,3.0194 -1.78134,0.56187 -3.56442,1.13378 -5.31489,1.77606 -2.89473,1.52569 -5.43227,3.61724 -7.94047,5.7005 -4.2817,3.73703 -8.32405,7.76259 -12.30794,11.8115 -1.41228,1.50514 -2.88298,3.03195 -4.21881,4.60891 -0.54545,0.64392 -2.16213,1.37043 -1.57814,1.97962 10.37811,10.8258 9.09373,11.67867 15.32582,6.79575 4.65474,-3.95024 9.15169,-8.10228 12.87166,-12.97238 3.18286,-3.85479 6.53093,-7.63942 9.17583,-11.89157 -4.19727,-2.86373 -8.27973,-5.90343 -12.59181,-8.59118 -0.077,-0.048 -1.09652,0.65829 -1.56463,0.98599 -1.5592,1.09152 -3.11333,2.19347 -4.61332,3.36563 -2.81974,2.1259 -5.54732,4.39017 -8.5582,6.24779 -2.08506,1.3181 -4.17789,2.62318 -6.27025,3.9296 -2.81241,1.94701 -5.33289,4.26353 -8.12848,6.23518 -2.89927,2.17261 -5.95114,4.11376 -8.92117,6.18537 -2.22251,1.51205 -4.43203,3.08505 -6.33475,4.9951 -1.64219,1.88227 -3.03883,3.95761 -4.61661,5.89356 -1.22912,1.79711 -2.42749,3.61668 -3.72627,5.3644 -1.44811,2.02933 -2.9611,4.01363 -4.29362,6.12305 -1.03598,1.43422 -2.15766,2.80499 -3.26514,4.18412 -1.28435,1.68579 -2.61731,3.34228 -3.73394,5.1467 -0.61541,1.23637 -1.10338,2.39149 -1.42745,3.7376 -1.42213,5.90724 -0.30903,0.87064 12.96295,8.86478 1.37512,-2.07493 1.70669,-4.65994 2.52212,-6.96893 0,0 -13.28274,-7.2196 -13.28274,-7.2196 z" />
65- <path
66- style="fill:#ffffff"
67- id="path1041"
68- d="m 444.95379,346.65456 c 1.06642,-2.60177 2.26717,-5.1388 3.53885,-7.64477 0.80062,-1.72005 1.70753,-3.33379 2.78034,-4.89383 0.99874,-1.25158 1.79112,-2.64198 2.78939,-3.89223 0.97549,-1.20036 1.66709,-2.60021 2.6938,-3.75713 1.13014,-1.5117 1.88641,-3.2593 2.68308,-4.96014 0.7471,-1.60342 1.60226,-3.14777 2.40761,-4.72157 1.18872,-1.66559 2.27151,-3.4063 3.48455,-5.05396 0.96809,-1.31722 1.78836,-2.73014 2.77318,-4.03489 0.90823,-1.43352 1.91835,-2.76684 3.06487,-4.0174 1.43521,-1.23262 2.86359,-2.45013 4.14673,-3.84264 1.10736,-0.88478 2.1079,-1.91275 3.22743,-2.78438 1.26625,-0.92328 2.40104,-2.02565 3.74382,-2.83749 1.36021,-0.84114 2.47965,-1.99587 3.75402,-2.93834 1.17313,-0.82096 2.1014,-1.91914 3.19251,-2.83265 0.96511,-1.06249 1.9825,-2.0389 3.15071,-2.87458 1.46588,-1.3037 3.03026,-2.5058 4.41761,-3.8948 1.26476,-0.99496 2.67815,-1.75513 3.97292,-2.70979 1.23962,-0.91909 2.67716,-1.37512 4.09945,-1.89879 1.53746,-0.34526 2.98,-0.81959 4.436,-1.41426 0.95752,-0.25952 2.32209,-0.8001 3.33156,-0.62574 0.20945,0.0362 0.38685,0.17626 0.58027,0.26439 -4.20114,-2.33375 -8.40228,-4.6675 -12.60343,-7.00126 0,0 13.96868,5.78186 13.96868,5.78186 v 0 c -4.4863,-2.61747 -8.9726,-5.23493 -13.4589,-7.8524 -1.51335,0.0943 -2.98338,0.40961 -4.37883,1.01983 -1.4393,0.56639 -2.96156,0.82534 -4.40858,1.37347 -1.5382,0.55268 -3.00033,1.2792 -4.32591,2.24462 -1.37284,0.95626 -2.85935,1.75693 -4.09307,2.90202 -1.38146,1.30479 -2.85379,2.51476 -4.31435,3.72921 -1.15751,0.90413 -2.18874,1.92435 -3.21309,2.97405 -1.03083,0.94401 -2.01396,1.93579 -3.14828,2.76194 -1.19514,0.98762 -2.37284,1.99266 -3.68464,2.82915 -1.32344,0.92776 -2.55283,1.98338 -3.82992,2.9733 -1.12752,0.96567 -2.25289,1.94069 -3.33352,2.95626 -1.30383,1.37931 -2.79466,2.54328 -4.15785,3.86097 -1.16319,1.29631 -2.19199,2.67816 -3.13817,4.13963 -0.9771,1.31712 -1.81169,2.72753 -2.78204,4.04984 -1.24707,1.72053 -2.39712,3.51107 -3.60412,5.25971 -0.815,1.58975 -1.6765,3.15157 -2.43517,4.77007 -0.73319,1.57868 -1.42437,3.21086 -2.47717,4.60741 -0.99785,1.20674 -1.75357,2.57945 -2.72447,3.80831 -1.00142,1.28731 -1.83229,2.69059 -2.83871,3.9755 -1.1137,1.6144 -2.04714,3.29226 -2.88163,5.06874 -1.2825,2.52926 -2.50723,5.08361 -3.57702,7.71214 0,0 13.17149,7.42062 13.17149,7.42062 z" />
69- <path
70- style="fill:#ffffff"
71- id="path1043"
72- d="m 47.723738,302.48814 c -0.235582,2.57035 -0.413918,5.14543 -0.527844,7.72396 -0.04401,1.66877 -0.637615,3.23133 -0.971978,4.84849 -0.49334,2.0254 -0.803411,4.07474 -0.962727,6.15115 -0.22051,2.38235 -0.149823,4.7319 0.269472,7.08396 0.481444,2.35269 0.997451,4.69608 1.45286,7.05476 0.457995,2.16877 0.573854,4.38236 0.790824,6.58142 0.233663,1.60072 0.456482,3.20102 0.57548,4.81477 0.08973,1.43491 0.08843,2.87328 0.09188,4.31034 -0.0031,5.94595 -0.872531,1.79721 13.666195,7.35427 0.230286,0.088 0.424137,-2.99784 0.437609,-3.20433 0.09063,-2.46503 0.06323,-4.93249 0.04974,-7.39861 -0.08329,-2.38251 0.008,-4.7492 0.31058,-7.11289 0.318125,-3.12377 1.109164,-6.1645 1.882127,-9.19858 0.603361,-2.38574 1.199296,-4.775 1.831907,-7.15334 0.5698,-2.1975 1.009163,-4.42888 1.423266,-6.66069 0.356849,-2.02356 0.443261,-4.078 0.494757,-6.12769 0.10103,-1.42529 0.383836,-2.82762 0.53435,-4.24816 -0.0029,-1.13487 0.180083,-2.1682 0.603434,-3.21105 0.321111,-0.5869 0.15321,-0.2786 0.503328,-0.92512 0,0 -13.113313,-7.52296 -13.113313,-7.52296 v 0 c -0.373806,0.69103 -0.177993,0.33257 -0.588169,1.07497 -0.466058,1.17181 -0.891486,2.30042 -0.810581,3.59475 -0.118838,1.46005 -0.383281,2.8963 -0.58493,4.34604 -0.03272,1.97659 -0.0796,3.95891 -0.384361,5.91626 -0.38509,2.19498 -0.834288,4.38156 -1.361672,6.54616 -0.65643,2.37946 -1.223845,4.78566 -1.843449,7.17462 -0.780965,3.08634 -1.636353,6.16979 -1.961447,9.34711 -0.358177,2.4239 -0.514696,4.85383 -0.409938,7.30476 0.01422,2.44019 0.03976,4.88083 -0.0099,7.32079 -0.02942,0.76513 -0.03695,1.29897 -0.112712,2.05085 -0.02471,0.24528 -0.307475,0.60077 -0.09933,0.73287 4.278007,2.71486 8.786753,5.0478 13.181546,7.56923 0.0572,0.0328 -0.153475,-0.0392 -0.168712,-0.10336 -0.04652,-0.19592 4.77e-4,-0.40274 7.16e-4,-0.60411 -7.7e-5,-1.46409 0.0069,-2.92885 -0.05054,-4.3921 -0.08299,-1.64226 -0.248525,-3.27751 -0.534015,-4.89805 -0.334753,-2.2089 -0.271582,-4.47121 -0.7496,-6.66552 -0.430658,-2.36726 -0.924448,-4.71652 -1.449337,-7.062 -0.472671,-2.26866 -0.697763,-4.4124 -0.444127,-6.73588 0.115274,-2.02246 0.364088,-4.02381 0.877321,-5.98869 0.294207,-1.70188 0.964594,-3.3091 1.075721,-5.04646 0.09428,-2.55627 0.265712,-5.10876 0.493001,-7.65667 0,0 -13.40746,-6.98527 -13.40746,-6.98527 z" />
73- <path
74- style="fill:#ffffff"
75- id="path1045"
76- d="m 638.45033,529.45411 c 0.32043,-1.43279 0.87109,-2.79362 1.51319,-4.10875 0.49898,-1.07666 1.0692,-2.11787 1.58949,-3.18392 0.3604,-0.79469 0.72883,-1.65852 0.63073,-2.55458 -0.0213,-0.19465 -0.0803,-0.38329 -0.12051,-0.57493 -12.05402,-7.29915 -8.97396,-7.82949 -16.48178,-7.87355 -1.51954,-0.003 -3.03908,0.007 -4.55861,0.0125 -0.43502,-0.13711 -1.85991,0.26465 -2.19604,0.10033 -0.0753,-0.0368 -0.16683,-0.27616 -0.0951,-0.23283 4.27548,2.58135 8.49877,5.24815 12.74816,7.87222 -0.0323,0.11502 -0.19798,0.28158 -0.0968,0.34508 0.11127,0.0698 0.23042,-0.12621 0.34594,-0.18875 0.82367,-0.44601 1.64573,-0.88641 2.48418,-1.30455 1.04859,-0.43593 2.07827,-0.92742 3.18607,-1.19426 0.1154,-0.0333 1.46149,-0.43299 1.45532,-0.21803 -0.003,0.12184 -0.0928,0.22543 -0.13915,0.33815 -15.99901,-11.90341 -7.73872,-9.62675 -14.63806,-7.60889 -1.12229,0.32922 -2.27318,0.57267 -3.42629,0.77165 -0.8597,0.13995 -1.69864,0.34359 -2.57144,0.3832 -0.54215,-0.0101 -1.09515,0.074 -1.62901,-0.0558 -0.24961,-0.0607 -0.48864,-0.15896 -0.73526,-0.23084 -0.0894,-0.0566 -0.19053,-0.098 -0.26821,-0.16984 -0.0706,-0.0653 -0.25175,-0.28213 -0.16846,-0.23411 4.25969,2.45608 8.54644,4.87008 12.7194,7.47078 0.0386,0.0241 -0.62896,0.70858 -0.14215,0.34641 0.41198,-0.21773 0.79311,-0.50185 1.21319,-0.7044 0.241,-0.1162 0.49677,-0.19887 0.74453,-0.29986 0.64051,-0.18906 1.25438,-0.4587 1.9201,-0.54617 0.53439,-0.0211 1.07056,0.004 1.60554,0.0114 0.66172,0.20645 1.36743,0.21542 2.0551,0.24269 0.2059,0.0384 0.41918,-0.0195 0.62534,0.0116 0.0937,0.0141 0.17952,0.0618 0.27192,0.0828 0.32123,0.0728 0.65864,0.0923 0.98578,0.12092 0,0 -12.12893,-9.02458 -12.12893,-9.02458 v 0 c -0.64258,-0.0911 -1.27712,-0.13195 -1.92456,-0.15915 -0.70608,-0.0683 -1.41657,-0.216 -2.12625,-0.14532 -0.60794,0.0224 -1.21803,0.0643 -1.79888,0.26164 -0.70334,0.23112 -1.4064,0.47243 -2.0796,0.78154 -0.8134,0.39578 -1.61239,0.85829 -2.26694,1.48977 -4.40189,5.22506 7.05828,9.21158 12.81615,9.58956 0.87601,0.1034 1.76219,0.0157 2.63569,-0.0834 0.88485,-0.14403 1.76111,-0.3377 2.64498,-0.48765 1.20051,-0.2558 2.40354,-0.52668 3.55497,-0.95904 7.22662,-2.97311 -0.75811,-4.03385 -9.72884,-10.2551 -0.10392,0.0865 -0.19775,0.18687 -0.31174,0.25961 -0.14271,0.0911 -0.37549,0.11873 -0.53047,0.15979 -0.37663,0.0998 -0.74429,0.23119 -1.11935,0.33636 -1.13859,0.40564 -2.2512,0.88549 -3.336,1.41811 -0.68468,0.35565 -1.37148,0.71028 -2.0284,1.11615 -0.26605,0.16437 -0.84138,0.52704 -1.05722,0.77979 -0.20267,0.23735 -0.35044,0.51655 -0.52566,0.77483 4.25429,2.7014 8.45898,5.48255 12.76287,8.1042 0.1058,0.0645 0.23191,-0.0911 0.35356,-0.11452 0.71214,-0.1371 1.45642,-0.0262 2.17386,-0.0697 1.50973,-0.005 3.02005,-0.019 4.52929,0.0278 0.63448,0.0341 1.28759,0.049 1.90849,0.20211 0.17207,0.0424 0.34359,0.0927 0.50543,0.16496 0.13833,0.0617 0.51975,0.31137 0.38936,0.2343 -4.17013,-2.46505 -8.33904,-4.93215 -12.50856,-7.39822 0.13533,-0.0252 0.26836,-0.0779 0.406,-0.0756 0.69198,0.0116 0.0239,1.25024 -0.0802,1.48859 -0.51442,1.06625 -1.09068,2.1018 -1.58591,3.17763 -0.69439,1.40539 -1.30774,2.85802 -1.66838,4.38859 0,0 13.29807,7.19133 13.29807,7.19133 z" />
775 </g>
786</svg>
public/img/koboldcpp.svg+4 -76
@@ -1,78 +1,6 @@
11<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-<!-- Created with Inkscape (http://www.inkscape.org/) -->
2+<svg id="svg2" width="660.14" height="621.01" version="1.1" viewBox="0 0 660.14 621.01" xmlns="http://www.w3.org/2000/svg">
3-
3+ <g id="g8" transform="translate(-12.7,-12.6)">
4-<svg
4+ <path id="path972" d="m171.3 47.3c5.5 33.5 12.9 56.6-16 78-15.6 13.6-27.3 32.5-43.1 45.3-12.7 6.5-49.7 1.9-63.5-0.6-15.9 28-27.4 57-36 88 13.7 10.8 35 39.1 53.3 38.7 0 0-3.8 77.9-8.1 90.5-10.8 15-30.6 29.8-45.2 41.5l25.3 69.3c7.3 3.1 54.7 9.3 54.7 9.3 42.8 3.6 33.4 58.2 89.3 46.7-0.6 3.6-2 9.1 1.2 11.9 4.9 4.2 11.7-3.7 17.4-1.8 14.2 4.8 19.7 29 37.4 16.5 3.7 14.9 13.7 53.6 34.5 53 20.5-0.5 4.5-62.6 42.8-63.7 20.7-0.6 53.1-5.3 69 10.9 11.9 12.1 12.1 52 28.2 50.8 7.5-0.6 21.5-24 22.8-55.1 21.4 13.6 26.8-8.6 45.3-15 5.7-2 13.9 4.9 18.7 1.1 3.4-2.8 0-8.5-1.3-11.4 42.4 4.4 54.3-1.8 78.7-45.3 21.7 0 43.4 2.3 65.2 2 25.1-43.5 31.5-72.2 30.9-76.7-2.7-18-34.1-27.2-42.5-42.8 1-27.9-1.7-56.9-1.7-85.3 0.5-18.7 33.7-29.7 42.8-44.6 4-6.5-1.4-16.9-3.5-23.4-6.5-19.7-14.1-42.9-26.5-59.7-9.7-13.1-44.3 5.1-63-2.8-19.3-20.2-39.2-39.3-60-58.5-15.9-14.8 2.9-45.4-3.6-63.8-3.8-10.8-75.4-40.2-86.8-35-16.2 9.4-22.6 33.9-39.6 41.8-28.2-5.2-60.9 7-88.8 0.6-14.8-10.2-30.4-30.8-41.5-45.1-31.1 6.9-58 21.6-86.7 34.7m400 218.7c-58.8-77.3-132.9-36.3-148 72-5.7-38.5-2.8-80.8-2.7-120 0-12.8 0.2-30.4 10.8-39.4 27.2-23 139.8-3.8 139.8 87.4m-301.3 53.3c-17.7-99.9-99.2-127.2-144-54.7-19.7-65 99.7-95.5 123.9-85 24 10.4 30.5 86.9 21.4 139.8m-46.6-96.1c5 13.9 18 23.4 25.5 36 7.4 12.5 9.7 32.1 22.5 40-3.9-24.8-18.5-72.1-48-76m200 77.3c10.9-7.5 13.7-23.5 19.9-34.7 7.7-13.8 22.1-25 26.8-40-27.9 4.5-44.2 50.2-46.7 74.7m-265.3-45.3c5.2-20.6 30.4-14.3 45-8.2m340.4 21.6c-18.3-11-31.3-18.6-53.3-18.7 19-16.3 44.5-1 53.3 18.7m-358.9-1.6c65.8-8.8 89.9 90 78.5 104.8-12.5 16.3-80.6 23.8-103.4 2.9-13.3-12.2-18.5-33.7-24.2-50.2l-4 1.3-5.3-17.3c24.3-2.8 31-32.7 54.4-40.6m333.6 2.3c32.6 19 36.5 42.5 55 39.9l-4.3 25-5.3-9.3c-8.7 20.9-12.7 50.6-37.3 58.5-40.9 13.1-102.3 2-98-29.1 13.4-39 40.6-87.6 86-86.8m-86.6 302.8c0 17.8-2.3 32.1-10.7 48-9.4-5.8-23.3-33.8-13.4-43.9 5.5-5.7 16.9-4.1 24.1-4.1m-145.3 4-8 46.7c-16.3-9.2-22.8-30.3-24-48l32 1.3"/>
5- version="1.1"
6- id="svg2"
7- width="682.66669"
8- height="682.66669"
9- viewBox="0 0 682.66669 682.66669"
10- sodipodi:docname="kobold.svg"
11- inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
12- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
14- xmlns="http://www.w3.org/2000/svg"
15- xmlns:svg="http://www.w3.org/2000/svg">
16- <defs
17- id="defs6" />
18- <sodipodi:namedview
19- id="namedview4"
20- pagecolor="#ffffff"
21- bordercolor="#000000"
22- borderopacity="0.25"
23- inkscape:showpageshadow="2"
24- inkscape:pageopacity="0.0"
25- inkscape:pagecheckerboard="0"
26- inkscape:deskcolor="#d1d1d1"
27- showgrid="false"
28- inkscape:zoom="1.5043945"
29- inkscape:cx="460.98281"
30- inkscape:cy="311.08862"
31- inkscape:window-width="2560"
32- inkscape:window-height="1377"
33- inkscape:window-x="2552"
34- inkscape:window-y="-8"
35- inkscape:window-maximized="1"
36- inkscape:current-layer="g8" />
37- <g
38- inkscape:groupmode="layer"
39- inkscape:label="Image"
40- id="g8">
41- <path
42- style="stroke:none;stroke-width:1.33333"
43- d="m 171.32749,47.331392 v 4 c 12.46,9.0412 4.032,24.8744 4.91067,37.33333 0.60933,8.61773 4.48133,15.097198 3.08933,23.999998 -9.768,-1.0508 -17.05467,6.59294 -24,12.62774 -15.61067,13.5656 -27.25574,32.5056 -43.14654,45.2576 -6.2576,5.02133 -14.829587,3.284 -22.186787,2.55733 -13.6492,-1.34666 -27.88494,-0.35466 -41.33334,-3.10933 -15.8956,27.97867 -27.41493,57.04 -36,88 13.74,10.83867 35.03,39.1 53.33334,38.66667 l -1.33334,6.66667 -5.33333,-2.66667 2.66667,14.66667 -4,-2.66667 -1.33334,6.66667 h 6.66667 l -1.33333,25.33333 h -1.33334 l -5.33333,-8 c 1.2756,13.55867 6.9132,37.864 2.5692,50.54 -2.02707,5.91467 -8.7172,10.132 -13.23613,14.05067 -10.5992,9.19067 -20.984,18.72 -31.99974,27.40933 l 25.33334,69.33334 h 1.33333 l 1.33333,-10.66667 17.33334,28 -2.66667,-5.33333 30.66667,4 1.33333,-6.66667 8,5.33334 -2.66667,-5.33334 c 33.997197,-5.85866 39.588927,35.29201 66.666667,47.46401 6.416,2.88533 16.03067,0.56933 22.66667,-0.79734 -0.58267,3.59734 -2.02267,9.11867 1.24133,11.92667 4.892,4.20667 11.732,-3.67467 17.35467,-1.764 14.21733,4.83467 19.67333,28.956 37.404,16.504 3.70133,14.85067 13.68667,53.584 34.496,53.04533 8.328,-0.216 10.988,-14.944 12.73867,-21.04533 5.44933,-18.99333 5.108,-41.92533 30.09866,-42.62667 20.65067,-0.58 53.08801,-5.31333 69.00801,10.92534 10.052,10.25333 8.66666,27.47066 15.73333,39.69333 2.42933,4.20133 6.73733,11.54 12.48667,11.09867 5.58133,-0.42934 9.27466,-8.18267 11.60533,-12.424 7.32533,-13.32667 10.54,-27.56534 11.16667,-42.66667 21.444,13.56267 26.752,-8.56 45.33333,-14.99333 5.74,-1.98667 13.94134,4.92 18.68,1.088 3.448,-2.788 0.0213,-8.492 -1.34666,-11.428 11.49466,1.196 36.74933,2.12 46.66667,-3.89067 13.856,-8.39867 22.12,-24.84267 33.33333,-36.10934 l -1.3333,-5.33333 41.3333,1.33333 c 1.3253,7.79467 6.2707,7.924 13.3333,8 v -6.66666 l 5.3334,5.33333 h 2.6666 c 10.9174,-18.93867 20.5987,-39.24933 27.5454,-60 2.1786,-6.504 6.8373,-15.692 5.808,-22.64934 -2.6627,-18.00933 -34.1347,-27.24133 -42.4814,-42.756 -4.5173,-8.39333 0.028,-24.07866 0.8147,-33.26133 1.5,-17.472 -3,-34.648 -2.5173,-51.99734 0.5226,-18.74133 33.68,-29.71733 42.796,-44.60933 3.9746,-6.49467 -1.3827,-16.932 -3.5227,-23.39334 -6.516,-19.676 -14.056,-42.93333 -26.5373,-59.73733 -4.7547,-6.40133 -10.9067,-4.772 -17.9054,-4.38667 -12.3666,0.68134 -33.6346,6.424 -45.12,1.564 -7.93997,-3.36133 -13.2693,-13.912 -18.93463,-20.06666 -12.87067,-13.98401 -27.26667,-25.57214 -41.10534,-38.47241 -15.90933,-14.829598 2.85333,-45.371998 -3.62133,-63.770668 -3.06134,-8.700397 -20.756,-14.264397 -28.552,-17.694131 -13.49867,-5.9384 -27.244,-11.378134 -41.33334,-15.735334 -5.22,-1.6144 -11.668,-4.60664 -16.88933,-1.585333 -16.16,9.350267 -22.56667,33.947998 -39.556,41.756398 -9.47467,4.3548 -27.90934,-1.2064 -38.22134,-1.7456 -15.03333,-0.786 -36.49067,6.88534 -50.54,2.33574 -5.92267,-1.9176 -10.304,-8.63254 -14.20267,-13.12894 -9.148,-10.549598 -18.88133,-20.827998 -27.25733,-31.999732 -31.076,6.852134 -58.048,21.650001 -86.66667,34.666672 m 297.33334,37.33333 v 1.33333 h -4 l 4,-1.33333 m 4,0 -1.33333,1.33333 1.33333,-1.33333 m -253.33334,1.33333 -1.33333,1.33334 1.33333,-1.33334 m 192.00001,44.000008 -1.33334,1.33333 1.33334,-1.33333 m 159.99997,136 h -1.3333 c -6.54533,-9.72 -13.10133,-19.16666 -22.67333,-26.17733 -6.84667,-5.01334 -14.52267,-8.54934 -22.66001,-10.896 -64.19333,-18.52 -94.28267,58.57733 -101.33333,109.07333 -4.65867,-12.744 -2.66667,-27.976 -2.66667,-41.33333 0,-26.22133 -0.096,-52.44534 0.003,-78.66667 0.048,-12.81733 0.236,-30.37867 10.848,-39.352 11.536,-9.75467 35,-9.336 49.14933,-7.74 50.02934,5.64267 90.66531,43.30267 90.66671,95.092 M 271.32782,319.3314 h -1.33333 c -8.73067,-49.15734 -41.592,-109.81467 -100,-92.31201 -8.24267,2.46934 -15.92267,6.46267 -22.66401,11.82 -8.93866,7.10267 -15.3944,16.20534 -21.336,25.82534 -14.76986,-48.75467 53.38134,-80.68134 90.66667,-85.05067 10.04267,-1.176 23.616,-4.14533 33.25467,0.0387 11.71067,5.08533 16.88267,22.98533 19.47067,34.34533 7.42133,32.56134 7.61067,72.44534 1.94133,105.33334 m -46.66667,-96.00001 c 4.99867,13.94667 17.988,23.35334 25.46001,36.00001 7.37733,12.484 9.68933,32.056 22.54,40 -3.912,-24.796 -18.472,-72.13734 -48.00001,-76.00001 m 200.00001,77.33334 c 10.88133,-7.50267 13.66933,-23.524 19.86667,-34.66667 7.652,-13.75733 22.06133,-25.04533 26.8,-40 -27.912,4.53067 -44.212,50.15067 -46.66667,74.66667 m -220.00034,-54.6667 v 1.33334 l -45.33333,8 c 5.248,-20.64801 30.736,-15.41734 45.33333,-9.33334 m 340.00002,22.66667 c -18.28667,-11.04667 -31.34001,-18.64267 -53.33334,-18.66667 19.048,-16.336 44.54933,-0.956 53.33334,18.66667 m -62.66667,-21.33333 -5.33334,8 5.33334,-8 m -330.66668,9.33333 v 9.33333 h -2.66667 l 2.66667,-9.33333 m 33.33333,10.66667 c -10.14666,28.372 -20.33733,64.79466 -3.732,93.332 23.412,40.23467 69.13467,19.88133 79.73201,-17.332 l -37.33334,12 c 7.55333,-29.95734 6.71333,-51.172 -5.33333,-80 l -4,1.33333 2.66666,-9.33333 c 25.18534,13.21066 33.43067,41.44266 45.02534,65.33333 5.99867,12.36134 12.33733,26.51467 2.584,39.25867 -4.64,6.06267 -15.18,9.508 -22.276,11.536 -21.07067,6.024 -63.98134,7.04267 -81.088,-8.61333 -13.28934,-12.164 -18.45467,-33.68534 -24.24534,-50.18134 l -4,1.33333 -5.33333,-17.33333 c 24.27867,-2.78133 33.91467,-33.52133 57.33333,-41.33333 m 328.00001,1.33333 c 13.32267,7.76533 22.768,16.98133 33.40401,27.99867 6.94666,7.19466 13.224,14.832 23.92933,13.33466 l 1.3333,1.33334 -5.3333,24 h -1.33333 l -2.66667,-9.33334 h -1.33333 c -8.71867,20.86401 -12.74534,50.58534 -37.33334,58.46534 -21.72,6.96133 -56.508,7.996 -77.33333,-1.67467 -10.91334,-5.068 -22.63334,-13.23333 -20.69734,-27.45733 0.92267,-6.78667 6.69067,-14.096 10.03067,-20 h 1.33333 c 0,8.11867 -0.77733,16.57467 3.116,24 22.06534,42.08667 74.49067,26.58933 82.21734,-16 l -33.33334,8 c 2.796,-20.556 10.612,-60.17867 -10.66666,-73.33334 l -2.66667,6.66667 h -1.33333 c 3.22666,-14.35067 15.708,-19.72133 27.70266,-8.79467 6.95467,6.336 9.74534,15.98934 12.044,24.79334 0.59067,2.264 2.49067,13.96 7.14667,10.308 8.94933,-7.02 -8.35467,-33.472 -8.22667,-42.30667 M 215.32749,311.3314 c -13.772,4.168 -8.14267,19.588 -8,30.66666 -7.48933,-6.38666 -6.66267,-16.27333 -6.66667,-25.33333 -18.06266,-3.052 -5.47733,-24.708 8.75467,-13.59333 2.832,2.212 4.33867,5.13333 5.912,8.26 m 261.33334,1.33333 v 2.66667 c -12.91733,5.19066 -8.896,15.32533 -6.66666,26.66666 -5.38134,-3.632 -15.92667,-20.34133 -17.05334,-26.78266 -1.472,-8.40667 8.44,-10.26267 14.36134,-7.90534 3.31866,1.32267 6.33733,3.48134 9.35866,5.35467 m -413.333337,2.66667 -1.33333,1.33333 1.33333,-1.33333 m -6.66667,13.33333 4,2.66667 v -1.33334 l -4,-1.33333 m 18.66667,180.00001 v 4 h -1.33333 l 1.33333,-4 m 349.333337,62.66667 c 0,17.77733 -2.26,32.136 -10.66667,48 -9.4,-5.81067 -23.30933,-33.76267 -13.44266,-43.90267 5.54,-5.69333 16.94133,-4.096 24.10933,-4.09733 m -145.33334,4 -8,46.66666 c -16.29333,-9.21866 -22.78666,-30.32 -24,-48 l 32,1.33334 m 124.00001,17.33333 v 10.66667 h 1.33333 z"
44- id="path972" />
45- <path
46- style="fill:#ffffff"
47- id="path1031"
48- d="m 171.07018,314.24465 c 1.43533,2.55362 3.43647,4.86204 6.02366,6.31063 15.51628,8.6877 9.94595,7.11918 18.45758,9.09921 2.9676,0.68502 6.00726,0.33711 9.00575,0.4851 0.51777,0.0256 1.97943,0.5002 1.53847,0.22762 -12.3291,-7.62122 -8.46459,-9.40075 -13.47067,-6.74985 -0.81161,0.51959 -1.60672,1.06589 -2.43482,1.55876 -4.52165,2.69122 -9.22362,5.08101 -13.79908,7.67767 -5.00626,2.84116 -5.79872,3.37745 -10.6807,6.35696 -3.21994,2.00581 -5.34709,3.29487 -8.43672,5.34334 -0.93485,0.61982 -1.86152,1.25236 -2.77524,1.90291 -0.53177,0.3786 -1.12785,0.69454 -1.56007,1.18372 -0.23297,0.26368 -0.67179,0.8249 -0.36192,0.99157 4.80104,2.58231 9.91238,4.5407 14.86857,6.81105 12.64758,-5.66419 24.4458,-13.03247 36.15953,-20.38753 8.83007,-5.82628 17.49966,-11.90043 26.13449,-18.01368 0.12258,-0.10977 0.51254,-0.25112 0.36775,-0.32931 -4.67223,-2.52289 -9.13452,-5.66802 -14.23142,-7.15676 -1.20748,-0.35269 -2.10539,1.37742 -3.14865,2.08026 -4.89213,3.29582 -9.54755,6.82945 -14.1897,10.46637 -9.49744,7.36804 -17.9992,15.83825 -26.01509,24.76432 -6.30592,7.59525 -8.14218,8.23949 10.2995,13.47521 1.78331,0.50629 3.3167,-1.66017 4.9328,-2.56834 5.73793,-3.22441 9.53881,-5.73893 15.19815,-9.3143 13.24893,-8.54994 25.96694,-17.90399 37.94901,-28.15332 1.04824,-0.91395 2.1079,-1.81498 3.14472,-2.74187 0.79084,-0.70699 1.56569,-1.43176 2.33769,-2.15926 0.53118,-0.50056 1.12786,-0.94513 1.57156,-1.52464 0.198,-0.25861 0.59124,-0.77034 0.30618,-0.92788 -14.71815,-8.13451 -10.1663,-9.48815 -17.93001,-5.21701 -11.41265,6.6473 -22.20287,14.27124 -32.75212,22.19736 -10.66088,8.29117 -20.89797,17.10296 -30.98403,26.07851 -0.98028,0.86051 -2.03371,1.64422 -2.94083,2.58153 -5.39678,5.57637 8.13193,7.60867 14.30926,9.09646 2.00094,0.48192 3.79185,-1.6031 5.66556,-2.45469 5.07424,-2.30622 9.60823,-4.57573 14.62099,-7.03268 11.14435,-5.70338 22.44245,-11.13252 33.38489,-17.22113 5.46451,-0.58006 14.20537,-1.41277 -14.20952,-6.74516 -1.73723,-0.32601 -2.8735,2.0593 -4.29587,3.10863 -5.86769,4.32876 -11.61624,8.8152 -17.40775,13.24456 -0.88721,0.84917 -1.97162,1.53157 -2.66162,2.54751 -0.28753,0.42334 -0.56545,1.30065 -0.10868,1.53142 5.12688,2.59017 10.33852,5.33462 15.9671,6.48047 1.71551,0.34924 3.17757,-1.47245 4.73846,-2.26529 4.90381,-2.49085 6.99171,-3.77194 11.81268,-6.53639 1.94264,-1.1845 3.88822,-2.3642 5.82792,-3.55351 4.47851,-2.74595 8.99301,-5.47352 13.30943,-8.47306 0.65181,-0.45295 2.58101,-1.00805 1.90658,-1.4266 -4.79779,-2.9775 -10.29254,-4.64766 -15.43882,-6.97148 -11.72135,5.99277 -22.91179,12.94688 -34.23961,19.6353 -6.60264,3.95287 -13.30201,7.72362 -20.18434,11.16672 -9.33663,4.8581 -9.99423,3.7481 8.12157,11.42393 0.83219,0.3526 1.70531,-0.59973 2.5527,-0.91405 1.02537,-0.38033 2.04352,-0.77986 3.06527,-1.1698 6.88738,-3.16305 13.93717,-5.90731 21.04709,-8.52021 4.06204,-1.52457 8.09972,-3.2178 12.32549,-4.25606 1.68407,-0.41377 2.88582,-0.56931 4.57605,-0.83571 -1.4108,-2.79602 3.18988,0.90932 2.44908,0.52434 -4.45476,-2.31509 -8.49787,-5.5392 -13.24768,-7.16519 -0.90867,-0.31106 -1.4379,1.27434 -2.1802,1.8838 -2.94314,2.41647 -3.50484,2.75165 -6.63769,5.0276 -6.26617,4.40862 -12.714,8.5456 -19.11454,12.75349 -4.45769,4.93043 -2.50115,2.00475 11.93444,10.09798 0.63232,0.3545 1.41442,-0.31883 2.11844,-0.49163 2.98621,-0.73295 5.93525,-1.60178 8.89459,-2.4344 5.27098,-1.7298 10.76117,-2.53812 16.20623,-3.48887 4.60406,-0.82453 9.1585,-1.85176 13.62925,-3.22747 3.11796,-1.15071 6.24676,-2.27099 9.18927,-3.82602 1.66601,-0.96562 3.51589,-1.59353 5.16478,-2.58761 0.59316,-0.3576 1.14403,-0.78107 1.71659,-1.1708 1.74687,-1.35545 2.65746,-2.35985 3.62271,-4.37357 0.84126,-2.07386 1.06124,-4.30847 1.19133,-6.52137 0.0789,-1.7605 0.0646,-3.52332 0.0576,-5.28505 0.42359,-6.7192 3.03353,-1.77248 -13.2615,-9.57841 -0.44856,-0.21487 -0.88179,0.46044 -1.3207,0.69441 -0.54734,0.29178 -1.09201,0.58855 -1.63801,0.88282 -3.72432,2.34742 -7.18224,5.08426 -10.82303,7.54849 -3.13256,2.27465 -6.59511,3.9843 -10.07352,5.64923 -2.68913,1.31556 -5.50527,2.2938 -8.12552,3.76052 4.28068,2.88035 8.26686,6.25599 12.84202,8.64104 0.64153,0.33443 1.30296,-0.62985 1.9647,-0.92223 4.26541,-1.8846 1.57695,-0.53142 6.36978,-2.80944 1.13947,-0.54159 6.36523,-3.15514 7.52569,-3.73405 2.81628,-1.45312 5.68135,-2.8072 8.50641,-4.24271 0.33246,-0.16893 1.29943,-0.30491 0.99237,-0.51652 -12.71423,-8.76181 -8.74434,-10.10314 -15.04117,-6.92273 -6.79382,3.53204 -13.74745,6.74262 -20.52354,10.31127 -7.5434,3.95521 -15.43438,7.12357 -23.44161,9.9938 -4.93666,1.93963 -10.00951,3.44204 -15.13808,4.76283 -0.61961,0.37578 -4.11298,0.37957 -4.53103,0.75257 -0.23898,0.21323 -0.7467,0.65064 -0.48212,0.83114 4.26858,2.91201 8.88087,5.28514 13.32131,7.92771 3.06017,-0.26036 6.1004,-0.66469 9.16565,-0.88282 3.5349,-0.27157 7.08772,-0.15372 10.62077,-0.41487 2.34311,-0.20178 4.68416,-0.42837 7.03502,-0.51429 1.98063,0.0101 3.95881,-0.2504 5.90142,-0.62775 1.98537,-0.26974 3.8929,-0.84394 5.80038,-1.43236 1.70419,-0.58082 3.50678,-0.50524 5.26603,-0.74464 2.53961,-0.30944 5.00901,-1.04243 7.50092,-1.60707 1.79673,-0.39005 3.62652,-0.69197 5.40419,-1.16894 0.77169,-0.20705 0.73152,-0.48877 0.6584,0.0322 -15.02092,-10.25008 -8.42834,-9.62098 -17.23578,-6.65309 -1.91185,0.77525 -3.84198,1.48065 -5.76047,2.23372 -2.1782,0.94213 -4.52971,1.33577 -6.77528,2.06463 -1.92286,0.62993 -3.95096,0.6523 -5.85054,1.34594 -1.43592,0.6249 -2.95828,0.80537 -4.50244,0.90949 -1.69973,0.41292 -3.45038,0.54237 -5.19138,0.64517 -1.88828,0.13475 -3.82163,0.27962 -5.61684,0.9172 -0.0211,-0.0295 -0.0943,-0.10741 -0.0634,-0.0885 4.4075,2.68861 8.5688,5.82092 13.20029,8.10215 0.50696,0.2497 0.95543,-0.60426 1.44328,-0.88951 2.47696,-1.44829 4.88574,-2.95444 7.29325,-4.51881 4.43353,-2.70197 8.70382,-5.65959 12.75546,-8.90627 1.32906,-0.98398 2.49335,-2.14207 3.62575,-3.33846 0,0 -12.90598,-7.87335 -12.90598,-7.87335 v 0 c -1.10475,1.13152 -2.24531,2.21955 -3.51853,3.16535 -4.0487,3.17935 -8.28306,6.10197 -12.67783,8.78408 -1.64902,1.06545 -3.39232,2.21147 -5.07695,3.22299 -0.72249,0.43381 -1.46465,0.83405 -2.19248,1.25885 -0.47497,0.27721 -1.81706,0.47105 -1.41578,0.8471 10.42477,9.76936 7.5619,11.33669 12.0127,8.76713 1.78097,-0.40206 3.61469,-0.5307 5.43243,-0.65354 1.74871,-0.15261 3.47842,-0.41518 5.21287,-0.6753 1.60693,-0.20434 3.15791,-0.59502 4.6777,-1.16108 1.93936,-0.48218 3.95267,-0.655 5.84512,-1.338 2.30159,-0.64532 4.63,-1.21698 6.8368,-2.15547 1.91913,-0.72053 3.81715,-1.49554 5.74076,-2.20447 8.62643,-3.25519 5.68409,-1.80659 -7.41768,-10.83793 -0.57951,0.42283 -1.36317,0.47509 -2.05091,0.62728 -1.43747,0.31811 -2.87464,0.63797 -4.31681,0.93429 -2.44658,0.58032 -4.88995,1.23651 -7.40174,1.46555 -1.83742,0.16771 -3.67129,0.3449 -5.42332,0.97071 -1.86505,0.56113 -3.76192,0.9855 -5.68941,1.27195 -1.92476,0.2718 -3.85014,0.48183 -5.79717,0.48098 -2.35121,0.13789 -4.69492,0.37057 -7.04479,0.52789 -3.54525,0.15356 -7.09771,0.15487 -10.63737,0.43806 -3.05823,0.25769 -6.10045,0.66447 -9.16265,0.87861 4.10095,2.6678 8.35865,5.10888 12.30286,8.00339 0.22243,0.16323 -0.50266,0.42091 -0.45351,0.69239 0.0141,0.0781 4.37054,-0.75142 4.58956,-0.83694 5.16506,-1.33006 10.23089,-2.98806 15.21856,-4.88087 8.04727,-2.92727 15.97342,-6.16211 23.55564,-10.16528 6.79245,-3.54284 13.74759,-6.75919 20.5298,-10.32082 6.1694,-3.46088 3.5939,-1.2335 -10.25015,-9.64727 -0.31437,-0.19106 -0.65421,0.33667 -0.98284,0.50202 -3.77307,1.89844 -0.71516,0.34579 -5.12115,2.54028 -1.12963,0.56264 -2.25547,1.13287 -3.38321,1.69931 -3.64669,1.81381 -7.27715,3.65196 -11.01842,5.26635 -0.92249,0.39807 -1.84114,0.80968 -2.78853,1.14419 -0.5191,0.18329 -2.01711,0.0453 -1.60091,0.40566 3.71695,3.21784 7.99353,5.72576 11.99029,8.58864 2.63436,-1.37598 5.4429,-2.34498 8.08645,-3.70104 3.53151,-1.71393 7.01231,-3.53031 10.19911,-5.84269 3.562,-2.48006 7.00535,-5.14327 10.72295,-7.3938 0.5256,-0.26341 1.03573,-0.56029 1.57681,-0.79022 0.34553,-0.14683 1.37754,-0.11453 1.07482,-0.33657 -13.82664,-10.14164 -11.55582,-12.30727 -12.54762,-6.01864 0.009,1.73726 0.0275,3.47508 -0.0202,5.21196 -0.0821,2.02459 -0.20817,4.07984 -0.90984,6.00091 -0.81376,1.75352 -1.64737,2.5454 -3.20333,3.67747 -2.14295,1.42279 -4.50639,2.42113 -6.78341,3.60728 -2.91093,1.43792 -5.96998,2.53986 -9.03631,3.59764 -4.44558,1.29448 -8.96449,2.28607 -13.52651,3.07895 -5.48707,0.94315 -10.98138,1.90216 -16.29401,3.61552 -1.12137,0.3136 -4.98063,1.40492 -6.11919,1.67531 -0.86475,0.20537 -1.72806,0.45221 -2.61364,0.5278 -0.54137,0.0462 -2.04418,-0.50768 -1.62151,-0.16625 11.42704,9.23077 8.40436,11.68934 12.60187,7.50083 6.42684,-4.20992 12.88092,-8.38087 19.1554,-12.81757 2.49735,-1.83513 4.3837,-3.14306 6.72175,-5.16166 0.83724,-0.72285 3.09224,-1.40127 2.41497,-2.27578 -6.33848,-8.18436 -7.96984,-10.4425 -14.66541,-9.60946 -5.89378,1.07124 -11.51294,3.08172 -17.0598,5.32199 -7.10879,2.62566 -14.12733,5.44994 -21.05306,8.52907 -1.01221,0.37581 -2.01669,0.77314 -3.03664,1.12742 -0.81369,0.28264 -3.03652,0.14035 -2.46272,0.78279 11.80339,13.21539 6.99615,10.9296 11.92284,8.12062 1.31947,-0.7523 3.74865,-1.97655 5.01135,-2.62453 6.89121,-3.49042 13.61336,-7.28283 20.24024,-11.25236 11.27734,-6.67828 22.43811,-13.59569 34.1506,-19.49953 -3.31035,-3.15832 -6.16118,-6.88237 -9.93104,-9.47496 -0.63445,-0.43632 -1.23051,0.92684 -1.86559,1.36225 -1.15761,0.79367 -2.33243,1.56218 -3.51117,2.32412 -5.12389,3.31211 -10.35273,6.45766 -15.57115,9.61718 -4.38295,2.49441 -7.24864,4.21481 -11.74375,6.43131 -1.51803,0.74853 -5.44101,0.61334 -4.62479,2.09606 2.12153,3.85393 7.02265,5.3155 10.31252,8.23619 0.20792,0.18458 -0.98326,0.41742 -0.80971,0.20021 0.73463,-0.91941 1.71836,-1.60849 2.57754,-2.41273 5.78153,-4.42323 11.52997,-8.88866 17.38712,-13.21198 1.42972,-1.05531 5.0733,-1.52866 4.31223,-3.13445 -5.81826,-12.27593 -6.3744,-11.75565 -11.23511,-9.58703 -10.94552,6.03635 -22.20646,11.4862 -33.34269,17.15676 -4.22937,2.06502 -10.22531,5.0476 -14.57292,6.96022 -1.81778,0.79967 -6.27975,0.40599 -5.52019,2.24089 1.70084,4.10876 6.3992,6.24034 10.07003,8.75027 1.49441,1.02179 0.88047,-9.44405 3.83393,-3.84665 10.06538,-8.93869 20.2702,-17.72751 30.91727,-25.97198 10.50559,-7.86626 21.24325,-15.45041 32.62871,-22.00546 7.36761,-3.8532 3.96829,-0.86418 -8.13522,-10.03306 -0.061,-0.0462 0.28008,-0.0735 0.22901,-0.0165 -0.47916,0.5348 -1.02792,1.00305 -1.55236,1.49352 -0.76503,0.71545 -1.53338,1.4276 -2.31581,2.12398 -1.03519,0.92135 -2.09105,1.81921 -3.13658,2.72881 -11.95628,10.17483 -24.62464,19.47989 -37.83252,27.9695 -4.36546,2.74513 -10.44576,6.69692 -15.10959,9.17839 -1.49209,0.79389 -5.49167,0.65909 -4.61365,2.10329 2.38531,3.9234 6.89231,6.21587 10.96295,8.34018 1.93356,1.00905 0.87576,-8.92908 3.97258,-5.19905 7.97197,-8.85114 16.43325,-17.24046 25.87613,-24.53933 4.62049,-3.60275 9.25775,-7.09819 14.12437,-10.36353 1.0398,-0.69767 3.88038,-1.05567 3.13843,-2.06435 -8.3794,-11.39171 -7.52997,-11.87727 -11.60778,-8.80552 -8.62141,6.08752 -17.25943,12.16238 -26.08925,17.94303 -11.65263,7.29252 -23.38273,14.63386 -36.00522,20.15284 3.76123,2.73954 7.65708,5.30322 11.28369,8.21863 0.17317,0.13921 -0.64943,0.598 -0.45933,0.48299 0.54064,-0.3271 1.0066,-0.76466 1.52415,-1.12721 3.62633,-2.54031 7.40811,-4.8487 11.16951,-7.18018 4.99607,-3.03314 5.5246,-3.4057 10.64708,-6.30516 4.6037,-2.6058 9.38129,-4.97264 13.86718,-7.78308 0.88771,-0.55616 1.72092,-1.19491 2.58138,-1.79236 0.50182,-0.59773 2.09084,-1.27701 1.50547,-1.79319 -3.87871,-3.42027 -8.57482,-5.79813 -13.05103,-8.38775 -0.47089,-0.27242 -1.08498,-0.086 -1.62866,-0.10489 -2.94034,-0.10204 -5.89671,-0.0548 -8.77687,-0.79164 -6.84974,-2.01396 -3.16404,-1.19536 7.6358,5.56497 0.6805,0.42597 -1.44013,-0.72013 -2.10585,-1.16884 -1.12844,-0.76059 -2.34725,-1.94327 -3.22517,-2.98418 -0.37019,-0.43891 -0.67583,-0.92844 -1.01374,-1.39266 0,0 -13.77387,-6.23173 -13.77387,-6.23173 z" />
49- <path
50- style="fill:#ffffff"
51- id="path1033"
52- d="m 232.73967,343.98425 c -0.16239,-4.21792 -0.38636,-8.43294 -0.53272,-12.65164 -0.17653,-6.95973 -1.85631,-4.87751 -13.29234,-10.63359 -0.04,-0.0201 -3.53227,3.33793 -3.78645,3.58181 -1.99028,1.91133 -4.00381,3.86285 -6.2969,5.4174 -1.06135,0.71952 -1.93388,1.15314 -3.0558,1.75665 -1.95113,0.78708 -4.0813,1.77702 -6.2486,1.72206 -0.41992,-0.0106 -0.83194,-0.11686 -1.24792,-0.1753 -0.48134,-0.10515 -0.97296,-0.17113 -1.44404,-0.31546 -1.88325,-0.57698 -3.69696,-1.49191 -5.50071,-2.26758 -2.1798,-0.9374 -2.2769,-0.95062 -4.52683,-1.82148 -4.44489,-1.59935 -9.05198,-2.51641 -13.72931,-3.06968 -0.51119,-0.087 -1.04311,-0.0927 -1.53355,-0.26109 -0.28832,-0.099 -1.01299,-0.67747 -0.75025,-0.5229 4.24182,2.49551 8.37529,5.17918 12.68969,7.54702 0.17035,0.0935 0.32998,-0.20543 0.49213,-0.31253 0.47218,-0.31192 0.93292,-0.64129 1.41076,-0.94447 0.64704,-0.41054 1.30876,-0.79748 1.96314,-1.19621 5.32786,-2.88263 10.80956,-5.47437 16.22858,-8.18209 4.78718,-1.95989 9.10461,-4.78507 13.46258,-7.52548 2.62671,-1.56346 5.19283,-3.22119 7.80177,-4.81406 1.1227,-0.93327 3.56593,-1.49092 4.53824,-2.47123 0.16716,-0.16853 0.24952,-0.40389 0.37428,-0.60583 -4.23667,-2.89754 -8.32988,-6.01688 -12.71002,-8.69261 -0.29187,-0.1783 -0.64392,0.2312 -0.96946,0.33612 -2.15826,0.69562 -4.30644,1.33446 -6.42473,2.15203 -4.29152,1.63988 -8.41307,3.66717 -12.6077,5.53018 -4.82936,2.0624 -9.56932,4.30241 -14.24575,6.68873 -3.90778,2.11644 -7.73683,4.3642 -11.5033,6.72178 -1.61315,0.98308 -3.112,2.13675 -4.70891,3.14424 -0.37871,0.23893 -0.78785,0.42969 -1.15472,0.68643 -0.77674,0.54356 -1.56314,1.30623 -2.2582,1.94248 -1.3674,1.53824 -3.49987,3.52541 -3.14491,5.83895 0.0542,0.35357 0.23681,0.67509 0.35521,1.01264 4.611,2.65737 9.10124,5.53626 13.833,7.9721 0.39531,0.2035 0.88276,-0.1075 1.32296,-0.17003 2.35331,-0.33431 4.41661,-1.16882 6.57287,-2.14245 4.66944,-2.08131 9.10602,-4.67138 13.44997,-7.35827 4.57957,-2.99451 9.14835,-6.0171 13.558,-9.25944 2.82993,-2.279 5.77344,-4.40642 8.73187,-6.51307 1.93406,-1.45357 3.91458,-2.83753 5.84743,-4.29188 1.50721,-1.13725 2.80699,-2.51181 4.20715,-3.77294 0.53578,-0.55497 1.54467,-0.85008 1.93694,-1.55022 2.9445,-5.25545 -8.98814,-6.22301 -12.95355,-8.48667 -2.46378,1.36153 -4.50197,3.20247 -6.39761,5.26251 -0.39857,0.48953 -5.58042,6.12839 -4.84005,6.67322 4.2012,3.09163 8.7864,5.62424 13.1796,8.43636 3.68207,-1.12239 6.2577,-3.8927 8.89656,-6.52432 0.0606,-0.0662 1.62195,-1.787 1.8513,-1.9885 0.12384,-0.10881 0.28737,-0.16161 0.43105,-0.24242 -4.39526,-2.2892 -8.63135,-4.91417 -13.18578,-6.86759 -0.34145,-0.14645 -0.2515,0.70012 -0.40601,1.03799 -0.22397,0.48975 -0.48546,0.96145 -0.72819,1.44218 -0.86092,1.6686 -2.0156,3.14343 -3.23225,4.56183 -0.24595,0.28673 -0.47983,0.58649 -0.7558,0.84446 -0.0265,0.0247 -0.071,-0.11872 -0.0392,-0.10134 4.40101,2.40648 8.77951,4.85392 13.16926,7.28089 1.46759,-2.4309 3.48191,-4.44214 4.9583,-6.86589 1.06137,-2.32718 0.93692,-4.83266 0.0823,-7.19189 -2.80578,-4.83762 -12.6773,-7.80282 -14.85026,-8.84928 -0.31655,-0.15245 -3.53109,1.16694 -3.87595,1.30947 -4.47781,1.99153 -8.79044,4.33462 -13.22011,6.43083 -4.21998,2.0963 -8.51473,4.03792 -12.87256,5.82964 -2.77042,1.16583 -5.6843,1.88722 -8.58772,2.62102 -2.80453,0.92069 -5.70723,1.44571 -8.56832,2.15075 -2.29694,0.62103 -4.58347,1.319 -6.66594,2.49128 -0.12684,0.0518 -0.49398,0.0785 -0.38052,0.15532 4.23064,2.86275 8.3752,5.89525 12.88037,8.30297 0.43136,0.23053 0.84219,-0.49762 1.26527,-0.74304 2.97907,-1.72813 5.83126,-3.60518 8.70066,-5.51101 5.75823,-3.98884 11.70446,-7.70199 17.74697,-11.24239 2.80788,-1.4419 5.6684,-2.82242 8.61115,-3.96542 2.70305,-1.02336 5.36367,-2.15739 8.10939,-3.06346 1.86988,-0.63702 3.74282,-1.3128 5.66466,-1.77909 1.44074,-0.18382 2.82028,-0.62291 4.25689,-0.83578 0.96481,-0.11847 1.939,-0.1301 2.9096,-0.15324 0.20012,-0.005 0.40765,-0.0685 0.60035,-0.0143 0.0912,0.0256 0.11119,0.15329 0.16679,0.22994 -12.44464,-7.68644 -11.33159,-11.89752 -15.32577,-3.63822 -0.92044,2.08126 -1.91653,4.06144 -3.30388,5.86997 -1.67673,1.99068 -4.14201,2.87169 -6.44358,3.91089 -5.85047,2.07862 -6.41927,-0.30481 11.67311,7.81678 0.39119,0.1756 0.56205,-0.64848 0.86246,-0.95446 0.8056,-0.82054 1.65794,-1.59387 2.48089,-2.39702 2.13255,-1.90308 3.73829,-3.81663 4.58765,-6.56018 0.51997,-2.17666 0.50849,-4.42627 0.51403,-6.65016 -0.0365,-1.98098 0.0282,-3.96596 -0.0874,-5.94446 -0.0469,-1.31854 -0.54398,-2.4502 -1.18941,-3.56455 -11.86794,-7.43097 -10.44561,-9.61528 -19.54019,-6.64195 -2.90504,1.06197 -5.64842,2.5092 -8.47323,3.76296 -2.82972,1.2514 -5.55757,2.71314 -8.35105,4.03951 -2.73802,1.46585 -5.62337,2.6416 -8.46532,3.89092 -2.93432,1.44932 -5.95304,2.71572 -8.93136,4.07021 -2.61697,1.18126 -5.17726,2.46451 -7.68626,3.85548 -0.43106,0.29155 -1.18643,0.74935 -1.55054,1.17364 -3.52989,4.11327 7.88695,6.33505 13.16143,8.67144 0.33506,0.14842 0.66895,-0.29947 1.00343,-0.44921 1.5692,-0.85195 3.27529,-1.39841 4.82461,-2.29156 0.80789,-0.46573 1.57825,-0.99384 2.37385,-1.48028 2.34583,-1.58221 4.30424,-3.63646 6.41685,-5.49806 1.46344,-1.30423 2.65707,-2.81893 3.34364,-4.6575 0.25683,-1.25844 0.81728,-2.81572 0.29801,-4.09133 -0.10581,-0.25993 -0.31332,-0.46569 -0.46998,-0.69854 -4.55348,-2.53758 -9.0634,-5.15497 -13.66045,-7.61274 -0.17399,-0.093 -0.39472,-0.015 -0.59186,-0.007 -0.12411,0.005 -0.47422,-0.0219 -0.36984,0.0455 4.24259,2.73583 8.50906,5.4383 12.85552,8.0059 0.073,0.0431 0.4994,-0.37586 0.75985,-0.61595 1.05025,-1.01942 1.86431,-2.28026 2.42797,-3.62806 0.31654,-0.52272 0.40322,-1.30266 0.96533,-1.64806 0.0979,-0.0602 0.21352,-0.0853 0.32028,-0.12794 0.13562,-0.0966 0.27123,-0.19324 0.40684,-0.28986 5.19693,-2.57523 10.31369,3.11931 -12.05308,-8.24825 -0.31398,-0.15958 -0.57926,0.4008 -0.86889,0.60121 -2.16543,1.33756 -4.16339,2.92389 -6.13481,4.52914 -1.66623,1.5478 -3.16695,3.26179 -4.80545,4.83609 -4.54417,4.78544 2.02375,3.79484 12.61783,9.20028 0.0449,0.0229 3.73072,-1.71529 3.99793,-1.84236 2.68136,-1.48749 5.35558,-2.96967 8.08898,-4.35972 1.77149,-0.97442 3.61795,-1.83865 5.50693,-2.55921 1.83273,-0.61603 3.6719,-1.20408 5.51169,-1.79859 1.74554,-0.54089 3.55893,-0.73243 5.34083,-1.11531 1.46277,-0.3253 2.95799,-0.3803 4.44952,-0.41387 1.62889,-0.0231 3.25805,-0.0161 4.88705,-0.0128 0.7882,0.19692 0.44532,0.0265 1.04462,0.47783 0,0 -11.91856,-9.30063 -11.91856,-9.30063 v 0 c -0.87451,-0.0778 -0.42265,-0.0541 -1.35592,-0.0645 -1.64103,8e-5 -3.28289,-0.005 -4.92286,0.0623 -1.54681,0.0866 -3.08424,0.26499 -4.59518,0.61963 -1.81956,0.37014 -3.67292,0.59739 -5.42732,1.241 -1.87635,0.58373 -3.73576,1.22084 -5.57977,1.8995 -1.8899,0.82 -3.77812,1.64933 -5.57518,2.66212 -2.72347,1.37718 -5.34241,2.93712 -8.06232,4.31931 -0.6843,0.3143 -2.00098,0.94025 -2.73657,1.19988 -0.32907,0.11614 -1.28367,0.058 -1.01018,0.27479 13.7453,10.89314 8.53254,11.61904 12.61923,7.42493 1.58097,-1.56706 3.0709,-3.22812 4.73039,-4.71521 1.96252,-1.57136 3.95718,-3.10421 6.08414,-4.44895 0.30082,-0.21807 1.18637,-0.41454 0.90245,-0.65421 -7.7916,-6.57739 -8.31092,-11.32876 -13.4836,-7.94085 -1.05349,0.71525 -1.69754,1.6303 -2.14331,2.82068 -0.43797,1.15738 -1.16584,2.19887 -2.07857,3.03463 -0.0545,0.0481 -0.92528,0.80376 -0.89054,0.83075 3.96643,3.08211 8.18217,5.82945 12.29744,8.70984 0.21262,0.14882 0.55613,0.0335 0.73193,0.26553 -4.11791,-2.3947 -8.23583,-4.78939 -12.35375,-7.18409 0.11825,0.0189 1.04835,0.11235 1.20019,0.31114 0.361,0.47262 -0.13437,2.13833 -0.19015,2.6962 -0.51158,1.63225 -1.70596,2.86555 -2.95481,3.99408 -2.02917,1.82599 -3.95843,3.79249 -6.27205,5.26938 -2.2578,1.37928 -4.6055,2.54372 -7.05381,3.5521 -5.58612,1.85709 -0.0223,-0.24329 11.68112,7.64689 0.40851,0.27541 -1.63103,1.30748 0.076,0.15975 0.2753,-0.18511 0.5646,-0.3485 0.84691,-0.52274 2.4821,-1.36587 5.04373,-2.57855 7.62031,-3.75523 2.97736,-1.34282 5.97456,-2.64164 8.92032,-4.05358 2.86232,-1.24381 5.72956,-2.48833 8.49579,-3.93692 2.76914,-1.35042 5.50943,-2.76014 8.32879,-4.00587 2.76516,-1.25474 5.47188,-2.67247 8.36843,-3.60722 1.26633,-0.36215 2.56448,-0.78342 3.89756,-0.80012 0.85192,-0.0107 0.82765,0.0752 1.53593,0.42768 -4.10255,-2.46371 -8.2051,-4.92742 -12.30765,-7.39113 0.87022,0.63506 1.41465,1.66666 1.57425,2.72657 0.29538,1.91362 0.0477,3.87804 0.15627,5.8075 0.009,2.09402 0.0596,4.20901 -0.31768,6.27765 -0.62923,2.46598 -2.34965,4.1958 -4.19368,5.88001 -0.32766,0.3196 -3.65762,3.49777 -3.50955,3.62853 10.25275,9.05415 7.97742,10.62804 13.96992,8.18094 2.5228,-1.13814 5.07757,-2.32734 6.8435,-4.54726 1.43052,-1.90044 2.50357,-3.95606 3.45643,-6.13263 3.80252,-7.93496 6.00519,-2.82532 -9.84222,-12.99277 -1.31575,0.30131 -2.7269,0.0687 -4.0426,0.43813 -1.44785,0.29793 -2.89095,0.62174 -4.3327,0.94508 -1.92223,0.5696 -3.83482,1.18028 -5.7206,1.8611 -2.74705,0.97129 -5.44361,2.07764 -8.158,3.13566 -2.96416,1.26301 -5.91996,2.56526 -8.73087,4.14719 -6.05698,3.565 -12.00387,7.32161 -17.79115,11.31094 -2.0647,1.36328 -4.22082,2.82155 -6.34676,4.10232 -0.74734,0.45023 -1.50867,0.8775 -2.2773,1.29035 -0.36773,0.19752 -1.45346,0.27595 -1.12997,0.53975 11.25958,9.18223 7.85353,10.77408 12.66198,8.30613 2.05871,-0.98645 4.25118,-1.63592 6.4617,-2.18772 2.86526,-0.69453 5.75463,-1.28469 8.57389,-2.15883 2.96319,-0.74043 5.89479,-1.61773 8.71335,-2.8061 4.36341,-1.83564 8.67479,-3.78933 12.91544,-5.89422 4.38121,-2.11024 8.66513,-4.43844 13.14946,-6.32484 0.79723,-0.27088 1.6527,-0.63379 2.51458,-0.67988 0.2792,-0.0149 1.07008,0.25495 0.83172,0.10879 -18.76731,-11.50759 -15.88476,-8.71313 -10.93879,-6.55684 1.1779,1.68282 1.33452,3.92655 0.61867,5.83 -1.51283,2.47276 -3.62336,4.50648 -5.0562,7.03708 4.23053,2.78584 8.38879,5.68466 12.6916,8.35752 0.11663,0.0725 0.19532,-0.19325 0.28736,-0.29514 0.26485,-0.29315 0.5285,-0.58756 0.78305,-0.88969 1.27543,-1.51383 2.4844,-3.08757 3.40139,-4.851 0.26271,-0.51611 0.54183,-1.02418 0.78813,-1.54831 0.2028,-0.43159 0.93409,-1.0235 0.55835,-1.31712 -12.01227,-9.38683 -10.01885,-11.64406 -15.33077,-5.90598 -1.79042,1.77947 -4.35818,4.82031 -7.00038,5.20786 -0.29162,0.0428 -0.58062,-0.10179 -0.87093,-0.15268 4.02282,2.65442 7.9953,5.38677 12.06847,7.96326 0.11339,0.0717 -0.15575,-0.27006 -0.0969,-0.39064 0.21338,-0.43739 0.56456,-0.79318 0.86224,-1.17819 1.14105,-1.47578 2.35791,-2.89629 3.60821,-4.2798 1.58596,-1.68233 3.44224,-3.73251 5.76525,-4.30599 -4.2131,-2.54932 -8.56527,-4.88182 -12.6393,-7.64798 -0.21311,-0.1447 0.33388,-0.3983 0.45002,-0.62823 0.0471,-0.0933 -0.15595,0.13926 -0.23661,0.20573 -0.55561,0.45787 -1.15351,0.8601 -1.69495,1.33579 -1.35883,1.24037 -2.64174,2.57235 -4.13464,3.65708 -1.919,1.45056 -3.90613,2.80573 -5.82514,4.25672 -2.96436,2.10935 -5.88464,4.27737 -8.74246,6.52975 -4.39397,3.19928 -8.92679,6.20958 -13.49223,9.15664 -4.30697,2.6285 -8.69454,5.17349 -13.33203,7.17839 -1.61461,0.66836 -3.19119,1.33514 -4.94353,1.54626 -5.13769,0.61898 -13.74732,-7.46153 9.72713,7.05026 -0.23995,-0.0713 -0.52011,-0.0631 -0.71985,-0.21402 -1.18523,-0.89541 1.85209,-3.97341 2.42794,-4.54589 2.44199,-2.14394 5.31075,-3.71203 7.98142,-5.55112 3.74114,-2.32298 7.54254,-4.54338 11.43148,-6.61142 4.66388,-2.36268 9.40496,-4.55199 14.20446,-6.62622 4.15951,-1.87582 8.27828,-3.84601 12.55452,-5.45065 1.64953,-0.59544 3.32117,-1.23265 5.04263,-1.59795 0.36175,-0.0768 0.73109,-0.18172 1.09941,-0.14867 0.18958,0.017 0.32738,0.19427 0.49107,0.29141 -4.04564,-2.7953 -8.09128,-5.59059 -12.13692,-8.38589 -1.49052,0.86633 -3.08899,1.54686 -4.54602,2.47941 -2.60577,1.60644 -5.19029,3.24617 -7.81092,4.82826 -4.32192,2.71934 -8.65468,5.41931 -13.38153,7.40097 -5.45576,2.72376 -10.99132,5.30639 -16.30537,8.29999 -0.95554,0.60573 -2.68974,1.62849 -3.56714,2.44843 -0.384,0.35884 -1.40022,0.87251 -1.00295,1.21659 7.44833,6.45106 8.6187,8.38054 15.11935,9.26037 4.61007,0.64895 9.14931,1.68107 13.50224,3.3662 3.83153,1.54046 7.61457,3.58519 11.77229,4.0085 2.81893,-0.10125 5.47716,-0.90361 7.97168,-2.23334 3.55204,-2.01968 6.71321,-4.56746 9.56112,-7.50291 0.0581,-0.0554 2.15763,-2.09521 2.47518,-2.27343 0.0932,-0.0523 0.3483,0.24732 0.25797,0.19025 -4.10953,-2.59651 -8.21085,-5.206 -12.31627,-7.80901 0.0837,0.25546 0.21511,0.49997 0.251,0.76638 0.0637,0.47319 0.0301,0.95446 0.0451,1.4317 0.13767,4.2041 0.32794,8.40601 0.55578,12.6062 0,0 13.47024,6.86343 13.47024,6.86343 z" />
53- <path
54- style="fill:#ffffff"
55- id="path1035"
56- d="m 185.8578,280.25304 c 2.1647,-1.03493 4.30367,-2.1219 6.44766,-3.19929 1.27197,-0.62913 2.53847,-1.12777 3.94755,-1.32128 1.43773,-0.12953 2.88474,-0.10256 4.32689,-0.10046 1.74211,0.005 3.48422,0.0109 5.22634,0.0137 1.74919,0.003 3.49839,0.003 5.24759,0.003 1.44315,9e-5 2.8863,0 4.32945,-3e-5 1.33089,-4e-5 2.66179,-5e-5 3.99269,-5e-5 1.32776,0 2.65551,0 3.98326,0 1.32931,0 2.65862,0 3.98793,0 0.17489,0.0539 0.34978,0.10779 0.52467,0.16169 0,0 -12.10961,-9.05049 -12.10961,-9.05049 v 0 c -0.21526,8.9e-4 -0.43052,0.002 -0.64578,0.003 -1.32931,0 -2.65861,0 -3.98792,0 -1.32776,0 -2.65552,0 -3.98327,10e-6 -1.33091,1e-5 -2.66181,3e-5 -3.99271,7e-5 -1.44308,3e-5 -2.88616,9e-5 -4.32924,-2.5e-4 -1.74869,-5.5e-4 -3.49737,-10e-4 -5.24605,-0.005 -1.74413,-0.004 -3.48827,-0.0127 -5.23239,-0.005 -1.50245,0.0167 -3.01946,0.0159 -4.49505,0.33689 -1.46312,0.37423 -2.82923,0.98965 -4.17862,1.66268 -2.15021,1.10101 -4.33465,2.13332 -6.47881,3.24688 0,0 12.66542,8.25477 12.66542,8.25477 z" />
57- <path
58- style="fill:#ffffff"
59- id="path1037"
60- d="m 517.46462,337.23029 c -2.56265,4.0164 -4.88261,8.17784 -7.31927,12.26999 -1.7766,3.09546 -4.32727,5.482 -7.18992,7.54762 -2.68734,1.95262 -5.15996,4.16998 -7.5556,6.46553 -1.38721,1.40056 -2.95482,2.59485 -4.53481,3.76485 -1.22963,0.91055 -2.3367,1.76135 -3.77132,2.29409 -5.09097,-1.74318 -1.08156,-0.40133 12.37559,6.98305 0.0258,0.0142 -0.004,-0.0636 0.0139,-0.0874 0.1837,-0.24854 0.37344,-0.49383 0.58333,-0.72068 0.75786,-0.81912 1.45438,-1.37948 2.30908,-2.12616 4.70831,-3.64505 10.04801,-6.34425 15.17505,-9.33458 2.03876,-1.27739 4.2988,-2.28601 6.19949,-3.78479 0.32219,-0.25406 0.62927,-0.53694 0.87598,-0.8648 0.15744,-0.20924 0.5329,-0.57258 0.31596,-0.71924 -4.29597,-2.90429 -8.86989,-5.37458 -13.30483,-8.06188 -3.57739,1.68493 -6.85132,3.90699 -10.27248,5.87217 -2.74604,1.62213 -5.6826,2.87125 -8.61663,4.10562 -2.65401,1.21151 -5.20027,2.67454 -7.58553,4.35282 -2.14447,1.40823 -4.28086,2.81275 -6.3615,4.31081 -1.40627,0.71348 -2.30929,1.77998 -2.62065,3.32068 0.81016,7.36832 12.11254,9.3282 18.52914,9.04592 2.50936,-0.31243 5.04454,-0.61344 7.40166,-1.56914 1.97441,-0.61026 2.76446,-1.84839 3.76012,-3.5379 1.07739,-2.05927 1.9625,-4.20328 2.99711,-6.28434 1.3492,-2.74011 3.16209,-5.22591 4.86933,-7.75101 1.18101,-1.65339 2.46708,-3.2623 3.46898,-5.03427 1.7824,-6.11494 -7.84664,-6.6416 -13.30612,-9.6205 -0.2292,-0.12506 -3.29355,0.22879 -3.63397,0.27387 -2.89195,0.30971 -5.62076,1.31686 -8.29068,2.41542 -2.82065,1.13197 -5.46628,2.58099 -8.06971,4.13143 -2.53638,1.22945 -4.83578,2.84226 -7.17486,4.3939 -1.3709,0.9908 -2.65439,2.08728 -3.75772,3.36967 -1.68219,1.5319 -3.2942,3.1344 -4.9593,4.68411 -1.45269,1.52497 -2.83742,3.11478 -4.17917,4.73738 -3.63798,5.37456 3.79556,4.99992 12.33474,9.71531 0.36277,0.20032 0.74858,-0.35639 1.13043,-0.51739 2.83931,-1.19711 0.0772,0.11931 3.2847,-1.4869 2.05827,-1.08132 4.03317,-2.30736 5.99544,-3.55117 0.47837,-0.30323 2.68062,-1.74462 3.32498,-2.03614 0.35437,-0.16032 0.74954,-0.2081 1.1243,-0.31215 5.21693,1.88088 0.36376,0.16898 -12.03101,-6.67223 -0.15964,-0.0881 0.50681,0.0961 0.47276,0.27524 -0.0452,0.23791 -0.50356,0.73864 -0.59892,0.8559 -1.01692,1.01725 -2.09668,1.99757 -3.23289,2.88304 -1.13366,0.77062 -1.78521,1.99288 -2.66487,3.00144 -0.4999,0.5181 -1.82578,2.76169 -0.85978,3.33392 4.5024,2.66709 9.1647,5.0545 13.74706,7.58175 2.23462,0.21341 4.48944,-0.29226 6.57794,-1.06304 0.183,-0.12081 0.39287,-0.20847 0.549,-0.36244 0.24654,-0.24315 0.89597,-1.50801 0.63823,-1.67783 -10.79379,-7.11175 -8.78038,-7.42141 -15.10327,-7.57337 -2.15751,-0.16966 -4.25743,0.16954 -6.33775,0.69945 -1.47207,0.22532 -2.86569,0.7409 -4.32815,0.98616 -1.4266,0.0627 -2.75764,0.56693 -4.1574,0.79381 -1.34956,0.0761 -2.77182,0.34036 -4.09815,0.0146 -0.19362,-0.0476 -0.39408,-0.0731 -0.58087,-0.14283 -0.44722,-0.167 -1.67781,-0.9967 -1.05789,-0.6456 20.98616,11.88561 15.60495,9.05661 11.16471,6.32842 -1.5196,-0.99201 -1.64758,-2.44567 -1.34593,-4.11665 0.81182,-3.10827 3.25991,-5.22887 5.61134,-7.23229 3.34059,-2.85841 7.1928,-4.9601 11.03557,-7.04621 4.20603,-2.0403 8.36157,-4.17644 12.43734,-6.46651 2.83986,-1.81873 5.72411,-3.57291 8.44219,-5.57327 1.02334,-0.93747 3.59337,-2.12394 4.07012,-3.46338 0.1339,-0.37621 0.1713,-0.78006 0.25695,-1.17009 -4.58492,-2.39842 -9.04567,-5.05089 -13.75477,-7.19526 -0.3497,-0.15925 -0.7063,0.30296 -1.06207,0.44814 -2.01467,0.82213 -3.9665,1.70254 -5.91589,2.67225 -3.65467,2.01385 -7.46814,3.79404 -10.77826,6.35973 -0.21249,0.34647 -0.5386,0.64517 -0.63747,1.03939 -1.30502,5.20313 13.83595,8.79595 14.32339,9.01748 0.61437,0.27922 1.34531,-0.12016 2.0121,-0.22411 1.77354,-0.2765 3.17125,-0.64859 4.92228,-1.06644 4.5451,-1.30218 8.81106,-3.4264 12.94588,-5.69043 1.97421,-0.92375 3.81889,-2.13756 5.873,-2.87661 1.77393,-1.01584 3.70407,-1.74284 5.64601,-2.36653 0.2903,-0.0996 0.5646,-0.27948 0.8709,-0.29874 0.14174,-0.009 0.48783,0.29097 0.36421,0.22107 -4.36595,-2.4686 -8.555,-5.27784 -13.09134,-7.41726 -0.35331,-0.16663 -0.51614,0.58655 -0.77843,0.87603 -1.08393,1.19626 -1.25521,1.36145 -2.40611,2.57297 -3.24235,3.30458 -6.48143,6.60383 -9.86178,9.77025 -3.28404,3.17757 -6.24306,6.66774 -9.44409,9.92313 -2.01291,2.02617 -4.18264,3.87968 -6.23082,5.8679 -1.9223,1.70425 -3.86185,3.39422 -5.88858,4.9743 -0.96722,0.65082 -1.83726,1.617 -2.96371,2.01994 -0.16514,0.0591 -0.37401,0.12202 -0.52514,0.033 -0.1206,-0.071 -0.22959,-0.47479 -0.10781,-0.4058 4.37015,2.47548 8.64001,5.12397 12.96002,7.68596 0.53794,-0.73321 1.39731,-1.28776 1.82599,-2.1042 2.47982,-4.72287 -10.9966,-8.27065 -13.75481,-9.82443 -3.01935,-0.16368 -6.00541,0.25868 -8.9999,0.5565 -3.33136,0.25062 -6.66491,0.53234 -9.97631,0.97952 -1.49207,0.0622 -2.91511,0.57342 -4.40233,0.66511 -0.31252,0.0193 -0.63635,0.0538 -0.93899,-0.0266 -0.22732,-0.0604 -0.79953,-0.49519 -0.59419,-0.3805 4.19573,2.34367 8.37764,4.712 12.56647,7.06801 -0.12644,-0.0812 -0.29432,-0.11966 -0.37932,-0.24357 -0.73362,-1.06945 0.52294,-2.95045 0.98506,-3.83506 1.54566,-2.92927 3.75683,-5.3607 6.04032,-7.72501 3.25479,-3.46003 6.89274,-6.53596 10.49318,-9.62639 3.01741,-2.51586 6.22504,-4.77774 9.2122,-7.33076 2.43349,-2.03466 5.14772,-3.71373 7.78144,-5.47881 7.74668,-5.34821 5.26549,-2.15496 -8.93837,-11.02194 -3.67679,2.35575 -7.28617,4.81658 -10.95286,7.1879 -3.60708,2.14166 -6.78641,4.82897 -9.89312,7.62168 -1.86144,1.77073 -3.57985,3.68242 -5.41099,5.4821 -1.46318,1.5278 -2.96981,3.07905 -4.72685,4.26969 -1.60606,1.0531 -3.23293,2.05513 -4.91002,2.98816 -1.48842,0.87657 -2.93256,1.85966 -4.62833,2.26668 -2.29749,0.73424 -4.56561,1.54956 -6.89065,2.19388 -0.21725,0.033 -1.44035,0.30317 -1.78598,0.0997 -0.1635,-0.0962 -0.41263,-0.60608 -0.24764,-0.51244 4.26109,2.41814 8.34115,5.14836 12.62053,7.53399 0.13307,0.0742 0.031,-0.30774 0.0903,-0.44808 0.14456,-0.342 0.34422,-0.658 0.51633,-0.987 2.37142,-3.19148 5.06218,-6.13906 7.75268,-9.063 2.78598,-3.28539 5.95741,-6.19831 9.12416,-9.10549 2.81779,-2.65813 5.52878,-5.42524 8.32563,-8.10749 2.35912,-2.2275 4.9302,-4.20401 7.51479,-6.15787 2.21682,-1.57455 4.70767,-2.64853 7.23257,-3.61266 2.59322,-1.05976 5.31693,-1.74942 7.95483,-2.67784 6.95804,-2.23836 5.26274,-0.70784 -10.5794,-8.877 -0.38723,-0.19968 -0.77405,0.40021 -1.15813,0.6059 -0.57211,0.30639 -1.14022,0.62019 -1.71034,0.93029 -5.10523,2.95137 -10.1952,5.93597 -15.4318,8.64977 -3.75271,2.05164 -7.48656,4.12742 -11.25821,6.14133 -2.71694,1.28113 -5.35381,2.71843 -8.01141,4.11619 -1.86867,0.9152 -3.66552,1.95908 -5.47922,2.98047 -1.72455,1.10921 -3.54098,2.13062 -5.05664,3.51931 -0.90463,0.82401 -2.04789,2.27228 -3.2307,2.66682 1.2635,-5.34205 -0.16936,0.0679 12.96906,7.26428 0.17418,0.0954 0.22262,-0.32904 0.32813,-0.49729 0.11736,-0.18717 0.22653,-0.37935 0.34031,-0.56871 0.0591,-0.0985 0.27684,-0.23544 0.17847,-0.2947 -4.39251,-2.6466 -8.90136,-5.09514 -13.35205,-7.6427 -1.49531,0.85332 -2.7992,1.97088 -4.22888,2.92082 -4.9037,3.89088 -8.1625,-1.2946 11.33893,8.0153 0.21258,0.10149 0.0789,-0.46569 0.14649,-0.69135 0.0927,-0.30958 0.22227,-0.60693 0.33341,-0.91039 0.9684,-2.27791 2.17116,-4.41805 3.44801,-6.53272 1.83613,-2.66105 4.2652,-4.84603 6.6056,-7.05325 2.98662,-2.55678 6.31032,-4.67369 9.50952,-6.94733 2.50895,-1.86606 5.26592,-3.33396 7.90531,-4.98838 2.47853,-1.36936 5.0954,-2.41932 7.72677,-3.44606 3.54649,-1.27142 7.2593,-1.95785 10.88689,-2.95161 3.44504,-1.09078 6.90807,-2.11655 10.36057,-3.18415 1.57896,-0.50775 3.15215,-1.01407 4.66702,-1.69465 0.22148,-0.0995 0.40862,-0.34535 0.65075,-0.32727 0.13052,0.01 -0.0964,0.24337 -0.14462,0.36505 -4.28721,-2.811 -8.39676,-5.91369 -12.86162,-8.433 -0.33959,-0.19162 -0.65762,0.4197 -0.9957,0.61396 -2.34749,1.34885 -4.80653,2.53706 -7.25277,3.6926 -4.36193,1.75711 -8.68282,3.60481 -12.99058,5.49322 -4.10098,1.72429 -7.9938,3.8697 -11.98214,5.82283 -2.74984,1.24947 -5.53582,2.41306 -8.32154,3.58056 -2.33776,0.90325 -4.64698,1.81777 -6.82899,3.06087 -1.6298,0.95465 -2.93181,2.35203 -4.53805,3.32872 -1.22767,0.5776 -2.9154,2.1317 -4.32374,1.80128 -2.79977,-4.49808 -0.18699,-0.17007 12.40537,6.86221 0.13592,0.0759 -0.24331,-0.20851 -0.31037,-0.34901 -0.3676,-0.7701 -0.29072,-1.79017 -0.29405,-2.60894 0.27412,-1.91104 1.29442,-3.60878 2.49962,-5.08165 1.12269,-1.14938 2.65266,-1.66837 3.92767,-2.62115 1.45993,-0.82313 2.77198,-1.84586 4.11828,-2.83995 1.71524,-1.19027 3.37924,-2.44494 5.19567,-3.48489 2.614,-1.40322 5.09913,-3.01589 7.68692,-4.46278 2.75467,-1.40031 5.54523,-2.81309 8.46488,-3.83629 3.572,-1.39268 7.35073,-2.15702 11.06617,-3.06688 2.1504,-0.43081 4.24068,-1.17674 6.24799,-2.0536 1.84007,-0.61386 3.57214,-1.48886 5.38903,-2.15968 2.39709,-0.73828 4.7544,-1.60292 7.1525,-2.33873 2.13542,-0.69731 4.39264,-0.70615 6.57979,-1.09077 1.97808,-0.16946 3.86686,-0.73435 5.77371,-1.23803 2.24307,-0.87141 4.58412,-1.44007 6.8694,-2.18694 2.22625,-0.79063 3.47755,-2.45154 4.24552,-4.59789 0.49708,-1.74364 0.66891,-3.12814 -0.47933,-4.63119 -3.93217,0.9071 -9.47789,-8.04187 -15.22808,-8.02582 -0.90173,0.003 -1.78593,0.33746 -2.64717,0.56315 -2.16621,0.70967 -3.8994,2.23159 -5.77133,3.47988 -1.28133,0.95004 -2.63044,1.80516 -3.90936,2.7585 -0.35558,0.26506 -0.71563,0.52642 -1.04581,0.82251 -0.19353,0.17354 -0.73737,0.43927 -0.51972,0.58138 4.38614,2.86373 9.04269,5.29022 13.56403,7.93533 2.86923,-1.07989 5.45916,-2.74922 7.90565,-4.5716 1.30728,-1.18837 2.73972,-2.28872 3.7411,-3.76068 0.69028,-1.09515 0.57253,-2.17863 0.22624,-3.35306 0,0 -13.75609,-6.27088 -13.75609,-6.27088 v 0 c 0.43127,0.58847 0.90259,1.25446 0.5011,1.95547 -0.93702,1.30814 -2.28597,2.27716 -3.47973,3.33299 -2.35429,1.69155 -4.84173,3.25548 -7.6231,4.14233 3.94379,2.81864 7.78552,5.78584 11.83137,8.45591 0.15408,0.10169 0.25521,-0.2681 0.39804,-0.38505 1.55286,-1.27142 3.28779,-2.30435 4.90031,-3.49462 1.73096,-1.16737 3.36424,-2.58064 5.46172,-3.01518 0.62992,-0.11694 0.95322,-0.2188 1.59062,-0.18113 4.62466,0.27329 16.94781,10.99054 -10.17189,-6.59562 1.74126,0.70663 1.64617,1.19544 1.27065,2.96111 -0.59489,1.92717 -1.5473,2.7829 -3.50056,3.38484 -2.28542,0.69029 -4.56126,1.40392 -6.83314,2.13394 -1.86368,0.50835 -3.75489,0.86129 -5.67334,1.09378 -2.26937,0.26543 -4.53808,0.57399 -6.71882,1.29155 -2.40682,0.79019 -4.81647,1.57331 -7.20528,2.41681 -1.80044,0.7305 -3.58032,1.50925 -5.40406,2.1821 -2.00043,0.75722 -4.01242,1.47936 -6.12371,1.87071 -3.75896,0.93132 -7.56641,1.75112 -11.16582,3.21306 -2.93909,1.16982 -5.82135,2.51337 -8.59563,4.03386 -2.57918,1.47872 -5.10173,3.05203 -7.70477,4.49029 -1.81837,1.12195 -3.53915,2.37807 -5.2793,3.6155 -1.32898,0.99565 -2.69698,1.91207 -4.09599,2.80484 -1.43419,0.93394 -3.04899,1.61757 -4.15253,2.98549 -1.3792,1.75296 -2.51107,3.72858 -2.9716,5.9322 -0.42607,6.72358 2.58546,13.89204 13.95641,11.7794 1.70484,-0.52484 3.23516,-1.48185 4.74126,-2.41844 1.50416,-1.05667 2.85238,-2.33425 4.47183,-3.22635 2.17907,-1.14349 4.47115,-1.99876 6.74447,-2.93278 2.80281,-1.1927 5.62687,-2.33761 8.37308,-3.65834 3.94777,-1.99116 7.87216,-4.0231 11.94217,-5.76243 4.31945,-1.89631 8.68307,-3.6801 13.01855,-5.53542 1.95914,-0.9522 3.90805,-1.926 5.82546,-2.96058 0.50397,-0.27193 0.99884,-0.56038 1.49829,-0.84052 0.31277,-0.17543 1.20979,-0.29179 0.93838,-0.52618 -3.74847,-3.2371 -7.92031,-5.94879 -11.88046,-8.92318 -1.22719,1.18526 -4.01355,1.49621 -5.55291,2.18899 -3.43466,1.07096 -6.88468,2.09121 -10.32725,3.13547 -3.67765,0.9652 -7.42888,1.70351 -10.98125,3.09056 -2.69832,1.05992 -5.36095,2.21186 -7.87351,3.66898 -2.70171,1.61585 -5.40577,3.22217 -7.95915,5.07237 -3.26329,2.2977 -6.63032,4.47577 -9.62334,7.12899 -2.43197,2.30605 -4.93949,4.60371 -6.81524,7.40375 -1.324,2.1933 -2.56344,4.41585 -3.58314,6.77064 -0.28262,0.72486 -0.78535,1.96378 -0.70523,2.0225 11.11095,8.14252 9.2911,11.16909 14.84179,7.04588 1.36792,-0.96609 2.68778,-2.00505 4.16026,-2.81433 -4.13289,-2.7284 -8.2024,-5.55535 -12.39869,-8.18521 -0.0147,-0.009 -0.14428,0.25714 -0.49677,0.8256 -0.14461,0.23321 -0.65593,0.52359 -0.4415,0.6948 11.86492,9.47378 8.42352,11.94948 12.84847,8.4416 1.25222,-0.84217 2.34972,-1.89283 3.41529,-2.95596 1.52098,-1.23338 3.20569,-2.25517 4.88718,-3.25333 1.81755,-0.98999 3.586,-2.06616 5.45951,-2.95033 2.66572,-1.38772 5.30731,-2.8214 8.0166,-4.1241 3.77769,-1.99676 7.47864,-4.12937 11.25287,-6.13319 5.2221,-2.75618 10.32081,-5.73451 15.44415,-8.66863 0.5681,-0.30701 1.13636,-0.61371 1.70429,-0.92104 0.39362,-0.213 1.51892,-0.34725 1.18035,-0.63995 -10.78271,-9.32163 -7.87059,-10.07916 -14.60754,-7.84877 -2.67927,0.86748 -5.3831,1.67329 -7.99425,2.73628 -2.62213,1.04815 -5.18008,2.27437 -7.45988,3.96169 -2.62589,1.99186 -5.21853,4.03187 -7.60812,6.30749 -2.7874,2.6763 -5.48264,5.44509 -8.30843,8.07963 -3.19517,2.93828 -6.36993,5.90573 -9.19109,9.21472 -2.77132,3.02267 -5.56044,6.06177 -7.9508,9.39909 -0.21109,0.3984 -0.44372,0.7861 -0.63328,1.19518 -0.17102,0.3691 -0.76758,0.88555 -0.44676,1.13567 8.73674,6.81144 8.79946,9.68501 15.48409,8.02983 2.31854,-0.69686 4.60328,-1.49915 6.91256,-2.22598 1.69473,-0.6526 3.23281,-1.61021 4.7984,-2.5221 1.71744,-0.96571 3.37745,-2.03192 5.0069,-3.13882 1.74325,-1.34381 3.35258,-2.85474 4.85028,-4.46755 1.79202,-1.80288 3.519,-3.67001 5.3695,-5.41483 3.06276,-2.73341 6.23067,-5.31534 9.76881,-7.42471 3.56377,-2.32805 7.04548,-4.81206 10.75925,-6.89388 -13.9328,-9.40918 -8.69904,-10.77058 -16.32658,-5.58395 -2.66559,1.80994 -5.43462,3.49608 -7.86903,5.61743 -3.01558,2.52213 -6.21582,4.80304 -9.21838,7.34065 -3.63424,3.12382 -7.29004,6.24902 -10.5699,9.75083 -2.36345,2.48192 -4.64404,5.04026 -6.25701,8.09181 -0.98025,1.86575 -1.87764,3.74138 -1.49605,5.91216 4.47978,2.60314 8.89349,5.32345 13.43933,7.80943 0.21105,0.11542 1.77486,-0.0717 1.89732,-0.0878 1.46508,-0.19215 2.91168,-0.51867 4.38649,-0.64271 3.31234,-0.38091 6.63266,-0.6784 9.95549,-0.95017 2.84468,-0.29162 5.80106,-0.75883 8.61293,-0.0364 -4.01164,-2.39741 -8.13559,-4.6162 -12.03493,-7.19225 -0.57156,-0.37759 1.09357,-0.4585 0.87991,-0.0213 -0.43108,0.88221 -1.47212,1.41354 -1.93076,2.28225 11.59732,8.66545 9.22916,11.9968 16.51576,6.29329 2.04341,-1.63486 4.03926,-3.33374 5.96812,-5.10217 2.06707,-1.98767 4.24691,-3.85399 6.25376,-5.90532 3.16428,-3.26173 6.1361,-6.70905 9.40961,-9.867 3.39505,-3.19207 6.64747,-6.51931 9.90836,-9.84623 0.25916,-0.27451 3.50217,-3.69601 3.39004,-3.79676 -9.96672,-8.9553 -7.81314,-10.57163 -13.77855,-8.49854 -1.94142,0.70676 -3.84608,1.49186 -5.68861,2.42977 -2.04138,0.89469 -3.9767,2.02298 -5.96838,3.02078 -4.06108,2.16276 -8.23356,4.22156 -12.71629,5.34929 -1.62184,0.34891 -3.11936,0.73218 -4.77701,0.8605 -0.61269,0.0474 -2.33845,-0.37035 -1.84358,-0.006 3.71143,2.73254 7.973,4.64748 11.74493,7.29587 0.25756,0.18084 -0.59442,0.45068 -0.8848,0.32936 -0.20091,-0.0839 0.22928,-0.37022 0.34392,-0.55533 3.31339,-2.409 7.02441,-4.18858 10.62438,-6.12136 1.49899,-0.72828 2.99183,-1.4498 4.55192,-2.04062 0.40737,-0.15427 0.8126,-0.31832 1.23226,-0.43509 0.27664,-0.077 1.09197,0.009 0.84932,-0.1441 -4.08456,-2.58476 -8.36799,-4.841 -12.55199,-7.26149 0.23631,-0.26817 0.46018,-0.54785 0.70893,-0.80451 0.0633,-0.0653 -0.085,0.16444 -0.15015,0.22795 -0.20966,0.20434 -0.43979,0.38689 -0.66838,0.5698 -0.49416,0.39542 -1.39427,1.05108 -1.89772,1.41656 -0.35135,0.25507 -0.70466,0.50742 -1.05699,0.76112 -2.69606,1.9333 -5.52364,3.67906 -8.3446,5.4215 -4.08556,2.2643 -8.25282,4.36573 -12.4297,6.4548 -3.91946,2.15218 -7.81879,4.36688 -11.20893,7.31806 -2.56409,2.24927 -5.13013,4.66762 -6.08973,8.05732 -0.12254,0.45699 -0.29555,0.90336 -0.36763,1.37098 -0.21991,1.42675 0.1583,3.14531 0.98221,4.31256 0.6152,0.57294 0.40284,0.43897 1.13778,0.86491 4.4268,2.56565 9.66767,7.64816 15.51758,8.2066 1.45832,-0.0239 2.92651,-0.0724 4.35816,-0.37589 1.38534,-0.3112 2.76572,-0.60664 4.17151,-0.81405 1.44323,-0.36329 2.89038,-0.69179 4.34497,-1.01042 2.00379,-0.416 4.03855,-0.52536 6.07966,-0.33812 5.72181,0.63545 5.81025,4.57384 -11.08516,-7.05781 -0.29961,-0.20627 0.46661,-0.55834 0.69003,-0.84538 0.0199,-0.0256 0.0697,-0.0944 0.0376,-0.0895 -0.14156,0.0215 -0.2695,0.0969 -0.40425,0.14532 -1.96016,0.55559 -4.04613,0.95541 -6.03614,0.31552 4.10167,2.38174 8.28145,4.63379 12.30502,7.14522 0.18644,0.11637 -0.43218,0.0811 -0.64597,0.1321 -0.13909,0.0332 -0.29574,0.20363 -0.41181,0.12012 -0.14738,-0.10602 0.68335,-1.16831 0.78671,-1.28219 0.79287,-0.96803 1.45595,-2.04923 2.51794,-2.76208 1.18285,-1.00593 2.39933,-1.99452 3.41218,-3.17648 0.54333,-0.72825 1.11914,-1.48461 1.24315,-2.42177 0.72239,-5.45934 -9.27368,-9.76863 -14.60467,-8.23205 -3.72115,1.5986 -6.82861,4.35869 -10.52914,6.02585 -0.79037,0.35684 -2.17456,1.06117 -3.10237,1.21728 -0.22721,0.0382 -0.8739,-0.22497 -0.68489,-0.0932 19.74632,13.76521 9.39646,11.90022 12.61258,7.28013 1.31409,-1.56503 2.64049,-3.12515 4.08106,-4.57625 1.69193,-1.58653 3.36292,-3.19567 5.0329,-4.80455 1.06326,-1.15314 2.25451,-2.16265 3.55446,-3.04299 2.30645,-1.56012 4.65827,-3.0362 7.13059,-4.32514 2.54667,-1.50998 5.18658,-2.83633 7.93981,-3.93423 2.59206,-1.0032 5.24439,-1.8682 8.03951,-2.05215 0.75935,-0.0307 1.59431,-0.11006 2.35351,0.0289 0.2913,0.0533 1.09313,0.43888 0.84307,0.28023 -4.01992,-2.55051 -8.1716,-4.89054 -12.1736,-7.46908 -0.12926,-0.0833 0.30923,-0.13404 0.45394,-0.082 0.0848,0.0305 0.0364,0.17647 0.0545,0.26471 -0.97645,1.73866 -2.26935,3.29571 -3.40716,4.92792 -1.74567,2.58014 -3.58934,5.12616 -4.97015,7.92547 -1.01904,2.04947 -1.90463,4.1522 -2.94347,6.19086 -0.97594,1.65484 -1.31084,2.04841 -3.20104,2.64489 -2.30959,0.69141 -4.70391,1.0518 -7.11217,1.15573 -1.14872,-0.0364 -1.5685,-0.0119 -2.68256,-0.18722 -0.36652,-0.0577 -0.73474,-0.11952 -1.08936,-0.22867 -0.27354,-0.0842 -1.02827,-0.49867 -0.78483,-0.34817 12.1521,7.51284 16.98049,7.11315 11.74075,7.44753 -0.64524,-0.69029 1.54481,-1.79198 1.90769,-2.06977 2.0632,-1.5008 4.23889,-2.83145 6.34049,-4.2778 2.35593,-1.56803 4.81573,-2.99305 7.42314,-4.10393 2.96865,-1.28918 5.94312,-2.576 8.71726,-4.2586 2.65138,-1.54923 0.9872,-0.56128 3.718,-2.22274 1.63477,-0.99462 3.31862,-2.11829 5.12482,-2.79026 0.3178,-0.11823 0.66178,-0.14818 0.99267,-0.22226 -4.06444,-2.69254 -8.12888,-5.38507 -12.19331,-8.07762 -0.0247,-0.0164 0.0948,0.0277 0.0743,0.0492 -0.22712,0.23843 -0.47605,0.45804 -0.74216,0.65199 -1.93532,1.41053 -4.11286,2.46482 -6.15034,3.70855 -5.2118,3.0227 -10.63123,5.78809 -15.33673,9.58923 -1.13626,1.02541 -2.40527,2.04636 -3.20845,3.3799 -0.23462,0.38955 -0.96384,0.95945 -0.59618,1.22707 11.84803,8.6244 8.4217,9.58039 13.95895,7.68437 3.19911,-1.63845 5.94733,-4.08316 8.58378,-6.49278 2.36984,-2.23814 4.80586,-4.40541 7.4576,-6.30866 2.96125,-2.25307 5.63225,-4.7928 7.49983,-8.05914 2.42312,-4.07031 4.73001,-8.20967 7.28048,-12.20339 0,0 -13.04362,-7.64316 -13.04362,-7.64316 z" />
61- <path
62- style="fill:#ffffff"
63- id="path1039"
64- d="m 432.49953,344.24829 c -0.74992,1.88333 -1.06676,4.9053 -2.27069,6.52441 17.7842,9.09169 10.87195,10.15007 14.63577,2.53805 1.10268,-1.69406 2.3453,-3.29642 3.58552,-4.89165 1.1227,-1.38745 2.25262,-2.77369 3.29055,-4.22698 1.32978,-2.11295 2.84636,-4.09666 4.29583,-6.1268 1.30305,-1.75714 2.50915,-3.58428 3.74208,-5.39123 1.56571,-1.86607 2.88779,-3.92042 4.5192,-5.73014 1.88665,-1.74545 3.98152,-3.25993 6.12942,-4.66512 2.9896,-2.09562 6.07519,-4.03418 8.97079,-6.26338 2.78,-1.954 5.25025,-4.28817 8.0846,-6.16612 2.11189,-1.2452 4.15986,-2.59054 6.24117,-3.88429 3.02352,-1.90978 5.76591,-4.21725 8.62645,-6.35471 1.48877,-1.16753 3.03884,-2.25756 4.59377,-3.33481 0.2987,-0.20694 0.56223,-0.49503 0.91007,-0.60017 0.10854,-0.0328 0.32024,0.31389 0.22153,0.25812 -4.34332,-2.4539 -8.64841,-4.97486 -12.97261,-7.46229 -2.62513,4.04578 -5.83026,7.70019 -8.9005,11.41297 -3.66383,4.80372 -8.12475,8.87004 -12.73052,12.74179 -5.91272,4.63036 -8.29371,2.67995 10.5926,8.76614 0.72744,0.23442 0.95585,-1.19331 1.45579,-1.7714 1.41805,-1.63974 2.66603,-2.92715 4.16048,-4.51228 2.07947,-2.31538 4.40674,-4.3784 6.66784,-6.50762 1.84986,-1.74198 3.62603,-3.51822 5.55816,-5.17079 2.44558,-1.96438 4.85201,-4.07114 7.7316,-5.37452 1.77224,-0.39347 3.46115,-1.03221 5.19071,-1.59061 2.31711,-0.81022 4.51877,-2.02167 6.60839,-3.31065 1.39458,-0.88305 2.74941,-1.91401 4.27263,-2.56811 0.19353,-0.1003 0.38287,-0.20912 0.58059,-0.3009 0.59457,-0.27597 0.58438,-0.16429 1.12628,-0.58611 0.45898,-0.35728 0.79797,-0.75022 1.30061,-1.05309 5.14148,-4.96325 -7.95067,-9.73777 -13.28583,-9.85314 -2.48876,0.35892 -4.69207,1.55828 -6.90425,2.67053 -2.3624,0.89905 -3.73647,2.37469 -4.94941,4.5448 -1.13905,2.59111 -2.308,5.17539 -3.35541,7.80576 -1.96526,6.34677 5.52532,5.98779 13.1748,9.82243 0.28349,0.14211 3.63407,-1.76581 4.01264,-1.97846 2.35819,-1.33273 4.68655,-2.71263 6.98133,-4.15154 1.37572,-0.7433 2.66223,-1.6029 3.75923,-2.71805 1.17204,-1.09559 1.77792,-2.49721 2.12557,-4.03109 0.0998,-1.18885 -0.16945,-2.73124 -1.30979,-3.41737 -13.34308,-8.02843 -8.61169,-6.96412 -14.89582,-8.20796 -2.60759,-0.54437 -5.18738,-0.18878 -7.72525,0.49502 -0.65085,0.12605 -1.04631,0.33591 -1.66437,0.0414 -0.177,-0.0843 -0.61222,-0.48509 -0.44157,-0.38856 4.20664,2.42581 8.41328,4.85163 12.61992,7.27744 -0.0556,-0.0879 -0.16461,-0.15979 -0.1668,-0.2638 -0.005,-0.23223 0.098,-0.45412 0.14694,-0.68117 0.55462,-1.72933 1.86737,-2.87785 3.2017,-4.01207 0.89484,-1.03596 2.11945,-1.6709 3.12761,-2.57143 0.86027,-0.47119 1.53234,-0.96433 1.98432,-1.83939 -10.36867,-6.55283 -10.56003,-10.30222 -16.32628,-5.43659 -1.3875,0.80231 -2.64757,1.80472 -3.95489,2.73368 -1.67535,0.85692 -3.39189,1.62876 -5.04528,2.53289 -1.98355,0.85518 -3.9245,1.76609 -5.9385,2.55617 -2.6027,1.25889 -5.19179,2.53692 -7.73292,3.91622 -3.16884,1.42333 -6.13113,3.23939 -9.20069,4.85676 -3.01773,1.63467 -5.94816,3.43762 -8.7288,5.45006 -1.8161,1.09651 -3.20033,2.68211 -4.6919,4.14933 -2.56301,2.52117 0.62871,-0.80843 -1.82713,1.78993 -1.29257,1.17689 -2.33137,2.57077 -3.45106,3.89804 -1.30688,1.49913 -2.76578,2.85213 -4.2452,4.17729 -1.67526,1.40297 -3.27521,2.85848 -4.81531,4.40468 -1.11976,1.32692 -1.82601,2.96903 -2.59642,4.51708 -0.89521,1.49446 -1.60042,3.07987 -2.33327,4.65731 -0.55628,0.99643 -1.16583,1.96813 -1.65121,3.00157 -0.53699,0.94305 -0.94412,1.93177 -1.55569,2.83258 -0.8921,1.16304 -1.39503,2.55479 -2.18277,3.77523 -0.75855,1.14369 -1.22847,2.45654 -1.66572,3.75099 -0.47382,1.2505 -0.77063,2.5524 -1.27452,3.79267 0.0176,-0.0459 0.01,-0.16167 0.0529,-0.13767 15.20424,8.50293 10.5337,11.06607 15.80147,3.6489 2.08695,-2.75768 3.99725,-5.64466 5.97647,-8.48077 1.92629,-2.76638 3.80319,-5.56446 5.65316,-8.38206 1.26769,-2.14829 2.54516,-4.29976 3.72202,-6.4994 0.99127,-1.696 1.86944,-3.43898 2.60941,-5.25851 0.6295,-1.85568 1.18095,-3.73524 1.94295,-5.54209 0.89682,-2.17472 2.06346,-4.22311 3.20854,-6.27422 1.00575,-1.47324 1.84774,-3.08799 2.93752,-4.50437 0.10494,-0.13639 1.21585,-1.33809 1.34628,-1.47981 0.9082,-1.31448 2.09267,-2.36626 3.17242,-3.52376 0.95213,-0.94011 1.9487,-1.7963 2.92104,-2.71484 1.11996,-1.04095 2.26946,-2.03134 3.34625,-3.11929 1.36036,-1.04378 2.73501,-2.1089 4.26504,-2.89286 1.62924,-1.00996 3.42299,-1.69537 5.08542,-2.63746 1.4904,-0.68881 3.08098,-1.11573 4.60632,-1.70803 1.61959,-0.626 3.03985,-1.70627 4.57116,-2.53507 1.26489,-0.46675 2.54065,-0.95411 3.84876,-1.29477 1.16488,-0.23001 2.35876,-0.2251 3.54126,-0.23621 0.64204,0.066 1.31618,-0.0895 1.95561,4.1e-4 0.38998,0.0548 0.54234,0.17649 0.85356,0.35797 -4.23079,-2.43099 -8.46158,-4.86198 -12.69237,-7.29297 -0.23596,0.40922 0.32876,-0.0608 0.27332,0.0117 -0.23121,0.30203 -1.16696,0.73465 -1.48098,0.91793 -1.89828,0.86389 -3.80655,1.66682 -5.80597,2.2709 -2.38683,0.81515 -4.90251,1.25962 -7.18253,2.35472 -1.59845,0.71242 -3.19309,1.38261 -4.74426,2.19516 -2.17518,1.09322 -4.14076,2.57229 -6.05109,4.07209 -1.04757,0.75941 -1.86421,1.78884 -2.84381,2.62126 -0.47353,0.40237 -1.00615,0.72951 -1.50758,1.09652 -2.60957,1.82293 -5.07268,3.85707 -7.44319,5.9788 -2.38247,2.05357 -4.89087,3.94947 -7.26141,6.01746 -1.45747,1.27654 -2.72386,2.74505 -3.98576,4.21007 -1.17894,1.2966 -2.3584,2.58174 -3.34479,4.03392 -0.80492,1.04949 -1.52848,2.15792 -2.2549,3.2618 -0.56841,1.04014 -0.90533,2.19537 -1.26485,3.32241 -0.20666,1.14317 -0.70444,2.17858 -1.1385,3.24297 -0.37682,1.24532 -0.9082,2.43344 -1.34007,3.65859 -0.59614,1.23685 -1.07713,2.51619 -1.55984,3.79939 -0.60561,1.64317 -1.22029,3.28304 -1.72453,4.96068 -0.49888,1.22534 -0.83747,2.50404 -1.31075,3.73744 -0.40444,1.06336 -0.97991,2.04175 -1.55375,3.01889 0,0 13.08691,7.56878 13.08691,7.56878 v 0 c 0.61569,-1.04805 1.22928,-2.09904 1.67579,-3.23392 0.49152,-1.27241 0.84263,-2.58551 1.37098,-3.84669 0.46931,-1.6472 1.08207,-3.24556 1.66603,-4.85454 0.50216,-1.29407 0.94652,-2.60845 1.58598,-3.84657 0.4,-1.21115 0.96582,-2.36112 1.30393,-3.59348 0.42896,-1.10903 1.01951,-2.16153 1.20003,-3.35429 0.28527,-0.99865 0.6179,-2.00144 1.04898,-2.94116 0.72768,-1.09373 1.44377,-2.19601 2.24241,-3.24048 0.96693,-1.3969 2.13628,-2.61995 3.25929,-3.89028 1.2158,-1.40982 2.45249,-2.8098 3.87076,-4.02235 2.38823,-2.05378 4.89907,-3.95769 7.27043,-6.03197 2.36178,-2.07152 4.79443,-4.06893 7.37306,-5.86698 1.54036,-1.15559 2.87935,-2.51821 4.3653,-3.73979 1.87881,-1.41018 3.76354,-2.83918 5.90154,-3.83977 1.53809,-0.76523 3.13304,-1.40357 4.69438,-2.11969 2.31022,-0.90327 4.75639,-1.41584 7.09887,-2.23031 2.062,-0.71132 4.09072,-1.52659 6.00735,-2.575 1.15677,-0.75487 2.31614,-1.57824 2.36727,-3.09574 -11.84239,-7.15983 -8.79952,-8.19681 -16.23653,-7.94552 -1.27385,0.0591 -2.55519,0.15442 -3.77266,0.56575 -1.36356,0.46811 -2.76869,0.85036 -4.03011,1.56676 -1.44448,0.83593 -2.83281,1.8206 -4.43538,2.33025 -1.60897,0.54133 -3.21588,1.11756 -4.72908,1.89291 -1.7058,0.87233 -3.44756,1.67232 -5.09656,2.65452 -1.58254,0.9473 -3.11251,2.01939 -4.48952,3.24809 -1.07479,1.06783 -2.23755,2.0338 -3.32634,3.0869 -0.97659,0.93843 -2.02953,1.78709 -2.95848,2.77592 -1.11653,1.14012 -2.228,2.27157 -3.18959,3.55198 -1.73615,1.91381 -3.12213,4.07803 -4.46152,6.28494 -1.17125,2.09783 -2.36412,4.19304 -3.28797,6.41396 -0.77999,1.81247 -1.36826,3.69143 -1.96005,5.57265 -0.69501,1.77895 -1.57036,3.46126 -2.52724,5.11323 -1.14995,2.16186 -2.41205,4.27023 -3.65327,6.38065 -1.84142,2.80594 -3.71367,5.58986 -5.63123,8.34468 -1.97876,2.83242 -3.8863,5.71663 -5.9713,8.47202 -5.09974,7.21931 -3.61585,3.58041 10.14902,11.86734 0.10559,0.0636 0.13753,-0.20454 0.2063,-0.30682 0.54562,-1.27233 0.85593,-2.60811 1.33519,-3.90196 0.366,-1.18393 0.79847,-2.37723 1.47685,-3.41749 0.76962,-1.22092 1.29148,-2.58159 2.16466,-3.74426 0.64823,-0.97619 1.10877,-2.02653 1.6781,-3.04528 0.45874,-1.04165 1.10992,-1.99296 1.6475,-2.99498 0.7231,-1.56766 1.42248,-3.13956 2.31322,-4.62198 0.69376,-1.40359 1.32111,-2.91229 2.35041,-4.10035 1.51226,-1.5356 3.15699,-2.90965 4.77261,-4.33535 1.51521,-1.38933 3.03002,-2.786 4.35719,-4.36096 1.07589,-1.31874 2.15268,-2.63092 3.39427,-3.80416 1.98924,-2.04024 3.98674,-4.10902 6.36889,-5.69823 2.76588,-1.9319 5.63882,-3.7109 8.62332,-5.28656 3.05605,-1.64619 6.06258,-3.38186 9.20471,-4.86291 2.50272,-1.33987 5.04539,-2.60674 7.63563,-3.76923 2.04517,-0.82102 4.03382,-1.76494 6.03055,-2.69514 1.72296,-0.88646 3.50857,-1.66371 5.15022,-2.6955 1.33179,-0.93952 2.63362,-1.91845 4.00061,-2.80598 5.75132,-4.27305 6.41889,-0.18499 -9.91136,-9.53506 0.0988,-0.15669 0.22735,-0.29822 0.29653,-0.47009 0.0309,-0.0768 -0.11478,0.11966 -0.17731,0.17392 -0.45656,0.39618 -1.06226,0.60574 -1.48578,1.04994 -1.07663,0.82836 -2.19514,1.6074 -3.14048,2.59206 -1.53745,1.34942 -2.95155,2.80683 -3.65012,4.78163 -0.0188,0.0593 -0.65643,1.94397 -0.51994,2.04078 7.75805,5.50234 9.22853,9.3143 15.73862,7.79935 2.42682,-0.47812 4.85872,-0.64526 7.24894,0.15555 5.24405,2.34611 1.12226,0.54529 -11.09364,-6.66691 -0.24934,-0.14721 0.51196,0.27175 0.75737,0.42541 0.62106,0.38887 1.13479,0.73189 1.24929,1.49492 -0.19447,1.28451 -0.67305,2.36229 -1.67066,3.23761 -1.0601,0.99375 -2.34252,1.69237 -3.58241,2.43713 -2.28455,1.40297 -4.58146,2.78316 -6.93152,4.07482 -0.6401,0.33075 -1.7557,0.97424 -2.50893,1.12733 -0.29072,0.0591 -1.13609,-0.18506 -0.88979,-0.0197 3.99496,2.68242 8.18171,5.06786 12.24564,7.64459 0.11606,0.0736 -0.36688,0.007 -0.3917,-0.1286 -0.0517,-0.28183 0.14352,-0.55483 0.21528,-0.83225 1.0086,-2.57511 2.17547,-5.08853 3.25754,-7.633 1.14069,-2.06393 2.33352,-2.87066 4.47439,-3.77341 1.99682,-0.99435 4.09811,-2.12487 6.39726,-1.94691 5.54739,1.31252 -1.16486,-0.19796 -11.70509,-7.15668 -0.22944,-0.15148 1.03266,-0.87795 0.47926,-0.42008 -0.1142,0.0945 -0.23753,0.17734 -0.3563,0.26601 -0.91035,0.69602 -1.797,1.34558 -2.88727,1.75467 -1.48835,0.81922 -2.91672,1.74573 -4.3402,2.6716 -2.04124,1.18747 -4.14532,2.33613 -6.41512,3.0194 -1.78134,0.56187 -3.56442,1.13378 -5.31489,1.77606 -2.89473,1.52569 -5.43227,3.61724 -7.94047,5.7005 -4.2817,3.73703 -8.32405,7.76259 -12.30794,11.8115 -1.41228,1.50514 -2.88298,3.03195 -4.21881,4.60891 -0.54545,0.64392 -2.16213,1.37043 -1.57814,1.97962 10.37811,10.8258 9.09373,11.67867 15.32582,6.79575 4.65474,-3.95024 9.15169,-8.10228 12.87166,-12.97238 3.18286,-3.85479 6.53093,-7.63942 9.17583,-11.89157 -4.19727,-2.86373 -8.27973,-5.90343 -12.59181,-8.59118 -0.077,-0.048 -1.09652,0.65829 -1.56463,0.98599 -1.5592,1.09152 -3.11333,2.19347 -4.61332,3.36563 -2.81974,2.1259 -5.54732,4.39017 -8.5582,6.24779 -2.08506,1.3181 -4.17789,2.62318 -6.27025,3.9296 -2.81241,1.94701 -5.33289,4.26353 -8.12848,6.23518 -2.89927,2.17261 -5.95114,4.11376 -8.92117,6.18537 -2.22251,1.51205 -4.43203,3.08505 -6.33475,4.9951 -1.64219,1.88227 -3.03883,3.95761 -4.61661,5.89356 -1.22912,1.79711 -2.42749,3.61668 -3.72627,5.3644 -1.44811,2.02933 -2.9611,4.01363 -4.29362,6.12305 -1.03598,1.43422 -2.15766,2.80499 -3.26514,4.18412 -1.28435,1.68579 -2.61731,3.34228 -3.73394,5.1467 -0.61541,1.23637 -1.10338,2.39149 -1.42745,3.7376 -1.42213,5.90724 -0.30903,0.87064 12.96295,8.86478 1.37512,-2.07493 1.70669,-4.65994 2.52212,-6.96893 0,0 -13.28274,-7.2196 -13.28274,-7.2196 z" />
65- <path
66- style="fill:#ffffff"
67- id="path1041"
68- d="m 444.95379,346.65456 c 1.06642,-2.60177 2.26717,-5.1388 3.53885,-7.64477 0.80062,-1.72005 1.70753,-3.33379 2.78034,-4.89383 0.99874,-1.25158 1.79112,-2.64198 2.78939,-3.89223 0.97549,-1.20036 1.66709,-2.60021 2.6938,-3.75713 1.13014,-1.5117 1.88641,-3.2593 2.68308,-4.96014 0.7471,-1.60342 1.60226,-3.14777 2.40761,-4.72157 1.18872,-1.66559 2.27151,-3.4063 3.48455,-5.05396 0.96809,-1.31722 1.78836,-2.73014 2.77318,-4.03489 0.90823,-1.43352 1.91835,-2.76684 3.06487,-4.0174 1.43521,-1.23262 2.86359,-2.45013 4.14673,-3.84264 1.10736,-0.88478 2.1079,-1.91275 3.22743,-2.78438 1.26625,-0.92328 2.40104,-2.02565 3.74382,-2.83749 1.36021,-0.84114 2.47965,-1.99587 3.75402,-2.93834 1.17313,-0.82096 2.1014,-1.91914 3.19251,-2.83265 0.96511,-1.06249 1.9825,-2.0389 3.15071,-2.87458 1.46588,-1.3037 3.03026,-2.5058 4.41761,-3.8948 1.26476,-0.99496 2.67815,-1.75513 3.97292,-2.70979 1.23962,-0.91909 2.67716,-1.37512 4.09945,-1.89879 1.53746,-0.34526 2.98,-0.81959 4.436,-1.41426 0.95752,-0.25952 2.32209,-0.8001 3.33156,-0.62574 0.20945,0.0362 0.38685,0.17626 0.58027,0.26439 -4.20114,-2.33375 -8.40228,-4.6675 -12.60343,-7.00126 0,0 13.96868,5.78186 13.96868,5.78186 v 0 c -4.4863,-2.61747 -8.9726,-5.23493 -13.4589,-7.8524 -1.51335,0.0943 -2.98338,0.40961 -4.37883,1.01983 -1.4393,0.56639 -2.96156,0.82534 -4.40858,1.37347 -1.5382,0.55268 -3.00033,1.2792 -4.32591,2.24462 -1.37284,0.95626 -2.85935,1.75693 -4.09307,2.90202 -1.38146,1.30479 -2.85379,2.51476 -4.31435,3.72921 -1.15751,0.90413 -2.18874,1.92435 -3.21309,2.97405 -1.03083,0.94401 -2.01396,1.93579 -3.14828,2.76194 -1.19514,0.98762 -2.37284,1.99266 -3.68464,2.82915 -1.32344,0.92776 -2.55283,1.98338 -3.82992,2.9733 -1.12752,0.96567 -2.25289,1.94069 -3.33352,2.95626 -1.30383,1.37931 -2.79466,2.54328 -4.15785,3.86097 -1.16319,1.29631 -2.19199,2.67816 -3.13817,4.13963 -0.9771,1.31712 -1.81169,2.72753 -2.78204,4.04984 -1.24707,1.72053 -2.39712,3.51107 -3.60412,5.25971 -0.815,1.58975 -1.6765,3.15157 -2.43517,4.77007 -0.73319,1.57868 -1.42437,3.21086 -2.47717,4.60741 -0.99785,1.20674 -1.75357,2.57945 -2.72447,3.80831 -1.00142,1.28731 -1.83229,2.69059 -2.83871,3.9755 -1.1137,1.6144 -2.04714,3.29226 -2.88163,5.06874 -1.2825,2.52926 -2.50723,5.08361 -3.57702,7.71214 0,0 13.17149,7.42062 13.17149,7.42062 z" />
69- <path
70- style="fill:#ffffff"
71- id="path1043"
72- d="m 47.723738,302.48814 c -0.235582,2.57035 -0.413918,5.14543 -0.527844,7.72396 -0.04401,1.66877 -0.637615,3.23133 -0.971978,4.84849 -0.49334,2.0254 -0.803411,4.07474 -0.962727,6.15115 -0.22051,2.38235 -0.149823,4.7319 0.269472,7.08396 0.481444,2.35269 0.997451,4.69608 1.45286,7.05476 0.457995,2.16877 0.573854,4.38236 0.790824,6.58142 0.233663,1.60072 0.456482,3.20102 0.57548,4.81477 0.08973,1.43491 0.08843,2.87328 0.09188,4.31034 -0.0031,5.94595 -0.872531,1.79721 13.666195,7.35427 0.230286,0.088 0.424137,-2.99784 0.437609,-3.20433 0.09063,-2.46503 0.06323,-4.93249 0.04974,-7.39861 -0.08329,-2.38251 0.008,-4.7492 0.31058,-7.11289 0.318125,-3.12377 1.109164,-6.1645 1.882127,-9.19858 0.603361,-2.38574 1.199296,-4.775 1.831907,-7.15334 0.5698,-2.1975 1.009163,-4.42888 1.423266,-6.66069 0.356849,-2.02356 0.443261,-4.078 0.494757,-6.12769 0.10103,-1.42529 0.383836,-2.82762 0.53435,-4.24816 -0.0029,-1.13487 0.180083,-2.1682 0.603434,-3.21105 0.321111,-0.5869 0.15321,-0.2786 0.503328,-0.92512 0,0 -13.113313,-7.52296 -13.113313,-7.52296 v 0 c -0.373806,0.69103 -0.177993,0.33257 -0.588169,1.07497 -0.466058,1.17181 -0.891486,2.30042 -0.810581,3.59475 -0.118838,1.46005 -0.383281,2.8963 -0.58493,4.34604 -0.03272,1.97659 -0.0796,3.95891 -0.384361,5.91626 -0.38509,2.19498 -0.834288,4.38156 -1.361672,6.54616 -0.65643,2.37946 -1.223845,4.78566 -1.843449,7.17462 -0.780965,3.08634 -1.636353,6.16979 -1.961447,9.34711 -0.358177,2.4239 -0.514696,4.85383 -0.409938,7.30476 0.01422,2.44019 0.03976,4.88083 -0.0099,7.32079 -0.02942,0.76513 -0.03695,1.29897 -0.112712,2.05085 -0.02471,0.24528 -0.307475,0.60077 -0.09933,0.73287 4.278007,2.71486 8.786753,5.0478 13.181546,7.56923 0.0572,0.0328 -0.153475,-0.0392 -0.168712,-0.10336 -0.04652,-0.19592 4.77e-4,-0.40274 7.16e-4,-0.60411 -7.7e-5,-1.46409 0.0069,-2.92885 -0.05054,-4.3921 -0.08299,-1.64226 -0.248525,-3.27751 -0.534015,-4.89805 -0.334753,-2.2089 -0.271582,-4.47121 -0.7496,-6.66552 -0.430658,-2.36726 -0.924448,-4.71652 -1.449337,-7.062 -0.472671,-2.26866 -0.697763,-4.4124 -0.444127,-6.73588 0.115274,-2.02246 0.364088,-4.02381 0.877321,-5.98869 0.294207,-1.70188 0.964594,-3.3091 1.075721,-5.04646 0.09428,-2.55627 0.265712,-5.10876 0.493001,-7.65667 0,0 -13.40746,-6.98527 -13.40746,-6.98527 z" />
73- <path
74- style="fill:#ffffff"
75- id="path1045"
76- d="m 638.45033,529.45411 c 0.32043,-1.43279 0.87109,-2.79362 1.51319,-4.10875 0.49898,-1.07666 1.0692,-2.11787 1.58949,-3.18392 0.3604,-0.79469 0.72883,-1.65852 0.63073,-2.55458 -0.0213,-0.19465 -0.0803,-0.38329 -0.12051,-0.57493 -12.05402,-7.29915 -8.97396,-7.82949 -16.48178,-7.87355 -1.51954,-0.003 -3.03908,0.007 -4.55861,0.0125 -0.43502,-0.13711 -1.85991,0.26465 -2.19604,0.10033 -0.0753,-0.0368 -0.16683,-0.27616 -0.0951,-0.23283 4.27548,2.58135 8.49877,5.24815 12.74816,7.87222 -0.0323,0.11502 -0.19798,0.28158 -0.0968,0.34508 0.11127,0.0698 0.23042,-0.12621 0.34594,-0.18875 0.82367,-0.44601 1.64573,-0.88641 2.48418,-1.30455 1.04859,-0.43593 2.07827,-0.92742 3.18607,-1.19426 0.1154,-0.0333 1.46149,-0.43299 1.45532,-0.21803 -0.003,0.12184 -0.0928,0.22543 -0.13915,0.33815 -15.99901,-11.90341 -7.73872,-9.62675 -14.63806,-7.60889 -1.12229,0.32922 -2.27318,0.57267 -3.42629,0.77165 -0.8597,0.13995 -1.69864,0.34359 -2.57144,0.3832 -0.54215,-0.0101 -1.09515,0.074 -1.62901,-0.0558 -0.24961,-0.0607 -0.48864,-0.15896 -0.73526,-0.23084 -0.0894,-0.0566 -0.19053,-0.098 -0.26821,-0.16984 -0.0706,-0.0653 -0.25175,-0.28213 -0.16846,-0.23411 4.25969,2.45608 8.54644,4.87008 12.7194,7.47078 0.0386,0.0241 -0.62896,0.70858 -0.14215,0.34641 0.41198,-0.21773 0.79311,-0.50185 1.21319,-0.7044 0.241,-0.1162 0.49677,-0.19887 0.74453,-0.29986 0.64051,-0.18906 1.25438,-0.4587 1.9201,-0.54617 0.53439,-0.0211 1.07056,0.004 1.60554,0.0114 0.66172,0.20645 1.36743,0.21542 2.0551,0.24269 0.2059,0.0384 0.41918,-0.0195 0.62534,0.0116 0.0937,0.0141 0.17952,0.0618 0.27192,0.0828 0.32123,0.0728 0.65864,0.0923 0.98578,0.12092 0,0 -12.12893,-9.02458 -12.12893,-9.02458 v 0 c -0.64258,-0.0911 -1.27712,-0.13195 -1.92456,-0.15915 -0.70608,-0.0683 -1.41657,-0.216 -2.12625,-0.14532 -0.60794,0.0224 -1.21803,0.0643 -1.79888,0.26164 -0.70334,0.23112 -1.4064,0.47243 -2.0796,0.78154 -0.8134,0.39578 -1.61239,0.85829 -2.26694,1.48977 -4.40189,5.22506 7.05828,9.21158 12.81615,9.58956 0.87601,0.1034 1.76219,0.0157 2.63569,-0.0834 0.88485,-0.14403 1.76111,-0.3377 2.64498,-0.48765 1.20051,-0.2558 2.40354,-0.52668 3.55497,-0.95904 7.22662,-2.97311 -0.75811,-4.03385 -9.72884,-10.2551 -0.10392,0.0865 -0.19775,0.18687 -0.31174,0.25961 -0.14271,0.0911 -0.37549,0.11873 -0.53047,0.15979 -0.37663,0.0998 -0.74429,0.23119 -1.11935,0.33636 -1.13859,0.40564 -2.2512,0.88549 -3.336,1.41811 -0.68468,0.35565 -1.37148,0.71028 -2.0284,1.11615 -0.26605,0.16437 -0.84138,0.52704 -1.05722,0.77979 -0.20267,0.23735 -0.35044,0.51655 -0.52566,0.77483 4.25429,2.7014 8.45898,5.48255 12.76287,8.1042 0.1058,0.0645 0.23191,-0.0911 0.35356,-0.11452 0.71214,-0.1371 1.45642,-0.0262 2.17386,-0.0697 1.50973,-0.005 3.02005,-0.019 4.52929,0.0278 0.63448,0.0341 1.28759,0.049 1.90849,0.20211 0.17207,0.0424 0.34359,0.0927 0.50543,0.16496 0.13833,0.0617 0.51975,0.31137 0.38936,0.2343 -4.17013,-2.46505 -8.33904,-4.93215 -12.50856,-7.39822 0.13533,-0.0252 0.26836,-0.0779 0.406,-0.0756 0.69198,0.0116 0.0239,1.25024 -0.0802,1.48859 -0.51442,1.06625 -1.09068,2.1018 -1.58591,3.17763 -0.69439,1.40539 -1.30774,2.85802 -1.66838,4.38859 0,0 13.29807,7.19133 13.29807,7.19133 z" />
775 </g>
786</svg>
public/img/koboldhorde.svg+5 -46
@@ -1,48 +1,7 @@
11<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-<svg
2+<svg id="svg8" width="431.92pt" height="430.69pt" preserveAspectRatio="xMidYMid" version="1.0" viewBox="0 0 431.92 430.69" xmlns="http://www.w3.org/2000/svg">
3- version="1.0"
3+ <g id="g3" transform="matrix(.1 0 0 -.1 -40.075 470.79)">
4- width="431.92484pt"
4+ <path id="path1" d="m2495 4698c-9-97-8-195-10-293-65-14-170 12-229-19 45-55 96-105 146-156-3-83-5-166-7-249-102-101-201-211-322-290-27-14-54-3-89 38-65 77-439-60-369 189 45 119-53 28-91-8-51 115-151 211-246 133-124-105-64-173-5-231 48-64 103-62 174-62 48-54 96-107 145-160 66 0 107 14 143-53 37-63 87-117 135-172-289 13-616-97-827 146-162 210-213 129-213 111 0-155 182-121 344-360 77-118 166-205 166-240-69-128-143-254-212-382h-159c-6 53 16 115-2 166-102-50-248-79-340-135-92-64-208-14-221-64-13-48-15-47 157-47 156 0 311-7 467-13v-77h561c52 44 105 87 166 116 87 39 78 44-89 45-145 2-166 2-503 14 42 19 81 47 127 55 43 0 115 188 171 225 56 21 118 23 177 28 56-38 107-85 177-91 31-3 53 0 53 5-53 72-127 130-198 184l-4 90c-5 123-28 104-28 121 210 14 425 14 637 21-58 55-127 86-127 175 0 72-30 121-30 128 0 2 28 4 61 4 68-7 111 42 159 82v139l55 51c106 98 95 132 95-292v-371c-30-28-59-56-89-84l-2-194c-2-219-6-209 76-210 22-1 50-8 62-16 18-13 23-13 30-2 5 9 33 16 71 20 35 3 66 8 69 11 6 6 10 155 16 393-31 21-62 43-93 64 3 251-4 502-13 753 60-51 142-89 150-174 7-89 10-96 65-134 44-37 95-36 149-36-12-68-15-137-20-205-37-35-78-66-111-105 0-22 2 19 629-12-23-39-28-82-29-126 0-85-1-86-161-228-66-58-118-111-116-118 9-27 130 16 213 76 45 32 47 33 164 34l118 2c46-83 124-154 157-245 0-47-496-7-555-43 81-49 166-97 246-150h632l18 75c20-2 119 19 109-12-20-51-3-96-55-148-40-41-33-57 13-30 32 20 119 49 242 90 9 34 6 78 29 107 12 8 86 8 194 8 4 12 22 62 22 65 0 2-46 4-103 7-194 6-120 46-241 79-97 26-138 76-178 81-44 11-38-118-41-152h-178c-47 93-87 190-141 279-88 140 63 263 142 365 149 210 291 154 331 366-217 0-138-117-381-269-71-42-690-15-690-12 70 58 140 211 217 211 110 0 171 181 256 166 107-20 302 172 135 294-157 116-226-120-245-120-9 0-60 37-87 42-17 3-19-3-17-57 6-274-307-57-389-190-12-19-29-35-38-35-17 0-405 306-429 337-18 45-13 96-13 143h89c53 0 93 4 96 11 17 26-154 157-166 161-21 7-9 351-19 361-2 2-126 29-135 5z"/>
5- height="430.70734pt"
5+ <path id="path3" d="m2160 2555c-33-28-66-57-99-85h-291v-220c-128 0-96-128-264-96-55 11-143 173-159 203-19 41-119 33-154 30-6-12 122-252 133-313 15-96-132-130-195-242-71-133-240-162-302-284-36-70-25-84 51-64 48 12 64 25 154 118 149 155 126 148 509 148 174 0 317-4 317-8-60-67-112-142-162-217-114 4-177-83-246-158-101 14-134 18-207-65-166-201 173-364 249-110 15 49 125-85 110 18-4 24-16 149 202 141 134-3 172 4 180 35 13 52 26 48 147-43 117-90 232-183 347-276v-114c-58-3-122-7-177-17-3-3 26-33 63-68 136-125 121-89 123-288 1-96 4-176 6-179 114 63 135 89 135 210v109c78 11 169-18 244 9-43 65-109 118-166 172-24 285 101 312 274 482 81 84 115 36 169-32l291-6c34-35 60-64 56-116-12-117 80 11 122-61 123-230 398-46 238 129-66 74-63 73-127 73-88 2-151 93-211 150h-79c-93 0-94 3-111 83-25 121-96 113-50 137 14 8 116 10 328 8 355-3 350-2 442-101 150-161 142-154 194-161 138-22 24 143-103 180-112 52-233 185-363 398 58 114 136 224 172 347 0 10-40 6-112-12-75-20-186-386-387-204-48 42-86 37-94 107-8 55-7 111-7 166-106 3-213 5-319 7l-78 76c-6 74-9 151-18 227-18 20-336-347-376-369-60-30-362 398-379 371-15-25-20-173-20-225zm394-315c12 0 229 220 244 220 16 0 72-75 72-96 0-18-290-304-308-304-11 0-326 287-330 301-4 10 41 75 65 95 23 20 239-216 257-216zm-751-149c84-11 197-15 284-16 140-120 271-251 403-380-10-256-8-257-19-505 0-5-57 42-136 106-8 53-16 107-24 160-89 89-191 52-182 84 3 11 9 61 20 175 54 50 55 51 96 104-12 21-352-1-676 26 51 48 91 72 91 148-4 72 60 129 120 187 7 7 17-43 23-89zm1647-23c1-67-10-140 20-186 6-15 39-17 36-31-5-9-121-15-294-23-146-138-294-274-447-404-8-49 3-111-29-153-36-39-125-103-136-96-8 6-10 64-10 502 141 137 282 273 430 403 299-9 306-19 310 51 16 48 96-42 120-63z"/>
6- viewBox="0 0 431.92484 430.70734"
7- preserveAspectRatio="xMidYMid"
8- id="svg8"
9- sodipodi:docname="koboldhorde.svg"
10- inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
11- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
13- xmlns="http://www.w3.org/2000/svg"
14- xmlns:svg="http://www.w3.org/2000/svg">
15- <defs
16- id="defs12" />
17- <sodipodi:namedview
18- id="namedview10"
19- pagecolor="#ffffff"
20- bordercolor="#000000"
21- borderopacity="0.25"
22- inkscape:showpageshadow="2"
23- inkscape:pageopacity="0.0"
24- inkscape:pagecheckerboard="0"
25- inkscape:deskcolor="#d1d1d1"
26- inkscape:document-units="pt"
27- showgrid="false"
28- inkscape:zoom="1.5043945"
29- inkscape:cx="237.63713"
30- inkscape:cy="285.82928"
31- inkscape:window-width="2560"
32- inkscape:window-height="1377"
33- inkscape:window-x="5112"
34- inkscape:window-y="-8"
35- inkscape:window-maximized="1"
36- inkscape:current-layer="svg8" />
37- <g
38- transform="matrix(0.1,0,0,-0.1,-40.075165,470.78832)"
39- stroke="none"
40- id="g6">
41- <path
42- d="m 2495,4698 c -3,-7 -6,-76 -7,-153 l -3,-140 -115,-5 c -63,-3 -114,-9 -114,-14 1,-4 34,-41 74,-82 l 72,-74 -4,-125 -3,-124 -65,-67 c -74,-75 -224,-205 -257,-223 -27,-14 -54,-3 -89,38 -22,26 -24,26 -156,26 -132,0 -135,0 -172,28 -52,40 -60,67 -41,135 9,30 14,56 11,59 -7,7 -47,-16 -77,-43 l -25,-24 -18,41 c -10,23 -39,56 -68,80 -63,49 -110,53 -160,12 -58,-49 -67,-65 -68,-118 0,-49 2,-53 63,-113 62,-61 63,-62 118,-62 h 56 l 72,-80 73,-80 h 51 c 61,0 66,-3 92,-53 11,-21 46,-69 77,-105 l 58,-67 -320,-3 -321,-2 -55,26 c -47,22 -67,42 -131,125 -79,102 -128,139 -186,139 -23,0 -27,-4 -27,-28 0,-61 91,-163 177,-198 64,-27 97,-59 167,-162 36,-55 75,-107 86,-117 24,-22 80,-108 80,-123 0,-6 -15,-35 -34,-64 -18,-29 -66,-113 -106,-185 l -72,-133 h -80 -79 l 2,80 c 1,44 0,83 -4,86 -3,3 -31,-8 -63,-24 -67,-33 -197,-82 -220,-82 -9,0 -34,-13 -57,-29 -36,-25 -55,-30 -129,-36 -77,-6 -87,-9 -92,-28 -13,-48 -15,-47 157,-47 90,0 232,-3 315,-7 l 152,-6 v -39 -38 h 281 280 l 57,47 c 31,25 80,56 109,69 87,39 78,44 -89,45 -84,1 -232,4 -328,8 l -175,6 55,28 c 30,15 63,27 72,27 16,0 58,44 58,61 0,17 92,150 113,164 12,7 57,17 100,21 l 77,7 62,-43 c 49,-35 72,-45 115,-48 31,-3 53,0 53,5 0,11 -93,102 -156,152 l -42,32 -4,90 c -2,50 -9,94 -16,101 -7,7 -12,16 -12,20 0,4 56,8 123,8 68,0 212,3 319,7 l 195,6 -56,50 c -72,65 -71,63 -71,125 0,29 -7,69 -15,89 -8,19 -15,37 -15,39 0,2 28,4 61,4 57,0 65,3 110,41 l 49,41 v 69 70 l 55,51 c 106,98 95,132 95,-292 v -371 l -45,-42 -44,-42 -2,-194 c -2,-219 -6,-209 76,-210 22,-1 50,-8 62,-16 18,-13 23,-13 30,-2 5,9 33,16 71,20 35,3 66,8 69,11 3,3 8,93 11,199 l 5,194 -47,32 -46,32 v 202 c 0,111 -3,281 -7,377 l -6,174 39,-31 c 107,-86 106,-85 111,-143 7,-89 10,-96 65,-134 43,-29 62,-36 101,-36 h 48 l -5,-32 c -3,-18 -8,-64 -10,-103 l -5,-70 -56,-50 c -30,-28 -55,-53 -55,-55 0,-3 78,-3 172,-2 95,2 237,1 315,-3 l 142,-7 -14,-29 c -9,-17 -15,-56 -15,-97 0,-85 -1,-86 -161,-228 -66,-58 -118,-111 -116,-118 9,-27 130,16 213,76 45,32 47,33 164,34 l 118,2 78,-114 c 44,-62 79,-122 79,-131 0,-16 -18,-19 -205,-24 -113,-3 -238,-7 -277,-8 -40,0 -73,-5 -73,-11 0,-5 21,-18 48,-30 26,-12 81,-43 123,-70 l 75,-50 h 316 316 l 9,38 9,37 58,3 c 53,3 58,1 51,-15 -4,-10 -11,-39 -15,-66 -5,-35 -15,-57 -40,-82 -40,-41 -33,-57 13,-30 18,11 79,35 137,55 l 105,35 9,50 c 5,28 14,53 20,57 6,4 52,8 103,8 h 91 l 11,31 c 6,18 11,33 11,34 0,2 -46,4 -103,7 -96,3 -106,5 -152,36 -27,18 -67,37 -89,43 -44,12 -112,44 -136,64 -8,7 -27,15 -42,17 -27,5 -27,5 -34,-73 l -7,-79 h -89 -89 l -57,118 c -31,64 -69,137 -84,161 -22,36 -28,57 -28,102 0,56 2,60 54,116 29,32 81,98 116,147 49,69 76,96 122,125 138,84 191,138 202,204 l 7,37 h -41 c -59,0 -88,-19 -159,-104 -65,-78 -120,-127 -181,-165 -34,-20 -46,-21 -362,-21 -192,0 -328,4 -328,9 0,5 19,24 42,42 22,18 61,64 85,101 44,68 44,68 90,68 44,0 49,3 134,86 84,82 89,86 122,80 49,-9 84,7 145,67 50,49 52,53 52,105 0,65 -10,84 -62,122 -61,45 -119,42 -165,-6 -18,-20 -43,-54 -54,-75 -10,-21 -22,-39 -26,-39 -3,0 -20,9 -37,19 -17,11 -40,21 -50,23 -17,3 -19,-3 -17,-57 1,-46 -3,-68 -19,-93 -31,-51 -68,-61 -191,-55 -131,7 -152,2 -179,-42 -12,-19 -29,-35 -38,-35 -17,0 -405,306 -429,337 -7,10 -13,45 -13,80 v 63 h 89 c 53,0 93,4 96,11 4,6 -4,22 -16,36 -32,34 -134,120 -150,125 -9,3 -13,49 -13,180 -1,97 -3,178 -6,181 -3,3 -33,8 -67,12 -49,5 -64,4 -68,-7 z"
43- id="path2" />
44- <path
45- d="m 2166,2764 c -3,-9 -6,-59 -6,-112 v -97 l -49,-42 -50,-43 h -145 -146 v -110 -110 h -25 c -17,0 -43,-16 -75,-46 -53,-49 -96,-63 -164,-50 -30,6 -42,18 -89,89 -30,45 -61,97 -70,114 -16,30 -20,31 -83,35 -36,1 -68,0 -71,-5 -2,-4 25,-67 61,-140 36,-72 68,-150 72,-173 6,-38 3,-44 -33,-80 -21,-21 -62,-58 -91,-82 -29,-24 -61,-60 -71,-80 -26,-49 -106,-125 -161,-152 -56,-28 -116,-83 -141,-132 -36,-70 -25,-84 51,-64 48,12 64,25 154,118 149,155 126,148 509,148 174,0 317,-4 317,-8 0,-5 -14,-21 -31,-38 -17,-16 -53,-63 -81,-104 l -50,-75 -57,-6 c -54,-7 -58,-9 -122,-79 l -67,-73 -58,6 c -71,8 -88,0 -149,-71 -34,-41 -45,-62 -45,-88 0,-51 23,-98 64,-129 31,-24 43,-27 85,-23 73,7 121,50 145,130 5,16 8,16 48,-2 63,-29 69,-27 62,20 -6,34 -2,45 20,75 48,63 67,70 182,66 134,-3 172,4 180,35 13,52 26,48 147,-43 63,-49 167,-130 231,-182 l 116,-94 v -57 -57 l -86,-6 c -48,-3 -89,-8 -91,-11 -3,-3 26,-33 63,-68 136,-125 121,-89 123,-288 1,-96 4,-176 6,-179 3,-2 22,12 44,32 22,19 51,43 65,52 25,17 26,20 26,126 v 109 h 119 c 66,0 122,4 125,9 3,5 -33,46 -80,90 l -86,82 7,109 c 8,130 17,146 140,256 43,38 100,90 127,117 64,61 94,63 137,8 l 32,-40 145,-3 146,-3 30,-34 c 27,-30 30,-39 26,-82 -5,-48 -4,-49 21,-48 14,1 40,4 56,8 26,5 32,2 45,-21 8,-15 32,-46 53,-68 35,-36 44,-40 86,-40 38,0 57,7 90,30 45,33 61,63 61,116 0,27 -11,46 -52,91 -66,74 -63,73 -127,73 -53,0 -55,1 -132,75 l -79,75 h -79 c -51,0 -82,4 -89,13 -5,6 -15,38 -22,70 -8,38 -22,70 -41,92 -30,33 -30,34 -9,45 14,8 116,10 328,8 355,-3 350,-2 442,-101 150,-161 142,-154 194,-161 64,-10 75,1 51,55 -26,56 -86,105 -154,125 -45,13 -65,26 -101,67 -25,28 -75,83 -111,122 -37,38 -86,101 -109,140 l -42,69 32,56 c 40,70 140,277 140,291 0,10 -40,6 -112,-12 -30,-8 -42,-18 -59,-52 -28,-54 -75,-125 -107,-159 -49,-53 -158,-50 -221,7 -21,19 -49,38 -63,44 -21,8 -26,18 -31,63 -4,29 -7,78 -7,110 v 56 l -160,4 -159,3 -39,38 -39,38 -7,112 c -4,61 -8,113 -11,115 -8,9 -40,-21 -195,-186 -89,-95 -170,-177 -181,-183 -30,-15 -11,-34 -263,249 -60,67 -112,122 -116,122 -4,0 -11,-7 -14,-16 z m 388,-524 c 4,0 59,50 121,110 62,61 117,110 123,110 16,0 72,-75 72,-96 0,-18 -290,-304 -308,-304 -11,0 -326,287 -330,301 -4,10 41,75 65,95 10,9 38,-13 131,-102 64,-62 121,-114 126,-114 z m -759,-97 c 4,-21 7,-44 8,-52 2,-11 31,-14 143,-15 l 141,-1 69,-60 c 37,-33 128,-118 201,-190 l 133,-130 v -230 c 0,-218 -4,-275 -19,-275 -3,0 -35,24 -71,53 l -65,53 -12,79 c -6,44 -12,81 -12,81 -1,1 -17,14 -37,29 -25,20 -54,30 -93,34 -42,5 -55,10 -52,21 2,8 7,51 12,95 l 8,80 51,47 c 29,25 49,51 45,57 -4,7 -93,11 -268,12 -144,1 -295,4 -335,8 l -73,6 45,43 c 45,43 46,45 46,105 0,61 1,63 56,125 31,34 60,62 64,62 5,0 12,-17 15,-37 z m 1608,-34 47,-41 v -82 c 0,-69 3,-86 20,-104 12,-12 25,-22 31,-22 6,0 8,-4 5,-9 -3,-5 -71,-12 -150,-16 l -144,-7 -159,-148 c -87,-81 -187,-171 -223,-201 l -65,-55 -5,-66 c -3,-45 -11,-72 -24,-87 -36,-39 -125,-103 -136,-96 -6,4 -10,92 -10,254 v 248 l 103,99 c 108,106 215,206 285,267 l 42,37 h 149 c 158,0 161,1 161,51 0,29 20,23 73,-22 z"
46- id="path4" />
476 </g>
487</svg>
public/img/logo.svg+2 -0
@@ -0,0 +1,2 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<svg id="svg1" width="329.35" height="307.64" version="1.1" viewBox="0 0 329.35 307.64" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="layer1" transform="translate(-779.42 -7.5519)"><path id="path9" transform="translate(429.83 -2.2682)" d="m647.8 18.2-1.9 3.5-45 0.1-36.5 42.6c-20.2-12.3-43-0.7-48.7 9.8-1 1.8-1.4 1.5-5.9-3.1-14.2-15-34.2-11.1-39.7-8.4-4.7 2.3-4.9 2.3-6.6 0.5-12.3-13.8-24.3-27.7-36.4-41.5l-44.3-0.1-2-3.2c-8.1-12.9-28.1-9.2-30.9 5.7-3.4 18.1 22.4 28.1 31.1 11.8l1.8-3.2h39.3l34 39c-2.7 5.4-6 10.6-7.3 16.6-0.5 3.2-1.4 4.3-4.8 6.3-5 3-9.2 7.1-13.5 11.1-10.6 0.1-23.9 1.6-25.6-12-0.4-14.2-13.9-20.4-24.7-14.7-11.9 6.3-11.7 24.3 0.4 30.1 4.2 2 10.8 2.1 14.5 0.2 7.5-3.9 4.9 7.4 21.5 7.4 6.2 0 6.9 0.2 6.2 1.8-1 13.1-3.6 10.8-10.2 38.8l-24.5 0.6-4.2-4.4c-11.8-12.6-29.6-5.5-29.6 10.5 0 16.3 20.2 24.6 31.3 8.1l2.9-4.3 24.2 0.6c5.7 25.5 10.2 17.2 7.7 27.1-1.9 18.1-6.8 13.4-21.7 13.4l-12 8.9c-27.2-8.2-33.1 21.6-18.3 29.1 11.7 6 24.9-1.8 24.9-14.6 0-5.1 0.1-5.4 4.7-8.9 6-5.1 14.3-3.3 21.6-3 5.3 9.6 12.3 18.6 24 20.1 9.1 1.2 4.4 5.9 14.5 18l-31.9 36.4h-30.6l-2.4-3.7c-6.3-9.5-21.8-10.7-28.7 1.3-8 14 7.1 29.9 21.3 23.5 3.8-1.7 9.5-7.2 9.5-9.2 12-1.6 24.4-0.9 36.5-0.9l17.4-20.1c14.3-16.5 17.8-20 19.5-19.6 20.4 5.4 32.4 0 45.7-14.6 2.9 3.8 17 20.1 39 14.8 6.9-1.7 7-1.7 8.7 0.3 11.2 13 22.5 26 33.7 39l36.2 0.1 2.4 3.8c9 14 32.2 8.1 30.7-10.4-1.5-18.4-23.8-19.2-30.8-7.9l-2.2 3.5h-30.7l-32-37.1c4.1-4.8 7.2-10 9.2-16 1.3-4.1 17 1.6 29.6-21.9 10.6-0.2 25.6-1.4 27.1 12.4 0 12.9 13.2 20.6 24.9 14.6 14.8-7.5 11.1-39.7-18.3-29.1-10.3-8.2-9.6-8.9-21.7-8.9-14.3 1.2-9.6-5.6-12.1-15.1-2.5-8.3 1.7-1.7 7.4-25.9h25.6l1.2 3c5.8 13.9 32 13.5 32-7.1 0-19.1-23.6-21.3-30.4-9.8l-1.7 2.9c-4.7-0.4-26.5 1.8-26.9-2.3 0-10.3-7.7-17.7-9-37.7 6.7 0 12.9 0.5 18.2-4.7 5.3-4 6.3-4.5 8-3.4 12.7 7.9 33.4-9.6 21.3-25.4-9.5-12.5-31.4-5.7-30.4 11.2-0.4 12.3-14.3 11.3-23.3 11.3-4.8-4.3-9.6-8.9-15.2-12.1-8.9-4.7-2.6-7.7-12.6-21.7 11.2-13 22.3-26.1 33.5-39.1h40l2 3.2c10 15.8 35.5 5.4 30.5-13.1-4.8-18-24-15.5-30.5-4.8zm-84.2 59.5c6.4 7.1 8.3 15.9 5.5 25.2-1.5 5.1-8.2 12.6-13.3 14.9-14.8 6.7-2.7-13.1-13.5-13.1-4.8 0-5.6 2-4.1 10.1 1.1 6.4 0.4 12.6-0.5 19 2.8 0 8.7 1.8 10.5-0.2 0.3-0.5 1.8-4 4.1-4.4 21.3-3.4 27.1-26.6 28-26.6 2.1 0 24.8 17.7 13.8 37.2-6.5-1.8-21.6-21.7-24.8-7.4 0 3.7 22.3 19.3 21.5 26.5-1 9.3 5.3 9.7 7.9 7.1 1.3-1.3 1.6-3.3 1.4-10 1.4-31.4 10.3 28.3-1.6 22.5-6.4-5.6-20-8.6-24.8-5.4-3.9 2.5-4.2 10.6 3.8 9.1 7.5-1.4 15.7 3.3 19.2 11 6.5 14.3 2.4 30.5-9.1 35.7-17.5 7.9-2.6-46.6-54.6-12.9-5.9 5.2-7.2 8.1-4.8 10.6 3.8 2.7 27.5-9.9 32.5-11.9 12.3 5.4 14.8 35-7.5 42.6-12.4 4.2-25.4-1.4-31.4-13.4-2.3-4.5-2.3-5-2.6-49.3v-84c0.4-23.8 1-26.2 7.6-33.1 9.3-9.7 26-11.8 36.8 0.2zm-55.3 163.3c-0.1 5.9-9.8 17.8-24.5 17.8-16.2 0-41.9-21.7-10.9-53.5 6.7 7.1 11.7 16.1 19.9 21.6 7.5 4.7 10.5-1.9 8.1-5.6-1-1.5-10.4-12.7-16.6-19.1 0.1-6.2 0-13.1 0-19.3-0.7 0-8.7-0.2-9 0-0.3 0.3-0.5 6.1-0.5 10-0.9 9.4-21.7 8.8-25.2 34.6-1.4 9.9-16.6-1-19.1-9.4-1.5-5.2-1.5-16.4 0-21.6 3.3-10.9 17.3-15.3 21.2-16.4-2.1-2.5-4.8-4.9-5.9-8.1 0-3.8-17.3 9.8-18.6 7.8-9.4-14.6 2.1-52.8 1.1-22.2-0.3 8.2 0.7 10.2 5 10.2 3.6 0 4.9-2.4 4.2-7.6-0.6-4.3 8.7-25.5 21.1-25.5-4-19.2-24 14.8-27-0.7-3.6-18.7 15.7-30.2 16.5-30.2 0.5 0 5.3 15.4 17.5 22.5 7.8 4.6 16.2 7.4 25.2 7.4-3.8-25.1 4.9-28.9-4.2-28.9-9 0-1.8 17.9-11.8 14.5-14.2-4.9-20.9-22.1-13.8-35.6 11.6-22.2 42.9-14.1 44.8 0.6z" fill="#a11b1b"/><path id="path2" transform="translate(429.83 -2.2682)" d="m461.3 217.7c-20-1-19-0.4-19-10.1 0-4.4 0.4-7.8 0.8-7.5s11.1 0.8 23.8 1.1c27.9 0.8 26.9 1.1 26.9-9.7v-7.3h-17.5c-25.1 0-30.4-2.1-33.7-13.4-2.4-8.1-1.5-24.5 1.5-29.4 4.3-6.9 7.7-8.1 25.1-8.9 9.7-0.4 15.7-0.6 39 0.5v15.7h-18.9c-18.3 0-23.6 0.7-25.3 3.3-1.3 2-0.9 10.2 0.6 12.2 3.5 4.8 31.3-0.3 41.5 5.4 6.9 3.8 7.9 8.6 8.3 21.4 0.4 12.6-0.2 17.1-4.4 21.4-6.1 6.2-12.7 6.9-48.9 5.1zm78.9-0.7c0.5-22.6-0.3-45.2-0.4-67.8l-25.5 0.2v-17.2h72.5l0.6 16.4-25.6 0.6c-0.5 22.2 0.5 45.5-0.3 67.8-0.3 3.3-21.3 2.3-21.3 0z" fill="#fff"/></g></svg>
public/img/minimax.svg+1 -0
@@ -0,0 +1 @@
1+<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"></path></svg>
1 \ No newline at end of file
public/img/pollinations.svg+4 -1
@@ -1 +1,4 @@
1-<svg xmlns="http://www.w3.org/2000/svg" viewBox="83.349 83.3488 333.396 333.395" width="333.396px" height="333.395px"><path d="M 246.799 88.148 C 237.605 99.343 230.641 107.833 225.288 114.438 C 219.934 121.04 216.192 125.76 213.443 129.413 C 210.694 133.068 208.937 135.657 207.555 138.004 C 206.173 140.351 205.166 142.451 203.918 145.134 C 203.529 145.968 203.154 146.767 202.809 147.499 C 202.464 148.229 202.148 148.895 201.878 149.459 C 201.608 150.023 201.384 150.483 201.222 150.812 C 201.06 151.139 200.96 151.332 200.939 151.353 C 200.919 151.375 200.764 151.356 200.506 151.3 C 200.246 151.244 199.879 151.153 199.436 151.037 C 198.991 150.917 198.469 150.773 197.892 150.603 C 197.317 150.436 196.689 150.247 196.036 150.046 C 193.038 149.119 190.766 148.419 188.573 147.896 C 186.381 147.37 184.269 147.017 181.59 146.781 C 178.913 146.546 175.671 146.422 171.216 146.362 C 166.763 146.297 161.1 146.294 153.58 146.292 L 146.676 146.288 L 139.771 146.285 L 132.866 146.285 L 125.963 146.282 L 125.963 161.225 L 125.963 176.168 L 125.963 191.11 L 125.963 206.054 L 124.505 206.222 L 123.043 206.394 L 121.58 206.565 L 120.123 206.739 C 118.48 206.931 116.775 207.193 115.008 207.526 C 113.241 207.858 111.416 208.259 109.537 208.733 C 107.656 209.205 105.722 209.74 103.738 210.348 C 101.755 210.954 99.725 211.627 97.647 212.364 C 96.339 212.83 94.795 213.413 93.208 214.032 C 91.621 214.654 90 215.308 88.538 215.915 C 87.078 216.521 85.78 217.079 84.849 217.507 C 83.916 217.933 83.349 218.228 83.349 218.304 C 83.349 218.42 83.547 218.985 83.877 219.854 C 84.21 220.722 84.681 221.891 85.224 223.212 C 85.766 224.536 86.385 226.012 87.016 227.493 C 87.65 228.973 88.298 230.46 88.895 231.803 C 92.249 239.318 96.203 246.526 100.658 253.31 C 105.109 260.093 110.057 266.454 115.394 272.272 C 120.73 278.09 126.455 283.367 132.461 287.984 C 138.465 292.602 144.751 296.561 151.21 299.743 C 154.416 301.323 157.57 302.733 160.7 303.984 C 163.828 305.234 166.929 306.324 170.027 307.259 C 173.121 308.195 176.211 308.975 179.318 309.607 C 182.422 310.239 185.543 310.723 188.7 311.066 C 191.033 311.319 193.65 311.47 196.411 311.526 C 199.175 311.58 202.086 311.54 205.006 311.41 C 207.923 311.28 210.852 311.059 213.651 310.756 C 216.452 310.453 219.124 310.065 221.53 309.602 C 222.605 309.396 223.636 309.202 224.579 309.032 C 225.521 308.86 226.377 308.712 227.1 308.591 C 227.825 308.472 228.417 308.38 228.834 308.325 C 229.253 308.271 229.495 308.252 229.52 308.277 C 229.639 308.401 229.468 308.844 229.097 309.479 C 228.727 310.113 228.16 310.942 227.484 311.844 C 226.807 312.746 226.024 313.719 225.224 314.643 C 224.423 315.568 223.606 316.443 222.867 317.146 C 221.275 318.657 219.615 319.961 217.836 321.079 C 216.055 322.197 214.155 323.128 212.083 323.891 C 210.01 324.654 207.767 325.248 205.3 325.694 C 202.832 326.139 200.141 326.437 197.175 326.604 C 193.395 326.816 189.994 327.211 186.887 327.829 C 183.779 328.444 180.965 329.279 178.358 330.369 C 175.752 331.46 173.355 332.803 171.08 334.439 C 168.806 336.074 166.656 338 164.545 340.251 C 163.003 341.894 161.632 343.591 160.417 345.379 C 159.202 347.155 158.141 349.036 157.218 351.03 C 156.299 353.03 155.514 355.155 154.851 357.438 C 154.187 359.726 153.647 362.172 153.207 364.827 C 153.04 365.842 152.913 366.599 152.77 367.206 C 152.629 367.806 152.477 368.253 152.26 368.612 C 152.039 368.982 151.76 369.267 151.359 369.577 C 150.959 369.876 150.447 370.191 149.76 370.613 C 146.959 372.322 144.585 374.482 142.688 376.955 C 140.791 379.425 139.371 382.199 138.474 385.12 C 137.578 388.043 137.204 391.112 137.403 394.171 C 137.604 397.235 138.377 400.285 139.767 403.172 C 140.861 405.439 142.364 407.507 144.142 409.321 C 145.922 411.128 147.979 412.675 150.17 413.873 C 152.363 415.081 154.697 415.943 157.03 416.386 C 159.365 416.839 161.698 416.875 163.895 416.418 C 164.949 416.196 166.066 415.865 167.196 415.439 C 168.328 415.022 169.477 414.508 170.59 413.938 C 171.704 413.364 172.786 412.725 173.788 412.048 C 174.788 411.367 175.707 410.655 176.498 409.921 C 178.133 408.414 179.505 406.573 180.589 404.517 C 181.676 402.458 182.476 400.186 182.966 397.809 C 183.455 395.427 183.639 392.941 183.488 390.468 C 183.336 387.983 182.855 385.516 182.015 383.162 C 181.582 381.95 180.935 380.688 180.125 379.438 C 179.315 378.191 178.341 376.955 177.262 375.785 C 176.185 374.625 174.998 373.524 173.755 372.556 C 172.515 371.586 171.22 370.754 169.926 370.097 C 168.776 369.515 167.913 369.078 167.286 368.673 C 166.659 368.268 166.268 367.888 166.061 367.416 C 165.854 366.955 165.831 366.396 165.939 365.633 C 166.049 364.866 166.289 363.903 166.61 362.606 C 167.239 360.073 168.181 357.677 169.396 355.447 C 170.611 353.217 172.097 351.164 173.814 349.33 C 175.528 347.5 177.474 345.885 179.607 344.542 C 181.739 343.195 184.058 342.114 186.521 341.341 C 186.953 341.205 187.6 341.061 188.405 340.918 C 189.209 340.774 190.17 340.632 191.23 340.497 C 192.288 340.361 193.447 340.231 194.643 340.119 C 195.841 340.003 197.079 339.906 198.295 339.827 C 201.179 339.647 203.718 339.414 206.01 339.104 C 208.301 338.796 210.345 338.408 212.239 337.919 C 214.133 337.43 215.877 336.839 217.568 336.12 C 219.258 335.404 220.895 334.56 222.575 333.563 C 224.387 332.489 226.003 331.431 227.5 330.319 C 228.997 329.208 230.375 328.042 231.706 326.75 C 233.036 325.458 234.32 324.041 235.631 322.425 C 236.941 320.811 238.279 318.999 239.716 316.919 L 240.575 315.675 L 241.434 314.431 L 242.295 313.186 L 243.154 311.942 L 243.154 326.201 L 243.154 340.461 L 243.154 354.718 L 243.154 368.982 L 242.045 369.749 L 240.935 370.52 L 239.826 371.287 L 238.717 372.06 C 237.028 373.232 235.59 374.381 234.363 375.574 C 233.136 376.776 232.119 378.012 231.271 379.369 C 230.425 380.723 229.744 382.188 229.194 383.828 C 228.644 385.475 228.221 387.29 227.883 389.357 C 227.479 391.83 227.522 394.366 227.954 396.838 C 228.384 399.312 229.204 401.725 230.354 403.963 C 231.502 406.2 232.982 408.267 234.729 410.037 C 236.476 411.812 238.496 413.299 240.725 414.378 C 242.961 415.451 245.261 416.147 247.564 416.456 C 249.866 416.765 252.172 416.695 254.42 416.259 C 256.67 415.821 258.86 415.016 260.936 413.85 C 263.011 412.685 264.971 411.168 266.752 409.303 C 267.88 408.125 268.805 407.004 269.558 405.869 C 270.312 404.73 270.893 403.56 271.335 402.289 C 271.779 401.005 272.081 399.612 272.276 398.009 C 272.469 396.41 272.555 394.602 272.563 392.497 C 272.57 390.978 272.569 389.795 272.536 388.827 C 272.504 387.847 272.44 387.086 272.323 386.397 C 272.208 385.705 272.038 385.089 271.792 384.412 C 271.547 383.734 271.226 382.997 270.806 382.07 C 270.242 380.813 269.59 379.645 268.853 378.552 C 268.115 377.458 267.292 376.441 266.385 375.504 C 265.476 374.575 264.482 373.72 263.404 372.943 C 262.327 372.17 261.164 371.474 259.918 370.858 L 258.956 370.387 L 257.997 369.912 L 257.034 369.443 L 256.073 368.964 L 256.069 361.769 L 256.067 354.564 L 256.064 347.36 L 256.061 340.163 C 256.059 336.205 256.072 332.439 256.094 329.025 C 256.116 325.612 256.15 322.547 256.192 319.988 C 256.233 317.43 256.282 315.375 256.339 313.979 C 256.395 312.583 256.457 311.846 256.52 311.925 C 256.586 312.001 256.737 312.211 256.957 312.525 C 257.177 312.839 257.463 313.255 257.799 313.747 C 258.134 314.24 258.514 314.803 258.923 315.413 C 259.33 316.023 259.767 316.677 260.21 317.345 C 261.259 318.93 262.251 320.334 263.231 321.608 C 264.21 322.884 265.181 324.03 266.189 325.097 C 267.199 326.165 268.243 327.152 269.376 328.113 C 270.508 329.075 271.726 330.008 273.076 330.964 C 275.237 332.493 277.247 333.763 279.249 334.822 C 281.25 335.88 283.244 336.729 285.372 337.409 C 287.499 338.088 289.763 338.603 292.306 338.994 C 294.847 339.391 297.667 339.661 300.908 339.859 C 302.102 339.933 303.315 340.021 304.482 340.117 C 305.649 340.212 306.771 340.317 307.787 340.422 C 308.804 340.531 309.713 340.637 310.456 340.741 C 311.2 340.842 311.775 340.944 312.123 341.031 C 314.804 341.716 317.399 342.888 319.797 344.43 C 322.195 345.971 324.398 347.874 326.3 350.033 C 328.2 352.182 329.795 354.583 330.987 357.111 C 332.176 359.636 332.954 362.288 333.219 364.942 L 333.311 365.851 L 333.4 366.755 L 333.487 367.664 L 333.577 368.57 L 332.828 368.973 L 332.08 369.369 L 331.331 369.766 L 330.58 370.171 C 328.268 371.397 326.239 372.85 324.51 374.503 C 322.782 376.157 321.349 378.012 320.228 380.046 C 319.108 382.076 318.298 384.294 317.809 386.655 C 317.321 389.026 317.161 391.537 317.332 394.194 C 317.514 396.986 318.125 399.626 319.11 402.047 C 320.093 404.473 321.455 406.682 323.135 408.609 C 324.812 410.535 326.813 412.191 329.079 413.492 C 331.346 414.804 333.877 415.771 336.617 416.34 C 340.253 417.085 343.896 416.704 347.28 415.477 C 350.658 414.247 353.773 412.16 356.353 409.511 C 358.941 406.853 360.988 403.623 362.234 400.105 C 363.484 396.582 363.929 392.762 363.303 388.924 C 362.986 386.999 362.469 385.132 361.769 383.362 C 361.063 381.582 360.179 379.902 359.126 378.334 C 358.078 376.779 356.865 375.337 355.511 374.047 C 354.153 372.762 352.659 371.629 351.044 370.681 C 350.548 370.39 350.073 370.101 349.631 369.823 C 349.186 369.544 348.781 369.287 348.427 369.052 C 348.074 368.816 347.784 368.608 347.563 368.445 C 347.35 368.279 347.213 368.156 347.174 368.089 C 347.136 368.028 347.076 367.839 347.002 367.559 C 346.926 367.279 346.841 366.906 346.738 366.472 C 346.641 366.033 346.533 365.524 346.426 364.982 C 346.319 364.434 346.202 363.845 346.093 363.251 C 345.827 361.805 345.469 360.28 345.026 358.734 C 344.585 357.193 344.07 355.622 343.499 354.103 C 342.926 352.579 342.301 351.092 341.649 349.713 C 340.989 348.331 340.301 347.057 339.606 345.941 C 338.365 343.951 336.881 342.049 335.214 340.264 C 333.55 338.488 331.694 336.833 329.706 335.343 C 327.721 333.854 325.599 332.529 323.396 331.407 C 321.193 330.286 318.908 329.368 316.592 328.692 C 315.968 328.51 315.181 328.325 314.274 328.149 C 313.368 327.97 312.343 327.796 311.24 327.635 C 310.135 327.472 308.952 327.322 307.73 327.188 C 306.509 327.055 305.25 326.938 303.992 326.845 C 302.835 326.758 301.629 326.652 300.44 326.533 C 299.251 326.415 298.077 326.285 296.986 326.151 C 295.893 326.015 294.886 325.877 294.026 325.743 C 293.163 325.608 292.45 325.478 291.951 325.359 C 290.551 325.028 289.144 324.58 287.755 324.032 C 286.368 323.482 284.996 322.832 283.67 322.094 C 282.343 321.357 281.061 320.531 279.845 319.631 C 278.63 318.731 277.484 317.757 276.431 316.723 C 275.528 315.836 274.654 314.903 273.863 313.989 C 273.069 313.075 272.358 312.184 271.778 311.386 C 271.197 310.585 270.749 309.877 270.482 309.33 C 270.215 308.783 270.132 308.398 270.279 308.242 C 270.319 308.199 270.599 308.205 271.069 308.254 C 271.539 308.302 272.201 308.388 273.006 308.512 C 273.81 308.633 274.756 308.789 275.797 308.972 C 276.837 309.155 277.971 309.361 279.151 309.589 C 281.082 309.962 282.663 310.252 284.125 310.478 C 285.586 310.702 286.929 310.864 288.385 310.98 C 289.843 311.095 291.412 311.165 293.33 311.207 C 295.246 311.249 297.507 311.264 300.348 311.27 C 303.731 311.276 306.472 311.257 308.843 311.18 C 311.214 311.103 313.217 310.969 315.118 310.744 C 317.024 310.519 318.828 310.203 320.809 309.766 C 322.789 309.328 324.942 308.768 327.542 308.055 C 332.681 306.644 337.629 304.941 342.407 302.934 C 347.183 300.926 351.786 298.616 356.237 295.996 C 360.679 293.374 364.967 290.442 369.116 287.188 C 373.258 283.937 377.262 280.363 381.138 276.462 C 384.43 273.143 387.487 269.801 390.337 266.384 C 393.193 262.965 395.853 259.47 398.333 255.848 C 400.816 252.227 403.137 248.477 405.327 244.549 C 407.513 240.621 409.562 236.514 411.525 232.177 C 412.561 229.876 413.445 227.881 414.159 226.174 C 414.883 224.465 415.448 223.047 415.864 221.895 C 416.273 220.745 416.543 219.863 416.662 219.23 C 416.79 218.598 416.766 218.215 416.617 218.065 C 416.403 217.855 415.469 217.39 414.106 216.784 C 412.749 216.177 410.956 215.431 409.029 214.662 C 407.111 213.893 405.047 213.1 403.143 212.404 C 401.237 211.7 399.492 211.096 398.191 210.7 C 396.677 210.239 395.049 209.785 393.395 209.353 C 391.728 208.922 390.04 208.513 388.387 208.145 C 386.734 207.78 385.125 207.449 383.627 207.179 C 382.127 206.908 380.746 206.695 379.552 206.553 L 378.67 206.449 L 377.799 206.345 L 376.92 206.242 L 376.049 206.139 L 376.049 191.175 L 376.049 176.209 L 376.049 161.245 L 376.049 146.282 L 369.208 146.288 L 362.377 146.294 L 355.542 146.3 L 348.71 146.305 C 341.359 146.316 335.755 146.325 331.3 146.393 C 326.842 146.461 323.533 146.582 320.775 146.808 C 318.016 147.032 315.807 147.359 313.553 147.835 C 311.298 148.315 308.996 148.944 306.05 149.775 C 305.408 149.956 304.791 150.123 304.223 150.27 C 303.656 150.416 303.14 150.545 302.701 150.646 C 302.263 150.75 301.902 150.821 301.644 150.865 C 301.384 150.909 301.232 150.919 301.208 150.892 C 301.185 150.865 301.079 150.664 300.909 150.329 C 300.739 149.991 300.505 149.516 300.222 148.939 C 299.941 148.362 299.611 147.679 299.251 146.929 C 298.889 146.183 298.499 145.366 298.094 144.516 C 296.849 141.904 295.91 139.934 294.85 138.024 C 293.792 136.115 292.615 134.263 290.896 131.882 C 289.177 129.5 286.916 126.595 283.689 122.568 C 280.463 118.545 276.269 113.409 270.689 106.572 C 266.798 101.8 263.658 97.966 261.13 94.892 C 258.6 91.819 256.681 89.508 255.231 87.798 C 253.782 86.083 252.8 84.967 252.148 84.281 C 251.494 83.597 251.168 83.34 251.03 83.349 C 250.988 83.351 250.839 83.488 250.602 83.734 C 250.365 83.978 250.045 84.33 249.661 84.765 C 249.277 85.2 248.832 85.719 248.346 86.291 C 247.861 86.861 247.338 87.491 246.799 88.148 M 266.415 121.571 C 270.276 126.312 273.576 130.479 276.367 134.164 C 279.161 137.852 281.446 141.053 283.281 143.861 C 285.116 146.665 286.5 149.073 287.489 151.167 C 288.478 153.261 289.071 155.042 289.325 156.6 C 289.391 157.001 289.427 157.324 289.406 157.608 C 289.387 157.891 289.312 158.132 289.156 158.371 C 288.999 158.614 288.764 158.85 288.422 159.121 C 288.081 159.394 287.635 159.704 287.057 160.086 C 286.13 160.701 284.911 161.698 283.538 162.934 C 282.166 164.169 280.642 165.642 279.106 167.213 C 277.57 168.782 276.02 170.446 274.598 172.064 C 273.176 173.682 271.881 175.253 270.852 176.635 C 269.742 178.128 268.593 179.804 267.454 181.582 C 266.313 183.359 265.182 185.239 264.108 187.135 C 263.031 189.033 262.014 190.945 261.098 192.793 C 260.183 194.64 259.372 196.421 258.712 198.054 C 257.763 200.401 256.843 203.065 255.996 205.859 C 255.15 208.65 254.379 211.569 253.725 214.423 C 253.07 217.278 252.534 220.067 252.159 222.599 C 251.786 225.133 251.572 227.407 251.565 229.235 C 251.559 231.007 251.505 232.294 251.409 233.097 C 251.313 233.901 251.172 234.221 250.988 234.059 C 250.805 233.896 250.578 233.251 250.308 232.123 C 250.039 230.997 249.728 229.388 249.375 227.297 C 248.928 224.65 248.419 221.945 247.871 219.288 C 247.324 216.629 246.741 214.02 246.144 211.567 C 245.548 209.108 244.941 206.809 244.349 204.765 C 243.755 202.725 243.176 200.941 242.636 199.525 C 242.096 198.111 241.387 196.463 240.57 194.704 C 239.751 192.947 238.827 191.079 237.856 189.225 C 236.887 187.368 235.876 185.527 234.884 183.818 C 233.891 182.112 232.922 180.542 232.034 179.229 C 231.241 178.051 230.127 176.6 228.825 175.023 C 227.525 173.446 226.04 171.74 224.505 170.059 C 222.969 168.38 221.388 166.72 219.895 165.239 C 218.402 163.755 216.997 162.448 215.818 161.464 C 215.336 161.059 214.877 160.657 214.458 160.269 C 214.038 159.881 213.662 159.514 213.344 159.185 C 213.026 158.855 212.769 158.564 212.591 158.328 C 212.412 158.094 212.315 157.918 212.315 157.821 C 212.315 157.515 212.586 156.721 213.037 155.637 C 213.488 154.55 214.117 153.171 214.832 151.693 C 215.545 150.216 216.346 148.636 217.135 147.152 C 217.926 145.667 218.707 144.278 219.388 143.171 C 220.106 142.009 220.79 140.942 221.523 139.864 C 222.255 138.785 223.035 137.697 223.945 136.49 C 224.854 135.283 225.892 133.959 227.139 132.41 C 228.388 130.863 229.845 129.087 231.591 126.978 C 232.242 126.195 233.071 125.195 234.012 124.055 C 234.953 122.912 236.008 121.635 237.109 120.301 C 238.209 118.966 239.355 117.572 240.481 116.204 C 241.608 114.835 242.714 113.49 243.733 112.249 L 245.587 109.989 L 247.441 107.734 L 249.294 105.474 L 251.148 103.218 L 251.468 103.562 L 251.787 103.908 L 252.107 104.253 L 252.425 104.6 C 252.602 104.789 253.127 105.411 253.925 106.367 C 254.724 107.321 255.795 108.617 257.063 110.156 C 258.33 111.693 259.793 113.476 261.378 115.41 C 262.962 117.344 264.666 119.429 266.415 121.571 M 183.998 160.363 C 185.176 160.625 186.407 160.937 187.616 161.281 C 188.827 161.622 190.016 161.992 191.109 162.365 C 192.203 162.735 193.201 163.112 194.031 163.464 C 194.862 163.817 195.522 164.145 195.942 164.429 C 196.089 164.529 196.193 164.698 196.252 164.982 C 196.308 165.272 196.318 165.679 196.28 166.259 C 196.241 166.839 196.151 167.592 196.008 168.57 C 195.864 169.545 195.668 170.747 195.412 172.22 C 195.151 173.746 194.94 175.096 194.774 176.424 C 194.605 177.758 194.481 179.068 194.391 180.521 C 194.301 181.972 194.241 183.564 194.208 185.453 C 194.172 187.342 194.161 189.528 194.161 192.172 C 194.164 195.029 194.175 197.291 194.21 199.182 C 194.244 201.077 194.306 202.596 194.408 203.967 C 194.511 205.335 194.654 206.558 194.858 207.853 C 195.061 209.146 195.321 210.509 195.658 212.173 C 195.862 213.183 196.089 214.264 196.321 215.352 C 196.555 216.44 196.797 217.535 197.032 218.574 C 197.265 219.613 197.493 220.596 197.7 221.462 C 197.905 222.326 198.091 223.072 198.243 223.638 C 198.394 224.202 198.535 224.745 198.659 225.244 C 198.782 225.742 198.892 226.196 198.979 226.582 C 199.065 226.966 199.132 227.283 199.171 227.509 C 199.212 227.735 199.225 227.868 199.206 227.889 C 199.187 227.907 199.005 227.826 198.692 227.662 C 198.379 227.498 197.934 227.251 197.392 226.94 C 196.848 226.63 196.208 226.256 195.501 225.836 C 194.794 225.418 194.021 224.953 193.218 224.462 C 191.127 223.191 189.283 222.093 187.58 221.118 C 185.876 220.143 184.316 219.291 182.796 218.508 C 181.276 217.728 179.798 217.016 178.258 216.323 C 176.721 215.629 175.121 214.956 173.362 214.247 C 170.717 213.183 168.056 212.21 165.422 211.339 C 162.788 210.463 160.182 209.691 157.644 209.029 C 155.104 208.371 152.636 207.82 150.279 207.395 C 147.925 206.969 145.681 206.665 143.592 206.499 L 142.411 206.402 L 141.228 206.307 L 140.044 206.211 L 138.86 206.118 L 138.86 194.393 L 138.86 182.673 L 138.86 170.946 L 138.86 159.226 L 143.872 159.226 L 148.881 159.228 L 153.894 159.231 L 158.904 159.235 C 163.288 159.238 166.776 159.245 169.604 159.275 C 172.433 159.304 174.608 159.354 176.374 159.432 C 178.138 159.514 179.492 159.621 180.68 159.775 C 181.87 159.924 182.895 160.118 183.998 160.363 M 364.273 182.135 C 364.273 185.703 364.256 188.927 364.232 191.757 C 364.21 194.592 364.168 197.034 364.123 199.037 C 364.073 201.036 364.017 202.602 363.951 203.678 C 363.884 204.753 363.806 205.345 363.72 205.401 C 363.647 205.446 363.37 205.516 362.943 205.606 C 362.51 205.689 361.917 205.788 361.219 205.895 C 360.52 206.004 359.703 206.12 358.815 206.24 C 357.924 206.356 356.965 206.475 355.976 206.593 C 353.857 206.84 351.706 207.169 349.519 207.584 C 347.333 207.998 345.11 208.5 342.841 209.089 C 340.568 209.674 338.248 210.35 335.881 211.113 C 333.508 211.88 331.079 212.734 328.586 213.682 C 326.553 214.453 324.965 215.067 323.583 215.633 C 322.202 216.201 321.032 216.721 319.838 217.313 C 318.643 217.904 317.43 218.565 315.96 219.411 C 314.488 220.256 312.763 221.287 310.551 222.616 C 309.679 223.141 308.838 223.631 308.064 224.069 C 307.29 224.507 306.584 224.894 305.98 225.209 C 305.376 225.524 304.876 225.767 304.515 225.919 C 304.155 226.073 303.935 226.136 303.892 226.09 C 303.846 226.043 303.838 225.853 303.86 225.549 C 303.882 225.248 303.933 224.832 304.011 224.332 C 304.09 223.832 304.193 223.251 304.319 222.615 C 304.443 221.979 304.59 221.291 304.752 220.58 C 305.47 217.443 306.014 215.037 306.432 212.994 C 306.85 210.95 307.141 209.27 307.353 207.569 C 307.562 205.873 307.692 204.161 307.789 202.063 C 307.885 199.964 307.946 197.477 308.022 194.23 C 308.085 191.376 308.129 189.088 308.139 187.161 C 308.15 185.237 308.129 183.672 308.067 182.266 C 308.006 180.861 307.902 179.617 307.75 178.328 C 307.598 177.043 307.396 175.711 307.135 174.134 C 306.71 171.557 306.387 169.65 306.2 168.219 C 306.013 166.785 305.963 165.825 306.087 165.14 C 306.209 164.453 306.505 164.042 307.009 163.705 C 307.512 163.372 308.223 163.115 309.179 162.732 C 311.103 161.968 312.767 161.372 314.532 160.905 C 316.301 160.441 318.17 160.105 320.508 159.868 C 322.849 159.628 325.656 159.482 329.301 159.394 C 332.944 159.308 337.422 159.278 343.102 159.267 L 348.391 159.254 L 353.686 159.245 L 358.977 159.235 L 364.273 159.226 L 364.273 164.952 L 364.273 170.68 L 364.273 176.408 L 364.273 182.135 M 294.672 175.255 C 295.22 178.215 295.627 180.998 295.891 183.706 C 296.156 186.417 296.277 189.05 296.257 191.703 C 296.236 194.356 296.071 197.028 295.763 199.821 C 295.454 202.614 295.003 205.519 294.406 208.644 C 293.797 211.84 293.112 214.843 292.333 217.708 C 291.556 220.571 290.684 223.295 289.702 225.927 C 288.719 228.56 287.624 231.1 286.4 233.599 C 285.175 236.098 283.82 238.555 282.318 241.02 C 281.712 242.01 280.648 243.522 279.328 245.293 C 278.009 247.063 276.436 249.097 274.815 251.131 C 273.192 253.166 271.522 255.203 270.01 256.985 C 268.497 258.766 267.144 260.289 266.154 261.3 L 265.432 262.034 L 264.711 262.769 L 263.989 263.505 L 263.268 264.24 L 263.324 259.533 L 263.381 254.828 L 263.438 250.12 L 263.494 245.413 C 263.544 241.302 263.592 238.027 263.653 235.351 C 263.714 232.674 263.788 230.597 263.886 228.883 C 263.984 227.17 264.11 225.82 264.274 224.597 C 264.438 223.375 264.644 222.28 264.901 221.077 C 265.759 217.074 266.758 213.304 267.919 209.718 C 269.078 206.136 270.399 202.738 271.899 199.486 C 273.402 196.236 275.083 193.128 276.966 190.122 C 278.847 187.116 280.93 184.215 283.231 181.372 C 284.057 180.355 285.031 179.27 286.048 178.218 C 287.064 177.162 288.122 176.139 289.116 175.244 C 290.109 174.346 291.038 173.58 291.794 173.034 C 292.549 172.49 293.134 172.169 293.439 172.169 C 293.53 172.169 293.633 172.257 293.74 172.41 C 293.849 172.568 293.962 172.795 294.074 173.075 C 294.185 173.355 294.296 173.687 294.397 174.058 C 294.5 174.427 294.593 174.834 294.672 175.255 M 214.395 177.903 C 216.932 180.555 219.226 183.303 221.306 186.208 C 223.386 189.113 225.254 192.18 226.941 195.469 C 228.628 198.754 230.134 202.263 231.491 206.056 C 232.848 209.852 234.059 213.927 235.152 218.351 C 235.497 219.75 235.762 220.875 235.967 222.052 C 236.173 223.23 236.32 224.462 236.431 226.076 C 236.542 227.689 236.617 229.684 236.678 232.391 C 236.739 235.098 236.787 238.515 236.843 242.97 C 236.897 247.165 236.932 250.429 236.945 252.96 C 236.956 255.491 236.943 257.287 236.9 258.545 C 236.857 259.805 236.783 260.525 236.67 260.902 C 236.559 261.279 236.409 261.312 236.215 261.199 C 235.931 261.033 235.183 260.258 234.159 259.102 C 233.133 257.946 231.83 256.409 230.435 254.715 C 229.04 253.02 227.55 251.167 226.157 249.385 C 224.763 247.601 223.46 245.887 222.435 244.464 C 221.492 243.154 220.43 241.461 219.336 239.557 C 218.241 237.652 217.115 235.538 216.04 233.384 C 214.966 231.23 213.945 229.037 213.062 226.975 C 212.18 224.913 211.436 222.985 210.915 221.36 C 210.715 220.733 210.491 219.974 210.254 219.14 C 210.019 218.305 209.774 217.396 209.534 216.468 C 209.293 215.536 209.057 214.589 208.838 213.677 C 208.62 212.764 208.422 211.887 208.257 211.105 C 207.734 208.612 207.373 204.972 207.161 200.908 C 206.951 196.847 206.892 192.364 206.97 188.194 C 207.047 184.025 207.261 180.165 207.598 177.346 C 207.937 174.531 208.397 172.757 208.967 172.757 C 209.037 172.757 209.24 172.9 209.55 173.163 C 209.861 173.425 210.276 173.8 210.768 174.267 C 211.26 174.734 211.827 175.29 212.443 175.906 C 213.057 176.521 213.719 177.198 214.395 177.903 M 362.287 222.616 C 362.039 224.822 361.696 226.999 361.25 229.168 C 360.802 231.336 360.258 233.494 359.605 235.661 C 358.949 237.827 358.193 240.002 357.321 242.202 C 356.451 244.402 355.468 246.626 354.373 248.894 C 353.42 250.857 352.54 252.568 351.651 254.143 C 350.761 255.716 349.869 257.152 348.886 258.564 C 347.908 259.976 346.841 261.366 345.604 262.846 C 344.366 264.324 342.963 265.895 341.318 267.67 C 339.751 269.361 338.317 270.826 336.939 272.137 C 335.552 273.447 334.221 274.599 332.854 275.666 C 331.483 276.729 330.076 277.706 328.545 278.66 C 327.016 279.613 325.361 280.546 323.495 281.524 C 321.16 282.75 318.988 283.737 316.707 284.525 C 314.424 285.314 312.031 285.904 309.247 286.342 C 306.462 286.78 303.287 287.064 299.44 287.237 C 295.592 287.414 291.074 287.479 285.605 287.479 L 281.702 287.479 L 277.799 287.479 L 273.896 287.479 L 269.992 287.479 L 270.084 287.147 L 270.176 286.818 L 270.267 286.486 L 270.359 286.155 C 270.515 285.585 271.039 284.472 271.797 283.034 C 272.555 281.597 273.55 279.834 274.654 277.964 C 275.759 276.092 276.971 274.114 278.164 272.243 C 279.357 270.373 280.53 268.61 281.557 267.176 C 286.49 260.272 291.976 254.005 297.894 248.447 C 303.812 242.89 310.161 238.045 316.821 233.98 C 323.478 229.92 330.443 226.642 337.598 224.224 C 344.751 221.805 352.094 220.246 359.505 219.621 C 359.812 219.595 360.119 219.566 360.408 219.538 C 360.702 219.512 360.977 219.484 361.223 219.458 C 361.467 219.431 361.682 219.408 361.848 219.385 C 362.019 219.365 362.146 219.345 362.209 219.333 C 362.27 219.318 362.323 219.401 362.356 219.562 C 362.393 219.721 362.413 219.958 362.42 220.256 C 362.429 220.554 362.42 220.912 362.398 221.311 C 362.377 221.709 362.343 222.151 362.287 222.616 M 127.615 226.441 C 128.292 230.02 129.18 233.67 130.242 237.294 C 131.303 240.92 132.536 244.52 133.907 247.993 C 135.276 251.468 136.779 254.816 138.38 257.942 C 139.98 261.066 141.674 263.97 143.424 266.55 C 144.672 268.387 146.181 270.359 147.856 272.368 C 149.529 274.375 151.367 276.418 153.273 278.395 C 155.18 280.373 157.152 282.285 159.098 284.03 C 161.042 285.778 162.959 287.36 164.749 288.675 C 165.572 289.278 166.345 289.86 167.036 290.395 C 167.727 290.928 168.34 291.415 168.84 291.828 C 169.34 292.244 169.73 292.585 169.977 292.83 C 170.226 293.076 170.333 293.222 170.27 293.245 C 170.079 293.318 169.361 293.128 168.296 292.752 C 167.229 292.375 165.819 291.812 164.242 291.142 C 162.667 290.473 160.928 289.694 159.204 288.884 C 157.485 288.076 155.783 287.236 154.28 286.444 C 148.912 283.61 143.725 280.241 138.787 276.403 C 133.85 272.565 129.161 268.258 124.779 263.552 C 120.398 258.843 116.327 253.735 112.627 248.29 C 108.926 242.846 105.598 237.068 102.705 231.024 C 102.347 230.274 102.01 229.555 101.708 228.897 C 101.405 228.236 101.138 227.636 100.919 227.125 C 100.7 226.615 100.527 226.195 100.416 225.896 C 100.305 225.595 100.254 225.419 100.28 225.393 C 100.305 225.366 100.453 225.299 100.693 225.199 C 100.936 225.098 101.274 224.965 101.685 224.809 C 102.093 224.653 102.577 224.475 103.105 224.282 C 103.634 224.09 104.209 223.883 104.806 223.672 C 106.009 223.25 107.545 222.805 109.237 222.376 C 110.928 221.944 112.775 221.528 114.594 221.164 C 116.416 220.798 118.211 220.485 119.804 220.256 C 121.393 220.028 122.779 219.888 123.781 219.869 L 124.429 219.858 L 125.074 219.846 L 125.721 219.835 L 126.365 219.822 L 126.679 221.477 L 126.992 223.132 L 127.305 224.785 L 127.615 226.441 M 150.401 221.008 C 154.24 221.696 158.178 222.712 162.136 224.018 C 166.097 225.324 170.077 226.921 174.005 228.775 C 177.933 230.628 181.809 232.737 185.555 235.067 C 189.3 237.396 192.918 239.945 196.335 242.681 C 197.475 243.592 198.972 244.936 200.637 246.513 C 202.302 248.09 204.136 249.904 205.952 251.762 C 207.767 253.618 209.561 255.52 211.151 257.272 C 212.738 259.023 214.118 260.626 215.102 261.887 C 216.675 263.902 218.309 266.157 219.896 268.474 C 221.483 270.789 223.022 273.167 224.404 275.429 C 225.787 277.689 227.013 279.833 227.971 281.68 C 228.931 283.527 229.625 285.078 229.943 286.155 L 230.04 286.486 L 230.138 286.818 L 230.234 287.147 L 230.332 287.479 L 226.503 287.477 L 222.673 287.477 L 218.844 287.476 L 215.015 287.475 C 209.688 287.474 205.347 287.414 201.67 287.244 C 197.995 287.073 194.981 286.791 192.311 286.34 C 189.64 285.89 187.313 285.277 185.006 284.44 C 182.7 283.607 180.415 282.551 177.83 281.222 C 174.857 279.694 171.933 277.808 169.117 275.629 C 166.299 273.452 163.592 270.984 161.055 268.292 C 158.521 265.6 156.16 262.686 154.036 259.619 C 151.913 256.549 150.026 253.325 148.439 250.014 C 147.262 247.55 146.142 244.939 145.129 242.32 C 144.116 239.702 143.208 237.077 142.447 234.583 C 141.685 232.09 141.071 229.729 140.647 227.639 C 140.221 225.549 139.987 223.73 139.984 222.322 C 139.984 221.708 139.984 221.246 140.018 220.897 C 140.049 220.548 140.113 220.311 140.234 220.152 C 140.354 219.994 140.534 219.91 140.801 219.868 C 141.068 219.825 141.421 219.822 141.887 219.822 C 142.149 219.822 142.585 219.855 143.15 219.915 C 143.714 219.974 144.405 220.063 145.175 220.169 C 145.948 220.277 146.798 220.405 147.684 220.547 C 148.573 220.689 149.493 220.845 150.401 221.008 M 386.42 221.248 C 387.353 221.423 388.673 221.736 390.135 222.113 C 391.591 222.492 393.193 222.935 394.695 223.376 C 396.202 223.819 397.608 224.256 398.663 224.619 C 399.736 224.982 400.461 225.272 400.609 225.415 C 400.68 225.485 400.53 225.956 400.194 226.725 C 399.861 227.493 399.357 228.559 398.729 229.813 C 398.108 231.071 397.359 232.516 396.542 234.047 C 395.729 235.578 394.837 237.194 393.935 238.788 C 391.085 243.831 387.831 248.73 384.286 253.39 C 380.74 258.048 376.892 262.47 372.836 266.561 C 368.783 270.648 364.517 274.407 360.141 277.739 C 355.761 281.071 351.268 283.979 346.75 286.368 C 345.452 287.056 344.007 287.771 342.554 288.454 C 341.116 289.135 339.666 289.782 338.358 290.338 C 337.05 290.892 335.883 291.352 335 291.658 C 334.116 291.965 333.516 292.119 333.34 292.056 C 333.27 292.032 333.351 291.899 333.557 291.677 C 333.766 291.455 334.099 291.147 334.531 290.774 C 334.964 290.402 335.5 289.964 336.102 289.485 C 336.708 289.004 337.386 288.483 338.112 287.941 C 341.136 285.688 343.974 283.293 346.63 280.752 C 349.291 278.211 351.77 275.522 354.078 272.675 C 356.388 269.829 358.521 266.827 360.496 263.659 C 362.468 260.491 364.28 257.157 365.927 253.651 C 367.035 251.305 368.003 249.097 368.864 246.948 C 369.735 244.801 370.491 242.715 371.167 240.614 C 371.845 238.512 372.436 236.399 372.969 234.196 C 373.503 231.992 373.983 229.7 374.42 227.243 L 374.728 225.536 L 375.036 223.826 L 375.34 222.118 L 375.649 220.411 L 376.434 220.411 L 377.215 220.411 L 378.007 220.411 L 378.79 220.412 C 379.221 220.412 379.759 220.435 380.363 220.477 C 380.966 220.519 381.638 220.582 382.324 220.657 C 383.019 220.733 383.73 220.823 384.425 220.923 C 385.125 221.022 385.801 221.133 386.42 221.248 M 166.406 383.113 C 167.419 383.787 168.276 384.729 168.956 385.829 C 169.636 386.933 170.14 388.194 170.444 389.526 C 170.75 390.856 170.855 392.249 170.74 393.607 C 170.623 394.962 170.287 396.289 169.705 397.491 C 169.275 398.374 168.776 399.155 168.21 399.84 C 167.643 400.526 167.009 401.111 166.308 401.589 C 165.608 402.076 164.842 402.456 164.012 402.74 C 163.182 403.017 162.286 403.196 161.331 403.266 C 160.319 403.339 159.365 403.306 158.461 403.149 C 157.561 402.989 156.714 402.722 155.918 402.326 C 155.12 401.943 154.38 401.436 153.684 400.806 C 152.989 400.182 152.347 399.439 151.749 398.569 C 150.993 397.473 150.484 396.193 150.213 394.844 C 149.94 393.491 149.901 392.061 150.082 390.669 C 150.263 389.281 150.665 387.932 151.269 386.723 C 151.88 385.518 152.69 384.454 153.693 383.652 C 154.574 382.95 155.573 382.409 156.637 382.032 C 157.704 381.652 158.834 381.438 159.969 381.389 C 161.104 381.338 162.25 381.458 163.342 381.743 C 164.433 382.032 165.476 382.487 166.406 383.113 M 345.257 382.546 C 345.746 382.799 346.29 383.216 346.852 383.725 C 347.405 384.236 347.969 384.833 348.486 385.466 C 349.005 386.089 349.475 386.74 349.844 387.357 C 350.214 387.97 350.484 388.541 350.592 389.004 C 350.641 389.209 350.681 389.489 350.711 389.834 C 350.741 390.167 350.758 390.564 350.768 390.987 C 350.78 391.407 350.775 391.861 350.764 392.314 C 350.753 392.762 350.73 393.223 350.694 393.657 C 350.519 395.874 349.727 397.797 348.544 399.335 C 347.358 400.876 345.774 402.039 344.023 402.733 C 342.273 403.416 340.349 403.642 338.478 403.309 C 336.608 402.974 334.783 402.084 333.238 400.552 C 332.63 399.949 332.127 399.371 331.714 398.778 C 331.303 398.185 330.981 397.582 330.739 396.925 C 330.496 396.269 330.327 395.558 330.219 394.757 C 330.11 393.949 330.063 393.055 330.063 392.027 C 330.063 391.002 330.137 390.074 330.287 389.23 C 330.437 388.384 330.669 387.62 330.99 386.92 C 331.313 386.21 331.724 385.565 332.24 384.952 C 332.751 384.338 333.368 383.766 334.095 383.201 C 334.781 382.662 335.614 382.248 336.528 381.942 C 337.445 381.642 338.436 381.46 339.45 381.399 C 340.465 381.338 341.495 381.406 342.484 381.595 C 343.476 381.782 344.419 382.102 345.257 382.546 M 256.549 383.921 C 257.04 384.288 257.567 384.871 258.086 385.576 C 258.603 386.279 259.108 387.117 259.552 387.97 C 259.996 388.827 260.38 389.704 260.654 390.521 C 260.927 391.338 261.089 392.084 261.091 392.668 C 261.093 393.424 260.884 394.317 260.52 395.267 C 260.156 396.208 259.636 397.208 259.024 398.158 C 258.412 399.112 257.705 400.024 256.963 400.796 C 256.221 401.575 255.444 402.216 254.691 402.622 C 253.829 403.093 252.859 403.383 251.845 403.496 C 250.833 403.619 249.778 403.563 248.745 403.359 C 247.713 403.153 246.705 402.789 245.785 402.286 C 244.865 401.776 244.035 401.126 243.361 400.348 C 242.582 399.455 241.949 398.455 241.467 397.409 C 240.988 396.359 240.659 395.258 240.488 394.145 C 240.317 393.027 240.304 391.897 240.457 390.794 C 240.607 389.696 240.924 388.62 241.412 387.617 C 241.927 386.563 242.454 385.689 243.041 384.979 C 243.624 384.273 244.264 383.725 245.007 383.317 C 245.748 382.912 246.591 382.65 247.577 382.499 C 248.565 382.347 249.696 382.312 251.015 382.365 C 251.715 382.396 252.322 382.44 252.859 382.506 C 253.397 382.575 253.866 382.662 254.293 382.784 C 254.72 382.906 255.104 383.052 255.472 383.239 C 255.839 383.435 256.189 383.652 256.549 383.921" fill-rule="evenodd" id="object-1" transform="matrix(1, 0, 0, 1, 1.4210854715202004e-14, 0)" style="stroke-opacity: 0;"></path></svg>
1 \ No newline at end of file
1+<?xml version="1.0" encoding="UTF-8"?>
2+<svg id="svg1" width="159.95" height="153.04" version="1.1" viewBox="15 17.641 119.96 114.78" xmlns="http://www.w3.org/2000/svg">
3+ <path id="path28" d="m66.09 28.369c-3.74 3.74-6.37 8.08-8.77 12.71-8.7-2.78-17.96-1.75-26.98-1.75v20.57c-5.23 0.57-10.62 1.81-15.34 4.21 8.44 22.69 28.34 34.63 52.61 30.97-1.51 3.38-5.23 5.31-8.72 5.99-5.49 0.19-15.41 1.83-18.16 10.93-0.54 1.67-0.27 3.51-1.83 4.53-7.48 4.33-4.75 14.41 2.62 15.76 0.84 0.15 1.68 0.17 2.47 0.01 6.73-1.35 11.11-11.06 1.22-16.43-1.35-0.82 0.74-4.51 0.76-4.55 4.95-8.64 13.73-2.88 22.43-10.45 1.61-1.5 2.84-2.76 4.12-4.53v19.63c-10.21 5.36-4.72 17.92 4.05 16.27 6.63-1.23 10.97-11.45 0.59-16.28 0.03-3.47-0.08-19.92 0.16-19.63 1.11 1.55 2.17 3.14 3.48 4.54 8.69 8.79 18.47 1.27 23.32 11.02 0.67 1.22 0.3 4.27-0.15 4.49-7.53 3.87-5.86 14.28 2.18 15.89 8.5 1.66 13.73-10.9 5.19-15.72-3.42-1.84-0.63-5.33-5.7-10.47-5.96-6.06-12.32-3.78-17.08-5.59-4.09-1.53-6.54-5.18-6.29-5.43 0.22-0.22 24.55 6.91 43.22-14.41 3.82-4.84 7.74-10.2 9.47-16.23 0.01-0.96-7.34-3.65-14.62-4.51v-20.6c-8.38 0.38-16.99-0.99-25.2 1.2-0.73 0.2-1.71 0.43-1.74 0.38-4.26-8.9-11.5-16-18.06-23.25m9.12 17.49c2.24 2.78 4.35 5.82 4.66 7.72 0.17 1.06-1.08 0.74-5.3 5.32-8.84 9.51-8.16 21.63-8.5 21.34-3.43-17.8-5.92-16.88-13.92-26.24 2.91-7.3 8.9-12.96 13.98-18.79m-24.17 19.67c1.46 0.45 3.24 0.53 4.41 1.59-1.57 7.36-0.34 14.39 1.06 21.65-1.71-0.53-8.68-6.49-21.72-7.5v-16.14c5.38 0.35 9.84-0.85 15.17 0.16m65.77 15.73c-1.37 0.5-9.12 0.18-19.14 5.92-1.23 0.71-2.33 1.26-2.4 1.2 3.34-16.15 0.34-18.66 0.79-20.98 0.58-1.79 7.99-1.97 8.36-1.98 4.19-0.07 8.39-0.05 12.59-0.06m-25.03 5.63c2.92 13.59-5.05 24.39-11.33 30.51-0.35-9.95 0.4-26.27 10.86-31.69m-28.45 1.97c7.76 7.75 9.06 19.24 8.61 29.72-14.31-13.23-10.59-31.49-10.56-31.49m55.18 17.16c-3.52 19.54-15.76 22.61-33.22 22.32 5.74-13.4 19.09-21.86 33.19-23.45 0.11-0.02 0.09 0.55 0.04 0.96m-83.54 5.23c3.07 12.37 15.29 18.99 14.41 19.25-12.76-3.72-21.69-15-25.19-23.35 2.99-1.21 6.18-1.77 9.39-1.92m8.65 0.41c17.46 3 27.77 19.13 28.77 22.88-16.7 0.78-29.72-4.91-32.42-23.17 0 0 1.34 0 2.43 0.13m86.54 0.42c1.35 0.24 4.55 1.07 4.74 1.27 0.12 0.11-1.41 2.94-2.4 4.6-8.44 14.22-21.37 18.47-21.81 18.34-0.38-0.17 10.75-6.06 14.27-19.92 0.38-1.57 0.67-3.16 0.96-4.74m-75.31 56c3 1.9 1.51 7.59-2.86 6.89-4.87-0.77-3.54-8.67 1.76-7.37m65.48 0.28c3.68 1.81 1.88 7.86-2.44 7.14-5.49-0.93-2.81-9.81 2.44-7.14m-31.37 1.04c3.31 4.38-2.57 8.05-5.3 5.08-3.31-3.6 1.54-8.07 4.75-5.65" fill-rule="evenodd"/>
4+</svg>
public/img/scale.svg+2 -60
@@ -1,60 +1,2 @@
11<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-<!-- Created with Inkscape (http://www.inkscape.org/) -->
2+<svg id="svg1" width="8.28mm" height="9.4349mm" version="1.1" viewBox="0 0 8.28 9.4349" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path id="path9" d="m4.14 0c-0.05 0.08-0.1 0.16-0.14 0.24-0.07 0.11-0.17 0.19-0.29 0.22-0.2 0.04-0.39 0.09-0.58 0.13 0.19 0.05 0.38 0.1 0.58 0.14 0.12 0.03 0.22 0.11 0.29 0.22 0.04 0.08 0.09 0.16 0.14 0.24 0.04-0.08 0.09-0.16 0.13-0.24 0.07-0.11 0.17-0.19 0.29-0.22 0.2-0.04 0.39-0.09 0.59-0.14-0.2-0.04-0.39-0.09-0.59-0.14-0.12-0.02-0.22-0.1-0.29-0.21-0.04-0.08-0.09-0.16-0.13-0.24zm-1.42 0.81c-0.04 0.08-0.09 0.16-0.14 0.24-0.06 0.11-0.16 0.19-0.29 0.22-0.19 0.05-0.38 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.05-0.09 0.09-0.17 0.14-0.25 0.06-0.11 0.17-0.18 0.29-0.21 0.19-0.05 0.39-0.1 0.58-0.14-0.19-0.05-0.39-0.09-0.58-0.14-0.12-0.03-0.23-0.11-0.29-0.22-0.05-0.08-0.09-0.16-0.14-0.24zm2.82 0c-0.04 0.08-0.09 0.16-0.14 0.24-0.06 0.11-0.16 0.19-0.29 0.22-0.19 0.05-0.38 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.05-0.09 0.09-0.17 0.14-0.25 0.06-0.11 0.17-0.18 0.29-0.21 0.19-0.05 0.39-0.1 0.58-0.14-0.19-0.05-0.39-0.09-0.58-0.14-0.12-0.03-0.23-0.11-0.29-0.22-0.05-0.08-0.09-0.16-0.14-0.24zm-1.4 0.83c-0.05 0.07-0.1 0.15-0.14 0.23-0.07 0.11-0.17 0.19-0.29 0.22-0.2 0.05-0.39 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.04-0.09 0.09-0.17 0.13-0.25 0.07-0.11 0.17-0.18 0.29-0.21 0.2-0.05 0.39-0.1 0.59-0.14-0.2-0.05-0.39-0.09-0.59-0.14-0.12-0.03-0.22-0.11-0.29-0.22-0.04-0.08-0.09-0.16-0.13-0.23zm-3.16 0.14-0.98 0.55v4.8c1.29 0.74 2.58 1.49 3.87 2.23 0.25 0.14 0.46 0.05 0.55 0 1.29-0.75 2.57-1.49 3.86-2.23v-4.8l-0.98-0.55c-1.11 0.86-2.96 0.79-3.16 2.4-0.2-1.61-2.04-1.55-3.16-2.4z"/></svg>
3-
4-<svg
5- width="8.2832384mm"
6- height="9.4351206mm"
7- viewBox="0 0 8.2832384 9.4351206"
8- version="1.1"
9- id="svg1"
10- xml:space="preserve"
11- inkscape:version="1.3 (0e150ed, 2023-07-21)"
12- sodipodi:docname="scale.svg"
13- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
14- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
15- xmlns="http://www.w3.org/2000/svg"
16- xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
17- id="namedview1"
18- pagecolor="#ffffff"
19- bordercolor="#000000"
20- borderopacity="0.25"
21- inkscape:showpageshadow="2"
22- inkscape:pageopacity="0.0"
23- inkscape:pagecheckerboard="0"
24- inkscape:deskcolor="#d1d1d1"
25- inkscape:document-units="mm"
26- showgrid="false"
27- inkscape:zoom="1.0289025"
28- inkscape:cx="8.7471843"
29- inkscape:cy="8.2612296"
30- inkscape:window-width="1280"
31- inkscape:window-height="688"
32- inkscape:window-x="0"
33- inkscape:window-y="25"
34- inkscape:window-maximized="1"
35- inkscape:current-layer="layer1" /><defs
36- id="defs1" /><g
37- inkscape:label="Layer 1"
38- inkscape:groupmode="layer"
39- id="layer1"
40- transform="translate(-88.746886,-143.1532)"><g
41- id="g1"
42- transform="matrix(0.26458333,0,0,0.26458333,88.077577,143.1532)"><path
43- d="m 14.3423,8.42148 0.0218,-0.00723 2.1725,-0.51502 c 0.4642,-0.10879 0.8631,-0.40619 1.1025,-0.81965 l 0.5041,-0.87044 0.0146,-0.02902 0.0145,0.02902 0.5041,0.87044 c 0.2394,0.41346 0.6383,0.71086 1.1025,0.81965 l 2.1725,0.51502 0.0254,0.01087 H 21.9514 L 19.7789,8.94737 C 19.3147,9.0562 18.9158,9.35359 18.6764,9.76705 l -0.5041,0.87045 -0.0145,0.029 -0.0146,-0.029 -0.5041,-0.87045 C 17.3997,9.35359 17.0008,9.0562 16.5366,8.94737 L 14.3641,8.43239 14.3423,8.42512 Z"
44- id="path10" /><path
45- d="M 14.3423,2.245 14.3641,2.23775 16.5366,1.72274 C 17.0008,1.61393 17.3997,1.31653 17.6391,0.903078 L 18.1432,0.0290146 18.1578,0 18.1723,0.0290146 18.6764,0.899453 C 18.9158,1.31291 19.3147,1.61031 19.7789,1.71911 L 21.9514,2.23412 21.9768,2.245 H 21.9514 L 19.7789,2.76726 C 19.3147,2.87607 18.9158,3.17347 18.6764,3.58692 L 18.1723,4.45738 18.1578,4.48639 18.1432,4.45738 17.6391,3.58692 C 17.3997,3.17347 17.0008,2.87607 16.5366,2.76726 L 14.3641,2.25225 Z"
46- id="path11" /><path
47- d="M 18.183,15.7998 C 17.9328,13.7832 16.754,11.9807 14.9697,10.9507 11.749,9.03938 8.96364,8.82176 6.23265,6.73999 L 2.52967,8.79638 18.183,17.8344 33.8364,8.79638 30.1334,6.74359 c -2.731,2.08181 -5.5164,2.29943 -8.737,4.21071 -1.7844,1.0301 -2.9631,2.8326 -3.2134,4.8491 z"
48- id="path12" /><path
49- d="M 18.183,35.6603 C 17.824,35.653 17.4649,35.5551 17.1458,35.3701 L 2.52967,26.9269 V 8.79646 L 17.1458,17.236 c 0.3227,0.185 0.6782,0.2829 1.0372,0.2902 0.3591,-0.0073 0.7181,-0.1052 1.0373,-0.2902 L 33.8364,8.79646 V 26.9306 l -14.6161,8.4395 c -0.3228,0.185 -0.6782,0.2829 -1.0373,0.2902 z"
50- id="path13" /><path
51- d="m 18.183,35.6603 c 0.3591,-0.0073 0.7181,-0.1052 1.0373,-0.2902 l 14.616,-8.4395 V 8.79646 L 19.2203,17.236 c -0.3228,0.185 -0.6782,0.2829 -1.0373,0.2902"
52- id="path14" /><path
53- d="M 18.183,17.5262 C 17.824,17.5189 17.4649,17.421 17.1458,17.236 L 2.52967,8.79646 V 26.9306 l 14.61613,8.4395 c 0.3227,0.185 0.6782,0.2829 1.0372,0.2902"
54- id="path15" /><path
55- d="M 18.1831,17.5262 C 17.824,17.5189 17.465,17.421 17.1458,17.236 l -1.4507,-0.8378 v 18.1341 l 1.4507,0.8378 c 0.3228,0.185 0.6782,0.2829 1.0373,0.2902 0.359,-0.0073 0.7181,-0.1052 1.0372,-0.2902 l 1.4508,-0.8378 V 16.3946 l -1.4508,0.8378 c -0.3227,0.1849 -0.6782,0.2829 -1.0372,0.2901 z"
56- id="path16" /><path
57- d="M 9,5.32055 9.02174,5.31328 11.1942,4.79826 c 0.4643,-0.10879 0.8632,-0.40618 1.1026,-0.81964 l 0.5041,-0.87044 0.0145,-0.02902 0.0145,0.02902 0.5041,0.87044 c 0.2394,0.41346 0.6384,0.71085 1.1026,0.81964 l 2.1725,0.51502 0.0253,0.01087 H 16.6091 L 14.4366,5.84644 C 13.9724,5.95523 13.5734,6.25263 13.334,6.66609 L 12.8299,7.53653 12.8154,7.56555 12.8009,7.53653 12.2968,6.66609 C 12.0574,6.25263 11.6585,5.95523 11.1942,5.84644 L 9.02174,5.33142 9,5.32415 Z"
58- id="path17" /><path
59- d="m 19.6593,5.32055 0.0218,-0.00727 2.1724,-0.51502 c 0.4642,-0.10879 0.8632,-0.40618 1.1026,-0.81964 l 0.5041,-0.87044 0.0145,-0.02902 0.0145,0.02902 0.5042,0.87044 c 0.2393,0.41346 0.6383,0.71085 1.1025,0.81964 l 2.1724,0.51502 0.0255,0.01087 H 27.2683 L 25.0959,5.84644 C 24.6317,5.95523 24.2327,6.25263 23.9934,6.66609 L 23.4892,7.53653 23.4747,7.56555 23.4602,7.53653 22.9561,6.66609 C 22.7167,6.25263 22.3177,5.95523 21.8535,5.84644 L 19.6811,5.33142 19.6593,5.32415 Z"
60- id="path18" /></g></g></svg>
public/img/textgenerationwebui.svg+5 -86
@@ -1,88 +1,7 @@
11<?xml version="1.0" standaloneencoding="noUTF-8"?>
2-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
2+<svg id="svg1" width="1281.8pt" height="1215.8pt" preserveAspectRatio="xMidYMid" version="1.0" viewBox="0 0 1281.8 1215.8" xmlns="http://www.w3.org/2000/svg">
3- "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
3+ <metadata id="metadata1">Created by potrace 1.15, written by Peter Selinger 2001-2017</metadata>
4-<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
4+ <g id="g1" transform="matrix(.1 0 0 -.1 1.9156 1216.5)">
5- width="1280.000000pt" height="1217.000000pt" viewBox="0 0 1280.000000 1217.000000"
5+ <path id="path1" d="m6998 11572c-96-258-333-414-533-587-370-329-494-412-1390-435-874 36-1595-534-2206-1104-62 39-142 56-199 104 0 9 262 294 464 632 74 121 192 285-66 247-417-111-1220-1151-572-305 100 137 564 539 318 683-471 46-1109-1370-1236-1102 23 505 514 977 207 1300-445 44-287-954-795-1541-1448-1651-1018-2700-654-2758 335-53 734 316 734 261 0-180-204-689-108-846 752-1155 2134-1636 3377-2057 99-35-1443-411-1876-970-121-157-587-192-679-346-256-719 2022-1610 3117-1657 812 100 1428-660 2088-1018 130-59 213-134 286 50-220 493-1110 853-956 1021 132 137 1731-15 1731 90 0 8-16 33-35 56-357 414-1665-11-1935 283 0 119 972 340 1341 597 121 97 56 186-113 194-574-4-1043-309-1478-410-184-41-447-69-725 3 83 35 240 88 530 225 448 212 563 317 748 487-10 44-16 92-41 131-101 125-621-82-1438-572-133-80-241-138-256-138-49 0-615 99-802 140-338 75-525 153-473 231 22 29 245 92 467 133 435 77 883 115 1287 305 216 102 386 272 568 422 257 211 446 400 314 650-170 319-510 265-909 653-388 377-556 379-810 585 78 10 157 17 235 26 2709 261 3344 2357 3261 1940-125-518-151-1060-65-1343 200-639 1746-555 2003-537 674 40 1167-320 1761-550 726-269 856-137 765 9-161 263-1223 582-1169 597 508 133 2142 769 1614 1010-297 136-1465-492-1435-455 91 57 965 1014 655 1059-597-178-1028-736-1477-1159-75 29-155 45-228 78 0 20 239 507 275 701 27 105 141 556-120 415-211-131-498-997-534-986-290 78-585 141-886 150-236-1-496 47-461 358 71 337 157 167 490 943 217 528-107 470 77 605 459 340 1060 1370 1115 1494 351 733 1106 1835 145 2401-1027 452-1067-31-2106 150-558 97-937 83-1207-543z"/>
6- preserveAspectRatio="xMidYMid meet">
7-<metadata>
8-Created by potrace 1.15, written by Peter Selinger 2001-2017
9-</metadata>
10-<g transform="translate(0.000000,1217.000000) scale(0.100000,-0.100000)" stroke="none">
11-<path d="M7704 12160 c-84 -17 -256 -71 -307 -96 -186 -94 -274 -202 -399
12--492 -32 -74 -72 -152 -90 -175 -80 -100 -141 -163 -211 -215 -41 -32 -145
13--120 -232 -197 -228 -203 -324 -268 -491 -331 -160 -60 -436 -92 -899 -104
14--428 -11 -478 -15 -585 -50 -499 -163 -1061 -520 -1543 -979 l-78 -75 -100 49
15-c-54 27 -99 52 -99 55 0 3 35 46 77 95 131 154 287 370 387 537 77 130 96 168
16-96 200 0 36 -2 38 -25 32 -15 -4 -39 0 -60 10 -29 14 -42 15 -77 5 -147 -39
17--405 -230 -705 -523 -79 -76 -143 -136 -143 -132 0 3 37 49 81 102 45 54 132
18-165 195 248 64 86 132 166 159 186 95 72 107 84 129 129 13 25 33 91 45 146
19-12 55 29 120 38 144 22 59 11 76 -53 78 -27 0 -63 4 -80 8 -23 5 -44 0 -85
20--20 -78 -39 -264 -232 -441 -455 -80 -102 -192 -243 -249 -315 -135 -171 -213
21--258 -273 -306 -57 -46 -91 -50 -108 -14 -16 35 15 222 42 250 10 11 35 65 54
22-120 19 55 69 182 110 283 42 100 75 186 74 190 -1 4 4 25 11 46 14 39 12 113
23--5 328 l-7 77 -72 6 c-80 8 -99 2 -153 -49 -101 -93 -192 -314 -302 -728 -110
24--413 -138 -498 -195 -589 -13 -20 -79 -99 -145 -175 -206 -235 -338 -412 -628
25--844 -188 -278 -318 -656 -353 -1020 -16 -171 -1 -459 33 -638 9 -50 15 -62
26-29 -60 11 2 31 -19 60 -62 60 -88 105 -118 205 -134 140 -22 189 -10 460 118
27-49 24 131 68 182 99 51 31 92 51 92 44 0 -7 -34 -185 -75 -396 l-74 -383 41
28--67 c23 -36 53 -82 67 -101 l25 -35 -33 30 c-51 46 -34 16 56 -98 105 -132
29-471 -492 628 -618 130 -105 509 -381 670 -489 384 -257 905 -493 1135 -515 49
30--4 137 -27 255 -65 192 -63 338 -106 475 -140 47 -11 92 -23 99 -26 23 -8 -73
31--49 -282 -120 -492 -168 -840 -315 -1062 -447 -330 -197 -432 -274 -532 -403
32--27 -35 -57 -53 -210 -124 -124 -57 -199 -86 -248 -95 -118 -21 -186 -60 -221
33--127 -14 -28 -17 -124 -5 -168 4 -15 1 -43 -10 -70 -19 -51 -16 -70 11 -70 14
34-0 21 -10 25 -37 4 -21 12 -49 18 -63 21 -43 179 -193 257 -243 41 -27 134 -87
35-205 -133 209 -137 484 -268 770 -367 55 -19 163 -62 240 -94 77 -33 201 -81
36-275 -107 74 -27 191 -68 260 -93 179 -65 382 -113 721 -172 271 -47 301 -50
37-350 -40 36 8 116 10 239 6 276 -8 386 -41 584 -172 53 -35 153 -111 222 -169
38-145 -121 277 -222 384 -293 143 -96 584 -356 659 -390 42 -19 87 -40 101 -48
39-14 -7 41 -16 62 -20 45 -9 93 23 107 69 5 17 12 39 16 49 5 12 2 17 -8 17 -8
40-0 -29 21 -45 47 -30 46 -189 207 -322 326 l-65 59 63 -47 c34 -26 110 -92 168
41--147 93 -89 129 -111 129 -82 0 5 4 16 9 23 7 11 -13 36 -81 100 -108 103
42--189 162 -433 316 -185 117 -268 185 -336 277 -19 25 -39 60 -45 78 -10 27 -9
43-34 10 54 27 28 108 40 386 57 219 13 1193 2 1213 -13 27 -22 132 15 132 46 0
44-8 -16 33 -35 56 -19 23 -35 50 -35 60 0 18 -46 35 -55 19 -3 -4 -31 1 -63 12
45--149 52 -318 76 -737 105 -235 16 -261 16 -435 1 -102 -10 -214 -17 -250 -17
46--114 0 -360 70 -360 103 0 16 32 46 80 75 45 26 325 127 407 146 34 8 75 23
47-90 34 60 42 161 85 358 152 235 80 316 118 406 190 81 65 86 74 65 117 -9 18
48--16 44 -16 57 l0 23 -162 -3 c-129 -1 -190 -7 -295 -28 -222 -44 -459 -119
49--758 -238 -238 -95 -287 -112 -425 -144 -169 -38 -325 -50 -487 -36 -76 7
50--160 18 -188 26 l-50 13 28 12 c16 6 30 11 32 11 12 0 345 143 470 202 332
51-157 506 266 675 420 l73 67 -12 55 c-7 31 -20 65 -29 76 -101 125 -621 -82
52--1438 -572 -133 -80 -241 -138 -256 -138 -49 0 -615 99 -802 140 -252 56 -364
53-92 -411 135 -20 19 -45 37 -56 40 -21 7 -24 32 -6 56 22 29 245 92 467 133 47
54-8 213 36 370 61 476 76 694 134 917 244 131 65 236 136 357 244 45 40 140 120
55-211 178 161 132 280 255 324 339 33 62 34 65 28 156 -5 80 -10 102 -38 155
56--31 58 -117 153 -186 206 -18 14 -116 65 -219 114 -243 116 -360 193 -504 333
57--188 183 -293 261 -565 422 -82 48 -171 105 -198 125 l-47 38 42 5 c24 3 111
58-12 193 21 493 52 979 180 1545 407 263 106 500 246 735 435 126 101 364 329
59-502 482 101 111 328 407 426 556 40 61 57 80 53 60 -125 -518 -151 -1060 -65
60--1343 25 -80 96 -214 139 -262 18 -19 27 -27 20 -17 -7 9 -10 17 -8 17 3 0 43
61--25 89 -55 171 -113 412 -161 1029 -206 451 -32 470 -33 734 -14 271 19 415
62-17 541 -10 110 -23 118 -29 72 -51 -41 -20 -43 -23 -24 -47 10 -14 18 -14 53
63--5 34 9 53 8 105 -6 90 -25 320 -118 479 -193 230 -110 431 -199 535 -238 124
64--46 312 -95 385 -99 30 -2 83 -9 117 -17 61 -13 62 -13 65 -48 2 -30 7 -37 28
65--39 51 -8 174 54 178 89 1 8 5 31 8 51 5 28 1 46 -16 72 -82 134 -460 330
66--1014 527 -140 50 -173 65 -155 70 96 28 461 151 584 196 326 121 639 263 813
67-371 42 26 99 58 125 71 114 55 208 178 193 251 -9 45 -55 100 -101 121 -162
68-74 -676 -86 -1330 -413 -73 -37 -114 -53 -105 -42 8 11 56 66 105 124 50 58
69-110 130 135 160 65 82 184 203 275 280 76 64 84 74 145 194 54 107 65 136 68
70-190 6 87 -5 103 -73 111 -67 9 -106 -5 -219 -76 -219 -139 -550 -418 -1035
71--873 l-223 -210 -114 38 c-63 20 -114 39 -114 40 0 2 14 34 31 72 18 38 61
72-139 96 224 36 85 80 191 98 235 19 44 41 121 50 170 9 50 25 132 36 183 10 51
73-19 108 19 128 0 45 -23 100 -48 113 -32 17 -92 13 -127 -9 -111 -69 -302 -428
74--489 -918 -22 -56 -32 -72 -45 -68 -53 17 -275 68 -386 90 -242 47 -310 55
75--500 60 -119 3 -209 10 -240 19 -107 30 -209 104 -164 119 16 5 -5 45 -43 80
76--32 30 -33 46 -14 140 25 118 65 191 296 548 48 74 101 169 118 210 16 41 50
77-125 76 185 84 204 97 320 50 450 -40 112 -41 105 27 155 85 63 378 365 475
78-490 101 130 244 343 354 526 46 76 124 201 172 278 49 76 100 166 114 200 28
79-66 55 121 281 566 218 431 289 627 323 890 6 47 18 125 27 173 14 75 14 101 4
80-165 -19 128 -101 273 -210 375 -28 26 -51 50 -51 52 0 3 14 5 30 5 17 0 30 3
81-30 8 -1 22 -136 101 -289 167 -216 95 -278 115 -466 153 -304 63 -465 66 -712
82-13 -355 -76 -560 -80 -928 -16 -304 53 -410 63 -501 45z m2941 -1516 c-15 -49
83--44 -132 -67 -184 -59 -140 -200 -441 -204 -437 -2 2 28 75 66 162 69 155 185
84-443 210 518 24 75 20 29 -5 -59z m-3225 -9228 c92 -8 187 -23 215 -32 l50 -18
85--35 -7 c-87 -19 -329 -31 -406 -21 -443 60 -615 88 -588 94 48 12 586 0 764
86--16z"/>
876 </g>
887</svg>
public/img/workers_ai.svg+9 -0
@@ -0,0 +1,9 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="64" height="64">
2+ <path d="M8.16 23h21.177v-5.86l-4.023-2.307-.694-.3-16.46.113z" fill="none" />
3+ <path
4+ d="M22.012 22.222c.197-.675.122-1.294-.206-1.754-.3-.422-.807-.666-1.416-.694l-11.545-.15c-.075 0-.14-.038-.178-.094s-.047-.13-.028-.206c.038-.113.15-.197.272-.206l11.648-.15c1.38-.066 2.88-1.182 3.404-2.55l.666-1.735a.38.38 0 0 0 .02-.225c-.75-3.395-3.78-5.927-7.4-5.927-3.34 0-6.17 2.157-7.184 5.15-.657-.488-1.5-.75-2.392-.666-1.604.16-2.9 1.444-3.048 3.048a3.58 3.58 0 0 0 .084 1.191A4.84 4.84 0 0 0 0 22.1c0 .234.02.47.047.703.02.113.113.197.225.197H21.58a.29.29 0 0 0 .272-.206l.16-.572z"
5+ />
6+ <path
7+ d="M25.688 14.803l-.32.01c-.075 0-.14.056-.17.13l-.45 1.566c-.197.675-.122 1.294.206 1.754.3.422.807.666 1.416.694l2.457.15c.075 0 .14.038.178.094s.047.14.028.206c-.038.113-.15.197-.272.206l-2.56.15c-1.388.066-2.88 1.182-3.404 2.55l-.188.478c-.038.094.028.188.13.188h8.797a.23.23 0 0 0 .225-.169A6.41 6.41 0 0 0 32 21.106a6.32 6.32 0 0 0-6.312-6.302"
8+ />
9+</svg>
9 \ No newline at end of file
public/index.html+147 -106
@@ -697,7 +697,7 @@
697697 </span>
698698 </div>
699699 </div>
700700 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,minimax,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai,workers_ai">
701701 <div class="range-block-title" data-i18n="Temperature">
702702 Temperature
703703 </div>
@@ -710,7 +710,7 @@
710710 </div>
711711 </div>
712712 </div>
713713 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes,workers_ai">
714714 <div class="range-block-title" data-i18n="Frequency Penalty">
715715 Frequency Penalty
716716 </div>
@@ -723,7 +723,7 @@
723723 </div>
724724 </div>
725725 </div>
726726 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes,workers_ai">
727727 <div class="range-block-title" data-i18n="Presence Penalty">
728728 Presence Penalty
729729 </div>
@@ -736,7 +736,7 @@
736736 </div>
737737 </div>
738738 </div>
739739 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt,workers_ai">
740740 <div class="range-block-title" data-i18n="Top K">
741741 Top K
742742 </div>
@@ -749,7 +749,7 @@
749749 </div>
750750 </div>
751751 </div>
752752 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,minimax,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai,workers_ai">
753753 <div class="range-block-title" data-i18n="Top P">
754754 Top P
755755 </div>
@@ -762,7 +762,7 @@
762762 </div>
763763 </div>
764764 </div>
765765 <div class="range-block" data-source="openrouter,nanogpt,chutes,workers_ai">
766766 <div class="range-block-title" data-i18n="Repetition Penalty">
767767 Repetition Penalty
768768 </div>
@@ -986,7 +986,7 @@
986986 </div>
987987 </div>
988988 </div>
989989 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai,workers_ai">
990990 <div class="range-block-title justifyLeft" data-i18n="Seed">
991991 Seed
992992 </div>
@@ -1269,8 +1269,8 @@
12691269 <span data-i18n="Top K">Top K</span>
12701270 <div class="fa-solid fa-circle-info opacity50p" title="Top K sets a maximum amount of top tokens that can be chosen from.&#13;E.g Top K is 20, this means only the 20 highest ranking tokens will be kept (regardless of their probabilities being diverse or limited).&#13;Set to 0 (or -1, depending on your backend) to disable." data-i18n="[title]Top_K_desc"></div>
12711271 </small>
12721272 <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="200500" step="1">
12731273 <input class="neo-range-input" type="number" min="-1" max="200500" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui">
12741274 </div>
12751275 <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
12761276 <small>
@@ -1400,7 +1400,7 @@
14001400 <input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="max_tokens_second_textgenerationwebui" id="max_tokens_second_counter_textgenerationwebui">
14011401 </div>
14021402
14031403 <div data-tg-type="koboldcpp, llamacpp, ooba, tabby" id="adaptive_p_block" class="wide100p">
14041404 <h4 class="wide100p textAlignCenter">
14051405 <label data-i18n="Adaptive-P">Adaptive-P</label>
14061406 <a href="https://github.com/MrJackSpade/adaptive-p-docs" target="_blank">
@@ -1831,6 +1831,7 @@
18311831 <div data-name="tfs" draggable="true"><span>Tail Free Sampling</span><small></small></div>
18321832 <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>
18331833 <div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>
1834+ <div data-name="adaptive_p" draggable="true"><span>Adaptive-P</span><small></small></div>
18341835 <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div>
18351836 <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div>
18361837 <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div>
@@ -1996,7 +1997,7 @@
19961997 </b>
19971998 </div>
19981999 </div>
19992000 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,minimax,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai,nanogpt,workers_ai">
20002001 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
20012002 <input id="openai_function_calling" type="checkbox" />
20022003 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -2010,8 +2011,22 @@
20102011 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
20112012 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
20122013 </div>
2014+ <div id="tool_call_recurse_limit_block" class="range-block wide100p">
2015+ <div class="range-block-title">
2016+ <small data-i18n="Tool Call Recurse Limit">Tool Call Recurse Limit</small>
2017+ <div class="fa-solid fa-circle-info opacity50p margin5" data-i18n="[title]Maximum number of tool call recursion steps per generation." title="Maximum number of tool call recursion steps per generation."></div>
2018+ </div>
2019+ <div class="range-block-range-and-counter">
2020+ <div class="range-block-range">
2021+ <input type="range" id="tool_call_recurse_limit" name="tool_call_recurse_limit" min="1" max="50" step="1">
2022+ </div>
2023+ <div class="range-block-counter">
2024+ <input type="number" min="1" max="50" step="1" data-for="tool_call_recurse_limit" id="tool_call_recurse_limit_counter">
2025+ </div>
20132026 </div>
2014- <div class="range-block" data-source="openrouter">
2027+ </div>
2028+ </div>
2029+ <div class="range-block" data-source="openrouter,custom">
20152030 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10">
20162031 <div class="flex-container oneline-dropdown">
20172032 <label for="tool_reasoning_mode" data-i18n="Interleaved Thinking">
@@ -2035,7 +2050,7 @@
20352050 </em>
20362051 </div>
20372052 </div>
20382053 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow,chutes,workers_ai">
20392054 <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand">
20402055 <input id="openai_media_inlining" type="checkbox" />
20412056 <span data-i18n="Send inline media">Send inline media</span>
@@ -2059,7 +2074,7 @@
20592074 <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong>
20602075 <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong>
20612076 </div>
20622077 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes,makersuite,vertexai,workers_ai">
20632078 <div class="flex-container oneline-dropdown">
20642079 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
20652080 Inline Image Quality
@@ -2120,7 +2135,7 @@
21202135 </div>
21212136 </div>
21222137 </div>
21232138 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,chutes,azure_openai,nanogpt,zai,workers_ai">
21242139 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
21252140 <input id="openai_show_thoughts" type="checkbox" />
21262141 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2129,12 +2144,12 @@
21292144 <span data-i18n="Allows the model to return its thinking process.">
21302145 Allows the model to return its thinking process.
21312146 </span>
21322147 <strong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai,moonshot,openrouter,deepseek">
21332148 This setting affects visibility only.
21342149 </strong>
21352150 </div>
21362151 </div>
21372152 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai,chutes,nanogpt,deepseek">
21382153 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
21392154 <label for="openai_reasoning_effort">
21402155 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2445,7 +2460,8 @@
24452460 <small data-i18n="Click Authorize below or get the key from">
24462461 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
24472462 <br>
24482463 <a href="https://openrouter.ai/accountsettings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
2464+ <span class="openrouter_credits_display marginLeft5"></span>
24492465 </div>
24502466 <div class="flex-container">
24512467 <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
@@ -2894,6 +2910,7 @@
28942910 <option value="azure_openai">Azure OpenAI</option>
28952911 <option value="chutes">Chutes</option>
28962912 <option value="claude">Claude</option>
2913+ <option value="workers_ai">Cloudflare Workers AI</option>
28972914 <option value="cohere">Cohere</option>
28982915 <!-- Temporarily disabled. -->
28992916 <!-- <option value="cometapi">CometAPI</option> -->
@@ -2904,6 +2921,7 @@
29042921 <option value="makersuite">Google AI Studio</option>
29052922 <option value="vertexai">Google Vertex AI</option>
29062923 <option value="mistralai">MistralAI</option>
2924+ <option value="minimax">MiniMax</option>
29072925 <option value="moonshot">Moonshot AI</option>
29082926 <option value="nanogpt">NanoGPT</option>
29092927 <option value="openrouter">OpenRouter</option>
@@ -3028,8 +3046,17 @@
30283046 <div>
30293047 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>
30303048 <select id="model_openai_select">
3049+ <optgroup label="GPT-5.5">
3050+ <option value="gpt-5.5">gpt-5.5</option>
3051+ <option value="gpt-5.5-2026-04-23">gpt-5.5-2026-04-23</option>
3052+ </optgroup>
30313053 <optgroup label="GPT-5.4">
30323054 <option value="gpt-5.4">gpt-5.4</option>
3055+ <option value="gpt-5.4-2026-03-05">gpt-5.4-2026-03-05</option>
3056+ <option value="gpt-5.4-mini">gpt-5.4-mini</option>
3057+ <option value="gpt-5.4-mini-2026-03-17">gpt-5.4-mini-2026-03-17</option>
3058+ <option value="gpt-5.4-nano">gpt-5.4-nano</option>
3059+ <option value="gpt-5.4-nano-2026-03-17">gpt-5.4-nano-2026-03-17</option>
30333060 </optgroup>
30343061 <optgroup label="GPT-5.3">
30353062 <option value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option>
@@ -3143,6 +3170,7 @@
31433170 <h4 data-i18n="Claude Model">Claude Model</h4>
31443171 <select id="model_claude_select">
31453172 <optgroup label="Versions">
3173+ <option value="claude-opus-4-7">claude-opus-4-7</option>
31463174 <option value="claude-opus-4-6">claude-opus-4-6</option>
31473175 <option value="claude-opus-4-5">claude-opus-4-5</option>
31483176 <option value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option>
@@ -3176,7 +3204,8 @@
31763204 <small data-i18n="Click Authorize below or get the key from">
31773205 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
31783206 <br>
31793207 <a href="https://openrouter.ai/accountsettings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
3208+ <span class="openrouter_credits_display marginLeft5"></span>
31803209 </div>
31813210 <div class="flex-container">
31823211 <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">
@@ -3195,36 +3224,6 @@
31953224 <input id="openrouter_use_fallback" type="checkbox" />
31963225 <span data-i18n="Allow fallback models">Allow fallback models</span>
31973226 </label>
3198- <div class="marginTopBot5">
3199- <div class="inline-drawer wide100p">
3200- <div class="inline-drawer-toggle inline-drawer-header">
3201- <b data-i18n="OpenRouter Model Sorting">OpenRouter Model Sorting</b>
3202- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3203- </div>
3204- <div class="inline-drawer-content m-b-1">
3205- <div class="marginTopBot5">
3206- <label for="openrouter_sort_models" class="checkbox_label">
3207- <select id="openrouter_sort_models">
3208- <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3209- <option data-i18n="Price" value="pricing.prompt">Price (cheapest)</option>
3210- <option data-i18n="Context Size" value="context_length">Context Size</option>
3211- </select>
3212- </label>
3213- </div>
3214- <div class="marginTopBot5">
3215- <label for="openrouter_group_models" class="checkbox_label">
3216- <input id="openrouter_group_models" type="checkbox" />
3217- <span data-i18n="Group by vendors">Group by vendors</span>
3218- </label>
3219- <div class="toggle-description justifyLeft wide100p">
3220- <span data-i18n="Group by vendors Description">
3221- Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting.
3222- </span>
3223- </div>
3224- </div>
3225- </div>
3226- </div>
3227- </div>
32283227 <div>
32293228 <h4>
32303229 <span data-i18n="Model Providers">Model Providers</span>
@@ -3344,6 +3343,8 @@
33443343 <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>
33453344 </optgroup>
33463345 <optgroup label="Gemma">
3346+ <option value="gemma-4-31b-it">gemma-4-31b-it</option>
3347+ <option value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option>
33473348 <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>
33483349 <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option>
33493350 <option value="gemma-3-27b-it">gemma-3-27b-it</option>
@@ -3566,6 +3567,32 @@
35663567 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
35673568 </select>
35683569 </div>
3570+ <div id="minimax_form" data-source="minimax">
3571+ <h4 data-i18n="MiniMax API Key">MiniMax API Key</h4>
3572+ <div class="flex-container">
3573+ <input id="api_key_minimax" name="api_key_minimax" class="text_pole flex1" value="" type="text" autocomplete="off">
3574+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_minimax"></div>
3575+ </div>
3576+ <div data-for="api_key_minimax" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3577+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3578+ </div>
3579+ <h4 data-i18n="MiniMax Endpoint">MiniMax Endpoint</h4>
3580+ <select id="minimax_endpoint">
3581+ <option value="global" data-i18n="Global (minimax.io)">Global (minimax.io)</option>
3582+ <option value="cn" data-i18n="China (minimaxi.com)">China (minimaxi.com)</option>
3583+ </select>
3584+ <h4 data-i18n="MiniMax Model">MiniMax Model</h4>
3585+ <select id="model_minimax_select">
3586+ <option value="MiniMax-M2.7">MiniMax-M2.7</option>
3587+ <option value="MiniMax-M2.7-highspeed">MiniMax-M2.7-highspeed</option>
3588+ <option value="MiniMax-M2.5">MiniMax-M2.5</option>
3589+ <option value="MiniMax-M2.5-highspeed">MiniMax-M2.5-highspeed</option>
3590+ <option value="MiniMax-M2.1">MiniMax-M2.1</option>
3591+ <option value="MiniMax-M2.1-highspeed">MiniMax-M2.1-highspeed</option>
3592+ <option value="MiniMax-M2">MiniMax-M2</option>
3593+ <option value="M2-her">M2-her</option>
3594+ </select>
3595+ </div>
35693596 <div id="electronhub_form" data-source="electronhub">
35703597 <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4>
35713598 <div>
@@ -3584,37 +3611,6 @@
35843611 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
35853612 </select>
35863613 </div>
3587- <div class="marginTopBot5">
3588- <div class="inline-drawer wide100p">
3589- <div class="inline-drawer-toggle inline-drawer-header">
3590- <b data-i18n="Electron Hub Model Sorting">Electron Hub Model Sorting</b>
3591- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3592- </div>
3593- <div class="inline-drawer-content m-b-1">
3594- <div class="marginTopBot5">
3595- <label for="electronhub_sort_models" class="checkbox_label">
3596- <select id="electronhub_sort_models">
3597- <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3598- <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option>
3599- <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option>
3600- <option data-i18n="Context Size" value="context_length">Context Size</option>
3601- </select>
3602- </label>
3603- </div>
3604- <div class="marginTopBot5">
3605- <label for="electronhub_group_models" class="checkbox_label">
3606- <input id="electronhub_group_models" type="checkbox" />
3607- <span data-i18n="Group by vendors">Group by vendors</span>
3608- </label>
3609- <div class="toggle-description justifyLeft wide100p">
3610- <span data-i18n="Group by vendors Description">
3611- Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting.
3612- </span>
3613- </div>
3614- </div>
3615- </div>
3616- </div>
3617- </div>
36183614 </div>
36193615 <div id="chutes_form" data-source="chutes">
36203616 <h4 data-i18n="Chutes API Key">Chutes API Key</h4>
@@ -3634,29 +3630,11 @@
36343630 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
36353631 </select>
36363632 </div>
3637- <div class="marginTopBot5">
3638- <div class="inline-drawer wide100p">
3639- <div class="inline-drawer-toggle inline-drawer-header">
3640- <b data-i18n="Chutes Model Sorting">Chutes Model Sorting</b>
3641- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3642- </div>
3643- <div class="inline-drawer-content m-b-1">
3644- <div class="marginTopBot5">
3645- <label for="chutes_sort_models" class="checkbox_label">
3646- <select id="chutes_sort_models">
3647- <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3648- <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option>
3649- <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option>
3650- <option data-i18n="Context Size" value="context_length">Context Size</option>
3651- </select>
3652- </label>
3653- </div>
3654- </div>
3655- </div>
3656- </div>
36573633 </div>
36583634 <div id="nanogpt_form" data-source="nanogpt">
36593635 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
3636+ <a href="https://nano-gpt.com/balance" target="_blank" rel="noopener noreferrer" class="nanogpt_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
3637+ <span class="nanogpt_credits_display marginLeft5"></span>
36603638 <div class="flex-container">
36613639 <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off">
36623640 <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_nanogpt"></div>
@@ -3670,6 +3648,39 @@
36703648 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
36713649 </select>
36723650 </div>
3651+ <div>
3652+ <h4>
3653+ <span data-i18n="Model Providers">Model Providers</span>
3654+ <i id="nanogpt_provider_warning" class="fa-solid fa-circle-exclamation displayNone" title="Deselect inapplicable provider(s) or select an applicable provider to avoid a 404 error."></i>
3655+ </h4>
3656+ <select id="nanogpt_provider">
3657+ <option value="" data-i18n="Auto">Auto</option>
3658+ </select>
3659+ <label class="checkbox_label marginTopBot5" for="nanogpt_payg_override" data-i18n="[title]Force NanoGPT pay-as-you-go billing for this request." title="Force NanoGPT pay-as-you-go billing for this request.">
3660+ <input id="nanogpt_payg_override" type="checkbox" />
3661+ <span data-i18n="Use pay-as-you-go billing">Use pay-as-you-go billing</span>
3662+ </label>
3663+ </div>
3664+ </div>
3665+ <div id="workers_ai_form" data-source="workers_ai">
3666+ <h4><a href="https://dash.cloudflare.com/?to=/:account/ai/workers-ai/api-quick-start" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Workers AI API Key">Cloudflare Workers AI API Key</a></h4>
3667+ <div class="flex-container">
3668+ <input id="api_key_workers_ai" name="api_key_workers_ai" class="text_pole flex1" value="" type="text" autocomplete="off">
3669+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_workers_ai"></div>
3670+ </div>
3671+ <div data-for="api_key_workers_ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3672+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3673+ </div>
3674+ <h4><a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Account ID">Cloudflare Account ID</a></h4>
3675+ <div class="flex-container">
3676+ <input id="workers_ai_account_id" class="text_pole flex1" value="" type="text" autocomplete="off" placeholder="e.g. 023e105f4ecef8ad9ca31a8372d0c353" data-i18n="[placeholder]e.g. 023e105f4ecef8ad9ca31a8372d0c353">
3677+ </div>
3678+ <div>
3679+ <h4 data-i18n="Workers AI Model">Workers AI Model</h4>
3680+ <select id="model_workers_ai_select">
3681+ <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3682+ </select>
3683+ </div>
36733684 </div>
36743685 <div id="deepseek_form" data-source="deepseek">
36753686 <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>
@@ -3683,9 +3694,7 @@
36833694 <div>
36843695 <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>
36853696 <select id="model_deepseek_select">
3686- <option value="deepseek-chat">deepseek-chat</option>
3697+ <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3687- <option value="deepseek-coder">deepseek-coder</option>
3688- <option value="deepseek-reasoner">deepseek-reasoner</option>
36893698 </select>
36903699 </div>
36913700 </div>
@@ -3924,6 +3933,7 @@
39243933 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>
39253934 <select id="model_zai_select">
39263935 <option value="glm-5-turbo">glm-5-turbo</option>
3936+ <option value="glm-5v-turbo">glm-5v-turbo</option>
39273937 <option value="glm-5.1">glm-5.1</option>
39283938 <option value="glm-5">glm-5</option>
39293939 <option value="glm-4.7">glm-4.7</option>
@@ -3986,6 +3996,37 @@
39863996 <small data-i18n="The underlying model of your deployment. This is detected automatically when you connect.">The underlying model of your deployment. This is detected automatically when you connect.</small>
39873997 </div>
39883998 </div>
3999+ <div id="model_sorting_form" data-source="openrouter,chutes,electronhub,nanogpt,aimlapi">
4000+ <div class="inline-drawer wide100p">
4001+ <div class="inline-drawer-toggle inline-drawer-header">
4002+ <b data-i18n="Model Sorting">Model Sorting</b>
4003+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
4004+ </div>
4005+ <div class="inline-drawer-content m-b-1">
4006+ <div class="marginTopBot5">
4007+ <label for="cc_sort_models" class="checkbox_label">
4008+ <select id="cc_sort_models">
4009+ <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
4010+ <option data-i18n="Prompt Price (cheapest)" value="pricing.prompt">Prompt Price (cheapest)</option>
4011+ <option data-i18n="Completion Price (cheapest)" value="pricing.completion">Completion Price (cheapest)</option>
4012+ <option data-i18n="Context Size" value="context_length">Context Size</option>
4013+ </select>
4014+ </label>
4015+ </div>
4016+ <div class="marginTopBot5">
4017+ <label for="cc_group_models" class="checkbox_label">
4018+ <input id="cc_group_models" type="checkbox" />
4019+ <span data-i18n="Group by vendors">Group by vendors</span>
4020+ </label>
4021+ <div class="toggle-description justifyLeft wide100p">
4022+ <span data-i18n="Group by vendors Description">
4023+ Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting.
4024+ </span>
4025+ </div>
4026+ </div>
4027+ </div>
4028+ </div>
4029+ </div>
39894030 <div id="prompt_post_processing_form">
39904031 <h4>
39914032 <span data-i18n="Prompt Post-Processing">
@@ -4365,7 +4406,7 @@
43654406 <small data-i18n="First User Prefix">First User Prefix</small>
43664407 <textarea id="instruct_first_input_sequence" data-macros class="text_pole textarea_compact autoSetHeight"></textarea>
43674408 </div>
43684409 <div class="flexAuto" title="Inserted before the last User's message or as a last prompt line when generating an impersonation." data-i18n="[title]instruct_last_input_sequence">
43694410 <small data-i18n="Last User Prefix">Last User Prefix</small>
43704411 <textarea id="instruct_last_input_sequence" data-macros class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
43714412 </div>
@@ -4775,7 +4816,7 @@
47754816 <input type="file" id="world_import_file" accept=".json,.lorebook,.png" name="avatar" hidden>
47764817 <div id="world_create_button" class="menu_button menu_button_icon">
47774818 <i class="fa-solid fa-globe"></i>
47784819 <span data-i18n="NewCreate">NewCreate</span>
47794820 </div>
47804821 <small data-i18n="or">or</small>
47814822 <select id="world_editor_select">
@@ -6733,7 +6774,7 @@
67336774 <small class="chat_file_size select_chat_block_filename_item"></small>
67346775 <small class="chat_messages_num select_chat_block_filename_item"></small>
67356776 </div>
67366777 <div class="select_chat_actions flex-container gap10px">
67376778 <div title="Export JSONL chat file" data-format="jsonl" class="exportRawChatButton opacity50p hoverglow fa-solid fa-file-export" data-i18n="[title]Export JSONL chat file"></div>
67386779 <div title="Download chat as plain text document" data-format="txt" class="exportChatButton opacity50p hoverglow fa-solid fa-file-lines" data-i18n="[title]Download chat as plain text document"></div>
67396780 <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div>
public/lib.js+3 -0
@@ -24,6 +24,7 @@ import chalk from 'chalk';
2424import yaml from 'yaml';
2525import * as chevrotain from 'chevrotain';
2626import { gzipSync, gzip } from 'fflate';
27+import { sha256 } from 'js-sha256';
2728
2829/**
2930 * Expose the libraries to the 'window' object.
@@ -105,6 +106,7 @@ export default {
105106 chevrotain,
106107 gzipSync,
107108 gzip,
109+ sha256,
108110};
109111
110112export {
@@ -132,4 +134,5 @@ export {
132134 chevrotain,
133135 gzipSync,
134136 gzip,
137+ sha256,
135138};
public/script.js+84 -28
@@ -213,7 +213,7 @@ import {
213213 tag_import_setting,
214214 applyCharacterTagsToMessageDivs,
215215} from './scripts/tags.js';
216216import { checkOpenRouterAuth, initSecrets, readSecretState } from './scripts/secrets.js';
217217import { markdownExclusionExt } from './scripts/showdown-exclusion.js';
218218import { markdownUnderscoreExt } from './scripts/showdown-underscore.js';
219219import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js';
@@ -717,6 +717,7 @@ async function firstLoadInit() {
717717 initLoaderOverlay.appendChild(splashMessage);
718718
719719 const initLoaderHandle = loader.show({
720+ slug: 'app-init',
720721 toastMode: loader.ToastMode.NONE,
721722 overlayContent: initLoaderOverlay,
722723 });
@@ -741,12 +742,13 @@ async function firstLoadInit() {
741742 initKoboldSettings();
742743 initNovelAISettings();
743744 initSystemPrompts();
744745 await initExtensions();
745746 initExtensionSlashCommands();
746747 ToolManager.initToolSlashCommands();
747748 await initPresetManager();
748749 await initSystemMessages();
749750 await getSettings(initLoaderHandle);
751+ await checkOpenRouterAuth();
750752 initKeyboard();
751753 initDynamicStyles();
752754 initTags();
@@ -1910,17 +1912,32 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
19101912}
19111913
19121914/**
1915+ * Creates an Image element for the given API/model icon.
1916+ * The image references the matching SVG file from `/img/` and includes a tooltip with API and model info.
1917+ * The caller is responsible for appending the image to the DOM and optionally calling `SVGInject` on it.
1918+ *
1919+ * @param {string} apiName - API identifier matching an SVG file in /img/ (e.g. 'openai', 'openrouter', 'claude')
1920+ * @param {string} [modelName=''] - Model name shown in the tooltip
1921+ * @returns {HTMLImageElement} The image element (not yet in the DOM)
1922+ */
1923+export function createModelIcon(apiName, modelName = '') {
1924+ const image = new Image();
1925+ image.classList.add('icon-svg');
1926+ image.src = `/img/${apiName}.svg`;
1927+ image.title = modelName ? `${apiName} - ${modelName}` : apiName;
1928+ return image;
1929+}
1930+
1931+/**
19131932 * Inserts or replaces an SVG icon adjacent to the provided message's timestamp.
19141933 *
19151934 * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.
19161935 * @param {ChatMessageExtra} extra - Contains the API and model details.
19171936 */
19181937function insertSVGIcon(mes, extra) {
1919- // Determine the SVG filename
1938+ const apiName = extra?.api || '';
1920- let modelName = extra?.api || '';
19211939
1922- // If there's no API information, we can't determine which SVG to use
1940+ if (!apiName) {
1923- if (!modelName) {
19241941 return;
19251942 }
19261943
@@ -1937,16 +1954,14 @@ function insertSVGIcon(mes, extra) {
19371954 };
19381955 };
19391956
19401957 const createModelImageinsertIcon = (className, targetSelector, insertBefore) => {
19411958 const image = new ImagecreateModelIcon(apiName, extra?.model);
19421959 image.classList.add('icon-svg', className);
1943- image.src = `/img/${modelName}.svg`;
1944- image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
19451960 insertOrReplaceSVG(image, className, targetSelector, insertBefore);
19461961 };
19471962
19481963 createModelImageinsertIcon('timestamp-icon', '.timestamp');
19491964 createModelImageinsertIcon('thinking-icon', '.mes_reasoning_header_title', true);
19501965}
19511966
19521967/**
@@ -2907,6 +2922,11 @@ export function substituteParamsLegacy(content, _name1, _name2, _original, _grou
29072922export function substituteParams(content, options = {}) {
29082923 if (!content) return '';
29092924
2925+ if (typeof content !== 'string') {
2926+ console.warn('substituteParams: content will be coerced to string', content);
2927+ content = String(content);
2928+ }
2929+
29102930 // Handle legacy signature calls to substituteParams
29112931 // We'll simply re-route them to a temporary legacy function. In the future, we'll remove this and cleanly build the options object ourselves.
29122932 const isOptionsObject = options && typeof options === 'object' && !Array.isArray(options);
@@ -3847,9 +3867,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst
38473867
38483868 // If the prompt was given as a string, convert to a message-style object assuming user role
38493869 if (typeof prompt === 'string') {
38503870 const message = api{ ===role: 'openaiuser', content: prompt.trim() };
3851- ? { role: 'user', content: prompt.trim() }
3852- : { role: 'system', content: prompt };
38533871 prompt = [message];
38543872 } else { // checks for message-style object
38553873 if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided');
@@ -3876,7 +3894,12 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst
38763894 // prepend system prompt, if provided
38773895 if (systemPrompt) {
38783896 systemPrompt = substituteParams(systemPrompt);
38793897 systemPrompt = isInstruct ? (formatInstructModeStoryString(systemPrompt) + '\n') : systemPrompt.trim();
3898+ if (isInstruct && systemPrompt.length > 0 && !systemPrompt.endsWith('\n')) {
3899+ if (power_user.instruct.wrap && !power_user.instruct.story_string_suffix) {
3900+ systemPrompt += '\n';
3901+ }
3902+ }
38803903 prompt.unshift({ role: 'system', content: systemPrompt });
38813904 }
38823905
@@ -3906,7 +3929,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst
39063929 * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used.
39073930 * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response.
39083931 * @prop {string} [prefill] An optional prefill for the prompt.
39093932 * @prop {objectJsonSchema} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction.
39103933 */
39113934
39123935/**
@@ -4018,7 +4041,7 @@ export async function generateRawData({ prompt = '', api = null, instructOverrid
40184041 }
40194042
40204043 if (jsonSchema) {
40214044 return extractJsonFromData(data, { mainApi: api, returnInvalidJson: jsonSchema.returnInvalid });
40224045 }
40234046
40244047 return data;
@@ -4181,6 +4204,7 @@ function removeLastMessage() {
41814204 * @property {object} value JSON schema value.
41824205 * @property {string} [description] Description of the schema.
41834206 * @property {boolean} [strict] If true, the schema will be used in strict mode, meaning that only the fields defined in the schema will be allowed.
4207+ * @property {boolean} [returnInvalid] If true, a string that can't be parsed as a JSON will be returned as is, instead of an empty object.
41844208 *
41854209 * @typedef {object} GenerateOptions
41864210 * @property {boolean} [automatic_trigger] If the generation was triggered automatically (e.g. group auto mode).
@@ -4267,7 +4291,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
42674291 if (selected_group && !is_group_generating) {
42684292 if (!dryRun) {
42694293 // Returns the promise that generateGroupWrapper returns; resolves when generation is done
42704294 return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, jsonSchema });
42714295 }
42724296
42734297 const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index]));
@@ -4449,9 +4473,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44494473 for (let i = coreChat.length - 1; i >= 0; i--) {
44504474 const depth = coreChat.length - i - (isContinue ? 2 : 1);
44514475 const isPrefix = isContinue && i === coreChat.length - 1;
4476+
4477+ // In group chats, only include reasoning from the currently generating character
4478+ const isOtherGroupMember = selected_group && coreChat[i].name !== name2;
4479+
44524480 coreChat[i] = {
44534481 ...coreChat[i],
44544482 mes: promptReasoning.addToMessage(isOtherGroupMember
4483+ ? coreChat[i].mes
4484+ : promptReasoning.addToMessage(
44554485 coreChat[i].mes,
44564486 getRegexedString(
44574487 String(coreChat[i].extra?.reasoning ?? ''),
@@ -4697,7 +4727,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
46974727 chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
46984728 }
46994729
47004730 if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct && !isImpersonate) {
47014731 // Reformat with the last input sequence (if any)
47024732 chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
47034733 }
@@ -5301,7 +5331,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
53015331 streamingProcessor.firstMessageText = '';
53025332 }
53035333
53045334 streamingProcessor.generator = await sendStreamingRequest(type, generate_data, { jsonSchema });
53055335
53065336 hideSwipeButtons();
53075337 let getMessage = await streamingProcessor.generate();
@@ -5390,7 +5420,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
53905420
53915421 if (jsonSchema) {
53925422 unblockGeneration(type);
5393- return extractJsonFromData(data);
5423+ return extractJsonFromData(data, { returnInvalidJson: jsonSchema.returnInvalid ?? false });
53945424 }
53955425
53965426 //const getData = await response.json();
@@ -5823,11 +5853,11 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
58235853 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt);
58245854 } else {
58255855 chat.push(message);
5856+ await saveChatConditional();
58265857 const chat_id = (chat.length - 1);
58275858 await eventSource.emit(event_types.MESSAGE_SENT, chat_id);
58285859 addOneMessage(message);
58295860 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
5830- await saveChatConditional();
58315861 }
58325862
58335863 return message;
@@ -6003,7 +6033,7 @@ function setInContextMessages(msgInContextCount, type) {
60036033
60046034 if (lastMessageBlock.length === 0) {
60056035 const firstMessageId = getFirstDisplayedMessageId();
60066036 chatElement.find(`.mes[mesid="${firstMessageId}"]`).addClass('lastInContext');
60076037 }
60086038
60096039 // Update last id to chat. No metadata save on purpose, gets hopefully saved via another call
@@ -6213,9 +6243,13 @@ export function extractMessageFromData(data, activeApi = null) {
62136243/**
62146244 * Extracts JSON from the response data.
62156245 * @param {object} data Response data
6246+ * @param {object} [options] Extraction options
6247+ * @param {string} [options.mainApi] Main API to use
6248+ * @param {string} [options.chatCompletionSource] Chat completion source
6249+ * @param {boolean} [options.returnInvalidJson=false] Whether to return the raw JSON string even if it fails to parse
62166250 * @returns {string} Extracted JSON string from the response data
62176251 */
62186252export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null, returnInvalidJson = false } = {}) {
62196253 mainApi = mainApi ?? main_api;
62206254 chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source;
62216255
@@ -6238,6 +6272,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
62386272 break;
62396273 case chat_completion_sources.PERPLEXITY:
62406274 result = tryParse(removeReasoningFromString(text));
6275+ if (!result && returnInvalidJson) {
6276+ return text;
6277+ }
62416278 break;
62426279 case chat_completion_sources.VERTEXAI:
62436280 case chat_completion_sources.MAKERSUITE:
@@ -6258,6 +6295,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
62586295 case chat_completion_sources.ZAI:
62596296 default:
62606297 result = tryParse(text);
6298+ if (!result && returnInvalidJson) {
6299+ return text;
6300+ }
62616301 break;
62626302 }
62636303 } break;
@@ -7924,6 +7964,15 @@ export async function getSettings(initLoaderHandle = null) {
79247964 const isVersionChanged = settings.currentVersion !== currentVersion;
79257965 await loadExtensionSettings(settings, isVersionChanged, enableAutoUpdate);
79267966 await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
7967+ } else {
7968+ Object.assign(extension_settings, (settings.extension_settings ?? {}));
7969+ $('#third_party_extension_button').addClass('disabled');
7970+ $('#extensions_details').addClass('disabled');
7971+ $('#extensions_connect').addClass('disabled');
7972+ $('#extensions_notify_updates').attr('disabled', 'disabled');
7973+ $('#extensions_autoconnect').attr('disabled', 'disabled');
7974+ $('#extensions_url').attr('disabled', 'disabled');
7975+ $('#extensions_api_key').attr('disabled', 'disabled');
79277976 }
79287977
79297978 firstRun = !!settings.firstRun;
@@ -10565,6 +10614,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
1056510614 }
1056610615
1056710616 const loaderHandle = showLoader ? loader.show({
10617+ slug: 'chat-rename',
1056810618 title: t`Rename Chat`,
1056910619 message: t`Renaming chat…`,
1057010620 toastMode: loader.ToastMode.STATIC,
@@ -10602,6 +10652,9 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
1060210652 if (currentChatId) {
1060310653 await reloadCurrentChat();
1060410654 }
10655+
10656+ const eventData = { avatarId: body.avatar_url, groupId, oldFileName: body.original_file, newFileName: body.renamed_file };
10657+ await eventSource.emit(event_types.CHAT_RENAMED, eventData);
1060510658 } catch {
1060610659 await delay(500);
1060710660 await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);
@@ -11166,6 +11219,7 @@ jQuery(async function () {
1116611219 $('#select_chat_cross').trigger('click');
1116711220
1116811221 const loaderHandle = loader.show({
11222+ slug: 'chat-delete',
1116911223 title: t`Delete Chat`,
1117011224 message: t`Deleting chat…`,
1117111225 toastMode: loader.ToastMode.STATIC,
@@ -12460,7 +12514,9 @@ jQuery(async function () {
1246012514 $('#avatar-and-name-block').slideToggle();
1246112515 });
1246212516
1246312517 $(document).on('mouseup touchendclick', '#show_more_messages', async function (event) {
12518+ event.stopPropagation();
12519+ event.preventDefault();
1246412520 await showMoreMessages();
1246512521 });
1246612522
public/scripts/BulkEditOverlay.js+1 -0
@@ -848,6 +848,7 @@ class BulkEditOverlay {
848848 const deleteChats = checkbox.prop('checked') ?? false;
849849
850850 const loaderHandle = loader.show({
851+ slug: 'bulk-delete',
851852 title: t`Bulk Delete`,
852853 message: t`Deleting ${characterIds.length} character(s)…`,
853854 toastMode: loader.ToastMode.STATIC,
public/scripts/RossAscends-mods.js+2 -0
@@ -407,6 +407,8 @@ function RA_autoconnect(PrevApi) {
407407 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)
408408 || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI)
409409 || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
410+ || (secret_state[SECRET_KEYS.WORKERS_AI] && oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI)
411+ || (secret_state[SECRET_KEYS.MINIMAX] && oai_settings.chat_completion_source == chat_completion_sources.MINIMAX)
410412 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
411413 || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)
412414 ) {
public/scripts/a11y.js+1 -0
@@ -16,6 +16,7 @@ const buttonSelectors = [
1616 '.jg-menu .jg-button',
1717 '.bg_example .mobile-only-menu-toggle',
1818 '.paginationjs-pages li a',
19+ '#show_more_messages',
1920].join(', ');
2021
2122const listSelectors = [
public/scripts/action-loader-slashcommands.js+22 -7
@@ -1,4 +1,4 @@
11import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoaderloader } from './action-loader.js';
22import { t } from './i18n.js';
33import { SlashCommand } from './slash-commands/SlashCommand.js';
44import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
@@ -7,6 +7,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
77import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
88import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
99import { isFalseBoolean } from './utils.js';
10+import { DOMPurify } from '../lib.js';
1011
1112/**
1213 * Registers slash commands for the action loader module.
@@ -116,6 +117,12 @@ export function registerActionLoaderSlashCommands() {
116117 typeList: [ARGUMENT_TYPE.STRING],
117118 }),
118119 SlashCommandNamedArgument.fromProps({
120+ name: 'slug',
121+ description: 'Unique slug for the loader (to identify it easily via code or CSS)',
122+ typeList: [ARGUMENT_TYPE.STRING],
123+ defaultValue: 'slash-wrap',
124+ }),
125+ SlashCommandNamedArgument.fromProps({
119126 name: 'stopTooltip',
120127 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
121128 typeList: [ARGUMENT_TYPE.STRING],
@@ -148,7 +155,8 @@ export function registerActionLoaderSlashCommands() {
148155 const title = args.title ? String(args.title) : '';
149156 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
150157
151158 const loaderactionLoader = showActionLoaderloader.show({
159+ slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap',
152160 blocking,
153161 toastMode,
154162 message,
@@ -162,7 +170,7 @@ export function registerActionLoaderSlashCommands() {
162170 const result = await closureCopy.execute();
163171 return result.pipe;
164172 } finally {
165173 await loaderactionLoader.hide();
166174 }
167175 },
168176 }));
@@ -232,6 +240,12 @@ export function registerActionLoaderSlashCommands() {
232240 typeList: [ARGUMENT_TYPE.STRING],
233241 }),
234242 SlashCommandNamedArgument.fromProps({
243+ name: 'slug',
244+ description: 'Unique slug for the loader (to identify it easily via code or CSS)',
245+ typeList: [ARGUMENT_TYPE.STRING],
246+ defaultValue: 'slash-show',
247+ }),
248+ SlashCommandNamedArgument.fromProps({
235249 name: 'stopTooltip',
236250 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
237251 typeList: [ARGUMENT_TYPE.STRING],
@@ -258,11 +272,12 @@ export function registerActionLoaderSlashCommands() {
258272 const title = args.title ? String(args.title) : '';
259273 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
260274
261275 const handle = showActionLoaderloader.show({
276+ slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
262277 blocking,
263278 toastMode,
264- message,
279+ message: DOMPurify.sanitize(message),
265- title,
280+ title: DOMPurify.sanitize(title),
266281 stopTooltip,
267282 onStop: createClosureHandler(args.onStop),
268283 onHide: createClosureHandler(args.onHide, { argName: 'onHide' }),
@@ -307,7 +322,7 @@ export function registerActionLoaderSlashCommands() {
307322 }
308323
309324 // No handle provided - hide all active loaders
310325 const result = await hideActionLoaderloader.hide();
311326 return result ? 'true' : 'false';
312327 },
313328 }));
public/scripts/action-loader.js+47 -2
@@ -33,6 +33,7 @@ export const ActionLoaderToastMode = {
3333 * @typedef {object} ActionLoaderOptions
3434 * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.
3535 * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode
36+ * @property {string} [slug=null] - Unique slug for the loader to identify it easily via code or CSS
3637 * @property {string} [message='Generating...'] - The message to display in the toast
3738 * @property {string} [title] - Optional title for the toast notification
3839 * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button
@@ -73,8 +74,20 @@ function hasBlockingLoaders() {
7374 * Manages its own toast, stop handler, and lifecycle.
7475 */
7576export class ActionLoaderHandle {
77+ /**
78+ * A special empty handle that is already disposed. Useful as a default value to avoid null checks.
79+ * Does not generate any id, toast, or overlay, and all its methods are no-ops.
80+ * @type {ActionLoaderHandle}
81+ */
82+ static get EMPTY() {
83+ return new ActionLoaderHandle({ predisposed: true });
84+ }
85+
7686 /** @type {string} Unique identifier for this handle */
7787 #id;
88+
89+ /** @type {string|null} Unique slug for the loader */
90+ #slug = null;
7891
7992 /** @type {JQuery<HTMLElement>|null} The toast element for this loader */
8093 #toast = null;
@@ -96,9 +109,11 @@ export class ActionLoaderHandle {
96109 * @param {object} options - Configuration options
97110 * @param {boolean} [options.blocking=true] - Whether to show blocking overlay
98111 * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode
112+ * @param {string|null} [options.slug] - Unique slug for the loader (to identify it easily via code or CSS)
99113 * @param {string} [options.message='Generating...'] - Message to display in the toast
100114 * @param {string} [options.title] - Title for the toast notification
101115 * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button
116+ * @param {boolean} [options.predisposed=false] - Whether this handle is already disposed (for special use)
102117 * @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner)
103118 * @param {(() => void)|null} [options.onStop] - Custom stop handler
104119 * @param {(() => void)|null} [options.onHide] - Custom hide handler
@@ -106,14 +121,22 @@ export class ActionLoaderHandle {
106121 constructor({
107122 blocking = true,
108123 toastMode = ActionLoaderToastMode.STOPPABLE,
124+ slug = null,
109125 message = t`Generating...`,
110126 title = '',
111127 stopTooltip = t`Stop`,
112128 overlayContent = null,
113129 onStop = null,
114130 onHide = null,
131+ predisposed = false,
115132 } = {}) {
116- this.id = generateLoaderId();
133+ if (predisposed) {
134+ this.#disposed = true;
135+ return;
136+ }
137+
138+ this.#id = generateLoaderId();
139+ this.#slug = slug;
117140 this.#blocking = blocking;
118141 this.#onStop = onStop;
119142 this.#onHide = onHide;
@@ -148,6 +171,12 @@ export class ActionLoaderHandle {
148171 const toastContent = document.createElement('div');
149172 toastContent.className = 'action-loader-toast';
150173
174+ if (this.#slug) {
175+ toastContent.dataset.slug = this.#slug;
176+ }
177+ toastContent.dataset.loaderId = this.#id;
178+ toastContent.dataset.blocking = this.#blocking.toString();
179+
151180 const messageSpan = document.createElement('span');
152181 messageSpan.className = 'action-loader-message';
153182 messageSpan.textContent = message;
@@ -202,6 +231,22 @@ export class ActionLoaderHandle {
202231 }
203232
204233 /**
234+ * The unique identifier for this loader handle.
235+ * @returns {string}
236+ */
237+ get id() {
238+ return this.#id;
239+ }
240+
241+ /**
242+ * The unique slug for this loader handle, used to identify it easily via code or CSS.
243+ * @returns {string|null}
244+ */
245+ get slug() {
246+ return this.#slug;
247+ }
248+
249+ /**
205250 * Whether this handle is still active (not disposed).
206251 * @returns {boolean}
207252 */
public/scripts/bookmarks.js+1 -0
@@ -700,6 +700,7 @@ export function initBookmarks() {
700700 }
701701
702702 const loaderHandle = loader.show({
703+ slug: 'chat-load',
703704 title: t`Chat History`,
704705 message: t`Loading chat…`,
705706 toastMode: loader.ToastMode.STATIC,
public/scripts/browser-fixes.js+8 -0
@@ -72,6 +72,14 @@ function applyBrowserFixes() {
7272
7373 if (isMobile()) {
7474 const fixFunkyPositioning = () => {
75+ if (isFirefox()) {
76+ const active = document.activeElement;
77+ if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) {
78+ // The positioning hack below breaks GBoard candidate replacement
79+ // in Firefox Mobile on Android.
80+ return;
81+ }
82+ }
7583 console.debug('[Mobile] Device viewport change detected.');
7684 document.documentElement.style.position = 'fixed';
7785 requestAnimationFrame(() => document.documentElement.style.position = '');
public/scripts/chats.js+4 -1
@@ -2288,11 +2288,14 @@ export function initChatUtilities() {
22882288 await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });
22892289 });
22902290
22912291 $(document).on('click', 'body .mes .mes_text, body .mes .mes_reasoning', function (event) {
22922292 if (!power_user.click_to_edit) return;
22932293 if (window.getSelection().toString()) return;
22942294 if ($('.edit_textarea').length) return;
22952295 $(this).closest('.mes').find('.mes_edit').trigger('click');
2296+ if ($(event.target).closest('.mes_reasoning').length) {
2297+ $('.reasoning_edit_textarea').trigger('focus');
2298+ }
22962299 });
22972300
22982301 $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog);
public/scripts/custom-request.js+3 -2
@@ -51,6 +51,7 @@ import EventSourceStream from './sse-stream.js';
5151 * @property {string} [reverse_proxy] - Optional reverse proxy URL
5252 * @property {string} [proxy_password] - Optional proxy password
5353 * @property {string} [custom_prompt_post_processing] - Optional custom prompt post-processing
54+ * @property {import('../script.js').JsonSchema} [json_schema] - Optional JSON schema for structured generation
5455 */
5556
5657/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
@@ -505,7 +506,7 @@ export class ChatCompletionService {
505506 return async function* streamData() {
506507 let text = '';
507508 const swipes = [];
508509 const state = { reasoning: '', imageimages: [], signature: '', toolSignatures: {} };
509510 while (true) {
510511 const { done, value } = await reader.read();
511512 if (done) return;
@@ -591,7 +592,7 @@ export class ChatCompletionService {
591592 }
592593
593594 // Ensure api-url is properly applied for all sources that accept it
594595 ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint', 'minimax_endpoint'].forEach(field => {
595596 // The order is: connection profile => CC preset => CC settings
596597 overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field];
597598 });
public/scripts/dynamic-styles.js+6 -0
@@ -126,6 +126,12 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) {
126126 // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside,
127127 // it should be manually defined in CSS.
128128 const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible');
129+
130+ // Skip pseudo-elements (::before, ::after, ::-webkit-scrollbar, etc.)
131+ // as they cannot have :focus-visible appended (invalid CSS syntax)
132+ if (focusSelector.includes('::')) {
133+ return;
134+ }
129135 let focusRule = `${focusSelector} { ${rule.style.cssText} }`;
130136
131137 // Wrap the generated rule into the same @media/@supports/@container chain (if any)
public/scripts/events.js+8 -0
@@ -50,6 +50,7 @@ export const event_types = {
5050 FORCE_SET_BACKGROUND: 'force_set_background',
5151 CHAT_DELETED: 'chat_deleted',
5252 CHAT_CREATED: 'chat_created',
53+ CHAT_RENAMED: 'chat_renamed',
5354 GROUP_CHAT_DELETED: 'group_chat_deleted',
5455 GROUP_CHAT_CREATED: 'group_chat_created',
5556 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
@@ -97,9 +98,16 @@ export const event_types = {
9798 WORLDINFO_SCAN_DONE: 'worldinfo_scan_done',
9899 MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted',
99100 PERSONA_CHANGED: 'persona_changed',
101+ PERSONA_CREATED: 'persona_created',
102+ PERSONA_UPDATED: 'persona_updated',
103+ PERSONA_RENAMED: 'persona_renamed',
104+ PERSONA_DELETED: 'persona_deleted',
100105 TTS_JOB_STARTED: 'tts_job_started',
101106 TTS_AUDIO_READY: 'tts_audio_ready',
102107 TTS_JOB_COMPLETE: 'tts_job_complete',
108+ ITEMIZED_PROMPTS_LOADED: 'itemized_prompts_loaded',
109+ ITEMIZED_PROMPTS_SAVED: 'itemized_prompts_saved',
110+ ITEMIZED_PROMPTS_DELETED: 'itemized_prompts_deleted',
103111};
104112
105113export const eventSource = new EventEmitter([event_types.APP_READY, event_types.APP_INITIALIZED]);
public/scripts/extensions.js+561 -123
@@ -1,9 +1,9 @@
11import { DOMPurify, Popper } from '../lib.js';
22
33import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js';
44import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
55import { renderTemplate, renderTemplateAsync } from './templates.js';
66import { delay, deleteValueByPath, equalsIgnoreCaseAndAccents, escapeHtml, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js';
77import { getContext } from './st-context.js';
88import { isAdmin } from './user.js';
99import { addLocaleData, getCurrentLocale, t } from './i18n.js';
@@ -61,6 +61,19 @@ let manifests = {};
6161 */
6262const defaultUrl = 'http://localhost:5100';
6363
64+/**
65+ * Checks if the extension is officially supported by its URL pattern.
66+ * @param {string} url URL to check
67+ * @returns {boolean} True if the URL matches the pattern, false otherwise (or not a valid URL)
68+ */
69+export const isOfficialExtension = (url) => {
70+ try {
71+ return /^https:\/\/github\.com\/SillyTavern\/(.+)$/i.test(new URL(url).href);
72+ } catch (e) {
73+ return false;
74+ }
75+};
76+
6477let requiresReload = false;
6578let stateChanged = false;
6679let saveMetadataTimeout = null;
@@ -267,6 +280,18 @@ export async function doExtrasFetch(endpoint, args = {}) {
267280}
268281
269282/**
283+ * Generates a CSS selector for an extension based on its name, allowing omission of a common prefix.
284+ * @param {string} name Name of the extension, with or without the "third-party" prefix
285+ * @param {object} [options] Optional parameters
286+ * @param {string} [options.prefix] Optional prefix to ignore when generating the selector (e.g. "third-party")
287+ * @returns {string} CSS selector for the extension, with the prefix removed if it was present and specified in options
288+ */
289+function getNameSelector(name, { prefix = 'third-party' } = {}) {
290+ const nameWithoutPrefix = prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name;
291+ return CSS.escape(nameWithoutPrefix);
292+}
293+
294+/**
270295 * Discovers extensions from the API.
271296 * @returns {Promise<{name: string, type: string}[]>}
272297 */
@@ -343,7 +368,7 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) {
343368 }
344369
345370 toggleContainer
346371 .find(`.extension_block[data-name="${name.replacegetNameSelector('third-party', ''name)}"] .extension_toggle input`)
347372 .prop('checked', enable)
348373 .toggleClass('toggle_enable', !enable)
349374 .toggleClass('toggle_disable', enable)
@@ -355,11 +380,27 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) {
355380}
356381
357382/**
383+ * Checks whether an extension has a specific hook defined in its manifest.
384+ * @param {string} name Extension name (with or without 'third-party' prefix)
385+ * @param {'install' | 'update' | 'delete' | 'clean' | 'enable' | 'disable' | 'activate'} hookName The hook to check
386+ * @returns {boolean}
387+ */
388+function hasExtensionHook(name, hookName) {
389+ const fullName = name.startsWith('third-party') ? name : `third-party${name}`;
390+ const manifest = manifests[fullName];
391+ if (!manifest || !manifest.hooks || typeof manifest.hooks !== 'object') {
392+ return false;
393+ }
394+ const hookFunctionName = manifest.hooks[hookName];
395+ return typeof hookFunctionName === 'string' && hookFunctionName.length > 0;
396+}
397+
398+/**
358399 * Calls a manifest hook for an extension.
359400 * Hooks are optional function names exported from the extension's JS entry point module.
360401 * The hook function can optionally return a Promise that will be awaited.
361402 * @param {string} name Extension name
362403 * @param {'install' | 'update' | 'delete' | 'clean' | 'enable' | 'disable' | 'activate'} hookName The hook to call
363404 * @returns {Promise<void>}
364405 */
365406async function callExtensionHook(name, hookName) {
@@ -474,6 +515,21 @@ export function findExtension(name) {
474515}
475516
476517/**
518+ * Returns a deep clone of the manifest for the given extension name.
519+ * Accepts either the short name (e.g. `SillyTavern-MyExtension`) or the full internal key
520+ * (e.g. `third-party/SillyTavern-MyExtension`). Returns null if the extension is not found.
521+ * @param {string} name - Extension name or internal key
522+ * @returns {object|null} Cloned manifest object, or null if not found
523+ */
524+export function getExtensionManifest(name) {
525+ const found = extensionNames.find(extName =>
526+ equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`),
527+ );
528+ const manifest = found ? manifests[found] : null;
529+ return manifest ? structuredClone(manifest) : null;
530+}
531+
532+/**
477533 * Loads manifest.json files for extensions.
478534 * @param {string[]} names Array of extension names
479535 * @returns {Promise<Record<string, object>>} Object with extension names as keys and their manifests as values
@@ -821,7 +877,7 @@ function addExtensionLocale(name, manifest) {
821877}
822878
823879/**
824880 * Generates HTMLan stringelement for displaying an extension in the UI.
825881 *
826882 * @param {string} name - The name of the extension.
827883 * @param {object} manifest - The manifest of the extension.
@@ -829,95 +885,180 @@ function addExtensionLocale(name, manifest) {
829885 * @param {boolean} isDisabled - Whether the extension is disabled or not.
830886 * @param {boolean} isExternal - Whether the extension is external or not.
831887 * @param {string} checkboxClass - The class for the checkbox HTML element.
832888 * @return {stringHTMLElement} - The HTML stringelement that represents the extension.
833889 */
834890function generateExtensionHtmlgenerateExtensionElement(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {
835891 function getExtensionIcon() {
836892 const type = getExtensionType(name);
893+ const icon = document.createElement('i');
894+ icon.classList.add('fa-sm', 'fa-fw', 'fa-solid');
837895 switch (type) {
838896 case 'global':
839- return '<i class="fa-sm fa-fw fa-solid fa-server" data-i18n="[title]ext_type_global" title="This is a global extension, available for all users."></i>';
897+ icon.classList.add('fa-server');
898+ icon.title = t`This is a global extension, available for all users.`;
899+ break;
840900 case 'local':
841- return '<i class="fa-sm fa-fw fa-solid fa-user" data-i18n="[title]ext_type_local" title="This is a local extension, available only for you."></i>';
901+ icon.classList.add('fa-user');
902+ icon.title = t`This is a local extension, available only for you.`;
903+ break;
842904 case 'system':
843- return '<i class="fa-sm fa-fw fa-solid fa-cog" data-i18n="[title]ext_type_system" title="This is a built-in extension. It cannot be deleted and updates with the app."></i>';
905+ icon.classList.add('fa-cog');
906+ icon.title = t`This is a built-in extension. It cannot be deleted and updates with the app.`;
907+ break;
844908 default:
845- return '<i class="fa-sm fa-fw fa-solid fa-question" title="Unknown extension type."></i>';
909+ icon.classList.add('fa-question');
910+ icon.title = t`Unknown extension type.`;
911+ break;
846912 }
913+ return icon;
847914 }
848915
849916 const isUserAdmin = isAdmin();
850- const extensionIcon = getExtensionIcon();
851917 const displayName = manifest.display_name;
852918 const displayVersion = manifest.version || '';
853919 const externalId = name.replace('third-party', '');
854- let originHtml = '';
920+
855- if (isExternal) {
921+ // Root block
856- originHtml = '<a>';
922+ const block = document.createElement('div');
923+ block.classList.add('extension_block');
924+ block.dataset.name = externalId;
925+
926+ // Toggle
927+ const toggleDiv = document.createElement('div');
928+ toggleDiv.classList.add('extension_toggle');
929+ const toggle = document.createElement('input');
930+ toggle.type = 'checkbox';
931+ toggle.dataset.name = name;
932+ if (isActive || isDisabled) {
933+ toggle.title = t`Click to toggle`;
934+ toggle.classList.add(isActive ? 'toggle_disable' : 'toggle_enable');
935+ if (checkboxClass) toggle.classList.add(checkboxClass);
936+ toggle.checked = isActive;
937+ } else {
938+ toggle.title = t`Cannot enable extension`;
939+ toggle.classList.add('extension_missing');
940+ if (checkboxClass) toggle.classList.add(checkboxClass);
941+ toggle.disabled = true;
857942 }
943+ toggleDiv.appendChild(toggle);
944+ block.appendChild(toggleDiv);
945+
946+ // Icon
947+ const iconDiv = document.createElement('div');
948+ iconDiv.classList.add('extension_icon');
949+ iconDiv.appendChild(getExtensionIcon());
950+ block.appendChild(iconDiv);
858951
859- let toggleElement = isActive || isDisabled ?
952+ // Text block
860- '<input type="checkbox" title="' + t`Click to toggle` + `" data-name="${name}" class="${isActive ? 'toggle_disable' : 'toggle_enable'} ${checkboxClass}" ${isActive ? 'checked' : ''}>` :
953+ const textBlock = document.createElement('div');
861- `<input type="checkbox" title="Cannot enable extension" data-name="${name}" class="extension_missing ${checkboxClass}" disabled>`;
954+ textBlock.classList.add('flexGrow', 'extension_text_block');
862955
863- let deleteButton = isExternal ? `<button class="btn_delete menu_button" data-name="${externalId}" data-i18n="[title]Delete" title="Delete"><i class="fa-fw fa-solid fa-trash-can"></i></button>` : '';
956+ const statusSpan = document.createElement('span');
864- let updateButton = isExternal ? `<button class="btn_update menu_button displayNone" data-name="${externalId}" title="Update available"><i class="fa-solid fa-download fa-fw"></i></button>` : '';
957+ statusSpan.className = isActive ? 'extension_enabled' : isDisabled ? 'extension_disabled' : 'extension_missing';
865- let moveButton = isExternal && isUserAdmin ? `<button class="btn_move menu_button" data-name="${externalId}" data-i18n="[title]Move" title="Move"><i class="fa-solid fa-folder-tree fa-fw"></i></button>` : '';
958+
866- let branchButton = isExternal && isUserAdmin ? `<button class="btn_branch menu_button" data-name="${externalId}" data-i18n="[title]Switch branch" title="Switch branch"><i class="fa-solid fa-code-branch fa-fw"></i></button>` : '';
959+ const nameSpan = document.createElement('span');
867- let modulesInfo = '';
960+ nameSpan.classList.add('extension_name');
961+ nameSpan.textContent = displayName;
962+
963+ const authorSpan = document.createElement('span');
964+ authorSpan.classList.add('extension_author');
965+
966+ const versionSpan = document.createElement('span');
967+ versionSpan.classList.add('extension_version');
968+ versionSpan.textContent = displayVersion;
969+
970+ statusSpan.append(nameSpan, authorSpan, versionSpan);
868971
869972 if (isActive && Array.isArray(manifest.optional)) {
870973 const optional = new Set(manifest.optional);
871974 modules.forEach(x => optional.delete(x));
872975 if (optional.size > 0) {
873976 const optionalStringmodulesDiv = DOMPurify.sanitize([...optional]document.joincreateElement(', div'));
874- modulesInfo = '<div class="extension_modules">' + t`Optional modules:` + ` <span class="optional">${optionalString}</span></div>`;
977+ modulesDiv.classList.add('extension_modules');
875- }
978+ const optionalSpan = document.createElement('span');
876- } else if (!isDisabled) { // Neither active nor disabled
979+ optionalSpan.classList.add('optional');
980+ optionalSpan.textContent = [...optional].join(', ');
981+ modulesDiv.append(t`Optional modules:`, ' ', optionalSpan);
982+ statusSpan.appendChild(modulesDiv);
983+ }
984+ } else if (!isDisabled) {
985+ // Neither active nor disabled
877986 const requirements = new Set(manifest.requires);
878987 modules.forEach(x => requirements.delete(x));
879988 if (requirements.size > 0) {
880989 const requirementsStringmodulesDiv = DOMPurify.sanitize([...requirements]document.joincreateElement(', div'));
881- modulesInfo = `<div class="extension_modules">Missing modules: <span class="failure">${requirementsString}</span></div>`;
990+ modulesDiv.classList.add('extension_modules');
991+ const failureSpan = document.createElement('span');
992+ failureSpan.classList.add('failure');
993+ failureSpan.textContent = [...requirements].join(', ');
994+ modulesDiv.append(t`Missing modules:`, ' ', failureSpan);
995+ statusSpan.appendChild(modulesDiv);
882996 }
883997 }
884998
885999 // if external, wrap the name in a link to the repo
1000+ if (isExternal) {
1001+ const originLink = document.createElement('a');
1002+ originLink.appendChild(statusSpan);
1003+ textBlock.appendChild(originLink);
1004+ } else {
1005+ textBlock.appendChild(statusSpan);
1006+ }
1007+
1008+ block.appendChild(textBlock);
1009+
1010+ // Actions
1011+ const actionsDiv = document.createElement('div');
1012+ actionsDiv.classList.add('extension_actions', 'flex-container', 'alignItemsCenter');
1013+
1014+ /**
1015+ * Helper function to create an action button for an extension.
1016+ * @param {string} cls Class name
1017+ * @param {string} dataName Name of the extension
1018+ * @param {string} title Title of the button
1019+ * @param {string} iconClasses Classes for the icon
1020+ * @returns {HTMLButtonElement} The created button element
1021+ */
1022+ function makeActionButton(cls, dataName, title, iconClasses) {
1023+ const btn = document.createElement('button');
1024+ btn.classList.add(cls, 'menu_button');
1025+ btn.dataset.name = dataName;
1026+ btn.title = title;
1027+ const icon = document.createElement('i');
1028+ icon.classList.add(...iconClasses.split(' '));
1029+ btn.appendChild(icon);
1030+ return btn;
1031+ }
1032+
1033+ if (isExternal) {
1034+ const updateBtn = makeActionButton('btn_update', externalId, t`Update available`, 'fa-solid fa-download fa-fw');
1035+ updateBtn.classList.add('displayNone');
1036+ actionsDiv.appendChild(updateBtn);
1037+ }
1038+
1039+ if (isExternal && hasExtensionHook(externalId, 'clean')) {
1040+ actionsDiv.appendChild(makeActionButton('btn_clean', externalId, t`Clean extension data`, 'fa-fw fa-solid fa-broom'));
1041+ }
1042+
1043+ if (isExternal && isUserAdmin) {
1044+ actionsDiv.appendChild(makeActionButton('btn_branch', externalId, t`Switch branch`, 'fa-solid fa-code-branch fa-fw'));
1045+ actionsDiv.appendChild(makeActionButton('btn_move', externalId, t`Move`, 'fa-solid fa-folder-tree fa-fw'));
1046+ }
1047+
1048+ if (isExternal) {
1049+ actionsDiv.appendChild(makeActionButton('btn_delete', externalId, t`Delete`, 'fa-fw fa-solid fa-trash-can'));
1050+ }
1051+
1052+ block.appendChild(actionsDiv);
8861053
887- let extensionHtml = `
1054+ return block;
888- <div class="extension_block" data-name="${externalId}">
889- <div class="extension_toggle">
890- ${toggleElement}
891- </div>
892- <div class="extension_icon">
893- ${extensionIcon}
894- </div>
895- <div class="flexGrow extension_text_block">
896- ${originHtml}
897- <span class="${isActive ? 'extension_enabled' : isDisabled ? 'extension_disabled' : 'extension_missing'}">
898- <span class="extension_name">${DOMPurify.sanitize(displayName)}</span>
899- <span class="extension_version">${DOMPurify.sanitize(displayVersion)}</span>
900- ${modulesInfo}
901- </span>
902- ${isExternal ? '</a>' : ''}
903- </div>
904-
905- <div class="extension_actions flex-container alignItemsCenter">
906- ${updateButton}
907- ${branchButton}
908- ${moveButton}
909- ${deleteButton}
910- </div>
911- </div>`;
912-
913- return extensionHtml;
9141055}
9151056
9161057/**
9171058 * Gets extension data and generates the corresponding HTMLelement for displaying the extension.
9181059 *
9191060 * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest.
9201061 * @return {object{isExternal: boolean, extensionElement: HTMLElement}} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtmlextensionElement' for the extension's HTML stringelement.
9211062 */
9221063function getExtensionData(extension) {
9231064 const name = extension[0];
@@ -927,33 +1068,43 @@ function getExtensionData(extension) {
9271068 const isExternal = name.startsWith('third-party');
9281069
9291070 const checkboxClass = isDisabled ? 'checkbox_disabled' : '';
1071+ const extensionElement = generateExtensionElement(name, manifest, isActive, isDisabled, isExternal, checkboxClass);
9301072
931- const extensionHtml = generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass);
1073+ return { isExternal, extensionElement };
932-
933- return { isExternal, extensionHtml };
9341074}
9351075
9361076
9371077/**
9381078 * Gets the module information to be displayed.
9391079 *
9401080 * @return {stringHTMLElement} - The HTML stringelement forcontaining the module information.
9411081 */
9421082function getModuleInformation() {
943- let moduleInfo = modules.length ? `<p>${DOMPurify.sanitize(modules.join(', '))}</p>` : '<p class="failure">' + t`Not connected to the API!` + '</p>';
1083+ const container = document.createElement('div');
944- return `
1084+
945- <h3>` + t`Modules provided by your Extras API:` + `</h3>
1085+ const heading = document.createElement('h3');
946- ${moduleInfo}
1086+ heading.textContent = t`Modules provided by your Extras API:`;
947- `;
1087+ container.appendChild(heading);
1088+
1089+ const moduleInfo = document.createElement('p');
1090+ if (modules.length) {
1091+ moduleInfo.textContent = modules.join(', ');
1092+ } else {
1093+ moduleInfo.classList.add('failure');
1094+ moduleInfo.textContent = t`Not connected to the API!`;
1095+ }
1096+ container.appendChild(moduleInfo);
1097+
1098+ return container;
9481099}
9491100
9501101/**
9511102 * Generates HTMLHTMLElement for the extension load errors.
9521103 * @returns {stringHTMLElement} HTML- stringThe element containing the errors that occurred whileextension loadingload extensionserrors.
9531104 */
9541105function getExtensionLoadErrorsHtmlgetExtensionLoadErrors() {
9551106 if (extensionLoadErrors.size === 0) {
9561107 return document.createElement('div');
9571108 }
9581109
9591110 const container = document.createElement('div');
@@ -965,7 +1116,7 @@ function getExtensionLoadErrorsHtml() {
9651116 container.appendChild(errorElement);
9661117 }
9671118
9681119 return container.outerHTML;
9691120}
9701121
9711122/**
@@ -982,22 +1133,35 @@ async function showExtensionsDetails() {
9821133 initialScrollTop = oldPopup.content.scrollTop;
9831134 await oldPopup.completeCancelled();
9841135 }
9851136 const htmlErrorserrors = getExtensionLoadErrorsHtmlgetExtensionLoadErrors();
986- const htmlDefault = $('<div class="marginBot10"><h3>' + t`Built-in Extensions:` + '</h3></div>');
1137+
987-
1138+ const defaultContainer = document.createElement('div');
988- const htmlExternal = $(`<div class="marginBot10">
1139+ defaultContainer.classList.add('marginBot10');
989- <div class="flex-container alignitemscenter spaceBetween flexnowrap marginBot10">
1140+ const defaultHeading = document.createElement('h3');
990- <h3 class="margin0">${t`Installed Extensions:`}</h3>
1141+ defaultHeading.textContent = t`Built-in Extensions:`;
991- <div class="flex-container third_party_toolbar"></div>
1142+ defaultContainer.appendChild(defaultHeading);
992- </div>
1143+
993- </div>`);
1144+ const externalContainer = document.createElement('div');
994-
1145+ externalContainer.classList.add('marginBot10');
995- const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">
1146+ const externalHeader = document.createElement('div');
996- <i class="fa-solid fa-spinner fa-spin"></i>
1147+ externalHeader.classList.add('flex-container', 'alignitemscenter', 'spaceBetween', 'flexnowrap', 'marginBot10');
997- <span>` + t`Loading third-party extensions... Please wait...` + `</span>
1148+ const externalHeading = document.createElement('h3');
998- </div>`);
1149+ externalHeading.classList.add('margin0');
999-
1150+ externalHeading.textContent = t`Installed Extensions:`;
1000- htmlExternal.append(htmlLoading);
1151+ const thirdPartyToolbar = document.createElement('div');
1152+ thirdPartyToolbar.classList.add('flex-container', 'third_party_toolbar');
1153+ externalHeader.append(externalHeading, thirdPartyToolbar);
1154+ externalContainer.appendChild(externalHeader);
1155+
1156+ const loadingEl = document.createElement('div');
1157+ loadingEl.classList.add('flex-container', 'alignItemsCenter', 'justifyCenter', 'marginTop10', 'marginBot5');
1158+ const loadingIcon = document.createElement('i');
1159+ loadingIcon.classList.add('fa-solid', 'fa-spinner', 'fa-spin');
1160+ const loadingSpan = document.createElement('span');
1161+ loadingSpan.textContent = t`Loading third-party extensions... Please wait...`;
1162+ loadingEl.append(loadingIcon, loadingSpan);
1163+
1164+ externalContainer.appendChild(loadingEl);
10011165
10021166 const sortOrderKey = 'extensions_sortByName';
10031167 const sortByName = accountStorage.getItem(sortOrderKey) === 'true';
@@ -1006,16 +1170,16 @@ async function showExtensionsDetails() {
10061170 let extensionsToToggle = [];
10071171
10081172 extensions.forEach(value => {
10091173 const { isExternal, extensionHtmlextensionElement } = value;
10101174 const container = isExternal ? htmlExternalexternalContainer : htmlDefaultdefaultContainer;
10111175 container.appendappendChild(extensionHtmlextensionElement);
10121176 });
10131177
10141178 const htmlextensionsMenu = $('<div></div>')
10151179 .addClass('extensions_info')
10161180 .append(htmlErrorserrors)
10171181 .append(htmlDefaultdefaultContainer)
10181182 .append(htmlExternalexternalContainer)
10191183 .append(getModuleInformation());
10201184
10211185 {
@@ -1041,23 +1205,24 @@ async function showExtensionsDetails() {
10411205 const toggleAllExtensionsButton = document.createElement('div');
10421206 toggleAllExtensionsButton.classList.add('menu_button', 'menu_button_icon');
10431207 toggleAllExtensionsButton.title = t`Bulk toggle third-party extensions.`;
1044- toggleAllExtensionsButton.innerHTML = `
1208+ const toggleAllLabel = document.createElement('span');
1045- <span>${t`Toggle extensions`}</span>
1209+ toggleAllLabel.textContent = t`Toggle extensions`;
1046- <div class="fa-solid fa-circle-info opacity50p"></div>
1210+ const toggleAllIcon = document.createElement('div');
1047- `;
1211+ toggleAllIcon.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
1212+ toggleAllExtensionsButton.append(toggleAllLabel, toggleAllIcon);
10481213
10491214 const restoreBulkToggledExtensionsButton = document.createElement('div');
10501215 restoreBulkToggledExtensionsButton.classList.add('menu_button', 'menu_button_icon', 'fa-solid', 'fa-arrow-right-rotate', 'displayNone');
10511216 restoreBulkToggledExtensionsButton.title = t`Restore toggled extensions.\n\nIt does not restore extensions toggled individually.`;
10521217
10531218 toggleAllExtensionsButton.addEventListener('click', () => {
10541219 extensionsToToggle = onToggleAllExtensions(extensionsToToggle, htmlExternal$(externalContainer));
10551220
10561221 for (const extension of extensionsToToggle) {
10571222 const { name } = extension;
10581223
1059- htmlExternal
1224+ $(externalContainer)
10601225 .find(`.extension_block[data-name="${name.replacegetNameSelector('third-party', ''name)}"] .extension_toggle input`)
10611226 .off('click')
10621227 .one('click', () => {
10631228 extensionsToToggle = extensionsToToggle.filter(ext => ext.name !== name);
@@ -1074,8 +1239,8 @@ async function showExtensionsDetails() {
10741239 const { name } = extension;
10751240 const isDisabled = extension_settings.disabledExtensions.includes(name);
10761241
1077- htmlExternal
1242+ $(externalContainer)
10781243 .find(`.extension_block[data-name="${name.replacegetNameSelector('third-party', ''name)}"] .extension_toggle input`)
10791244 .prop('checked', !isDisabled)
10801245 .toggleClass('toggle_enable', isDisabled)
10811246 .toggleClass('toggle_disable', !isDisabled)
@@ -1099,13 +1264,13 @@ async function showExtensionsDetails() {
10991264 });
11001265
11011266 toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton);
11021267 htmlExternal.find('.third_party_toolbar')thirdPartyToolbar.append(restoreBulkToggledExtensionsButton, toggleAllExtensionsButton);
11031268 htmlextensionsMenu.prepend(toolbar);
11041269 }
11051270
11061271 let waitingForSave = false;
11071272
11081273 const popup = new Popup(htmlextensionsMenu, POPUP_TYPE.TEXT, '', {
11091274 okButton: t`Close`,
11101275 wide: true,
11111276 large: true,
@@ -1147,7 +1312,7 @@ async function showExtensionsDetails() {
11471312 });
11481313 popupPromise = popup.show();
11491314 popup.content.scrollTop = initialScrollTop;
11501315 checkForUpdatesManual(sortFn, abortController.signal).finally(() => htmlLoadingloadingEl.remove());
11511316 } catch (error) {
11521317 toastr.error(t`Error loading extensions. See browser console for details.`);
11531318 console.error(error);
@@ -1234,6 +1399,7 @@ async function updateExtension(extensionName, quiet, timeout = null) {
12341399 * This function makes a POST request to '/api/extensions/delete' with the extension's name.
12351400 * If the extension is deleted, it displays a success message.
12361401 * Creates a popup for the user to confirm before delete.
1402+ * If the extension has a 'clean' hook, an optional checkbox to also run the cleanup is shown.
12371403 */
12381404async function onDeleteClick() {
12391405 const extensionName = $(this).data('name');
@@ -1244,11 +1410,48 @@ async function onDeleteClick() {
12441410 return;
12451411 }
12461412
1247- // use callPopup to create a popup for the user to confirm before delete
1413+ const hasCleanHook = hasExtensionHook(extensionName, 'clean');
1248- const confirmation = await callGenericPopup(t`Are you sure you want to delete ${extensionName}?`, POPUP_TYPE.CONFIRM, '', {});
1414+
1415+ /** @type {import('./popup.js').CustomPopupInput[]} */
1416+ const customInputs = hasCleanHook ? [{ id: 'extension_delete_cleanup', label: t`Also clean up extension data`, defaultState: false }] : null;
1417+
1418+ const popup = new Popup(t`Are you sure you want to delete ${escapeHtml(extensionName)}?`, POPUP_TYPE.CONFIRM, '', { customInputs });
1419+ const confirmation = await popup.show();
12491420 if (confirmation === POPUP_RESULT.AFFIRMATIVE) {
1250- await deleteExtension(extensionName);
1421+ const shouldClean = hasCleanHook && Boolean(popup.inputResults?.get('extension_delete_cleanup'));
1422+ await deleteExtension(extensionName, shouldClean);
1423+ }
1424+}
1425+
1426+/**
1427+ * Handles the click event for the clean button of an extension.
1428+ * Runs the extension's 'clean' hook after user confirmation, then reloads the page.
1429+ */
1430+async function onCleanClick() {
1431+ const extensionName = $(this).data('name');
1432+
1433+ const confirmation = await Popup.show.confirm(t`Clean extension data`, t`Are you sure you want to clean up data for ${escapeHtml(extensionName)}? This action cannot be undone.`);
1434+ if (!confirmation) {
1435+ return;
12511436 }
1437+
1438+ await cleanExtension(extensionName);
1439+}
1440+
1441+/**
1442+ * Runs the 'clean' hook for an extension and reloads the page.
1443+ * @param {string} extensionName Extension name (without 'third-party' prefix)
1444+ * @returns {Promise<void>}
1445+ */
1446+async function cleanExtension(extensionName) {
1447+ const fullExtensionName = extensionName.startsWith('third-party') ? extensionName : `third-party${extensionName}`;
1448+ await callExtensionHook(fullExtensionName, 'clean');
1449+
1450+ // Clean might have updated settings, which could race with the page reload, so we'll force save here
1451+ await saveSettings();
1452+
1453+ toastr.success(t`Extension ${extensionName} data cleaned`);
1454+ delay(1000).then(() => location.reload());
12521455}
12531456
12541457async function onBranchClick() {
@@ -1303,8 +1506,8 @@ async function onMoveClick() {
13031506
13041507 const confirmationHeader = t`Move extension`;
13051508 const confirmationText = source == 'global'
13061509 ? t`Are you sure you want to move ${escapeHtml(extensionName)} to your local extensions? This will make it available only for you.`
13071510 : t`Are you sure you want to move ${escapeHtml(extensionName)} to the global extensions? This will make it available for all users.`;
13081511
13091512 const confirmation = await Popup.show.confirm(confirmationHeader, confirmationText);
13101513
@@ -1353,9 +1556,16 @@ async function moveExtension(extensionName, source, destination) {
13531556/**
13541557 * Deletes an extension via the API.
13551558 * @param {string} extensionName Extension name to delete
1559+ * @param {boolean} [shouldClean=false] Whether to also run the 'clean' hook before deleting
13561560 */
13571561export async function deleteExtension(extensionName, shouldClean = false) {
1358- await callExtensionHook(extensionName, 'delete');
1562+ const fullExtensionName = extensionName.startsWith('third-party') ? extensionName : `third-party${extensionName}`;
1563+
1564+ if (shouldClean) {
1565+ await callExtensionHook(fullExtensionName, 'clean');
1566+ }
1567+
1568+ await callExtensionHook(fullExtensionName, 'delete');
13591569
13601570 try {
13611571 await fetch('/api/extensions/delete', {
@@ -1370,6 +1580,9 @@ export async function deleteExtension(extensionName) {
13701580 console.error('Error:', error);
13711581 }
13721582
1583+ // Delete or clean might have updated settings, which could race with the page reload, so we'll force save here
1584+ await saveSettings();
1585+
13731586 toastr.success(t`Extension ${extensionName} deleted`);
13741587 delay(1000).then(() => location.reload());
13751588}
@@ -1398,6 +1611,9 @@ async function getExtensionVersion(extensionName, abortSignal) {
13981611 const data = await response.json();
13991612 return data;
14001613 } catch (error) {
1614+ if (error instanceof Error && error.name === 'AbortError') {
1615+ return;
1616+ }
14011617 console.error('Error:', error);
14021618 }
14031619}
@@ -1476,9 +1692,53 @@ async function switchExtensionBranch(extensionName, isGlobal, branch) {
14761692 * Installs a third-party extension via the API.
14771693 * @param {string} url Extension repository URL
14781694 * @param {boolean} global Is the extension global?
1479- * @returns {Promise<void>}
1695+ * @param {string} [branch] Optional branch to install, if not provided the default branch will be used
1696+ * @returns {Promise<boolean>} True if the extension was installed successfully, false otherwise
14801697 */
14811698export async function installExtension(url, global, branch = '') {
1699+ try {
1700+ const parsedUrl = new URL(url);
1701+ if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
1702+ throw new Error('Invalid URL protocol');
1703+ }
1704+
1705+ // Normalize the URL (resolve relative paths, remove redundant segments, etc.)
1706+ url = parsedUrl.href;
1707+ } catch (error) {
1708+ console.error('Invalid URL:', error);
1709+ toastr.error(t`Only valid HTTP and HTTPS URLs are allowed.`, t`Invalid URL`);
1710+ return false;
1711+ }
1712+
1713+ if (!isOfficialExtension(url)) {
1714+ const extensionInstallationWarningKey = 'extensionInstallationWarningShown';
1715+ if (accountStorage.getItem(extensionInstallationWarningKey)) {
1716+ console.debug('Bypassed URL check for third-party extension (account preference).', url);
1717+ } else {
1718+ let dismissWarning = false;
1719+ const confirmation = await Popup.show.confirm(
1720+ t`Install a third-party extension?`,
1721+ await renderTemplateAsync('thirdPartyExtensionWarning'),
1722+ {
1723+ customInputs: [{ id: 'dontAskAgain', type: 'checkbox', label: t`Don't show this warning again`, defaultState: false }],
1724+ onClose: (popup) => {
1725+ if (!popup.result) {
1726+ return;
1727+ }
1728+ dismissWarning = Boolean(popup.inputResults?.get('dontAskAgain') ?? false);
1729+ },
1730+ okButton: t`Yes, install it`,
1731+ cancelButton: t`No, cancel`,
1732+ });
1733+ if (!confirmation) {
1734+ return false;
1735+ }
1736+ if (dismissWarning) {
1737+ accountStorage.setItem(extensionInstallationWarningKey, '1');
1738+ }
1739+ }
1740+ }
1741+
14821742 console.debug('Extension installation started', url);
14831743
14841744 toastr.info(t`Please wait...`, t`Installing extension`);
@@ -1497,11 +1757,11 @@ export async function installExtension(url, global, branch = '') {
14971757 const text = await request.text();
14981758 toastr.warning(text || request.statusText, t`Extension installation failed`, { timeOut: 5000 });
14991759 console.error('Extension installation failed', request.status, request.statusText, text);
15001760 return false;
15011761 }
15021762
15031763 const response = await request.json();
15041764 toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`);
15051765 console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);
15061766 await loadExtensionSettings({}, false, false);
15071767 await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response);
@@ -1510,6 +1770,8 @@ export async function installExtension(url, global, branch = '') {
15101770 const extensionName = `third-party/${response.folderName}`;
15111771 await callExtensionHook(extensionName, 'install');
15121772 }
1773+
1774+ return true;
15131775}
15141776
15151777/**
@@ -1589,7 +1851,11 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
15891851 const promise = enqueueVersionCheck(async () => {
15901852 try {
15911853 const data = await getExtensionVersion(externalId, abortSignal);
1592- const extensionBlock = document.querySelector(`.extension_block[data-name="${externalId}"]`);
1854+ if (!data) {
1855+ return;
1856+ }
1857+ const selector = getNameSelector(externalId, { prefix: '' });
1858+ const extensionBlock = document.querySelector(`.extension_block[data-name="${selector}"]`);
15931859 if (extensionBlock && data) {
15941860 if (data.isUpToDate === false) {
15951861 const buttonElement = extensionBlock.querySelector('.btn_update');
@@ -1620,6 +1886,18 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
16201886 }
16211887 }
16221888
1889+ const authorElement = extensionBlock.querySelector('.extension_author');
1890+ if (authorElement) {
1891+ const author = getAuthorFromUrl(origin) || EMPTY_AUTHOR;
1892+ if (author.name) {
1893+ const icon = document.createElement('i');
1894+ icon.classList.add('fa-solid', 'fa-at', 'fa-xs');
1895+ const name = document.createElement('span');
1896+ name.textContent = author.name;
1897+ authorElement.append(icon, name);
1898+ }
1899+ }
1900+
16231901 const versionElement = extensionBlock.querySelector('.extension_version');
16241902 if (versionElement) {
16251903 versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`;
@@ -1672,6 +1950,9 @@ async function checkForExtensionUpdates(force) {
16721950 const promise = enqueueVersionCheck(async () => {
16731951 try {
16741952 const data = await getExtensionVersion(id.replace('third-party', ''));
1953+ if (!data) {
1954+ return;
1955+ }
16751956 if (!data.isUpToDate) {
16761957 updatesAvailable.push(manifest.display_name);
16771958 }
@@ -1759,6 +2040,18 @@ export async function runGenerationInterceptors(chat, contextSize, type) {
17592040}
17602041
17612042/**
2043+ * Sentinel value that signals a field should be completely removed (unset)
2044+ * from the character card rather than being set to any value. Pass this as
2045+ * the `value` argument to {@link writeExtensionField} or
2046+ * {@link writeExtensionFieldBulk} to delete the key entirely.
2047+ *
2048+ * Using `null` as a value will set the field to `null` (the key remains).
2049+ * Using this sentinel will delete the key from the character card.
2050+ * @type {string}
2051+ */
2052+export const UNSET_VALUE = '__@@UNSET@@__';
2053+
2054+/**
17622055 * Writes a field to the character's data extensions object.
17632056 * @param {number|string} characterId Index in the character array
17642057 * @param {string} key Field name
@@ -1772,13 +2065,23 @@ export async function writeExtensionField(characterId, key, value) {
17722065 console.warn('Character not found', characterId);
17732066 return;
17742067 }
17752068 const pathextensionPath = `data.extensions.${key}`;
1776- setValueByPath(character, path, value);
2069+ const isUnset = value === UNSET_VALUE;
2070+
2071+ if (isUnset) {
2072+ deleteValueByPath(character, extensionPath);
2073+ } else {
2074+ setValueByPath(character, extensionPath, value);
2075+ }
17772076
17782077 // Process JSON data
17792078 if (character.json_data) {
17802079 const jsonData = JSON.parse(character.json_data);
1781- setValueByPath(jsonData, path, value);
2080+ if (isUnset) {
2081+ deleteValueByPath(jsonData, extensionPath);
2082+ } else {
2083+ setValueByPath(jsonData, extensionPath, value);
2084+ }
17822085 character.json_data = JSON.stringify(jsonData);
17832086
17842087 // Make sure the data doesn't get lost when saving the current character
@@ -1808,6 +2111,107 @@ export async function writeExtensionField(characterId, key, value) {
18082111}
18092112
18102113/**
2114+ * @typedef {object} BulkExtensionFieldResult
2115+ * @property {string[]} updated Avatar filenames that were successfully updated
2116+ * @property {string[]} skipped Avatar filenames skipped (filter didn't match or unreadable)
2117+ * @property {string[]} failed Avatar filenames where the update failed
2118+ */
2119+
2120+/**
2121+ * Writes (or deletes) an extension field for multiple characters in a single
2122+ * bulk request. Unlike {@link writeExtensionField}, this sends one API call
2123+ * for all characters, and the server processes them in parallel.
2124+ *
2125+ * When `value` is {@link UNSET_VALUE} the extension key is **deleted** from
2126+ * each matching character card. Passing `null` sets the field to `null`
2127+ * (the key is preserved).
2128+ *
2129+ * @param {string[]|null} avatars Avatar filenames to update. Pass `null` or an
2130+ * empty array to target **all** characters in the user's character directory.
2131+ * @param {string} key Extension field name (e.g. "greeting_tools")
2132+ * @param {any} value Field value, `null` to set null, or
2133+ * {@link UNSET_VALUE} to delete the key entirely
2134+ * @param {object} [options={}] Optional settings
2135+ * @param {string} [options.filterPath] Dot-path filter — the server will only
2136+ * update characters where this path is present and not `undefined`;
2137+ * `null` still counts as a match. Useful when the frontend has shallow
2138+ * character data and cannot pre-filter.
2139+ * Defaults to `data.extensions.<key>` when unsetting, so deletion requests
2140+ * automatically skip characters where the field is missing/`undefined`.
2141+ * @returns {Promise<BulkExtensionFieldResult>} Summary of the bulk operation
2142+ */
2143+export async function writeExtensionFieldBulk(avatars, key, value, { filterPath } = {}) {
2144+ const context = getContext();
2145+ const extensionPath = `data.extensions.${key}`;
2146+ const isUnset = value === UNSET_VALUE;
2147+
2148+ // Build the server request
2149+ const requestBody = {
2150+ avatars: Array.isArray(avatars) && avatars.length > 0 ? avatars : [],
2151+ data: {
2152+ data: {
2153+ extensions: {
2154+ [key]: value,
2155+ },
2156+ },
2157+ },
2158+ };
2159+
2160+ // Default filter: when unsetting, only touch characters that have the field
2161+ const resolvedFilterPath = filterPath ?? (isUnset ? extensionPath : undefined);
2162+ if (resolvedFilterPath) {
2163+ requestBody.filter = { path: resolvedFilterPath };
2164+ }
2165+
2166+ const mergeResponse = await fetch('/api/characters/merge-attributes', {
2167+ method: 'POST',
2168+ headers: getRequestHeaders(),
2169+ body: JSON.stringify(requestBody),
2170+ });
2171+
2172+ if (!mergeResponse.ok) {
2173+ console.error('Bulk extension field update failed', mergeResponse.statusText);
2174+ return { updated: [], skipped: [], failed: [] };
2175+ }
2176+
2177+ /** @type {BulkExtensionFieldResult} */
2178+ const result = await mergeResponse.json();
2179+
2180+ // Sync in-memory character objects for successfully updated characters
2181+ const updatedSet = new Set(result.updated);
2182+ for (const character of context.characters) {
2183+ if (!character || !updatedSet.has(character.avatar)) continue;
2184+
2185+ if (isUnset) {
2186+ deleteValueByPath(character, extensionPath);
2187+ } else {
2188+ setValueByPath(character, extensionPath, value);
2189+ }
2190+
2191+ // Keep json_data in sync
2192+ if (character.json_data) {
2193+ const jsonData = JSON.parse(character.json_data);
2194+ if (isUnset) {
2195+ deleteValueByPath(jsonData, extensionPath);
2196+ } else {
2197+ setValueByPath(jsonData, extensionPath, value);
2198+ }
2199+ character.json_data = JSON.stringify(jsonData);
2200+ }
2201+ }
2202+
2203+ // If the currently active character was updated, sync the hidden input
2204+ if (context.characterId !== undefined) {
2205+ const activeChar = context.characters[context.characterId];
2206+ if (activeChar && updatedSet.has(activeChar.avatar) && activeChar.json_data) {
2207+ $('#character_json_data').val(activeChar.json_data);
2208+ }
2209+ }
2210+
2211+ return result;
2212+}
2213+
2214+/**
18112215 * Prompts the user to enter the Git URL of the extension to import.
18122216 * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
18132217 * If the extension is imported successfully, a success message is displayed.
@@ -1853,6 +2257,39 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
18532257 await installExtension(url, global, branchName);
18542258}
18552259
2260+/**
2261+ * Sentinel value representing an empty author, used when author information cannot be extracted from a URL.
2262+ * @type {{name: string, url: string}}
2263+ */
2264+export const EMPTY_AUTHOR = Object.freeze({
2265+ name: '',
2266+ url: '',
2267+});
2268+
2269+/**
2270+ * Extracts the repository author from a given URL.
2271+ * @param {string} url - The URL of the repository.
2272+ * @returns {{name: string, url: string}} Object containing the author's name and URL, or empty strings if not found.
2273+ */
2274+export function getAuthorFromUrl(url) {
2275+ const result = structuredClone(EMPTY_AUTHOR);
2276+
2277+ try {
2278+ const parsedUrl = new URL(url);
2279+ const pathSegments = parsedUrl.pathname.split('/').filter(s => s.length > 0);
2280+
2281+ // TODO: Handle non-GitHub URLs if needed
2282+ if (parsedUrl.host === 'github.com' && pathSegments.length >= 2) {
2283+ result.name = pathSegments[0];
2284+ result.url = `${parsedUrl.protocol}//${parsedUrl.hostname}/${result.name}`;
2285+ }
2286+ } catch (error) {
2287+ console.debug('Error parsing URL:', error);
2288+ }
2289+
2290+ return result;
2291+}
2292+
18562293export async function initExtensions() {
18572294 await addExtensionsButtonAndMenu();
18582295 $('#extensionsMenuButton').css('display', 'flex');
@@ -1865,6 +2302,7 @@ export async function initExtensions() {
18652302 $(document).on('click', '.extensions_info .extension_block .toggle_enable', onEnableExtensionClick);
18662303 $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick);
18672304 $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick);
2305+ $(document).on('click', '.extensions_info .extension_block .btn_clean', onCleanClick);
18682306 $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick);
18692307 $(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick);
18702308
public/scripts/extensions/assets/index.js+226 -127
@@ -5,12 +5,12 @@ TODO:
55
66import { DOMPurify } from '../../../lib.js';
77import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js';
88import { deleteExtension, EMPTY_AUTHOR, extensionNames, getAuthorFromUrl, getContext, installExtension, renderExtensionTemplateAsync, isOfficialExtension } from '../../extensions.js';
99import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
10-import { executeSlashCommandsWithOptions } from '../../slash-commands.js';
1110import { accountStorage } from '../../util/AccountStorage.js';
1211import { escapeHtml, flashHighlight, getStringHash, isValidUrl } from '../../utils.js';
1312import { t, translate } from '../../i18n.js';
13+import { SlashCommandParser } from '/scripts/slash-commands/SlashCommandParser.js';
1414export { MODULE_NAME };
1515
1616const MODULE_NAME = 'assets';
@@ -60,100 +60,34 @@ const KNOWN_TYPES = {
6060 'blip': t`Blip sounds`,
6161};
6262
63-const EMPTY_AUTHOR = {
64- name: '',
65- url: '',
66-};
67-
6863/**
69- * Extracts the repository author from a given URL.
64+ * Creates the download/delete button element for a single asset, with all interaction handlers attached.
70- * @param {string} url - The URL of the repository.
65+ * @param {object} asset The asset data object, containing at least id, name, description and url fields
71- * @returns {{name: string, url: string}} Object containing the author's name and URL, or empty strings if not found.
66+ * @param {string} assetType Asset type, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip'
67+ * @param {number} index Index of the asset in the list of available assets of the same type, used to create a unique element ID
68+ * @returns {JQuery} The button element
7269 */
7370function getAuthorFromUrlcreateAssetButton(urlasset, assetType, index) {
74- const result = structuredClone(EMPTY_AUTHOR);
71+ const elemId = `assets_install_${assetType}_${index}`;
75-
72+ const element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
76- try {
77- const parsedUrl = new URL(url);
78- const pathSegments = parsedUrl.pathname.split('/').filter(s => s.length > 0);
79-
80- // TODO: Handle non-GitHub URLs if needed
81- if (parsedUrl.host === 'github.com' && pathSegments.length >= 2) {
82- result.name = pathSegments[0];
83- result.url = `${parsedUrl.protocol}//${parsedUrl.hostname}/${result.name}`;
84- }
85- } catch (error) {
86- console.debug(DEBUG_PREFIX, 'Error parsing URL:', error);
87- }
88-
89- return result;
90-}
91-
92-async function downloadAssetsList(url) {
93- updateCurrentAssets().then(async function () {
94- fetch(url, { cache: 'no-cache' })
95- .then(response => response.json())
96- .then(async function (json) {
97- availableAssets = {};
98- $('#assets_menu').empty();
99-
100- console.debug(DEBUG_PREFIX, 'Received assets dictionary', json);
101-
102- for (const i of json) {
103- //console.log(DEBUG_PREFIX,i)
104- if (availableAssets[i.type] === undefined)
105- availableAssets[i.type] = [];
106-
107- availableAssets[i.type].push(i);
108- }
109-
110- console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets);
111- // First extensions, then everything else
112- const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0);
113-
114- $('#assets_type_select').empty();
115- $('#assets_search').val('');
116- $('#assets_type_select').append($('<option />', { value: '', text: t`All` }));
117-
118- for (const type of assetTypes) {
119- const text = translate(KNOWN_TYPES[type] || type);
120- const option = $('<option />', { value: type, text: text });
121- $('#assets_type_select').append(option);
122- }
123-
124- if (assetTypes.includes('extension')) {
125- $('#assets_type_select').val('extension');
126- }
127-
128- $('#assets_type_select').off('change').on('change', filterAssets);
129- $('#assets_search').off('input').on('input', filterAssets);
130-
131- for (const assetType of assetTypes) {
132- let assetTypeMenu = $('<div />', { id: 'assets_audio_ambient_div', class: 'assets-list-div' });
133- assetTypeMenu.attr('data-type', assetType);
134- assetTypeMenu.append(`<h3>${KNOWN_TYPES[assetType] || assetType}</h3>`).hide();
135-
136- if (assetType == 'extension') {
137- assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation'));
138- }
139-
140- for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a.name.localeCompare(b.name))) {
141- const i = availableAssets[assetType].indexOf(asset);
142- const elemId = `assets_install_${assetType}_${i}`;
143- let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
14473 const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
14574 element.append(label);
14675
147- //if (DEBUG_TONY_SAMA_FORK_MODE)
148- // asset["url"] = asset["url"].replace("https://github.com/SillyTavern/","https://github.com/Tony-sama/"); // DBG
149-
15076 console.debug(DEBUG_PREFIX, 'Checking asset', asset.id, asset.url);
15177
15278 const assetInstall = async function () {
15379 element.off('click');
15480 label.removeClass('fa-download');
15581 this.classList.add('asset-download-button-loading');
15682 const result = await installAsset(asset.url, assetType, asset.id);
83+ if (!result) {
84+ this.classList.remove('asset-download-button-loading');
85+ label.addClass('fa-download');
86+ label.removeClass('fa-spinner');
87+ label.removeClass('fa-spin');
88+ element.on('click', assetInstall);
89+ return;
90+ }
15791 label.addClass('fa-check');
15892 this.classList.remove('asset-download-button-loading');
15993 element.on('click', assetDelete);
@@ -171,7 +105,7 @@ async function downloadAssetsList(url) {
171105 const assetDelete = async function () {
172106 if (assetType === 'character') {
173107 toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
174- await executeSlashCommandsWithOptions(`/go ${asset.id}`);
108+ await SlashCommandParser.commands.go.callback(null, asset.id);
175109 return;
176110 }
177111 element.off('click');
@@ -204,6 +138,17 @@ async function downloadAssetsList(url) {
204138 element.on('click', assetInstall);
205139 }
206140
141+ return element;
142+}
143+
144+/**
145+ * Creates the full visual block element for a single asset.
146+ * @param {object} asset The asset data object, containing at least id, name, description and url fields
147+ * @param {string} assetType Asset type, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip'
148+ * @param {JQuery} element The button element from createAssetButton
149+ * @returns {JQuery} The asset block element
150+ */
151+function createAssetBlock(asset, assetType, element) {
207152 console.debug(DEBUG_PREFIX, 'Created element for ', asset.id);
208153
209154 const displayName = DOMPurify.sanitize(asset.name || asset.id);
@@ -214,23 +159,31 @@ async function downloadAssetsList(url) {
214159 const toolTag = assetType === 'extension' && asset.tool;
215160 const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR;
216161
217- const assetBlock = $('<i></i>')
162+ const nameSpan = $('<span>', { class: 'asset-name flex-container alignitemscenter' })
218- .append(element)
163+ .append($('<b>').text(displayName))
219- .append(`<div class="flex-container flexFlowColumn flexNoGap wide100p overflowHidden">
164+ .append($('<a>', { class: 'asset_preview', href: url, target: '_blank', title: title })
220- <span class="asset-name flex-container alignitemscenter">
165+ .append($('<i>', { class: `fa-solid fa-sm ${previewIcon}` })));
221- <b>${displayName}</b>
166+
222- <a class="asset_preview" href="${url}" target="_blank" title="${title}">
167+ if (toolTag) {
223- <i class="fa-solid fa-sm ${previewIcon}"></i>
168+ const tagSpan = $('<span>', { class: 'tag', title: t`Adds a function tool` })
224- </a>` +
169+ .append($('<i>', { class: 'fa-solid fa-sm fa-wrench' }))
225- (toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' +
170+ .append(document.createTextNode(` ${t`Tool`}`));
226- t`Tool` + '</span>' : '') +
171+ nameSpan.append(tagSpan);
227- '<span class="expander"></span>' +
172+ }
228- (author.name ? `<a href="${author.url}" target="_blank" class="asset-author-info"><i class="fa-solid fa-at fa-xs"></i><span>${author.name}</span></a>` : '') +
173+
229- `</span>
174+ nameSpan.append($('<span>', { class: 'expander' }));
230- <small class="asset-description">
175+
231- ${description}
176+ if (author.name) {
232- </small>
177+ nameSpan.append($('<a>', { href: author.url, target: '_blank', class: 'asset-author-info' })
233- </div>`);
178+ .append($('<i>', { class: 'fa-solid fa-at fa-xs' }))
179+ .append($('<span>').text(author.name)));
180+ }
181+
182+ const infoDiv = $('<div>', { class: 'flex-container flexFlowColumn flexNoGap wide100p overflowHidden' })
183+ .append(nameSpan)
184+ .append($('<small>', { class: 'asset-description' }).text(description));
185+
186+ const assetBlock = $('<i></i>').append(element).append(infoDiv);
234187
235188 assetBlock.find('.tag').on('click', function (e) {
236189 const a = document.createElement('a');
@@ -241,38 +194,128 @@ async function downloadAssetsList(url) {
241194
242195 if (assetType === 'character') {
243196 if (asset.highlight) {
244197 assetBlock.find('.asset-name') nameSpan.append($('<i>', { class=": 'fa-solid fa-sm fa-trophy"></i>' }));
245198 }
246- assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset.url}" alt="${displayName}"></div>`);
199+ nameSpan.prepend($('<div>', { class: 'avatar' }).append($('<img>', { src: asset.url, alt: displayName })));
247200 }
248201
249202 assetBlock.addClass('asset-block');
203+ return assetBlock;
204+}
250205
206+/**
207+ * Builds and appends the menu section for a single asset type.
208+ * @param {string} assetType Asset type, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip'
209+ * @returns {Promise<void>}
210+ */
211+async function buildAssetTypeSection(assetType) {
212+ const assetTypeMenu = $('<div />', { id: `assets_${assetType}_div`, class: 'assets-list-div' });
213+ assetTypeMenu.attr('data-type', assetType);
214+ assetTypeMenu.append($('<h3>').text(KNOWN_TYPES[assetType] || assetType)).hide();
215+
216+ if (assetType == 'extension') {
217+ assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation'));
218+ }
219+
220+ for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a.name.localeCompare(b.name))) {
221+ const i = availableAssets[assetType].indexOf(asset);
222+ const element = createAssetButton(asset, assetType, i);
223+ const assetBlock = createAssetBlock(asset, assetType, element);
224+
225+ if (assetType === 'extension') {
226+ const extensionBlockList = isOfficialExtension(asset.url)
227+ ? assetTypeMenu.find('.assets-list-extensions-official .assets-list-extensions')
228+ : assetTypeMenu.find('.assets-list-extensions-community .assets-list-extensions');
229+ extensionBlockList.append(assetBlock);
230+ } else {
251231 assetTypeMenu.append(assetBlock);
252232 }
233+ }
234+
253235 assetTypeMenu.appendTo('#assets_menu');
254236 assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
255237}
256238
239+/**
240+ * Parses the fetched assets JSON and renders the full assets menu.
241+ * @param {object[]} json Array of asset objects, each containing at least id, name, description, url and type fields
242+ */
243+async function populateAssetsMenu(json) {
244+ availableAssets = {};
245+ $('#assets_menu').empty();
246+
247+ console.debug(DEBUG_PREFIX, 'Received assets dictionary', json);
248+
249+ for (const i of json) {
250+ if (availableAssets[i.type] === undefined)
251+ availableAssets[i.type] = [];
252+ availableAssets[i.type].push(i);
253+ }
254+
255+ console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets);
256+ // First extensions, then everything else
257+ const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0);
258+
259+ $('#assets_type_select').empty();
260+ $('#assets_search').val('');
261+ $('#assets_type_select').append($('<option />', { value: '', text: t`All` }));
262+
263+ for (const type of assetTypes) {
264+ const text = translate(KNOWN_TYPES[type] || type);
265+ const option = $('<option />', { value: type, text: text });
266+ $('#assets_type_select').append(option);
267+ }
268+
269+ if (assetTypes.includes('extension')) {
270+ $('#assets_type_select').val('extension');
271+ }
272+
273+ $('#assets_type_select').off('change').on('change', filterAssets);
274+ $('#assets_search').off('input').on('input', filterAssets);
275+
276+ for (const assetType of assetTypes) {
277+ await buildAssetTypeSection(assetType);
278+ }
279+
257280 filterAssets();
258281 $('#assets_filters').show();
259282 $('#assets_menu').show();
260283 })
261- .catch((error) => {
284+
262- // Info hint if the user maybe... likely accidently was trying to install an extension and we wanna help guide them? uwu :3
285+/**
286+ * Downloads the assets list from the given URL and populates the menu. Shows error message if something goes wrong.
287+ * @param {URL} url URL to fetch from
288+ */
289+async function downloadAssetsList(url) {
290+ await updateCurrentAssets();
291+ try {
292+ const response = await fetch(url, { cache: 'no-cache' });
293+ if (!response.ok) {
294+ throw new Error('Cannot download the assets list.');
295+ }
296+ const json = await response.json();
297+ if (!Array.isArray(json)) {
298+ throw new Error('Assets list is not an array');
299+ }
300+ await populateAssetsMenu(json);
301+ } catch (error) {
302+ // Info hint if the user maybe... likely accidentally was trying to install an extension and we wanna help guide them? uwu :3
263303 const installButton = $('#third_party_extension_button');
264304 flashHighlight(installButton, 10_000);
265305 toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 });
266306
267307 // Error logged after, to appear on top
268308 console.error(error);
269309 toastr.error('Problem with assets URL', DEBUG_PREFIX + 'Cannot get assets list');
270310 $('#assets-connect-button').addClass('fa-plug-circle-exclamation');
271311 $('#assets-connect-button').addClass('redOverlayGlow');
272- });
312+ }
273- });
274313}
275314
315+/**
316+ * Previews the asset by opening its URL. If it's an audio asset, it plays a preview sound. Otherwise, it opens the URL in a new tab.
317+ * @param {JQuery.Event} e Click event
318+ */
276319function previewAsset(e) {
277320 const href = $(this).attr('href');
278321 const audioExtensions = ['.mp3', '.ogg', '.wav'];
@@ -295,6 +338,15 @@ function previewAsset(e) {
295338 }
296339}
297340
341+/**
342+ * Checks if the asset is already installed.
343+ * For extensions, it checks if the extension name is in the list of installed extensions.
344+ * For characters, it checks if any character has the same avatar URL.
345+ * For other asset types, it checks if any installed asset of the same type has a URL that includes the filename.
346+ * @param {string} assetType Type of the asset, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip'
347+ * @param {string} filename Name or ID of the asset
348+ * @returns {boolean} True if the asset is installed, false otherwise
349+ */
298350function isAssetInstalled(assetType, filename) {
299351 let assetList = currentAssets[assetType];
300352
@@ -316,15 +368,22 @@ function isAssetInstalled(assetType, filename) {
316368 return false;
317369}
318370
371+/**
372+ * Installs the asset by sending a request to the server to download it. If it's an extension, it uses the existing installExtension function.
373+ * @param {string} url URL of the asset to download
374+ * @param {string} assetType Type of the asset, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip'
375+ * @param {string} filename Name or ID of the asset
376+ * @returns {Promise<boolean>} True if the asset was successfully installed, false otherwise
377+ */
319378async function installAsset(url, assetType, filename) {
320379 console.debug(DEBUG_PREFIX, 'Downloading ', url);
321380 const category = assetType;
322381 try {
323382 if (category === 'extension') {
324383 console.debug(DEBUG_PREFIX, 'Installing extension ', url);
325384 const result = await installExtension(url, false);
326385 console.debug(DEBUG_PREFIX, 'Extension installed.');
327386 return result;
328387 }
329388
330389 const body = { url, category, filename };
@@ -340,16 +399,25 @@ async function installAsset(url, assetType, filename) {
340399 console.debug(DEBUG_PREFIX, 'Importing character ', filename);
341400 const blob = await result.blob();
342401 const file = new File([blob], filename, { type: blob.type });
343402 awaitconst processDroppedFilesfileNameMap = new Map([[file, filename]]);
403+ await processDroppedFiles([file], fileNameMap);
344404 console.debug(DEBUG_PREFIX, 'Character downloaded.');
345405 }
406+ return true;
346407 }
408+ return false;
347409 } catch (err) {
348410 console.log(err);
349411 return []false;
350412 }
351413}
352414
415+/**
416+ * Deletes the asset by sending a request to the server to delete it. If it's an extension, it uses the existing deleteExtension function.
417+ * @param {string} assetType Type of the asset, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip'
418+ * @param {string} filename Name or ID of the asset
419+ * @returns {Promise<boolean>} True if the asset was successfully deleted, false otherwise
420+ */
353421async function deleteAsset(assetType, filename) {
354422 console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename);
355423 const category = assetType;
@@ -358,6 +426,7 @@ async function deleteAsset(assetType, filename) {
358426 console.debug(DEBUG_PREFIX, 'Deleting extension ', filename);
359427 await deleteExtension(filename);
360428 console.debug(DEBUG_PREFIX, 'Extension deleted.');
429+ return true;
361430 }
362431
363432 const body = { category, filename };
@@ -369,19 +438,37 @@ async function deleteAsset(assetType, filename) {
369438 });
370439 if (result.ok) {
371440 console.debug(DEBUG_PREFIX, 'Deletion success.');
441+ return true;
372442 }
443+ return false;
373444 } catch (err) {
374445 console.log(err);
375446 return []false;
376447 }
377448}
378449
450+/**
451+ * Opens the character browser popup, which shows all available characters and allows downloading them.
452+ * @param {boolean} forceDefault If true, it uses the default ASSETS_JSON_URL instead of the one from the input field.
453+ * @returns {Promise<void>}
454+ */
379455async function openCharacterBrowser(forceDefault) {
380456 const url = forceDefault ? ASSETS_JSON_URL : String($('#assets-json-url-field').val());
457+ if (!isValidUrl(url)) {
458+ toastr.error('Please enter a valid URL');
459+ return;
460+ }
381461 const fetchResult = await fetch(url, { cache: 'no-cache' });
462+ if (!fetchResult.ok) {
463+ toastr.error('Cannot download the assets list.');
464+ return;
465+ }
382466 const json = await fetchResult.json();
383- const characters = json.filter(x => x.type === 'character');
467+ if (!Array.isArray(json)) {
384-
468+ toastr.error('Assets list is not an array');
469+ return;
470+ }
471+ const characters = json.filter(x => x && x.type === 'character');
385472 if (!characters.length) {
386473 toastr.error('No characters found in the assets list', 'Character browser');
387474 return;
@@ -398,12 +485,19 @@ async function openCharacterBrowser(forceDefault) {
398485
399486 downloadButton.toggle(!isInstalled).on('click', async () => {
400487 downloadButton.toggleClass('fa-download fa-spinner fa-spin');
401488 const result = await installAsset(character.url, 'character', character.id);
489+ if (result) {
402490 downloadButton.hide();
403491 checkMark.show();
492+ } else {
493+ downloadButton.toggleClass('fa-download fa-spinner fa-spin');
494+ }
404495 });
405496
406- checkMark.toggle(isInstalled);
497+ checkMark.toggle(isInstalled).on('click', async () => {
498+ toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
499+ await SlashCommandParser.commands.go.callback(null, character.id);
500+ });
407501
408502 listElement.append(characterElement);
409503 }
@@ -435,7 +529,7 @@ async function updateCurrentAssets() {
435529//#############################//
436530
437531// This function is called when the extension is loaded
438532jQuery(export async function init() => {
439533 // This is an example of loading HTML from a file
440534 const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {});
441535 const windowHtml = $(windowTemplate);
@@ -457,11 +551,16 @@ jQuery(async () => {
457551
458552 const connectButton = windowHtml.find('#assets-connect-button');
459553 connectButton.on('click', async function () {
460554 const urlurlString = DOMPurify.sanitize(String(assetsJsonUrl.val()).trim();
461- const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`;
555+ if (!isValidUrl(urlString)) {
556+ toastr.error('Please enter a valid URL');
557+ return;
558+ }
559+ const url = new URL(urlString);
560+ const rememberKey = `Assets_SkipConfirm_${getStringHash(url.href)}`;
462561 const skipConfirm = accountStorage.getItem(rememberKey) === 'true';
463562
464563 const confirmation = skipConfirm || await Popup.show.confirm(t`Loading Asset List`, '<span>' + t`Are you sure you want to connect to the following url?` + `</span><var>${escapeHtml(url.href)}</var>`, {
465564 customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }],
466565 onClose: popup => {
467566 if (popup.result) {
@@ -480,7 +579,7 @@ jQuery(async () => {
480579 connectButton.addClass('fa-plug-circle-check');
481580 } catch (error) {
482581 console.error('Error:', error);
483582 toastr.error(`Cannot get assets list from ${url.href}`);
484583 connectButton.removeClass('fa-plug-circle-check');
485584 connectButton.addClass('fa-plug-circle-exclamation');
486585 connectButton.removeClass('redOverlayGlow');
@@ -496,4 +595,4 @@ jQuery(async () => {
496595 eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {
497596 openCharacterBrowser(forceDefault);
498597 });
499598});
public/scripts/extensions/assets/installation.html+18 -0
@@ -2,3 +2,21 @@
22 <span data-i18n="extension_install_1">To download extensions from this page, you need to have </span><a href="https://git-scm.com/downloads" target="_blank">Git</a><span data-i18n="extension_install_2"> installed.</span><br>
33 <span data-i18n="extension_install_3">Click the </span><i class="fa-solid fa-sm fa-arrow-up-right-from-square"></i><span data-i18n="extension_install_4"> icon to visit the Extension's repo for tips on how to use it.</span>
44</div>
5+<div class="assets-list-extensions-official">
6+ <h2 data-i18n="Official Extensions">Official Extensions</h2>
7+ <div class="info-block hint">
8+ <small class="assets-list-description" data-i18n="These extensions are maintained by the SillyTavern team.">
9+ These extensions are maintained by the SillyTavern team.
10+ </small>
11+ </div>
12+ <div class="assets-list-extensions"></div>
13+</div>
14+<div class="assets-list-extensions-community">
15+ <h2 data-i18n="Community Extensions">Community Extensions</h2>
16+ <div class="info-block warning">
17+ <small data-i18n="Community extensions are not reviewed or verified by the SillyTavern team. Please exercise caution when installing.">
18+ Community extensions are not reviewed or verified by the SillyTavern team. Please exercise caution when installing.
19+ </small>
20+ </div>
21+ <div class="assets-list-extensions"></div>
22+</div>
public/scripts/extensions/assets/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "Keij#6799",
99 "version": "0.1.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/assets/style.css+16 -3
@@ -27,15 +27,20 @@
2727 margin-bottom: 0.25em;
2828}
2929
30+.assets-list-div h2 {
31+ margin: 0;
32+ font-size: 1.1em;
33+}
34+
3035.assets-list-div h3 {
3136 text-transform: capitalize;
3237}
3338
3439.assets-list-div i.asset-block a {
3540 color: inherit;
3641}
3742
3843.assets-list-div>i .asset-block {
3944 display: flex;
4045 flex-direction: row;
4146 align-items: center;
@@ -46,7 +51,7 @@
4651 border-bottom: 1px solid var(--SmartThemeBorderColor);
4752}
4853
4954.assets-list-div i.asset-block span:first-of-type {
5055 font-weight: bold;
5156}
5257
@@ -198,3 +203,11 @@
198203.asset-name>b {
199204 font-weight: 600;
200205}
206+
207+div:is(.assets-list-extensions-official, .assets-list-extensions-community):has(.assets-list-extensions:empty) {
208+ display: none;
209+}
210+
211+div:is(.assets-list-extensions-official, .assets-list-extensions-community) {
212+ margin-top: 10px;
213+}
public/scripts/extensions/attachments/index.js+2 -2
@@ -243,7 +243,7 @@ function handleCharacterRename(oldAvatar, newAvatar) {
243243 }
244244}
245245
246246jQuery(export async function init() => {
247247 eventSource.on(event_types.APP_READY, cleanUpAttachments);
248248 eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments);
249249 eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename);
@@ -407,4 +407,4 @@ jQuery(async () => {
407407 }),
408408 ],
409409 }));
410410});
public/scripts/extensions/attachments/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "Cohee1207",
99 "version": "1.0.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/caption/index.js+12 -6
@@ -1,8 +1,9 @@
11import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
22import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
33import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtendedsubstituteParams } from '../../../script.js';
44import { getMessageTimeStamp } from '../../RossAscends-mods.js';
55import { SECRET_KEYS, secret_state } from '../../secrets.js';
6+import { oai_settings } from '../../openai.js';
67import { getMultimodalCaption } from '../shared.js';
78import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js';
89import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
@@ -99,7 +100,7 @@ async function wrapCaptionTemplate(caption) {
99100 template += ' {{caption}}';
100101 }
101102
102103 let messageText = substituteParamsExtendedsubstituteParams(template, { dynamicMacros: { caption: caption } });
103104
104105 if (extension_settings.caption.refine_mode) {
105106 messageText = await Popup.show.input(
@@ -321,6 +322,8 @@ async function captionMultimodal(base64Img, externalPrompt) {
321322 prompt = String(customPrompt).trim();
322323 }
323324
325+ prompt = substituteParams(prompt);
326+
324327 const caption = await getMultimodalCaption(base64Img, prompt);
325328 return { caption };
326329}
@@ -454,7 +457,7 @@ function isVideoCaptioningAvailable() {
454457 return ['google', 'vertexai', 'zai'].includes(extension_settings.caption.multimodal_api);
455458}
456459
457460jQuery(export async function init() {
458461 function addSendPictureButton() {
459462 const sendButton = $(`
460463 <div id="send_picture" class="list-group-item flex-container flexGap5">
@@ -504,6 +507,7 @@ jQuery(async function () {
504507 'chutes': SECRET_KEYS.CHUTES,
505508 'electronhub': SECRET_KEYS.ELECTRONHUB,
506509 'pollinations': SECRET_KEYS.POLLINATIONS,
510+ 'workers_ai': SECRET_KEYS.WORKERS_AI,
507511 };
508512
509513 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
@@ -580,7 +584,7 @@ jQuery(async function () {
580584 }
581585
582586 async function addRemoteEndpointModels() {
583587 async function processEndpoint(api, url, additionalParams = {}) {
584588 const dropdown = document.getElementById('caption_multimodal_model');
585589 if (!(dropdown instanceof HTMLSelectElement)) {
586590 return;
@@ -591,7 +595,8 @@ jQuery(async function () {
591595 const options = Array.from(dropdown.options);
592596 const response = await fetch(url, {
593597 method: 'POST',
594598 headers: getRequestHeaders({ omitContentType: true }),
599+ body: JSON.stringify(additionalParams),
595600 });
596601 if (!response.ok) {
597602 return;
@@ -620,6 +625,7 @@ jQuery(async function () {
620625 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');
621626 await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');
622627 await processEndpoint('moonshot', '/api/backends/chat-completions/multimodal-models/moonshot');
628+ await processEndpoint('workers_ai', '/api/backends/chat-completions/multimodal-models/workers_ai', { workers_ai_account_id: oai_settings.workers_ai_account_id });
623629 }
624630
625631 await addSettings();
@@ -804,4 +810,4 @@ jQuery(async function () {
804810 }));
805811
806812 document.body.classList.add('caption');
807813});
public/scripts/extensions/caption/manifest.json+4 -1
@@ -9,5 +9,8 @@
99 "css": "style.css",
1010 "author": "Cohee#1207",
1111 "version": "1.0.0",
1212 "homePage": "https://github.com/SillyTavern/SillyTavern",
13+ "hooks": {
14+ "activate": "init"
15+ }
1316}
public/scripts/extensions/caption/settings.html+15 -0
@@ -20,6 +20,7 @@
2020 <option value="aimlapi">AI/ML API</option>
2121 <option value="chutes">Chutes</option>
2222 <option value="anthropic">Claude</option>
23+ <option value="workers_ai">Cloudflare Workers AI</option>
2324 <option value="cohere">Cohere</option>
2425 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
2526 <option value="electronhub">Electron Hub</option>
@@ -53,7 +54,14 @@
5354 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
5455 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
5556 <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option>
57+ <option data-type="openai" value="gpt-5.5">gpt-5.5</option>
58+ <option data-type="openai" value="gpt-5.5-2026-04-23">gpt-5.5-2026-04-23</option>
5659 <option data-type="openai" value="gpt-5.4">gpt-5.4</option>
60+ <option data-type="openai" value="gpt-5.4-2026-03-05">gpt-5.4-2026-03-05</option>
61+ <option data-type="openai" value="gpt-5.4-mini">gpt-5.4-mini</option>
62+ <option data-type="openai" value="gpt-5.4-mini-2026-03-17">gpt-5.4-mini-2026-03-17</option>
63+ <option data-type="openai" value="gpt-5.4-nano">gpt-5.4-nano</option>
64+ <option data-type="openai" value="gpt-5.4-nano-2026-03-17">gpt-5.4-nano-2026-03-17</option>
5765 <option data-type="openai" value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option>
5866 <option data-type="openai" value="gpt-5.2">gpt-5.2</option>
5967 <option data-type="openai" value="gpt-5.2-2025-12-11">gpt-5.2-2025-12-11</option>
@@ -88,6 +96,7 @@
8896 <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
8997 <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>
9098 <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
99+ <option data-type="anthropic" value="claude-opus-4-7">claude-opus-4-7</option>
91100 <option data-type="anthropic" value="claude-opus-4-6">claude-opus-4-6</option>
92101 <option data-type="anthropic" value="claude-opus-4-5">claude-opus-4-5</option>
93102 <option data-type="anthropic" value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option>
@@ -144,6 +153,11 @@
144153 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
145154 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
146155 <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option>
156+ <option data-type="google" value="gemma-4-31b-it">gemma-4-31b-it</option>
157+ <option data-type="google" value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option>
158+ <option data-type="google" value="gemma-3-27b-it">gemma-3-27b-it</option>
159+ <option data-type="google" value="gemma-3-12b-it">gemma-3-12b-it</option>
160+ <option data-type="google" value="gemma-3-4b-it">gemma-3-4b-it</option>
147161 <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option>
148162 <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option>
149163 <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option>
@@ -174,6 +188,7 @@
174188 <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option>
175189 <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option>
176190 <option data-type="ollama" value="llama4">llama4</option>
191+ <option data-type="zai" value="glm-5v-turbo">glm-5v-turbo</option>
177192 <option data-type="zai" value="glm-4.6v">glm-4.6v</option>
178193 <option data-type="zai" value="glm-4.6v-flashx">glm-4.6v-flashx</option>
179194 <option data-type="zai" value="glm-4.6v-flash">glm-4.6v-flash</option>
public/scripts/extensions/connection-manager/index.js+336 -5
@@ -1,7 +1,7 @@
11import { DOMPurify, Fuse } from '../../../lib.js';
22
33import { activateSendButtons, deactivateSendButtons, event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js';
44import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
55import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
66import { SlashCommand } from '../../slash-commands/SlashCommand.js';
77import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';
@@ -9,11 +9,16 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
99import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
1010import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js';
1111import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
12+import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
1213import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
1314import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';
1415import { collapseSpaces, getUniqueName, isFalseBoolean, isTrueBoolean, uuidv4, waitUntilCondition } from '../../utils.js';
1516import { t } from '../../i18n.js';
1617import { getSecretLabelById } from '../../secrets.js';
18+import { performFuzzySearch } from '/scripts/power-user.js';
19+import { StreamingDisplay } from '/scripts/streaming-display.js';
20+import { ConnectionManagerRequestService } from '../shared.js';
21+import { formatReasoning } from '/scripts/reasoning.js';
1722
1823const MODULE_NAME = 'connection-manager';
1924const NONE = '<None>';
@@ -474,7 +479,223 @@ async function renderDetailsContent(detailsContent) {
474479 }
475480}
476481
477-(async function () {
482+/**
483+ * Callback for the /profile-genstream command
484+ * Generates text using Connection Manager with streaming display support.
485+ * @param {object} args Named arguments
486+ * @param {string} value Unnamed argument (the prompt)
487+ * @returns {Promise<string>} The generated text, optionally with formatted reasoning
488+ */
489+async function generateStreamCallback(args, value) {
490+ if (!value) {
491+ console.warn('WARN: No argument provided for /profile-genstream command');
492+ return '';
493+ }
494+
495+ // Check if Connection Manager is available
496+ const context = getContext();
497+ if (context.extensionSettings.disabledExtensions.includes('connection-manager')) {
498+ toastr.error(t`Connection Manager is required for /profile-genstream. Use /gen or /genraw instead.`);
499+ return '';
500+ }
501+
502+ const profileIdOrName = args?.profile;
503+ const includeReasoning = isTrueBoolean(args?.reasoning);
504+ const systemPrompt = typeof args?.system == 'string' ? args.system : '';
505+ const maxTokens = Number(args?.length ?? 2048) || 2048;
506+ const lock = isTrueBoolean(args?.lock);
507+ const generatingLabel = typeof args?.generating === 'string' ? args.generating : 'Generating...';
508+ const completedLabel = typeof args?.completed === 'string' ? args.completed : 'Generated';
509+ const enableStop = !isFalseBoolean(args?.stop);
510+ const onStopClosure = args?.onStop instanceof SlashCommandClosure ? args.onStop : null;
511+ const onCompleteClosure = args?.onComplete instanceof SlashCommandClosure ? args.onComplete : null;
512+
513+ // Parse delay: 'infinite' or negative = null (stay open), number = delay in ms
514+ let completeDelay = 3000; // Default 3 seconds
515+ if (args?.delay !== undefined) {
516+ if (typeof args.delay === 'string' && args.delay.toLowerCase() === 'infinite') {
517+ completeDelay = null; // Stay until user closes
518+ } else {
519+ const parsed = Number(args.delay);
520+ if (!isNaN(parsed) && parsed >= 0) {
521+ completeDelay = parsed;
522+ } else if (!isNaN(parsed) && parsed < 0) {
523+ completeDelay = null; // Negative = infinite
524+ }
525+ }
526+ }
527+
528+ // Create abort controller for stop functionality (when stop is enabled)
529+ const abortController = enableStop ? new AbortController() : null;
530+
531+ // Compose the stop handler: abort the request + optionally invoke user closure
532+ const onStopHandler = enableStop ? async () => {
533+ abortController.abort();
534+ if (onStopClosure) {
535+ try {
536+ const localClosure = onStopClosure.getCopy();
537+ localClosure.onProgress = () => { };
538+ await localClosure.execute();
539+ } catch (e) {
540+ console.error('[GenStream] Error executing onStop closure', e);
541+ }
542+ }
543+ } : null;
544+
545+ try {
546+ if (lock) {
547+ deactivateSendButtons();
548+ }
549+
550+ // Determine which profile to use
551+ // Use the currently selected profile if no profile specified
552+ let effectiveProfileId = context.extensionSettings.connectionManager.selectedProfile;
553+
554+ const profiles = context.extensionSettings.connectionManager.profiles;
555+
556+ if (profileIdOrName) {
557+ // Use try to find profile by id first, then fuse search
558+ const profile = profiles.find(p => p.id === profileIdOrName);
559+ if (profile) {
560+ effectiveProfileId = profile.id;
561+ } else {
562+ const keys = [
563+ { name: 'name', weight: 10 },
564+ ];
565+ const fuseResults = performFuzzySearch('profile', profiles, keys, profileIdOrName);
566+ if (fuseResults.length > 0) {
567+ effectiveProfileId = fuseResults[0].item.id;
568+ } else {
569+ toastr.warning(t`Connection profile not found: ${profileIdOrName}`);
570+ return '';
571+ }
572+ }
573+ }
574+
575+ if (!effectiveProfileId) {
576+ toastr.error(t`No connection profile specified or selected. Use profile= argument or select a profile in Connection Manager.`);
577+ return '';
578+ }
579+
580+ // Create streaming display
581+ const display = new StreamingDisplay();
582+ display.show({
583+ label: generatingLabel,
584+ icon: ConnectionManagerRequestService.getProfileIcon(effectiveProfileId),
585+ onStop: onStopHandler,
586+ });
587+
588+ const messages = [
589+ ...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
590+ { role: 'user', content: value },
591+ ];
592+
593+ let finalText = '';
594+ let finalReasoning = '';
595+
596+ /** Gets the final (if requested, formatted) text to return for this command @returns {string} */
597+ function buildResultText() {
598+ // Format output with reasoning if requested
599+ if (includeReasoning && finalReasoning) {
600+ const { formatted } = formatReasoning(finalReasoning, finalText);
601+ return formatted;
602+ }
603+
604+ return finalText;
605+ }
606+
607+ try {
608+ // Attempt streaming first
609+ const streamResponse = await ConnectionManagerRequestService.sendRequest(
610+ effectiveProfileId,
611+ messages,
612+ maxTokens,
613+ { extractData: true, includePreset: true, stream: true, signal: abortController?.signal ?? undefined },
614+ );
615+
616+ if (typeof streamResponse === 'function') {
617+ const generator = streamResponse();
618+ for await (const chunk of generator) {
619+ finalText = chunk.text;
620+ finalReasoning = chunk.state?.reasoning || '';
621+ display.updateReasoning(finalReasoning);
622+ display.updateContent(finalText);
623+ }
624+ } else {
625+ // Non-streaming fallback within the try block
626+ const extracted = streamResponse;
627+ finalText = extracted?.content || '';
628+ finalReasoning = extracted?.reasoning || '';
629+ if (finalReasoning) {
630+ display.updateReasoning(finalReasoning);
631+ }
632+ display.updateContent(finalText);
633+ }
634+ } catch (error) {
635+ // If the user clicked stop, don't retry — show stopped state and return empty
636+ if (abortController?.signal?.aborted) {
637+ display.markStopped({ label: `${generatingLabel} [Stopped]` });
638+ return buildResultText();
639+ }
640+
641+ console.warn('[Slash Commands] Streaming failed, falling back to non-streaming:', error);
642+ display.hide({ instant: true });
643+
644+ // Retry with non-streaming
645+ const response = await ConnectionManagerRequestService.sendRequest(
646+ effectiveProfileId,
647+ messages,
648+ maxTokens,
649+ { extractData: true, includePreset: true, stream: false },
650+ );
651+
652+ const extracted = /** @type {import('../../custom-request.js').ExtractedData} */ (response);
653+ finalText = extracted?.content || '';
654+ finalReasoning = extracted?.reasoning || '';
655+
656+ // Show quick non-streaming display
657+ display.show({
658+ label: generatingLabel,
659+ icon: ConnectionManagerRequestService.getProfileIcon(effectiveProfileId),
660+ });
661+ if (finalReasoning) {
662+ display.updateReasoning(finalReasoning);
663+ }
664+ display.updateContent(finalText);
665+ }
666+
667+ // Mark as complete with delay (null = stay open until user closes)
668+ display.complete({ label: completedLabel, delay: completeDelay });
669+
670+ // Invoke onComplete closure if provided
671+ if (onCompleteClosure) {
672+ try {
673+ const localClosure = onCompleteClosure.getCopy();
674+ localClosure.onProgress = () => { };
675+ await localClosure.execute();
676+ } catch (e) {
677+ console.error('[GenStream] Error executing onComplete closure', e);
678+ }
679+ }
680+
681+ if (!finalText) {
682+ toastr.warning(t`Generation returned empty result`);
683+ return '';
684+ }
685+
686+ return buildResultText();
687+ } catch (err) {
688+ console.error('Error on /genstream generation', err);
689+ toastr.error(err.message, t`API Error`, { preventDuplicates: true });
690+ return '';
691+ } finally {
692+ if (lock) {
693+ activateSendButtons();
694+ }
695+ }
696+}
697+
698+export async function init() {
478699 extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS);
479700
480701 for (const key of Object.keys(DEFAULT_SETTINGS)) {
@@ -824,4 +1045,114 @@ async function renderDetailsContent(detailsContent) {
8241045 return JSON.stringify(profile);
8251046 },
8261047 }));
827-})();
1048+
1049+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1050+ name: 'profile-genstream',
1051+ callback: generateStreamCallback,
1052+ returns: t`generated text`,
1053+ namedArgumentList: [
1054+ new SlashCommandNamedArgument(
1055+ 'lock', t`lock user input during generation`, [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
1056+ ),
1057+ SlashCommandNamedArgument.fromProps({
1058+ name: 'profile',
1059+ description: t`connection profile ID to use for generation`,
1060+ typeList: [ARGUMENT_TYPE.STRING],
1061+ enumProvider: commonEnumProviders.connectionProfiles(),
1062+ }),
1063+ SlashCommandNamedArgument.fromProps({
1064+ name: 'reasoning',
1065+ description: t`include formatted reasoning in the output`,
1066+ typeList: [ARGUMENT_TYPE.BOOLEAN],
1067+ defaultValue: 'false',
1068+ enumProvider: commonEnumProviders.boolean('trueFalse'),
1069+ }),
1070+ SlashCommandNamedArgument.fromProps({
1071+ name: 'system',
1072+ description: t`system prompt at the start`,
1073+ typeList: [ARGUMENT_TYPE.STRING],
1074+ }),
1075+ SlashCommandNamedArgument.fromProps({
1076+ name: 'length',
1077+ description: t`API response length in tokens`,
1078+ typeList: [ARGUMENT_TYPE.NUMBER],
1079+ defaultValue: '2048',
1080+ }),
1081+ SlashCommandNamedArgument.fromProps({
1082+ name: 'generating',
1083+ description: t`label/title for the generation display`,
1084+ typeList: [ARGUMENT_TYPE.STRING],
1085+ defaultValue: 'Generating...',
1086+ }),
1087+ SlashCommandNamedArgument.fromProps({
1088+ name: 'completed',
1089+ description: t`updated label/title for when generation completes`,
1090+ typeList: [ARGUMENT_TYPE.STRING],
1091+ defaultValue: 'Generated',
1092+ }),
1093+ SlashCommandNamedArgument.fromProps({
1094+ name: 'delay',
1095+ description: t`auto-hide delay in ms after generation completes. Use "infinite" or negative to keep until manually closed`,
1096+ typeList: [ARGUMENT_TYPE.NUMBER],
1097+ defaultValue: '3000',
1098+ enumList: [
1099+ new SlashCommandEnumValue('infinite', 'Keep the streaming display open until manually closed', 'command', '♾️'),
1100+ new SlashCommandEnumValue('any delay in seconds', null, 'number', '⌚', () => true, input => input),
1101+ ],
1102+ }),
1103+ SlashCommandNamedArgument.fromProps({
1104+ name: 'stop',
1105+ description: t`show a stop button on the streaming display that aborts generation when clicked`,
1106+ typeList: [ARGUMENT_TYPE.BOOLEAN],
1107+ defaultValue: 'true',
1108+ enumProvider: commonEnumProviders.boolean('trueFalse'),
1109+ }),
1110+ SlashCommandNamedArgument.fromProps({
1111+ name: 'onStop',
1112+ description: t`closure to execute when the stop button is clicked (in addition to aborting the request)`,
1113+ typeList: [ARGUMENT_TYPE.CLOSURE],
1114+ }),
1115+ SlashCommandNamedArgument.fromProps({
1116+ name: 'onComplete',
1117+ description: t`closure to execute after generation completes successfully`,
1118+ typeList: [ARGUMENT_TYPE.CLOSURE],
1119+ }),
1120+ ],
1121+ unnamedArgumentList: [
1122+ SlashCommandArgument.fromProps({
1123+ description: 'prompt',
1124+ typeList: [ARGUMENT_TYPE.STRING],
1125+ isRequired: true,
1126+ }),
1127+ ],
1128+ helpString: `
1129+ <div>
1130+ ${t`Generates text using Connection Manager with streaming display. Shows live generation progress including reasoning (thinking) and content.`}
1131+ </div>
1132+ <div>
1133+ ${t`Requires Connection Manager extension. Uses the currently selected profile or the specified profile= argument.`}
1134+ </div>
1135+ <div>
1136+ ${t`Use reasoning=true to include formatted reasoning in the output (using the defined reasoning template). This can be parsed later with /reasoning-parse.`}
1137+ </div>
1138+ <div>
1139+ ${t`Use delay to control auto-hide behavior: number (ms), "infinite", or negative to keep the display open until manually closed. The display shows a green LED when complete.`}
1140+ </div>
1141+ <div>
1142+ ${t`A stop button is shown by default (stop=true). Click it to abort generation and return whatever was streamed so far. Use stop=false to hide the stop button.`}
1143+ </div>
1144+ <div>
1145+ ${t`Use onStop and onComplete closures for custom behavior when generation is stopped or completes.`}
1146+ </div>
1147+ <div>
1148+ ${t`Example: <pre><code>/profile-genstream profile=my-profile-id reasoning=true Summarize the following text</code></pre>`}
1149+ </div>
1150+ <div>
1151+ ${t`Example with infinite display: <pre><code>/profile-genstream delay=infinite Tell me a story</code></pre>`}
1152+ </div>
1153+ <div>
1154+ ${t`Example with custom stop handler: <pre><code>/profile-genstream onStop={: /echo "Generation stopped!" :} Tell me a story</code></pre>`}
1155+ </div>
1156+ `,
1157+ }));
1158+}
public/scripts/extensions/connection-manager/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "Cohee1207",
99 "version": "1.0.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/expressions/index.js+65 -3
@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateQuietPrompt, generateRaw,
44import { dragElement, isMobile } from '../../RossAscends-mods.js';
55import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
66import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
77import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean, includesIgnoreCaseAndAccents } from '../../utils.js';
88import { hideMutedSprites, selected_group } from '../../group-chats.js';
99import { isJsonSchemaSupported } from '../../textgen-settings.js';
1010import { debounce_timeout } from '../../constants.js';
@@ -787,6 +787,32 @@ async function setSpriteSlashCommand({ type }, searchTerm) {
787787}
788788
789789/**
790+ * @param {string} expressionName - Label of the expression to set as fallback
791+ */
792+function setFallBackExpressionSlashCommand(args, expressionName) {
793+ expressionName = expressionName.trim().toLowerCase();
794+
795+ if (!expressionName) return extension_settings?.expressions?.fallback_expression || '';
796+
797+ const select = /** @type {HTMLSelectElement} */(document.getElementById('expression_fallback'));
798+ const fallbackExpressions = Array
799+ .from(select?.options || [])
800+ .map(option => option.value)
801+ .filter(expression => expression?.length > 0);
802+
803+ const expressionMatch = fallbackExpressions.find(expression => includesIgnoreCaseAndAccents(expression, expressionName));
804+
805+ if (!expressionMatch) {
806+ toastr.warning(t`No expression found for search term ${expressionName}`, t`Set Fallback Expression`);
807+ return '';
808+ }
809+
810+ $(select).val(expressionMatch).trigger('change');
811+
812+ return expressionMatch;
813+}
814+
815+/**
790816 * Returns the sprite folder name (including override) for a character.
791817 * @param {object} char Character object
792818 * @param {string} char.avatar Avatar filename with extension
@@ -2140,7 +2166,7 @@ function migrateSettings() {
21402166 }
21412167}
21422168
21432169(export async function init() {
21442170 function addExpressionImage() {
21452171 const html = `
21462172 <div id="expression-wrapper">
@@ -2317,6 +2343,42 @@ function migrateSettings() {
23172343 returns: 'The currently set expression label after setting it.',
23182344 }));
23192345 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2346+ name: 'expression-fallback',
2347+ callback: setFallBackExpressionSlashCommand,
2348+ unnamedArgumentList: [
2349+ SlashCommandArgument.fromProps({
2350+ description: 'expression label to set',
2351+ typeList: [ARGUMENT_TYPE.STRING],
2352+ isRequired: false,
2353+ enumProvider: () => [
2354+ new SlashCommandEnumValue('#none', 'Sets the fallback expression to no image'),
2355+ new SlashCommandEnumValue('#emoji', 'Sets the fallback expression to emojis'),
2356+ ...localEnumProviders.expressions(),
2357+ ],
2358+ }),
2359+ ],
2360+ helpString: `
2361+ <div>
2362+ Gets the currently selected expression fallback for all characters.<br />
2363+ If a valid expression label is sent, it will be set as the new fallback.
2364+ </div>
2365+ <div>
2366+ <strong>Example:</strong>
2367+ <ul>
2368+ <li>
2369+ <pre><code>/expression-fallback | /echo</code></pre>
2370+ <small>Returns the currently selected fallback.</small>
2371+ </li>
2372+ <li>
2373+ <pre><code>/expression-fallback admiration</code></pre>
2374+ <small>Sets a new expression as fallback.</small>
2375+ </li>
2376+ </ul>
2377+ </div>
2378+ `,
2379+ returns: 'The currently set expression label after setting it.',
2380+ }));
2381+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
23202382 name: 'expression-folder-override',
23212383 aliases: ['spriteoverride', 'costume'],
23222384 callback: setSpriteFolderCommand,
@@ -2511,4 +2573,4 @@ function migrateSettings() {
25112573 </div>
25122574 `,
25132575 }));
2514-})();
2576+}
public/scripts/extensions/expressions/manifest.json+4 -1
@@ -9,5 +9,8 @@
99 "css": "style.css",
1010 "author": "Cohee#1207",
1111 "version": "1.0.0",
1212 "homePage": "https://github.com/SillyTavern/SillyTavern",
13+ "hooks": {
14+ "activate": "init"
15+ }
1316}
public/scripts/extensions/gallery/index.js+2 -2
@@ -818,7 +818,7 @@ function addGalleryWandButton() {
818818}
819819
820820// On extension load, ensure the settings are initialized
821821(export async function init() {
822822 initSettings();
823823 eventSource.on(event_types.CHARACTER_RENAMED, (oldAvatar, newAvatar) => {
824824 const context = SillyTavern.getContext();
@@ -850,4 +850,4 @@ function addGalleryWandButton() {
850850 }),
851851 );
852852 addGalleryWandButton();
853-})();
853+}
public/scripts/extensions/gallery/manifest.json+4 -1
@@ -8,5 +8,8 @@
88 "css": "style.css",
99 "author": "City-Unit",
1010 "version": "1.5.0",
1111 "homePage": "https://github.com/SillyTavern/SillyTavern",
12+ "hooks": {
13+ "activate": "init"
14+ }
1215}
public/scripts/extensions/memory/index.js+2 -2
@@ -1063,7 +1063,7 @@ function setupListeners() {
10631063 });
10641064}
10651065
10661066jQuery(export async function init() {
10671067 async function addExtensionControls() {
10681068 const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings });
10691069 $('#summarize_container').append(settingsHtml);
@@ -1128,4 +1128,4 @@ jQuery(async function () {
11281128 () => summaryMacroHandler(),
11291129 'Returns the latest memory/summary from the current chat.');
11301130 }
11311131});
public/scripts/extensions/memory/manifest.json+4 -1
@@ -9,5 +9,8 @@
99 "css": "style.css",
1010 "author": "Cohee#1207",
1111 "version": "1.0.0",
1212 "homePage": "https://github.com/SillyTavern/SillyTavern",
13+ "hooks": {
14+ "activate": "init"
15+ }
1316}
public/scripts/extensions/quick-reply/index.js+4 -3
@@ -169,7 +169,7 @@ const handleCharChange = () => {
169169 settings.charConfig = charConfig;
170170};
171171
172172const init =export async function init() => {
173173 await loadSets();
174174 await loadSettings();
175175 log('settings: ', settings);
@@ -214,7 +214,8 @@ const init = async () => {
214214 eventSource.on(event_types.APP_READY, async () => await finalizeInit());
215215
216216 globalThis.quickReplyApi = quickReplyApi;
217217};
218+
218219const finalizeInit = async () => {
219220 debug('executing startup');
220221 await autoExec.handleStartup();
@@ -229,7 +230,7 @@ const finalizeInit = async () => {
229230 isReady = true;
230231 debug('READY');
231232};
232-await init();
233+
233234
234235const purgeCharacterQuickReplySets = ({ character }) => {
235236 // Remove the character's Quick Reply Sets from the settings.
public/scripts/extensions/quick-reply/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "RossAscends#1779",
99 "version": "2.0.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/regex/index.js+34 -3
@@ -1639,7 +1639,7 @@ async function checkCharEmbeddedRegexScripts() {
16391639function notifyReloadCurrentChat(presetName) {
16401640 toastr.info(
16411641 t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>',
16421642 t`Preset '${escapeHtml(presetName)}' contains enabled regex scripts`,
16431643 {
16441644 timeOut: 5000,
16451645 escapeHtml: false,
@@ -1709,7 +1709,7 @@ function onPresetRenamed({ apiId, oldName, newName }) {
17091709
17101710// Workaround for loading in sequence with other extensions
17111711// NOTE: Always puts extension at the top of the list, but this is fine since it's static
17121712jQuery(export async function init() => {
17131713 if (!Array.isArray(extension_settings.regex)) {
17141714 extension_settings.regex = [];
17151715 }
@@ -2069,6 +2069,37 @@ jQuery(async () => {
20692069 helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.',
20702070 }));
20712071 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2072+ name: 'regex-state',
2073+ /** @param {object} _ @param {string} name */
2074+ callback: (_, name) => {
2075+ if (!name) {
2076+ toastr.warning('No regex script name provided.');
2077+ return '';
2078+ }
2079+
2080+ const scripts = getRegexScripts();
2081+ const script = scripts.find(s => equalsIgnoreCaseAndAccents(s.scriptName, name));
2082+
2083+ if (!script) {
2084+ toastr.warning(`Regex script "${name}" not found.`);
2085+ return '';
2086+ }
2087+
2088+ return script.disabled ? 'false' : 'true';
2089+ },
2090+ returns: 'true (for enabled) or false (for disabled)',
2091+ unnamedArgumentList: [
2092+ SlashCommandArgument.fromProps({
2093+ description: 'script name',
2094+ typeList: [ARGUMENT_TYPE.STRING],
2095+ isRequired: true,
2096+ enumProvider: localEnumProviders.regexScripts,
2097+ }),
2098+ ],
2099+ helpString: 'Returns the current state of a regex script.',
2100+ }));
2101+
2102+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
20722103 name: 'regex-toggle',
20732104 callback: toggleRegexCallback,
20742105 returns: 'The name of the script that was toggled',
@@ -2123,4 +2154,4 @@ jQuery(async () => {
21232154
21242155 presetManager.setupEventListeners();
21252156 presetManager.registerSlashCommands();
21262157});
public/scripts/extensions/regex/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "kingbri",
99 "version": "1.0.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/shared.js+35 -1
@@ -1,4 +1,4 @@
11import { CONNECT_API_MAP, createModelIcon, getRequestHeaders } from '../../script.js';
22import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
33import { t } from '../i18n.js';
44import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js';
@@ -126,6 +126,10 @@ export async function getMultimodalCaption(base64Img, prompt) {
126126 requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
127127 }
128128
129+ if (extension_settings.caption.multimodal_api === 'workers_ai') {
130+ requestBody.workers_ai_account_id = oai_settings.workers_ai_account_id;
131+ }
132+
129133 function getEndpointUrl() {
130134 switch (extension_settings.caption.multimodal_api) {
131135 case 'google':
@@ -283,6 +287,10 @@ function throwIfInvalidModel(useReverseProxy) {
283287 if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) {
284288 throw new Error('Pollinations API key is not set.');
285289 }
290+
291+ if (multimodalApi === 'workers_ai' && (!secret_state[SECRET_KEYS.WORKERS_AI] || !oai_settings.workers_ai_account_id)) {
292+ throw new Error('Workers AI API key or account ID is not set.');
293+ }
286294}
287295
288296/**
@@ -435,10 +443,12 @@ export class ConnectionManagerRequestService {
435443 max_tokens: maxTokens,
436444 model: profile.model,
437445 chat_completion_source: selectedApiMap.source,
446+ secret_id: profile['secret-id'],
438447 custom_url: profile['api-url'],
439448 vertexai_region: profile['api-url'],
440449 zai_endpoint: profile['api-url'],
441450 siliconflow_endpoint: profile['api-url'],
451+ minimax_endpoint: profile['api-url'],
442452 reverse_proxy: proxyPreset?.url,
443453 proxy_password: proxyPreset?.password,
444454 custom_prompt_post_processing: profile['prompt-post-processing'],
@@ -459,6 +469,7 @@ export class ConnectionManagerRequestService {
459469 model: profile.model,
460470 api_type: selectedApiMap.type,
461471 api_server: profile['api-url'],
472+ secret_id: profile['secret-id'],
462473 ...overridePayload,
463474 }, {
464475 instructName: includeInstruct ? profile.instruct : undefined,
@@ -534,6 +545,29 @@ export class ConnectionManagerRequestService {
534545 }
535546
536547 /**
548+ * Creates a model icon Image element for the given profile (or the currently selected profile).
549+ * Returns null if the profile is not found, has no API, or Connection Manager is unavailable.
550+ * @param {string} [profileId] - Profile ID. If omitted, uses the currently selected profile.
551+ * @returns {HTMLImageElement | null}
552+ */
553+ static getProfileIcon(profileId) {
554+ if ((SillyTavern.getContext()).extensionSettings.disabledExtensions.includes('connection-manager')) {
555+ return null;
556+ }
557+
558+ const id = profileId ?? (SillyTavern.getContext()).extensionSettings.connectionManager.selectedProfile;
559+ if (!id) return null;
560+
561+ try {
562+ const profile = this.getProfile(id);
563+ if (!profile?.api) return null;
564+ return createModelIcon(profile.api, profile.model);
565+ } catch {
566+ return null;
567+ }
568+ }
569+
570+ /**
537571 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
538572 * @returns {boolean}
539573 */
public/scripts/extensions/stable-diffusion/button.html+0 -4
@@ -2,7 +2,3 @@
22 <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div>
33 <span data-i18n="Generate Image">Generate Image</span>
44</div>
5-<div id="sd_stop_gen" class="list-group-item flex-container flexGap5">
6- <div class="fa-solid fa-circle-stop extensionsMenuExtensionButton" title="Abort current image generation task" data-i18n="[title]Abort current image generation task"></div>
7- <span data-i18n="Stop Image Generation">Stop Image Generation</span>
8-</div>
public/scripts/extensions/stable-diffusion/index.js+136 -89
@@ -62,18 +62,13 @@ import { t, translate } from '../../i18n.js';
6262import { oai_settings } from '../../openai.js';
6363import { power_user } from '/scripts/power-user.js';
6464import { MacrosParser } from '/scripts/macros.js';
65+import { ActionLoaderHandle, loader } from '/scripts/action-loader.js';
6566
6667export { MODULE_NAME };
6768
6869const MODULE_NAME = 'sd';
6970// This is a 1x1 transparent PNG
7071const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
71-const CUSTOM_STOP_EVENT = 'sd_stop_generation';
72-
73-// Generation tracking for status indicator
74-let activeGenerations = 0;
75-/** @type {JQuery<HTMLElement>|null} */
76-let generationToast = null;
7772
7873const sources = {
7974 extras: 'extras',
@@ -99,6 +94,7 @@ const sources = {
9994 google: 'google',
10095 zai: 'zai',
10196 openrouter: 'openrouter',
97+ workersai: 'workersai',
10298};
10399const comfyTypes = {
104100 standard: 'standard',
@@ -1747,6 +1743,9 @@ async function loadSamplers() {
17471743 case sources.openrouter:
17481744 samplers = ['N/A'];
17491745 break;
1746+ case sources.workersai:
1747+ samplers = ['N/A'];
1748+ break;
17501749 }
17511750
17521751 for (const sampler of samplers) {
@@ -1815,6 +1814,34 @@ async function loadAutoSamplers() {
18151814 }
18161815}
18171816
1817+async function loadSdcppModels() {
1818+ if (!extension_settings.sd.sdcpp_url) {
1819+ return [{ value: '', text: 'N/A' }];
1820+ }
1821+
1822+ try {
1823+ const result = await fetch('/api/sd/sdcpp/models', {
1824+ method: 'POST',
1825+ headers: getRequestHeaders(),
1826+ body: JSON.stringify({ url: extension_settings.sd.sdcpp_url }),
1827+ });
1828+
1829+ if (!result.ok) {
1830+ return [{ value: '', text: 'N/A' }];
1831+ }
1832+
1833+ const data = await result.json();
1834+
1835+ if (data?.data?.length > 0) {
1836+ return data.data.map(model => ({ value: model.id, text: model.name || model.id }));
1837+ }
1838+ } catch (error) {
1839+ console.error('Failed to load sd.cpp models:', error);
1840+ }
1841+
1842+ return [{ value: '', text: 'N/A' }];
1843+}
1844+
18181845async function loadSdcppSamplers() {
18191846 // The sdcpp server does not provide an API for samplers, so we return the known list.
18201847 return ['euler', 'euler_a', 'heun', 'dpm2', 'dpm++2s_a', 'dpm++2m', 'dpm++2mv2', 'ipndm', 'ipndm_v', 'lcm', 'ddim_trailing', 'tcd'];
@@ -1910,7 +1937,7 @@ async function loadModels() {
19101937 models = await loadAutoModels();
19111938 break;
19121939 case sources.sdcpp:
1913- models = [{ value: '', text: 'N/A' }];
1940+ models = await loadSdcppModels();
19141941 break;
19151942 case sources.drawthings:
19161943 models = await loadDrawthingsModels();
@@ -1969,6 +1996,9 @@ async function loadModels() {
19691996 case sources.openrouter:
19701997 models = await loadOpenRouterModels();
19711998 break;
1999+ case sources.workersai:
2000+ models = await loadWorkersAIImageModels();
2001+ break;
19722002 }
19732003
19742004 if (extension_settings.sd.source === sources.electronhub) {
@@ -2096,6 +2126,33 @@ async function loadXAIModels() {
20962126 ];
20972127}
20982128
2129+async function loadWorkersAIImageModels() {
2130+ $('#sd_cf_workers_key').toggleClass('success', !!secret_state[SECRET_KEYS.WORKERS_AI]);
2131+
2132+ if (!secret_state[SECRET_KEYS.WORKERS_AI]) {
2133+ return [];
2134+ }
2135+
2136+ if (!oai_settings.workers_ai_account_id) {
2137+ toastr.warning('Workers AI account ID is required. Save it in the "API Connections" panel.', 'Image Generation');
2138+ return [];
2139+ }
2140+
2141+ const result = await fetch('/api/sd/workersai/models', {
2142+ method: 'POST',
2143+ headers: getRequestHeaders(),
2144+ body: JSON.stringify({
2145+ account_id: oai_settings.workers_ai_account_id,
2146+ }),
2147+ });
2148+
2149+ if (result.ok) {
2150+ return await result.json();
2151+ }
2152+
2153+ return [];
2154+}
2155+
20992156async function loadPollinationsModels() {
21002157 $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]);
21012158
@@ -2325,6 +2382,8 @@ async function loadDrawthingsModels() {
23252382
23262383async function loadOpenAiModels() {
23272384 return [
2385+ { value: 'gpt-image-2', text: 'gpt-image-2' },
2386+ { value: 'gpt-image-2-2026-04-21', text: 'gpt-image-2-2026-04-21' },
23282387 { value: 'gpt-image-1.5', text: 'gpt-image-1.5' },
23292388 { value: 'gpt-image-1-mini', text: 'gpt-image-1-mini' },
23302389 { value: 'gpt-image-1', text: 'gpt-image-1' },
@@ -2581,6 +2640,9 @@ async function loadSchedulers() {
25812640 case sources.openrouter:
25822641 schedulers = ['N/A'];
25832642 break;
2643+ case sources.workersai:
2644+ schedulers = ['N/A'];
2645+ break;
25842646 }
25852647
25862648 for (const scheduler of schedulers) {
@@ -2701,6 +2763,9 @@ async function loadVaes() {
27012763 case sources.openrouter:
27022764 vaes = ['N/A'];
27032765 break;
2766+ case sources.workersai:
2767+ vaes = ['N/A'];
2768+ break;
27042769 }
27052770
27062771 for (const vae of vaes) {
@@ -2913,62 +2978,6 @@ function ensureSelectionExists(setting, selector) {
29132978}
29142979
29152980/**
2916- * Updates the generation status indicator based on active generation count.
2917- * Shows/hides various UI indicators to inform user of background image generation.
2918- */
2919-function updateGenerationIndicator() {
2920- if (activeGenerations > 0) {
2921- const countText = activeGenerations > 1 ? ` (${activeGenerations})` : '';
2922- const toastText = `<i class="fa-solid fa-spinner fa-spin"></i> ${t`Generating an image`}${countText}...`;
2923-
2924- // Show persistent toast if not already showing
2925- if (!generationToast) {
2926- generationToast = toastr.info(
2927- toastText,
2928- 'Image Generation',
2929- {
2930- timeOut: 0,
2931- extendedTimeOut: 0,
2932- tapToDismiss: true,
2933- escapeHtml: false,
2934- onHidden: () => {
2935- generationToast = null;
2936- },
2937- },
2938- );
2939- } else if (activeGenerations > 1) {
2940- // Update count in existing toast
2941- const toastMessage = $(generationToast).find('.toast-message');
2942- if (toastMessage.length) {
2943- toastMessage.html(toastText);
2944- }
2945- }
2946- } else {
2947- // Hide toast when done
2948- if (generationToast) {
2949- toastr.clear(generationToast);
2950- generationToast = null;
2951- }
2952- }
2953-}
2954-
2955-/**
2956- * Increments the active generation counter and updates indicators.
2957- */
2958-function startGenerationTracking() {
2959- activeGenerations++;
2960- updateGenerationIndicator();
2961-}
2962-
2963-/**
2964- * Decrements the active generation counter and updates indicators.
2965- */
2966-function endGenerationTracking() {
2967- activeGenerations = Math.max(0, activeGenerations - 1);
2968- updateGenerationIndicator();
2969-}
2970-
2971-/**
29722981 * Generates an image based on the given trigger word.
29732982 * @param {string} initiator The initiator of the image generation
29742983 * @param {Record<string, object>} args Command arguments
@@ -3028,12 +3037,13 @@ async function generatePicture(initiator, args, trigger, message, callback) {
30283037
30293038 const dimensions = setTypeSpecificDimensions(generationType);
30303039 const abortController = new AbortController();
3031- const stopButton = document.getElementById('sd_stop_gen');
30323040 let negativePromptPrefix = args?.negative || '';
30333041 let imagePath = '';
30343042
30353043 const stopListener = () => abortController.abort('Aborted by user');
30363044
3045+ let loaderHandle = ActionLoaderHandle.EMPTY;
3046+
30373047 try {
30383048 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
30393049
@@ -3046,16 +3056,19 @@ async function generatePicture(initiator, args, trigger, message, callback) {
30463056 await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData);
30473057 prompt = eventData.prompt; // Allow extensions to modify the prompt
30483058
3049- // Track this generation for status indicator
3050- startGenerationTracking();
3051- // Show stop button after prompt is ready (prompt generation uses separate abort mechanism)
3052- $(stopButton).show();
3053- eventSource.once(CUSTOM_STOP_EVENT, stopListener);
3054-
30553059 if (typeof args?._abortController?.addEventListener === 'function') {
30563060 args._abortController.addEventListener('abort', stopListener);
30573061 }
30583062
3063+ // Show non-blocking stoppable toast for this generation
3064+ loaderHandle = loader.show({
3065+ blocking: false,
3066+ slug: `${MODULE_NAME}-image-generation`,
3067+ title: t`Image Generation`,
3068+ message: t`Generating an image...`,
3069+ onStop: stopListener,
3070+ });
3071+
30593072 // generate the image
30603073 imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
30613074 } catch (err) {
@@ -3074,10 +3087,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
30743087 toastr.error(errorText, 'Image Generation');
30753088 throw new Error(errorText);
30763089 } finally {
3077- $(stopButton).hide();
30783090 restoreOriginalDimensions(dimensions);
30793091 eventSourceawait loaderHandle.removeListenerhide(CUSTOM_STOP_EVENT, stopListener);
3080- endGenerationTracking();
30813092 }
30823093
30833094 return imagePath;
@@ -3404,6 +3415,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
34043415 case sources.openrouter:
34053416 result = await generateOpenRouterImage(prefixedPrompt, signal);
34063417 break;
3418+ case sources.workersai:
3419+ result = await generateWorkersAIImage(prefixedPrompt, negativePrompt, signal);
3420+ break;
34073421 }
34083422
34093423 if (!result.data) {
@@ -3858,6 +3872,7 @@ async function generateAutoImage(prompt, negativePrompt, signal) {
38583872async function generateSdcppImage(prompt, negativePrompt, signal) {
38593873 const payload = {
38603874 url: extension_settings.sd.sdcpp_url,
3875+ model: extension_settings.sd.model || undefined,
38613876 prompt: prompt,
38623877 negative_prompt: negativePrompt,
38633878 steps: extension_settings.sd.steps,
@@ -4062,7 +4077,7 @@ async function generateOpenAiImage(prompt, signal) {
40624077
40634078 const isDalle2 = /dall-e-2/.test(extension_settings.sd.model);
40644079 const isDalle3 = /dall-e-3/.test(extension_settings.sd.model);
40654080 const isGptImg = /gpt-image-(1|2|latest)/.test(extension_settings.sd.model);
40664081 const isSora2 = /sora-2/.test(extension_settings.sd.model);
40674082
40684083 if (isDalle2 && prompt.length > dalle2PromptLimit) {
@@ -4719,6 +4734,33 @@ async function generateOpenRouterImage(prompt, signal) {
47194734 throw new Error(text);
47204735}
47214736
4737+async function generateWorkersAIImage(prompt, negativePrompt, signal) {
4738+ const result = await fetch('/api/sd/workersai/generate', {
4739+ method: 'POST',
4740+ headers: getRequestHeaders(),
4741+ signal: signal,
4742+ body: JSON.stringify({
4743+ prompt: prompt,
4744+ negative_prompt: negativePrompt,
4745+ model: extension_settings.sd.model,
4746+ width: extension_settings.sd.width,
4747+ height: extension_settings.sd.height,
4748+ steps: extension_settings.sd.steps,
4749+ scale: extension_settings.sd.scale,
4750+ seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
4751+ account_id: oai_settings.workers_ai_account_id,
4752+ }),
4753+ });
4754+
4755+ if (result.ok) {
4756+ const data = await result.json();
4757+ return { format: data?.format, data: data?.image };
4758+ } else {
4759+ const text = await result.text();
4760+ throw new Error(text);
4761+ }
4762+}
4763+
47224764async function onComfyOpenWorkflowEditorClick() {
47234765 let workflow = await (await fetch('/api/sd/comfy/workflow', {
47244766 method: 'POST',
@@ -5029,10 +5071,6 @@ async function addSDGenButtons() {
50295071 generatePicture(initiators.wand, {}, param);
50305072 }
50315073 });
5032-
5033- const stopGenButton = $('#sd_stop_gen');
5034- stopGenButton.hide();
5035- stopGenButton.on('click', () => eventSource.emit(CUSTOM_STOP_EVENT));
50365074}
50375075
50385076function isValidState() {
@@ -5091,6 +5129,8 @@ function isValidState() {
50915129 return secret_state[SECRET_KEYS.ZAI];
50925130 case sources.openrouter:
50935131 return secret_state[SECRET_KEYS.OPENROUTER];
5132+ case sources.workersai:
5133+ return !!oai_settings.workers_ai_account_id && secret_state[SECRET_KEYS.WORKERS_AI];
50945134 default:
50955135 return false;
50965136 }
@@ -5115,10 +5155,6 @@ async function sdMessageButton($icon, { animate } = {}) {
51155155 $icon.toggleClass(classes.idle, !isBusy);
51165156 $icon.toggleClass(classes.busy, isBusy);
51175157 $media.toggleClass(classes.animation, isBusy);
5118-
5119- // Update generation counter toast
5120- const trackingFunction = isBusy ? startGenerationTracking : endGenerationTracking;
5121- trackingFunction();
51225158 }
51235159
51245160 let $media = jQuery();
@@ -5233,7 +5269,6 @@ async function writePromptFields(characterId) {
52335269 * @returns {Promise<MediaAttachment|null>} - A promise that resolves to the newly generated media attachment, or null if generation failed or was aborted.
52345270 */
52355271async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, abortController = new AbortController()) {
5236- const stopButton = document.getElementById('sd_stop_gen');
52375272 const stopListener = () => abortController.abort('Aborted by user');
52385273 const generationType = mediaAttachment.generation_type ?? message?.extra?.generationType ?? generationMode.FREE;
52395274 let dimensions = { width: extension_settings.sd.width, height: extension_settings.sd.height };
@@ -5247,9 +5282,9 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
52475282 source: MEDIA_SOURCE.GENERATED,
52485283 };
52495284
5285+ let loaderHandle = ActionLoaderHandle.EMPTY;
5286+
52505287 try {
5251- $(stopButton).show();
5252- eventSource.once(CUSTOM_STOP_EVENT, stopListener);
52535288 const callback = (_a, _b, _c, _d, _e, _f, format) => { result.type = isVideo(format) ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.IMAGE; };
52545289 const savedPrompt = mediaAttachment.title ?? message.extra.title ?? '';
52555290 const savedNegative = mediaAttachment.negative ?? message.extra.negative ?? '';
@@ -5265,6 +5300,15 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
52655300 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
52665301 : context.characters[context.characterId]?.name;
52675302
5303+ // Show non-blocking stoppable toast for this generation
5304+ loaderHandle = loader.show({
5305+ blocking: false,
5306+ slug: `${MODULE_NAME}-image-generation`,
5307+ title: t`Image Generation`,
5308+ message: t`Generating an image...`,
5309+ onStop: stopListener,
5310+ });
5311+
52685312 onStart();
52695313 result.url = await sendGenerationRequest(generationType, prompt, refineArgs.negative, characterName, callback, initiators.swipe, abortController.signal);
52705314 result.generation_type = generationType;
@@ -5276,11 +5320,10 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
52765320 }
52775321 } finally {
52785322 onComplete();
5279- $(stopButton).hide();
5280- eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener);
52815323 restoreOriginalDimensions(dimensions);
52825324 extension_settings.sd.seed = extension_settings.sd.original_seed;
52835325 delete extension_settings.sd.original_seed;
5326+ await loaderHandle.hide();
52845327 }
52855328
52865329 if (!result.url) {
@@ -5441,7 +5484,7 @@ function registerFunctionTool() {
54415484 });
54425485}
54435486
54445487jQuery(export async function init() => {
54455488 await addSDGenButtons();
54465489
54475490 const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null));
@@ -5850,6 +5893,9 @@ jQuery(async () => {
58505893 extension_settings.sd.google_duration = Number($(this).val());
58515894 saveSettingsDebounced();
58525895 });
5896+ $('#sd_models_refresh').on('click', async () => {
5897+ await loadModels();
5898+ });
58535899 $('#sd_electronhub_quality').on('change', function () {
58545900 extension_settings.sd.electronhub_quality = String($(this).val());
58555901 saveSettingsDebounced();
@@ -5893,6 +5939,7 @@ jQuery(async () => {
58935939 [sources.aimlapi]: SECRET_KEYS.AIMLAPI,
58945940 [sources.comfy]: SECRET_KEYS.COMFY_RUNPOD,
58955941 [sources.pollinations]: SECRET_KEYS.POLLINATIONS,
5942+ [sources.workersai]: SECRET_KEYS.WORKERS_AI,
58965943 };
58975944 const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key);
58985945 if (!shouldReloadOptions) {
@@ -5948,4 +5995,4 @@ jQuery(async () => {
59485995 t`Character's negative Image Generation prompt prefix`,
59495996 );
59505997 }
59515998});
public/scripts/extensions/stable-diffusion/manifest.json+4 -1
@@ -10,5 +10,8 @@
1010 "css": "style.css",
1111 "author": "Cohee#1207",
1212 "version": "1.0.0",
1313 "homePage": "https://github.com/SillyTavern/SillyTavern",
14+ "hooks": {
15+ "activate": "init"
16+ }
1417}
public/scripts/extensions/stable-diffusion/settings.html+36 -16
@@ -40,10 +40,11 @@
4040 <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span>
4141 </label>
4242 <label for="sd_source" data-i18n="Source">Source</label>
4343 <select id="sd_source" class="text_pole">
4444 <option value="aimlapi">AI/ML API</option>
4545 <option value="bfl">BFL (Black Forest Labs)</option>
4646 <option value="chutes">Chutes</option>
47+ <option value="workersai">Cloudflare Workers AI</option>
4748 <option value="comfy">ComfyUI</option>
4849 <option value="drawthings">DrawThings HTTP API</option>
4950 <option value="electronhub">Electron Hub</option>
@@ -123,7 +124,7 @@
123124 <div class="flex-container" id="sd_electronhub_quality_row">
124125 <div class="flex1">
125126 <label for="sd_electronhub_quality" data-i18n="Image Quality">Image Quality</label>
126127 <select id="sd_electronhub_quality" class="text_pole"></select>
127128 </div>
128129 </div>
129130 </div>
@@ -191,14 +192,14 @@
191192 <div class="flex-container">
192193 <div data-sd-model="dall-e-3" class="flex1">
193194 <label for="sd_openai_style" data-i18n="Image Style">Image Style</label>
194195 <select id="sd_openai_style" class="text_pole">
195196 <option value="vivid">Vivid</option>
196197 <option value="natural">Natural</option>
197198 </select>
198199 </div>
199200 <div data-sd-model="gpt-image" class="flex1">
200201 <label for="sd_openai_quality_gpt" data-i18n="Image Quality">Image Quality</label>
201202 <select id="sd_openai_quality_gpt" class="text_pole">
202203 <option value="auto" data-i18n="Auto">Auto</option>
203204 <option value="low" data-i18n="Low">Low</option>
204205 <option value="medium" data-i18n="Medium">Medium</option>
@@ -207,7 +208,7 @@
207208 </div>
208209 <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1">
209210 <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>
210211 <select id="sd_openai_quality" class="text_pole">
211212 <option value="standard" data-i18n="Standard">Standard</option>
212213 <option value="hd" data-i18n="HD">HD</option>
213214 </select>
@@ -216,7 +217,7 @@
216217 <div data-sd-model="sora-2,sora-2-pro" class="flex-container">
217218 <div class="flex1">
218219 <label for="sd_openai_duration" data-i18n="Duration">Duration</label>
219220 <select id="sd_openai_duration" class="text_pole">
220221 <option value="4" data-i18n="Short (4 seconds)">Short (4 seconds)</option>
221222 <option value="8" data-i18n="Medium (8 seconds)">Medium (8 seconds)</option>
222223 <option value="12" data-i18n="Long (16 seconds)">Long (12 seconds)</option>
@@ -226,7 +227,7 @@
226227 </div>
227228 <div data-sd-source="comfy">
228229 <label for="sd_comfy_type">Server Type</label>
229230 <select id="sd_comfy_type" class="text_pole">
230231 <option value="standard">Standard Server</option>
231232 <option value="runpod_serverless">RunPod Serverless Endpoint</option>
232233 </select>
@@ -318,7 +319,7 @@
318319 <div class="flex-container">
319320 <div class="flex1">
320321 <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label>
321322 <select id="sd_stability_style_preset" class="text_pole">
322323 <option value="anime">Anime</option>
323324 <option value="3d-model">3D Model</option>
324325 <option value="analog-film">Analog Film</option>
@@ -375,6 +376,20 @@
375376 </div>
376377 </div>
377378
379+ <div data-sd-source="workersai">
380+ <a href="https://dash.cloudflare.com" target="_blank" rel="noopener noreferrer">Cloudflare Workers AI</a>
381+ <div class="flex-container flexnowrap alignItemsBaseline marginBot5">
382+ <strong class="flex1" data-i18n="API Key">API Key</strong>
383+ <div id="sd_cf_workers_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_workers_ai">
384+ <i class="fa-fw fa-solid fa-key"></i>
385+ <span data-i18n="Click to set">Click to set</span>
386+ </div>
387+ </div>
388+ <div class="flex-container flexnowrap alignItemsBaseline">
389+ <small class="flex1" data-i18n="Hint: Account ID and API key are pulled from API connections.">Hint: Account ID and API key are pulled from API connections.</small>
390+ </div>
391+ </div>
392+
378393 <div data-sd-source="google">
379394 <div class="flex-container">
380395 <div class="flex1">
@@ -394,7 +409,7 @@
394409 </label>
395410 <div class="flex1">
396411 <label for="sd_google_duration" data-i18n="Duration (Veo)">Duration (Veo)</label>
397412 <select id="sd_google_duration" class="text_pole">
398413 <option value="4">Short (4 seconds)</option>
399414 <option value="6">Medium (6 seconds)</option>
400415 <option value="8">Long (8 seconds)</option>
@@ -405,37 +420,42 @@
405420
406421 <div class="flex-container">
407422 <div class="flex1">
408423 <label for="sd_model" data-i18nclass="Modelflex-container justifySpaceBetween">Model</label>
409424 <selectspan iddata-i18n="sd_modelModel">Model</selectspan>
425+ <div id="sd_models_refresh" class="right_menu_button margin0 padding0" title="Refresh model list" data-i18n="[title]Refresh model list">
426+ <i class="fa-solid fa-sync"></i>
427+ </div>
428+ </label>
429+ <select id="sd_model" class="text_pole"></select>
410430 </div>
411431
412432 <div class="flex1" data-sd-source="comfy,auto">
413433 <label for="sd_vae">VAE</label>
414434 <select id="sd_vae" class="text_pole"></select>
415435 </div>
416436 </div>
417437
418438 <div class="flex-container">
419439 <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp">
420440 <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>
421441 <select id="sd_sampler" class="text_pole"></select>
422442 </div>
423443
424444 <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp">
425445 <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>
426446 <select id="sd_scheduler" class="text_pole"></select>
427447 </div>
428448 </div>
429449
430450 <div class="flex-container">
431451 <div class="flex1">
432452 <label for="sd_resolution" data-i18n="Resolution">Resolution</label>
433453 <select id="sd_resolution" class="text_pole"><!-- Populated in JS --></select>
434454 </div>
435455
436456 <div class="flex1" data-sd-source="auto,vlad,drawthings">
437457 <label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label>
438458 <select id="sd_hr_upscaler" class="text_pole"></select>
439459 </div>
440460 </div>
441461
public/scripts/extensions/stable-diffusion/style.css+0 -4
@@ -1,7 +1,3 @@
1-.sd_settings label:not(.checkbox_label) {
2- display: block;
3-}
4-
51#sd_dropdown {
62 z-index: 30000;
73 backdrop-filter: blur(var(--SmartThemeBlurStrength));
public/scripts/extensions/token-counter/index.js+2 -2
@@ -101,7 +101,7 @@ async function doCount() {
101101 return count;
102102}
103103
104-jQuery(() => {
104+export function init() {
105105 const buttonHtml = `
106106 <div id="token_counter" class="list-group-item flex-container flexGap5">
107107 <div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>` +
@@ -115,4 +115,4 @@ jQuery(() => {
115115 returns: 'number of tokens',
116116 helpString: 'Counts the number of tokens in the current chat.',
117117 }));
118118});
public/scripts/extensions/token-counter/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "Cohee#1207",
99 "version": "1.0.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/translate/index.js+2 -2
@@ -707,7 +707,7 @@ const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayTe
707707
708708globalThis.translate = translate;
709709
710710jQuery(export async function init() => {
711711 const html = await renderExtensionTemplateAsync('translate', 'index');
712712 const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons');
713713
@@ -801,4 +801,4 @@ jQuery(async () => {
801801 },
802802 returns: ARGUMENT_TYPE.STRING,
803803 }));
804804});
public/scripts/extensions/translate/manifest.json+4 -1
@@ -7,5 +7,8 @@
77 "css": "style.css",
88 "author": "Cohee#1207",
99 "version": "1.0.0",
1010 "homePage": "https://github.com/SillyTavern/SillyTavern",
11+ "hooks": {
12+ "activate": "init"
13+ }
1114}
public/scripts/extensions/tts/index.js+39 -14
@@ -1,6 +1,7 @@
11import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
22import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
33import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js';
4+import { accountStorage } from '../../util/AccountStorage.js';
45import { EdgeTtsProvider } from './edge.js';
56import { ElevenLabsTtsProvider } from './elevenlabs.js';
67import { SileroTtsProvider } from './silerotts.js';
@@ -165,7 +166,7 @@ async function onNarrateOneMessage() {
165166 }
166167
167168 resetTtsPlayback();
168169 processAndQueueTtsMessage(message, Number(id), { manual: true });
169170 moduleWorker();
170171}
171172
@@ -186,13 +187,20 @@ async function onNarrateText(args, text) {
186187 ? voiceMap[DEFAULT_VOICE_MARKER]
187188 : voiceMap[name];
188189
189190 if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {
191+ toastr.info(`TTS voice for ${name} is disabled.`);
192+ await initVoiceMap(false);
193+ return;
194+ }
195+
196+ if (!voiceMapEntry) {
190197 toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`);
198+ await initVoiceMap(false);
191199 return;
192200 }
193201
194202 resetTtsPlayback();
195203 processAndQueueTtsMessage({ mes: text, name: name }, null, { manual: true });
196204 await moduleWorker();
197205
198206 // Return back to the chat voices
@@ -245,7 +253,7 @@ function isTtsProcessing() {
245253}
246254
247255/**
248256 * @typedef {ChatMessage & { id?: number, manual?: boolean, segmentText?: string, segmentType?: string }} TtsMessage
249257 */
250258
251259/**
@@ -253,12 +261,15 @@ function isTtsProcessing() {
253261 * (if enabled) and adds each part to the TTS job queue.
254262 * @param {ChatMessage} message - The message object to be processed.
255263 * @param {number|null} [messageId=null] - The chat message index to associate with TTS events.
264+ * @param {object} [options={}] - Additional options for processing.
265+ * @param {boolean} [options.manual=false] - Whether this TTS job was manually triggered (e.g., from the UI) rather than automatically from a new chat message.
256266 * @returns {void}
257267 */
258268function processAndQueueTtsMessage(message, messageId = null, { manual = false } = {}) {
259269 /** @type {TtsMessage} */
260270 const clone = structuredClone(message);
261271 clone.id = messageId ?? null;
272+ clone.manual = manual ?? false;
262273
263274 if (!extension_settings.tts.narrate_by_paragraphs) {
264275 ttsJobQueue.push(clone);
@@ -408,9 +419,10 @@ function onAudioControlClicked() {
408419 // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful
409420 if (!audioElement.paused || isTtsProcessing()) {
410421 resetTtsPlayback();
411- } else {
422+ } else if (context?.chat?.length > 0) {
412423 // Default play behavior if not processing or playing is to play the last message.
413424 processAndQueueTtsMessage(context.chat[const id = context.chat.length - 1]);
425+ processAndQueueTtsMessage(context.chat[id], id, { manual: true });
414426 }
415427 updateUiAudioPlayState();
416428}
@@ -481,8 +493,10 @@ async function processAudioJobQueue() {
481493// TTS Control //
482494//################//
483495
496+/** @type {TtsMessage[]} */
484497const ttsJobQueue = [];
485-let currentTtsJob; // Null if nothing is currently being processed
498+/** @type {TtsMessage|null} */
499+let currentTtsJob = null; // Null if nothing is currently being processed
486500
487501function completeTtsJob() {
488502 console.info(`Current TTS job for ${currentTtsJob?.name} completed.`);
@@ -621,7 +635,18 @@ async function processTtsQueue() {
621635
622636 const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey];
623637
624638 if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {
639+ const storageKey = `tts_disabled_warned_${char}`;
640+ if (!accountStorage.getItem(storageKey) || currentTtsJob.manual) {
641+ accountStorage.setItem(storageKey, 'true');
642+ toastr.info(`TTS voice for ${char} is disabled.`);
643+ }
644+ currentTtsJob = null;
645+ setTimeout(() => wrapper.update(), 0);
646+ return;
647+ }
648+
649+ if (!voiceMapEntry) {
625650 throw `${char} not in voicemap. Configure character in extension settings voice map`;
626651 }
627652
@@ -649,7 +674,6 @@ async function processTtsQueue() {
649674 text = substituteParams(text);
650675
651676 if (extension_settings.tts.skip_codeblocks) {
652- text = text.replace(/^\s{4}.*$/gm, '').trim();
653677 text = text.replace(/```.*?```/gs, '').trim();
654678 text = text.replace(/~~~.*?~~~/gs, '').trim();
655679 }
@@ -724,6 +748,7 @@ async function processTtsQueue() {
724748 mes: currentTtsJob.mes,
725749 extra: currentTtsJob.extra,
726750 id: currentTtsJob.id,
751+ manual: currentTtsJob.manual,
727752 };
728753 ttsJobQueue.unshift(segmentJob);
729754 }
@@ -824,7 +849,7 @@ async function playFullConversation() {
824849
825850 context.chat.forEach((msg, i) => {
826851 if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') {
827852 processAndQueueTtsMessage(msg, i, { manual: false });
828853 }
829854 });
830855
@@ -1164,7 +1189,7 @@ async function onMessageEvent(messageId, lastCharIndex) {
11641189 message.id = messageId;
11651190 ttsJobQueue.push(message);
11661191 } else {
11671192 processAndQueueTtsMessage(message, messageId, { manual: false });
11681193 }
11691194}
11701195
@@ -1506,7 +1531,7 @@ async function initVoiceMapInternal(unrestricted) {
15061531 updateVoiceMap();
15071532}
15081533
15091534jQuery(export async function init() {
15101535 async function addExtensionControls() {
15111536 const settingsHtml = $(await renderExtensionTemplateAsync('tts', 'settings'));
15121537 $('#tts_container').append(settingsHtml);
@@ -1594,4 +1619,4 @@ jQuery(async function () {
15941619 }));
15951620
15961621 document.body.appendChild(audioElement);
15971622});
public/scripts/extensions/tts/manifest.json+4 -1
@@ -11,5 +11,8 @@
1111 "css": "style.css",
1212 "author": "Ouoertheo#7264",
1313 "version": "1.0.0",
1414 "homePage": "None",
15+ "hooks": {
16+ "activate": "init"
17+ }
1518}
public/scripts/extensions/vectors/index.js+258 -213
@@ -44,6 +44,7 @@ import { oai_settings } from '../../openai.js';
4444 * @property {string} text - The hashed message text
4545 * @property {number} hash - The hash used as the vector key
4646 * @property {number} index - The index of the message in the chat
47+ * @property {boolean} [summaryFailed] - Whether summarization failed for this message (used internally to skip messages that fail summarization)
4748 */
4849
4950const MODULE_NAME = 'vectors';
@@ -77,10 +78,13 @@ const settings = {
7778 summarize_sent: false,
7879 summary_source: 'main',
7980 summary_prompt: 'Ignore previous instructions. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.',
81+ summary_retries: 2,
82+ summary_threshold: 200,
8083 force_chunk_delimiter: '',
8184
8285 // For chats
8386 enabled_chats: false,
87+ keep_hidden: false,
8488 template: 'Past events:\n{{text}}',
8589 depth: 2,
8690 position: extension_prompt_types.IN_PROMPT,
@@ -117,10 +121,77 @@ const settings = {
117121
118122const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
119123const webllmProvider = new WebLlmVectorProvider();
124+/**
125+ * Cache for storing summaries of messages by their hash.
126+ * @type {Map<number, string>}
127+ */
120128const cachedSummaries = new Map();
129+/**
130+ * Hashes skipped this Vectorize All session (summary or embed failure). Cleared on next Vectorize All click.
131+ * @type {Set<number>}
132+ */
133+const skippedHashes = new Set();
134+/**
135+ * Error causes treated as fatal — abort Vectorize All rather than skip.
136+ * @type {Set<string>}
137+ */
138+const FATAL_CAUSES = new Set(['account_id_missing', 'api_key_missing', 'api_url_missing', 'api_model_missing', 'extras_module_missing', 'webllm_not_supported', 'summary_endpoint_invalid']);
121139const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];
122140
123141/**
142+ * @typedef {object} RemoteEmbeddingEndpointConfig
143+ * @property {string} url - The API endpoint URL
144+ * @property {string} settingsKey - The key in settings for the selected model
145+ * @property {string} selectId - The ID of the select element (without #)
146+ * @property {string} [valueProperty='id'] - Property name for the option value
147+ * @property {string} [textProperty] - Property name for the option text. Falls back to valueProperty
148+ * @property {() => object} [getBody] - Function returning the request body
149+ * @property {(models: any[]) => any[]} [filter] - Optional post-fetch filter for models
150+ */
151+
152+/** @type {Record<string, RemoteEmbeddingEndpointConfig>} */
153+const remoteEmbeddingEndpoints = {
154+ chutes: {
155+ url: '/api/openai/chutes/models/embedding',
156+ settingsKey: 'chutes_model',
157+ selectId: 'vectors_chutes_model',
158+ valueProperty: 'slug',
159+ textProperty: 'name',
160+ },
161+ nanogpt: {
162+ url: '/api/openai/nanogpt/models/embedding',
163+ settingsKey: 'nanogpt_model',
164+ selectId: 'vectors_nanogpt_model',
165+ textProperty: 'name',
166+ },
167+ electronhub: {
168+ url: '/api/openai/electronhub/models',
169+ settingsKey: 'electronhub_model',
170+ selectId: 'vectors_electronhub_model',
171+ textProperty: 'name',
172+ filter: models => models.filter(m => Array.isArray(m?.endpoints) && m.endpoints.includes('/v1/embeddings')),
173+ },
174+ openrouter: {
175+ url: '/api/openrouter/models/embedding',
176+ settingsKey: 'openrouter_model',
177+ selectId: 'vectors_openrouter_model',
178+ textProperty: 'name',
179+ },
180+ siliconflow: {
181+ url: '/api/openai/siliconflow/models/embedding',
182+ settingsKey: 'siliconflow_model',
183+ selectId: 'vectors_siliconflow_model',
184+ getBody: () => ({ siliconflow_endpoint: oai_settings.siliconflow_endpoint }),
185+ },
186+ workers_ai: {
187+ url: '/api/openai/workers-ai/models/embedding',
188+ settingsKey: 'workers_ai_model',
189+ selectId: 'vectors_workers_ai_model',
190+ getBody: () => ({ workers_ai_account_id: oai_settings.workers_ai_account_id }),
191+ },
192+};
193+
194+/**
124195 * Gets the Collection ID for a file embedded in the chat.
125196 * @param {string} fileUrl URL of the file
126197 * @returns {string} Collection ID
@@ -145,10 +216,12 @@ async function onVectorizeAllClick() {
145216 // Clear all cached summaries to ensure that new ones are created
146217 // upon request of a full vectorise
147218 cachedSummaries.clear();
219+ skippedHashes.clear();
148220
149221 const batchSize = getBatchSize();
150222 const elapsedLog = [];
151223 let finished = false;
224+ let initialPending = null; // total items pending at the start of this run — set on first sync return
152225 $('#vectorize_progress').show();
153226 $('#vectorize_progress_percent').text('0');
154227 $('#vectorize_progress_eta').text('...');
@@ -162,16 +235,27 @@ async function onVectorizeAllClick() {
162235 const startTime = Date.now();
163236 const remaining = await synchronizeChat(batchSize);
164237 const elapsed = Date.now() - startTime;
238+
239+ if (remaining === null) {
240+ // synchronizeChat already surfaced a toast; bail out of the loop.
241+ throw new Error('Vectorization aborted');
242+ }
243+
165244 elapsedLog.push(elapsed);
166245 finished = remaining <= 0;
167246
168- const total = getContext().chat.length;
247+ if (initialPending === null) {
169- const processed = total - remaining;
248+ initialPending = Math.max(0, remaining + batchSize);
170- const processedPercent = Math.round((processed / total) * 100); // percentage of the work done
249+ }
250+ const pending = Math.max(0, remaining);
251+ const processed = Math.max(0, initialPending - pending);
252+ const processedPercent = initialPending > 0
253+ ? Math.min(100, Math.round((processed / initialPending) * 100))
254+ : 100;
171255 const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times
172256 const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item
173257 const pace = averageElapsed / batchSize; // time needed to process one item
174258 const remainingTime = Math.round(pace * remainingpending / 1000);
175259
176260 $('#vectorize_progress_percent').text(processedPercent);
177261 $('#vectorize_progress_eta').text(remainingTime);
@@ -180,6 +264,9 @@ async function onVectorizeAllClick() {
180264 throw new Error('Chat changed');
181265 }
182266 }
267+ if (skippedHashes.size > 0) {
268+ toastr.warning(`${skippedHashes.size} message(s) skipped due to errors. Click Vectorize All again to retry.`, 'Vectorization partial');
269+ }
183270 } catch (error) {
184271 console.error('Vectors: Failed to vectorize all', error);
185272 } finally {
@@ -250,7 +337,7 @@ async function summarizeExtra(element) {
250337
251338 if (apiResult.ok) {
252339 const data = await apiResult.json();
253340 element.text = removeReasoningFromString(data.summary);
254341 }
255342 } catch (error) {
256343 console.log(error);
@@ -282,45 +369,70 @@ async function summarizeWebLLM(element) {
282369 }
283370
284371 const messages = [{ role: 'system', content: settings.summary_prompt }, { role: 'user', content: element.text }];
285372 element.text = removeReasoningFromString(await generateWebLlmChatPrompt(messages));
286373
287374 return true;
288375}
289376
290377/**
291378 * SummarizesRuns messagesone usingsummarization attempt for a single element via the chosen methodendpoint.
292379 * @param {HashedMessage[]} hashedMessages Array of hashed messageselement
293380 * @param {string} endpoint Type of endpoint to use
294381 * @returns {Promise<HashedMessage[]boolean>} SummarizedWhether messagesthe attempt succeeded.
295382 */
296383async function summarizesummarizeOne(hashedMessageselement, endpoint = 'main') {
297- for (const element of hashedMessages) {
298- const cachedSummary = cachedSummaries.get(element.hash);
299- if (!cachedSummary) {
300- let success = true;
301384 switch (endpoint) {
302385 case 'main':
303386 success = return await summarizeMain(element);
304- break;
305387 case 'extras':
306388 success = return await summarizeExtra(element);
307- break;
308389 case 'webllm':
309390 success = return await summarizeWebLLM(element);
310- break;
311391 default:
312- console.error('Unsupported endpoint', endpoint);
392+ throw new Error(`Unsupported summary endpoint: ${endpoint}`, { cause: 'summary_endpoint_invalid' });
313- success = false;
314- break;
315393 }
316- if (success) {
317- cachedSummaries.set(element.hash, element.text);
318- } else {
319- break;
320394}
321- } else {
395+
396+/**
397+ * Summarizes messages using the chosen method. Every returned element has been
398+ * summarized (via live call or cache). Throws if any element fails after
399+ * `settings.summary_retries` attempts.
400+ * @param {HashedMessage[]} hashedMessages Array of hashed messages (mutated in place)
401+ * @param {string} endpoint Type of endpoint to use
402+ * @param {Object} [options] Options for summarization behavior
403+ * @param {boolean} [options.skipOnFailure=false] If true, tags failed elements with `summaryFailed = true` instead of throwing
404+ * @returns {Promise<HashedMessage[]>} Summarized messages
405+ */
406+async function summarize(hashedMessages, endpoint = 'main', { skipOnFailure = false } = {}) {
407+ const maxAttempts = Math.max(1, Number(settings.summary_retries) || 1);
408+ for (const element of hashedMessages) {
409+ const cachedSummary = cachedSummaries.get(element.hash);
410+ if (cachedSummary) {
322411 element.text = cachedSummary;
412+ continue;
413+ }
414+
415+ let success = false;
416+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
417+ try {
418+ success = await summarizeOne(element, endpoint);
419+ if (success) break;
420+ } catch (error) {
421+ if (FATAL_CAUSES.has(error?.cause)) throw error;
422+ console.warn(`Vectors: summary attempt ${attempt}/${maxAttempts} threw for hash ${element.hash}`, error);
423+ }
424+ console.warn(`Vectors: summary attempt ${attempt}/${maxAttempts} failed for hash ${element.hash}`);
425+ }
426+ if (!success) {
427+ if (skipOnFailure) {
428+ console.warn(`Vectors: summarization exhausted ${maxAttempts} attempt(s) for hash ${element.hash} — marking for skip`);
429+ element.summaryFailed = true;
430+ continue;
431+ }
432+
433+ throw new Error(`Summarization failed after ${maxAttempts} attempt(s)`, { cause: 'summary_failed' });
323434 }
435+ cachedSummaries.set(element.hash, element.text);
324436 }
325437 return hashedMessages;
326438}
@@ -347,21 +459,43 @@ async function synchronizeChat(batchSize = 5) {
347459 return -1;
348460 }
349461
350- const hashedMessages = context.chat.filter(x => !x.is_system).map(x => ({ text: String(substituteParams(x.mes)), hash: getStringHash(substituteParams(x.mes)), index: context.chat.indexOf(x) }));
462+ /** @type {HashedMessage[]} */
463+ const hashedMessages = context.chat.filter(x => settings.keep_hidden || !x.is_system).map(x => ({ text: String(substituteParams(x.mes)), hash: getStringHash(substituteParams(x.mes)), index: context.chat.indexOf(x) }));
351464 const hashesInCollection = await getSavedHashes(chatId);
352465
353- let newVectorItems = hashedMessages.filter(x => !hashesInCollection.includes(x.hash));
466+ const newVectorItems = hashedMessages
467+ .filter(x => !hashesInCollection.includes(x.hash))
468+ .filter(x => !skippedHashes.has(x.hash));
354469 const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x));
355470
471+ let batch = newVectorItems.slice(0, batchSize);
472+
356473 if (settings.summarize) {
357474 newVectorItemsconst =minLength await= summarizeMath.max(newVectorItems0, Number(settings.summary_sourcesummary_threshold) || 0);
475+ const toSummarize = minLength > 0 ? batch.filter(x => x.text.length >= minLength) : batch;
476+ if (toSummarize.length > 0) {
477+ await summarize(toSummarize, settings.summary_source, { skipOnFailure: true });
478+ const failed = toSummarize.filter(x => x.summaryFailed);
479+ if (failed.length > 0) {
480+ for (const item of failed) skippedHashes.add(item.hash);
481+ batch = batch.filter(x => !x.summaryFailed);
482+ }
483+ }
358484 }
359485
360486 if (newVectorItemsbatch.length > 0) {
361487 const chunkedBatch = splitByChunks(newVectorItems.slice(0, batchSize)batch);
362488
363489 console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batchSizebatch.length}...`);
490+ try {
364491 await insertVectorItems(chatId, chunkedBatch);
492+ } catch (insertError) {
493+ if (FATAL_CAUSES.has(insertError?.cause)) {
494+ throw insertError;
495+ }
496+ console.warn('Vectors: insert failed for batch — marking for skip', insertError);
497+ for (const item of batch) skippedHashes.add(item.hash);
498+ }
365499 }
366500
367501 if (deletedHashes.length > 0) {
@@ -388,6 +522,12 @@ async function synchronizeChat(batchSize = 5) {
388522 return 'Extras API must provide an "embeddings" module.';
389523 case 'webllm_not_supported':
390524 return 'WebLLM extension is not installed or the model is not set.';
525+ case 'account_id_missing':
526+ return 'Workers AI account ID is required. Save it in the "API Connections" panel.';
527+ case 'summary_endpoint_invalid':
528+ return 'Summarization endpoint is not supported.';
529+ case 'summary_failed':
530+ return 'Summarization failed after the configured number of retries.';
391531 default:
392532 return 'Check server console for more details';
393533 }
@@ -397,7 +537,7 @@ async function synchronizeChat(batchSize = 5) {
397537
398538 const message = getErrorMessage(error.cause);
399539 toastr.error(message, 'Vectorization failed', { preventDuplicates: true });
400540 return -1null;
401541 } finally {
402542 syncBlocked = false;
403543 }
@@ -771,7 +911,11 @@ async function getQueryText(chat, initiator) {
771911 .slice(0, settings.query);
772912
773913 if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) {
774914 hashedMessagesconst =minLength await= summarizeMath.max(hashedMessages0, Number(settings.summary_sourcesummary_threshold) || 0);
915+ const toSummarize = minLength > 0 ? hashedMessages.filter(x => x.text.length >= minLength) : hashedMessages;
916+ if (toSummarize.length > 0) {
917+ await summarize(toSummarize, settings.summary_source, { skipOnFailure: true });
918+ }
775919 }
776920
777921 const queryText = hashedMessages.map(x => x.text).join('\n');
@@ -842,6 +986,10 @@ function getVectorsRequestBody(args = {}) {
842986 body.model = extension_settings.vectors.siliconflow_model;
843987 body.siliconflow_endpoint = oai_settings.siliconflow_endpoint;
844988 break;
989+ case 'workers_ai':
990+ body.model = extension_settings.vectors.workers_ai_model || '@cf/baai/bge-m3';
991+ body.workers_ai_account_id = oai_settings.workers_ai_account_id;
992+ break;
845993 default:
846994 break;
847995 }
@@ -935,6 +1083,7 @@ function throwIfSourceInvalid() {
9351083 settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] ||
9361084 settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] ||
9371085 settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE] ||
1086+ settings.source === 'workers_ai' && !secret_state[SECRET_KEYS.WORKERS_AI] ||
9381087 settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) {
9391088 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
9401089 }
@@ -963,6 +1112,10 @@ function throwIfSourceInvalid() {
9631112 if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) {
9641113 throw new Error('Vectors: WebLLM is not supported', { cause: 'webllm_not_supported' });
9651114 }
1115+
1116+ if (settings.source === 'workers_ai' && !oai_settings.workers_ai_account_id) {
1117+ throw new Error('Vectors: Workers AI account ID missing', { cause: 'account_id_missing' });
1118+ }
9661119}
9671120
9681121/**
@@ -972,11 +1125,12 @@ function throwIfSourceInvalid() {
9721125 * @returns {Promise<void>}
9731126 */
9741127async function deleteVectorItems(collectionId, hashes) {
1128+ const args = await getAdditionalArgs([]);
9751129 const response = await fetch('/api/vector/delete', {
9761130 method: 'POST',
9771131 headers: getRequestHeaders(),
9781132 body: JSON.stringify({
9791133 ...getVectorsRequestBody(args),
9801134 collectionId: collectionId,
9811135 hashes: hashes,
9821136 source: settings.source,
@@ -1154,211 +1308,77 @@ function toggleSettings() {
11541308 $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');
11551309 $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai');
11561310 $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow');
1311+ $('#workers_ai_vectorsModel').toggle(settings.source === 'workers_ai');
11571312 $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));
11581313 switchif (settings.source === 'webllm') {
1159- case 'webllm':
11601314 loadWebLlmModels();
1161- break;
1315+ } else if (settings.source in remoteEmbeddingEndpoints) {
1162- case 'electronhub':
1316+ loadRemoteEmbeddingModels(settings.source);
1163- loadElectronHubModels();
1164- break;
1165- case 'openrouter':
1166- loadOpenRouterModels();
1167- break;
1168- case 'chutes':
1169- loadChutesModels();
1170- break;
1171- case 'nanogpt':
1172- loadNanoGPTModels();
1173- break;
1174- case 'siliconflow':
1175- loadSiliconFlowModels();
1176- break;
1177- }
1178-}
1179-
1180-async function loadChutesModels() {
1181- try {
1182- const response = await fetch('/api/openai/chutes/models/embedding', {
1183- method: 'POST',
1184- headers: getRequestHeaders({ omitContentType: true }),
1185- });
1186- if (!response.ok) {
1187- throw new Error(`HTTP ${response.status}`);
1188- }
1189- /** @type {Array<any>} */
1190- const data = await response.json();
1191- const models = Array.isArray(data) ? data : [];
1192- populateChutesModelSelect(models);
1193- } catch (err) {
1194- console.warn('Chutes models fetch failed', err);
1195- populateChutesModelSelect([]);
1196- }
1197-}
1198-
1199-function populateChutesModelSelect(models) {
1200- const select = $('#vectors_chutes_model');
1201- select.empty();
1202- for (const m of models) {
1203- const option = document.createElement('option');
1204- option.value = m.slug;
1205- option.text = m.name;
1206- select.append(option);
1207- }
1208- if (!settings.chutes_model && models.length) {
1209- settings.chutes_model = models[0].slug;
1210- }
1211- $('#vectors_chutes_model').val(settings.chutes_model);
1212-}
1213-
1214-async function loadNanoGPTModels() {
1215- try {
1216- const response = await fetch('/api/openai/nanogpt/models/embedding', {
1217- method: 'POST',
1218- headers: getRequestHeaders({ omitContentType: true }),
1219- });
1220- if (!response.ok) {
1221- throw new Error(`HTTP ${response.status}`);
1222- }
1223- /** @type {Array<any>} */
1224- const data = await response.json();
1225- const models = Array.isArray(data) ? data : [];
1226- populateNanoGPTModelSelect(models);
1227- } catch (err) {
1228- console.warn('NanoGPT models fetch failed', err);
1229- populateNanoGPTModelSelect([]);
1230- }
1231-}
1232-
1233-function populateNanoGPTModelSelect(models) {
1234- const select = $('#vectors_nanogpt_model');
1235- select.empty();
1236- for (const m of models) {
1237- const option = document.createElement('option');
1238- option.value = m.id;
1239- option.text = m.name || m.id;
1240- select.append(option);
1241- }
1242- if (!settings.nanogpt_model && models.length) {
1243- settings.nanogpt_model = models[0].id;
1244- }
1245- $('#vectors_nanogpt_model').val(settings.nanogpt_model);
1246-}
1247-
1248-async function loadElectronHubModels() {
1249- try {
1250- const response = await fetch('/api/openai/electronhub/models', {
1251- method: 'POST',
1252- headers: getRequestHeaders({ omitContentType: true }),
1253- });
1254- if (!response.ok) {
1255- throw new Error(`HTTP ${response.status}`);
1256- }
1257- /** @type {Array<any>} */
1258- const data = await response.json();
1259- // filter by embeddings endpoint
1260- const models = Array.isArray(data) ? data.filter(m => Array.isArray(m?.endpoints) && m.endpoints.includes('/v1/embeddings')) : [];
1261- populateElectronHubModelSelect(models);
1262- } catch (err) {
1263- console.warn('Electron Hub models fetch failed', err);
1264- populateElectronHubModelSelect([]);
12651317 }
12661318}
12671319
12681320/**
12691321 * PopulatesLoads themodels Electronfrom Huba modelremote embedding endpoint and populates the corresponding select element.
12701322 * @param {{string} id:source string,- name:The stringsource }[]}key modelsmatching Electrona HubremoteEmbeddingEndpoints modelsentry
12711323 */
12721324async function populateElectronHubModelSelectloadRemoteEmbeddingModels(modelssource) {
12731325 const selectconfig = $('#vectors_electronhub_model')remoteEmbeddingEndpoints[source];
1274- select.empty();
1326+ if (!config) {
1275- for (const m of models) {
1327+ return;
1276- const option = document.createElement('option');
1277- option.value = m.id;
1278- option.text = m.name || m.id;
1279- select.append(option);
1280- }
1281- if (!settings.electronhub_model && models.length) {
1282- settings.electronhub_model = models[0].id;
1283- }
1284- $('#vectors_electronhub_model').val(settings.electronhub_model);
12851328 }
12861329
1287-async function loadOpenRouterModels() {
1330+ const { url, settingsKey, selectId, getBody, filter } = config;
1288- try {
1331+ const valueProperty = config.valueProperty || 'id';
1289- const response = await fetch('/api/openrouter/models/embedding', {
1332+ const textProperty = config.textProperty;
1290- method: 'POST',
1291- headers: getRequestHeaders({ omitContentType: true }),
1292- });
1293- if (!response.ok) {
1294- throw new Error(`HTTP ${response.status}`);
1295- }
1296- /** @type {Array<any>} */
1297- const data = await response.json();
1298- const models = Array.isArray(data) ? data : [];
1299- populateOpenRouterModelSelect(models);
1300- } catch (err) {
1301- console.warn('OpenRouter models fetch failed', err);
1302- populateOpenRouterModelSelect([]);
1303- }
1304-}
13051333
13061334 /**
13071335 * Populates the OpenRouter model select element with the given models.
1308- * @param {{ id: string, name: string }[]} models OpenRouter models
1336+ * @param {any[]} models - Array of model objects
13091337 */
13101338 function populateOpenRouterModelSelectpopulateSelect(models) {
13111339 const select = $('`#vectors_openrouter_model'${selectId}`);
13121340 select.empty();
13131341 for (const m of models) {
13141342 const option = document.createElement('option');
13151343 option.value = m.id[valueProperty];
13161344 option.text = textProperty ? (m.name[textProperty] || m.id[valueProperty]) : m[valueProperty];
13171345 select.append(option);
13181346 }
13191347 if (!settings.openrouter_model[settingsKey] && models.length) {
13201348 settings.openrouter_model[settingsKey] = models[0].id[valueProperty];
1349+ Object.assign(extension_settings.vectors, settings);
1350+ saveSettingsDebounced();
13211351 }
1322- $('#vectors_openrouter_model').val(settings.openrouter_model);
1352+ select.val(settings[settingsKey]);
13231353 }
13241354
1325-async function loadSiliconFlowModels() {
13261355 try {
1327- const response = await fetch('/api/openai/siliconflow/models/embedding', {
1356+ const body = typeof getBody === 'function' ? getBody() : {};
1357+
1358+ /** @type {RequestInit} */
1359+ const fetchOptions = {
13281360 method: 'POST',
13291361 headers: getRequestHeaders(),
13301362 body: JSON.stringify(body || {}),
1331- siliconflow_endpoint: oai_settings.siliconflow_endpoint,
1363+ };
1332- }),
1333- });
13341364
1365+ const response = await fetch(url, fetchOptions);
13351366 if (!response.ok) {
13361367 throw new Error(`HTTP ${response.status}`);
13371368 }
13381369
13391370 /** @type {Array<any>} */
13401371 const data = await response.json();
13411372 constlet models = Array.isArray(data) ? data : [];
1342- populateSiliconFlowModelSelect(models);
1373+ if (filter) {
1343- } catch (err) {
1374+ models = filter(models);
1344- console.warn('SiliconFlow models fetch failed', err);
1345- populateSiliconFlowModelSelect([]);
1346- }
13471375 }
13481376
1349-function populateSiliconFlowModelSelect(models) {
1377+ populateSelect(models);
1350- const select = $('#vectors_siliconflow_model');
1378+ } catch (err) {
1351- select.empty();
1379+ console.warn(`${source} models fetch failed`, err);
1352- for (const m of models) {
1380+ populateSelect([]);
1353- const option = document.createElement('option');
1354- option.value = m.id;
1355- option.text = m.id;
1356- select.append(option);
1357- }
1358- if (!settings.siliconflow_model && models.length) {
1359- settings.siliconflow_model = models[0].id;
13601381 }
1361- $('#vectors_siliconflow_model').val(settings.siliconflow_model);
13621382}
13631383
13641384/**
@@ -1496,10 +1516,11 @@ async function onViewStatsClick() {
14961516 { timeOut: 10000, escapeHtml: false },
14971517 );
14981518
1519+ $('#chat .mes.vectorized').removeClass('vectorized');
14991520 const chat = getContext().chat;
15001521 for (const message of chat) {
15011522 if (hashesInCollection.includes(getStringHash(substituteParams(message.mes)))) {
15021523 const messageElement = $(`#chat .mes[mesid="${chat.indexOf(message)}"]`);
15031524 messageElement.addClass('vectorized');
15041525 }
15051526 }
@@ -1704,7 +1725,7 @@ async function activateWorldInfo(chat) {
17041725 await eventSource.emit(event_types.WORLDINFO_FORCE_ACTIVATE, activatedEntries);
17051726}
17061727
17071728jQuery(export async function init() => {
17081729 if (!extension_settings.vectors) {
17091730 extension_settings.vectors = settings;
17101731 }
@@ -1726,6 +1747,11 @@ jQuery(async () => {
17261747 saveSettingsDebounced();
17271748 toggleSettings();
17281749 });
1750+ $('#vectors_keep_hidden').prop('checked', settings.keep_hidden).on('input', () => {
1751+ settings.keep_hidden = !!$('#vectors_keep_hidden').prop('checked');
1752+ Object.assign(extension_settings.vectors, settings);
1753+ saveSettingsDebounced();
1754+ });
17291755 $('#vectors_enabled_files').prop('checked', settings.enabled_files).on('input', () => {
17301756 settings.enabled_files = $('#vectors_enabled_files').prop('checked');
17311757 Object.assign(extension_settings.vectors, settings);
@@ -1778,6 +1804,11 @@ jQuery(async () => {
17781804 Object.assign(extension_settings.vectors, settings);
17791805 saveSettingsDebounced();
17801806 });
1807+ $('#vectors_workers_ai_model').val(settings.workers_ai_model).on('change', () => {
1808+ settings.workers_ai_model = String($('#vectors_workers_ai_model').val());
1809+ Object.assign(extension_settings.vectors, settings);
1810+ saveSettingsDebounced();
1811+ });
17811812 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {
17821813 settings.openrouter_model = String($('#vectors_openrouter_model').val());
17831814 Object.assign(extension_settings.vectors, settings);
@@ -1888,6 +1919,20 @@ jQuery(async () => {
18881919 saveSettingsDebounced();
18891920 });
18901921
1922+ $('#vectors_summary_retries').val(settings.summary_retries).on('input', () => {
1923+ const parsed = Number($('#vectors_summary_retries').val());
1924+ settings.summary_retries = Number.isFinite(parsed) && parsed >= 1 ? Math.floor(parsed) : 1;
1925+ Object.assign(extension_settings.vectors, settings);
1926+ saveSettingsDebounced();
1927+ });
1928+
1929+ $('#vectors_summary_threshold').val(settings.summary_threshold).on('input', () => {
1930+ const parsed = Number($('#vectors_summary_threshold').val());
1931+ settings.summary_threshold = Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 0;
1932+ Object.assign(extension_settings.vectors, settings);
1933+ saveSettingsDebounced();
1934+ });
1935+
18911936 $('#vectors_message_chunk_size').val(settings.message_chunk_size).on('input', () => {
18921937 settings.message_chunk_size = Number($('#vectors_message_chunk_size').val());
18931938 Object.assign(extension_settings.vectors, settings);
@@ -2310,4 +2355,4 @@ jQuery(async () => {
23102355 }
23112356 await purgeAllVectorIndexes();
23122357 });
23132358});
public/scripts/extensions/vectors/manifest.json+4 -1
@@ -10,5 +10,8 @@
1010 "css": "style.css",
1111 "author": "Cohee#1207",
1212 "version": "1.0.0",
1313 "homePage": "https://github.com/SillyTavern/SillyTavern",
14+ "hooks": {
15+ "activate": "init"
16+ }
1417}
public/scripts/extensions/vectors/settings.html+25 -0
@@ -11,6 +11,7 @@
1111 </label>
1212 <select id="vectors_source" class="text_pole">
1313 <option value="chutes">Chutes</option>
14+ <option value="workers_ai">Cloudflare Workers AI</option>
1415 <option value="cohere">Cohere</option>
1516 <option value="electronhub">Electron Hub</option>
1617 <option value="extras">Extras (deprecated)</option>
@@ -205,6 +206,16 @@
205206 </i>
206207 </div>
207208
209+ <div class="flex-container flexFlowColumn" id="workers_ai_vectorsModel">
210+ <label for="vectors_workers_ai_model" data-i18n="Vectorization Model">
211+ Vectorization Model
212+ </label>
213+ <select id="vectors_workers_ai_model" class="text_pole"></select>
214+ <i data-i18n="Hint: Set your Workers AI API key and Account ID in API Connections.">
215+ Hint: Set your Workers AI API key and Account ID in API Connections.
216+ </i>
217+ </div>
218+
208219 <div class="flex-container marginTopBot5">
209220 <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance.">
210221 <label for="vectors_query">
@@ -408,6 +419,10 @@
408419 <hr>
409420
410421 <div id="vectors_chats_settings">
422+ <label class="checkbox_label expander" for="vectors_keep_hidden" title="Keep hidden (system) messages in the vector index and include them in query results.">
423+ <input id="vectors_keep_hidden" type="checkbox" class="checkbox">
424+ <span data-i18n="Include hidden messages">Include hidden messages</span>
425+ </label>
411426 <div id="vectors_advanced_settings">
412427 <label for="vectors_template" data-i18n="Injection Template">
413428 Injection Template
@@ -478,6 +493,16 @@
478493 <label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label>
479494 <small data-i18n="Only used when Main API or WebLLM Extension is selected.">Only used when Main API or WebLLM Extension is selected.</small>
480495 <textarea id="vectors_summary_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation."></textarea>
496+
497+ <label for="vectors_summary_retries" title="Number of attempts per message before aborting vectorization.">
498+ <span data-i18n="Summarization retries per message">Summarization retries per message</span>
499+ </label>
500+ <input id="vectors_summary_retries" type="number" class="text_pole widthUnset" min="1" max="10" step="1" />
501+
502+ <label for="vectors_summary_threshold" title="Messages shorter than this (in characters) are embedded as-is without summarization. Set to 0 to always summarize.">
503+ <span data-i18n="Summarization min length (chars)">Summarization min length (chars)</span>
504+ </label>
505+ <input id="vectors_summary_threshold" type="number" class="text_pole widthUnset" min="0" step="1" />
481506 </div>
482507 </div>
483508 <small data-i18n="Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.">
public/scripts/filters.js+11 -1
@@ -289,6 +289,16 @@ export class FilterHelper {
289289 return this.filterDataByState(data, state, isFolder);
290290 }
291291
292+ /**
293+ * Filters an array of entities based on a tri-state filter value.
294+ * SELECTED keeps entities where filterFunc returns true; EXCLUDED removes them; UNDEFINED returns data unchanged.
295+ * @param {any[]} data The data to filter
296+ * @param {FilterState|string} state The tri-state filter value (SELECTED, EXCLUDED, or UNDEFINED)
297+ * @param {Function} filterFunc A predicate function applied to each entity
298+ * @param {object} [options] Options object
299+ * @param {boolean} [options.includeFolders=false] If true, entities with type 'tag' always pass through
300+ * @returns {any[]} The filtered data
301+ */
292302 filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) {
293303 if (isFilterState(state, FILTER_STATES.SELECTED)) {
294304 return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag'));
@@ -414,7 +424,7 @@ export class FilterHelper {
414424 typeScores.set(uid, score);
415425 }
416426 this.scoreCache.set(type, typeScores);
417427 console.debug('search scores chachedcached', type, typeScores);
418428 }
419429
420430 /**
public/scripts/i18n.js+2 -1
@@ -139,7 +139,8 @@ async function getLocaleData(language) {
139139function findLang(language) {
140140 const supportedLang = langs.find(x => x.lang === language);
141141
142- if (!supportedLang && language !== 'en') {
142+ const isEn = language.startsWith('en'); // includes 'en', and more specific locales like 'en-us', 'en-au', etc
143+ if (!supportedLang && !isEn) {
143144 console.warn(`Unsupported language: ${language}`);
144145 }
145146 return supportedLang;
public/scripts/instruct-mode.js+1 -1
@@ -597,7 +597,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
597597 function getSequence() {
598598 // User impersonation prompt
599599 if (isImpersonate) {
600600 return instruct.last_input_sequence || instruct.input_sequence;
601601 }
602602
603603 // Neutral / system / quiet prompt
public/scripts/itemized-prompts.js+5 -0
@@ -31,6 +31,8 @@ export async function loadItemizedPrompts(chatId) {
3131 if (!itemizedPrompts) {
3232 itemizedPrompts = [];
3333 }
34+
35+ await eventSource.emit(event_types.ITEMIZED_PROMPTS_LOADED, { chatId: chatId });
3436 } catch {
3537 console.log('Error loading itemized prompts for chat', chatId);
3638 itemizedPrompts = [];
@@ -48,6 +50,7 @@ export async function saveItemizedPrompts(chatId) {
4850 }
4951
5052 await promptStorage.setItem(chatId, itemizedPrompts);
53+ await eventSource.emit(event_types.ITEMIZED_PROMPTS_SAVED, { chatId: chatId });
5154 } catch {
5255 console.log('Error saving itemized prompts for chat', chatId);
5356 }
@@ -84,6 +87,7 @@ export async function deleteItemizedPrompts(chatId) {
8487 }
8588
8689 await promptStorage.removeItem(chatId);
90+ await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { chatId: chatId, all: false });
8791 } catch {
8892 console.log('Error deleting itemized prompts for chat', chatId);
8993 }
@@ -96,6 +100,7 @@ export async function clearItemizedPrompts() {
96100 try {
97101 await promptStorage.clear();
98102 itemizedPrompts = [];
103+ await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { all: true });
99104 } catch {
100105 console.log('Error clearing itemized prompts');
101106 }
public/scripts/kai-settings.js+1 -0
@@ -202,6 +202,7 @@ export function getKoboldGenerationData(finalPrompt, settings, maxLength, maxCon
202202 mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined,
203203 use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined,
204204 grammar: (kai_flags.can_use_grammar || isHorde) ? substituteParams(kai_settings.grammar) : undefined,
205+ grammar_retain_state: (kai_flags.can_use_grammar && !!isContinue) ? true : undefined,
205206 sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined,
206207 api_server: kai_settings.api_server,
207208 };
public/scripts/loader.js+1 -0
@@ -28,6 +28,7 @@ export function showLoader() {
2828
2929 // Create a blocking loader with no toast (matches old behavior)
3030 legacyLoaderHandle = loader.show({
31+ slug: 'legacy-loader',
3132 blocking: true,
3233 toastMode: loader.ToastMode.NONE,
3334 });
public/scripts/macros.js+7 -6
@@ -58,10 +58,11 @@ export class MacrosParser {
5858 *
5959 * @param {string} method
6060 * @param {string} replacement
61+ * @param {IArguments} [methodArgs=null]
6162 * @returns {void}
6263 */
6364 static #logDeprecated(method, replacement, methodArgs = null) {
6465 console.warn(`[DEPRECATED] MacrosParser.${method} is deprecated and will be removed in a future version. Use ${replacement} instead. Arguments:`, (methodArgs ?? 'none'));
6566 }
6667
6768 /**
@@ -155,7 +156,7 @@ export class MacrosParser {
155156 * @returns {string|MacroFunction|undefined} The macro value
156157 */
157158 static get(key) {
158159 MacrosParser.#logDeprecated('get', 'macros.registry.getMacro (from scripts/macros/macro-system.js)', arguments);
159160 return MacrosParser.#macros.get(key);
160161 }
161162
@@ -165,7 +166,7 @@ export class MacrosParser {
165166 * @returns {boolean} True if the macro is registered, false otherwise
166167 */
167168 static has(key) {
168169 MacrosParser.#logDeprecated('has', 'macros.registry.hasMacro (from scripts/macros/macro-system.js)', arguments);
169170 if (power_user.experimental_macro_engine) {
170171 return macroSystem.registry.hasMacro(key);
171172 }
@@ -180,7 +181,7 @@ export class MacrosParser {
180181 * @param {string} [description] Optional description of the macro
181182 */
182183 static registerMacro(key, value, description = '') {
183184 MacrosParser.#logDeprecated('registerMacro', 'macros.registry.registerMacro (from scripts/macros/macro-system.js) or substituteParams({ dynamicMacros })', arguments);
184185 if (typeof key !== 'string') {
185186 throw new Error('Macro key must be a string');
186187 }
@@ -223,7 +224,7 @@ export class MacrosParser {
223224 * @param {string} key Macro name (key)
224225 */
225226 static unregisterMacro(key) {
226227 MacrosParser.#logDeprecated('unregisterMacro', 'macros.registry.unregisterMacro (from scripts/macros/macro-system.js)', arguments);
227228 if (typeof key !== 'string') {
228229 throw new Error('Macro key must be a string');
229230 }
public/scripts/openai.js+573 -320
@@ -47,6 +47,7 @@ import { SECRET_KEYS, secret_state, writeSecret } from './secrets.js';
4747
4848import { getEventSourceStream } from './sse-stream.js';
4949import {
50+ clamp,
5051 createThumbnail,
5152 delay,
5253 download,
@@ -79,7 +80,7 @@ import { t } from './i18n.js';
7980import { ToolManager } from './tool-calling.js';
8081import { accountStorage } from './util/AccountStorage.js';
8182import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js';
8283import { syncNanoGptProvidersForModel, syncOpenRouterProvidersForModel, updateNanoGptProvidersWarning, updateOpenRouterProvidersWarning } from './textgen-models.js';
8384
8485export {
8586 openai_messages_count,
@@ -196,6 +197,8 @@ export const chat_completion_sources = {
196197 AZURE_OPENAI: 'azure_openai',
197198 ZAI: 'zai',
198199 SILICONFLOW: 'siliconflow',
200+ WORKERS_AI: 'workers_ai',
201+ MINIMAX: 'minimax',
199202};
200203
201204const character_names_behavior = {
@@ -256,6 +259,7 @@ export const tool_reasoning_modes = {
256259// Providers that support interleaved reasoning forwarding in tool-call chains.
257260const interleaved_reasoning_providers = [
258261 chat_completion_sources.OPENROUTER,
262+ chat_completion_sources.CUSTOM,
259263];
260264
261265export const ZAI_ENDPOINT = {
@@ -268,6 +272,11 @@ export const SILICONFLOW_ENDPOINT = {
268272 CN: 'cn',
269273};
270274
275+export const MINIMAX_ENDPOINT = {
276+ GLOBAL: 'global',
277+ CN: 'cn',
278+};
279+
271280const sensitiveFields = [
272281 'reverse_proxy',
273282 'proxy_password',
@@ -279,6 +288,7 @@ const sensitiveFields = [
279288 'vertexai_express_project_id',
280289 'azure_base_url',
281290 'azure_deployment_name',
291+ 'workers_ai_account_id',
282292];
283293
284294/**
@@ -296,12 +306,12 @@ export const settingsToUpdate = {
296306 min_p: ['#min_p_openai', 'min_p_openai', false, false],
297307 repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false],
298308 max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false],
309+ group_models: ['#cc_group_models', 'group_models', true, true],
310+ sort_models: ['#cc_sort_models', 'sort_models', false, true],
299311 openai_model: ['#model_openai_select', 'openai_model', false, true],
300312 claude_model: ['#model_claude_select', 'claude_model', false, true],
301313 openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true],
302314 openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true],
303- openrouter_group_models: ['#openrouter_group_models', 'openrouter_group_models', false, true],
304- openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false, true],
305315 openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true],
306316 openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true],
307317 openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true],
@@ -313,13 +323,14 @@ export const settingsToUpdate = {
313323 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
314324 groq_model: ['#model_groq_select', 'groq_model', false, true],
315325 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],
316- chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true],
317326 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],
318327 siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true],
328+ minimax_model: ['#model_minimax_select', 'minimax_model', false, true],
329+ minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true],
319330 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
320- electronhub_sort_models: ['#electronhub_sort_models', 'electronhub_sort_models', false, true],
321- electronhub_group_models: ['#electronhub_group_models', 'electronhub_group_models', false, true],
322331 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],
332+ nanogpt_provider: ['#nanogpt_provider', 'nanogpt_provider', false, true],
333+ nanogpt_payg_override: ['#nanogpt_payg_override', 'nanogpt_payg_override', true, true],
323334 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
324335 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],
325336 xai_model: ['#model_xai_select', 'xai_model', false, true],
@@ -337,6 +348,8 @@ export const settingsToUpdate = {
337348 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],
338349 zai_model: ['#model_zai_select', 'zai_model', false, true],
339350 zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true],
351+ workers_ai_model: ['#model_workers_ai_select', 'workers_ai_model', false, true],
352+ workers_ai_account_id: ['#workers_ai_account_id', 'workers_ai_account_id', false, true],
340353 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
341354 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
342355 names_behavior: ['#names_behavior', 'names_behavior', false, false],
@@ -369,6 +382,7 @@ export const settingsToUpdate = {
369382 continue_prefill: ['#continue_prefill', 'continue_prefill', true, false],
370383 continue_postfix: ['#continue_postfix', 'continue_postfix', false, false],
371384 function_calling: ['#openai_function_calling', 'function_calling', true, false],
385+ tool_call_recurse_limit: ['#tool_call_recurse_limit', 'tool_call_recurse_limit', false, false],
372386 show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false],
373387 reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false],
374388 verbosity: ['#openai_verbosity', 'verbosity', false, false],
@@ -413,6 +427,8 @@ const default_settings = {
413427 group_nudge_prompt: default_group_nudge_prompt,
414428 scenario_format: default_scenario_format,
415429 personality_format: default_personality_format,
430+ sort_models: 'alphabetically',
431+ group_models: false,
416432 openai_model: 'gpt-4-turbo',
417433 claude_model: 'claude-sonnet-4-5',
418434 google_model: 'gemini-2.5-pro',
@@ -423,14 +439,15 @@ const default_settings = {
423439 perplexity_model: 'sonar-pro',
424440 groq_model: 'llama-3.3-70b-versatile',
425441 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',
426- chutes_sort_models: 'alphabetically',
427442 siliconflow_model: 'deepseek-ai/DeepSeek-V3',
428443 siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL,
444+ minimax_model: 'MiniMax-M2.7',
445+ minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL,
429446 electronhub_model: 'gpt-4o-mini',
430- electronhub_sort_models: 'alphabetically',
431- electronhub_group_models: false,
432447 nanogpt_model: 'gpt-4o-mini',
433448 deepseek_modelnanogpt_provider: 'deepseek-chat',
449+ nanogpt_payg_override: false,
450+ deepseek_model: 'deepseek-v4-flash',
434451 aimlapi_model: 'chatgpt-4o-latest',
435452 xai_model: 'grok-3-beta',
436453 pollinations_model: 'openai',
@@ -439,6 +456,8 @@ const default_settings = {
439456 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
440457 zai_model: 'glm-4.6',
441458 zai_endpoint: ZAI_ENDPOINT.COMMON,
459+ workers_ai_model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
460+ workers_ai_account_id: '',
442461 azure_base_url: '',
443462 azure_deployment_name: '',
444463 azure_api_version: '2024-02-15-preview',
@@ -450,8 +469,6 @@ const default_settings = {
450469 custom_include_headers: '',
451470 openrouter_model: openrouter_website_model,
452471 openrouter_use_fallback: false,
453- openrouter_group_models: false,
454- openrouter_sort_models: 'alphabetically',
455472 openrouter_providers: [],
456473 openrouter_quantizations: [],
457474 openrouter_allow_fallbacks: true,
@@ -474,6 +491,7 @@ const default_settings = {
474491 bypass_status_check: false,
475492 continue_prefill: false,
476493 function_calling: false,
494+ tool_call_recurse_limit: 5,
477495 names_behavior: character_names_behavior.DEFAULT,
478496 continue_postfix: continue_postfix_types.SPACE,
479497 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
@@ -597,8 +615,10 @@ function setOpenAIMessages(chat) {
597615 const originApi = chat[j]?.extra?.api;
598616 const originModel = chat[j]?.extra?.model;
599617 const isSameModel = originApi === currentApi && originModel === currentModel;
600- const signature = isSameModel ? chat[j]?.extra?.reasoning_signature : null;
618+ // In group chats, only include reasoning from the currently generating character
601619 const reasoningisOtherGroupMember = isSameModelselected_group ?&& String(chat[j]?.extra?.reasoning ?? '')name :!== ''name2;
620+ const signature = isSameModel && !isOtherGroupMember ? chat[j]?.extra?.reasoning_signature : null;
621+ const reasoning = isSameModel && !isOtherGroupMember ? String(chat[j]?.extra?.reasoning ?? '') : '';
602622
603623 // Remove reasoning metadata from invocations if the API/model don't match
604624 if (Array.isArray(invocations) && invocations.length > 0) {
@@ -1013,8 +1033,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
10131033 const clone = structuredClone(invocation);
10141034 if (!reasoningIsEligible) {
10151035 delete clone.reasoning;
10161036 } else if (previousAssistantReasoning && !clone.reasoning) {
10171037 // PreferFall currentlyback editableto adjacent assistant-text reasoning basedonly onwhen forwardingthe modeinvocation overhas invocationnone snapshotof its own.
10181038 clone.reasoning = previousAssistantReasoning;
10191039 }
10201040 return clone;
@@ -1703,6 +1723,8 @@ export function getChatCompletionModel(settings = null) {
17031723 return settings.groq_model;
17041724 case chat_completion_sources.SILICONFLOW:
17051725 return settings.siliconflow_model;
1726+ case chat_completion_sources.MINIMAX:
1727+ return settings.minimax_model;
17061728 case chat_completion_sources.ELECTRONHUB:
17071729 return settings.electronhub_model;
17081730 case chat_completion_sources.CHUTES:
@@ -1727,6 +1749,8 @@ export function getChatCompletionModel(settings = null) {
17271749 return settings.azure_openai_model;
17281750 case chat_completion_sources.ZAI:
17291751 return settings.zai_model;
1752+ case chat_completion_sources.WORKERS_AI:
1753+ return settings.workers_ai_model;
17301754 default:
17311755 console.error(`Unknown chat completion source: ${source}`);
17321756 return '';
@@ -1871,6 +1895,33 @@ function getChutesModelTemplate(option) {
18711895 `));
18721896}
18731897
1898+function calculateChutesCost() {
1899+ if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) {
1900+ return;
1901+ }
1902+
1903+ let cost = 'Unknown';
1904+ const model = model_list.find(x => x.id === oai_settings.chutes_model);
1905+
1906+ if (model?.pricing) {
1907+ const outputPrice = model.pricing?.output;
1908+ const inputPrice = model.pricing?.input;
1909+
1910+ if (outputPrice !== undefined && inputPrice !== undefined) {
1911+ const outputCost = Number(outputPrice / 1000000);
1912+ const inputCost = Number(inputPrice / 1000000);
1913+ const outputTokens = oai_settings.openai_max_tokens;
1914+ const inputTokens = (oai_settings.openai_max_context - outputTokens);
1915+ const totalCost = (outputCost * outputTokens) + (inputCost * inputTokens);
1916+ if (!isNaN(totalCost)) {
1917+ cost = '$' + totalCost.toFixed(4);
1918+ }
1919+ }
1920+ }
1921+
1922+ $('#chutes_max_prompt_cost').text(cost);
1923+}
1924+
18741925function getNanoGptModelTemplate(option) {
18751926 const model = model_list.find(x => x.id === option?.element?.value);
18761927
@@ -1880,10 +1931,9 @@ function getNanoGptModelTemplate(option) {
18801931
18811932 const inputPrice = model.pricing?.prompt;
18821933 const outputPrice = model.pricing?.completion;
1883-
18841934 let price = 'Unknown';
1935+
18851936 if (inputPrice !== undefined && outputPrice !== undefined) {
1886- // Check if both prices are 0 (free model)
18871937 if (inputPrice === 0 && outputPrice === 0) {
18881938 price = 'Free';
18891939 } else {
@@ -1891,40 +1941,61 @@ function getNanoGptModelTemplate(option) {
18911941 }
18921942 }
18931943
1944+ const visionIcon = model.capabilities?.vision ? '<i class="fa-solid fa-eye fa-sm" title="This model supports vision"></i>' : '';
1945+ const reasoningIcon = model.capabilities?.reasoning ? '<i class="fa-solid fa-brain fa-sm" title="This model supports reasoning"></i>' : '';
1946+ const toolCallsIcon = model.capabilities?.tool_calling ? '<i class="fa-solid fa-wrench fa-sm" title="This model supports tool calling"></i>' : '';
1947+
1948+ let subHtml = '';
1949+ const sub = model.subscription;
1950+
1951+ if (sub) {
1952+ if (sub.included) {
1953+ let titleText = 'Included in subscription';
1954+ let multiplierText = '';
1955+
1956+ if (sub.inputTokenMultiplier && sub.inputTokenMultiplier !== 1) {
1957+ multiplierText = ` (${sub.inputTokenMultiplier}x)`;
1958+ titleText += ` - Input Multiplier: ${sub.inputTokenMultiplier}x`;
1959+ }
1960+ subHtml = ` <small title="${titleText}"><i class="fa-solid fa-crown fa-sm"></i> Sub${multiplierText}</small>`;
1961+ } else if (sub.note) {
1962+ const safeNote = DOMPurify.sanitize(sub.note);
1963+ subHtml = ` <small title="${safeNote}"><i class="fa-solid fa-circle-info fa-sm"></i> Not in Sub</small>`;
1964+ }
1965+ }
1966+
1967+ const iconsContainer = document.createElement('span');
1968+ iconsContainer.insertAdjacentHTML('beforeend', visionIcon);
1969+ iconsContainer.insertAdjacentHTML('beforeend', reasoningIcon);
1970+ iconsContainer.insertAdjacentHTML('beforeend', toolCallsIcon);
1971+ iconsContainer.insertAdjacentHTML('beforeend', subHtml);
1972+
1973+ const capabilities = (iconsContainer.children.length) ? ` | ${iconsContainer.innerHTML}` : '';
1974+
18941975 const contextLength = model.context_length || 'Unknown';
1976+ const modelName = model.name || model.id;
18951977
18961978 return $((`
18971979 <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}">
18981980 <strong>${DOMPurify.sanitize(model.idmodelName)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities}
18991981 </div>
19001982 `));
19011983}
19021984
19031985function calculateChutesCostgetAimlapiModelTemplate(option) {
1904- if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) {
1986+ const model = model_list.find(x => x.id === option?.element?.value);
1905- return;
1906- }
1907-
1908- let cost = 'Unknown';
1909- const model = model_list.find(x => x.id === oai_settings.chutes_model);
1910-
1911- if (model?.pricing) {
1912- const outputPrice = model.pricing?.output;
1913- const inputPrice = model.pricing?.input;
19141987
1915- if (outputPrice !== undefined && inputPrice !== undefined) {
1988+ if (!option.id || !model) {
1916- const outputCost = Number(outputPrice / 1000000);
1989+ return option.text;
1917- const inputCost = Number(inputPrice / 1000000);
1918- const outputTokens = oai_settings.openai_max_tokens;
1919- const inputTokens = (oai_settings.openai_max_context - outputTokens);
1920- const totalCost = (outputCost * outputTokens) + (inputCost * inputTokens);
1921- if (!isNaN(totalCost)) {
1922- cost = '$' + totalCost.toFixed(4);
1923- }
1924- }
19251990 }
19261991
1927- $('#chutes_max_prompt_cost').text(cost);
1992+ const vendor = model.id.split('/')[0];
1993+
1994+ return $((`
1995+ <div class="flex-container flexFlowColumn" title="${DOMPurify.sanitize(model.id)}">
1996+ <div><strong>${DOMPurify.sanitize(model.info?.name || model.name || model.id)}</strong> | ${vendor}</div>
1997+ </div>
1998+ `));
19281999}
19292000
19302001function saveModelList(data) {
@@ -1932,14 +2003,22 @@ function saveModelList(data) {
19322003 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));
19332004
19342005 if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
19352006 model_list = openRouterSortBysortModelsBy(model_list, oai_settings.openrouter_sort_modelssort_models, chat_completion_sources.OPENROUTER);
1936-
19372007 $('#model_openrouter_select').empty();
2008+ $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` }));
19382009
19392010 if (true === oai_settings.openrouter_group_modelsgroup_models) {
1940- appendOpenRouterOptions(openRouterGroupByVendor(model_list), oai_settings.openrouter_group_models);
2011+ groupModelsByVendor(model_list, chat_completion_sources.OPENROUTER).forEach((models, vendor) => {
2012+ const optgroup = $('<optgroup>').attr('label', vendor);
2013+ models.forEach((model) => {
2014+ optgroup.append($('<option>', { value: model.id, text: model.name }));
2015+ });
2016+ $('#model_openrouter_select').append(optgroup);
2017+ });
19412018 } else {
1942- appendOpenRouterOptions(model_list);
2019+ model_list.forEach((model) => {
2020+ $('#model_openrouter_select').append($('<option>', { value: model.id, text: model.name }));
2021+ });
19432022 }
19442023
19452024 $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change');
@@ -1979,13 +2058,26 @@ function saveModelList(data) {
19792058 }
19802059
19812060 if (oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI) {
2061+ model_list = model_list.filter(m => m.type === 'chat-completion');
2062+ model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.AIMLAPI);
19822063 $('#model_aimlapi_select').empty();
1983- const chatModels = model_list.filter(m => m.type === 'chat-completion');
19842064
1985- appendAimlapiOptions(aimlapiGroupByVendor(chatModels));
2065+ if (oai_settings.group_models) {
2066+ groupModelsByVendor(model_list, chat_completion_sources.AIMLAPI).forEach((models, vendor) => {
2067+ const optgroup = $('<optgroup>').attr('label', vendor);
2068+ models.forEach((model) => {
2069+ optgroup.append($('<option>', { value: model.id, text: model.info?.name || model.id }));
2070+ });
2071+ $('#model_aimlapi_select').append(optgroup);
2072+ });
2073+ } else {
2074+ model_list.forEach((model) => {
2075+ $('#model_aimlapi_select').append($('<option>', { value: model.id, text: model.info?.name || model.id }));
2076+ });
2077+ }
19862078
19872079 if (!oai_settings.aimlapi_model && chatModelsmodel_list.length > 0) {
19882080 oai_settings.aimlapi_model = chatModelsmodel_list[0].id;
19892081 }
19902082
19912083 $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change');
@@ -2008,13 +2100,22 @@ function saveModelList(data) {
20082100
20092101 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
20102102 model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions'));
2011-
2103+ model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.ELECTRONHUB);
2012- model_list = electronHubSortBy(model_list, oai_settings.electronhub_sort_models);
2013-
20142104 $('#model_electronhub_select').empty();
20152105
2016- const groupedList = oai_settings.electronhub_group_models ? electronHubGroupByVendor(model_list) : model_list;
2106+ if (oai_settings.group_models) {
2017- appendElectronHubOptions(groupedList, oai_settings.electronhub_group_models);
2107+ groupModelsByVendor(model_list, chat_completion_sources.ELECTRONHUB).forEach((models, vendor) => {
2108+ const optgroup = $('<optgroup>').attr('label', vendor);
2109+ models.forEach((model) => {
2110+ optgroup.append($('<option>', { value: model.id, text: model.name }));
2111+ });
2112+ $('#model_electronhub_select').append(optgroup);
2113+ });
2114+ } else {
2115+ model_list.forEach((model) => {
2116+ $('#model_electronhub_select').append($('<option>', { value: model.id, text: model.name }));
2117+ });
2118+ }
20182119
20192120 const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);
20202121 if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {
@@ -2026,15 +2127,21 @@ function saveModelList(data) {
20262127
20272128 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {
20282129 model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine'));
2029-
2130+ model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.CHUTES);
2030- model_list = chutesSortBy(model_list, oai_settings.chutes_sort_models);
2031-
20322131 $('#model_chutes_select').empty();
20332132
20342133 forif (const model of model_listoai_settings.group_models) {
2035- const option = $('<option>').val(model.id).text(model.id);
2134+ groupModelsByVendor(model_list, chat_completion_sources.CHUTES).forEach((models, vendor) => {
2036- option.attr('data-model', JSON.stringify(model));
2135+ const optgroup = $('<optgroup>').attr('label', vendor);
2037- $('#model_chutes_select').append(option);
2136+ models.forEach((model) => {
2137+ optgroup.append($('<option>', { value: model.id, text: model.id }));
2138+ });
2139+ $('#model_chutes_select').append(optgroup);
2140+ });
2141+ } else {
2142+ model_list.forEach((model) => {
2143+ $('#model_chutes_select').append($('<option>', { value: model.id, text: model.id }));
2144+ });
20382145 }
20392146
20402147 const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model);
@@ -2046,14 +2153,22 @@ function saveModelList(data) {
20462153 }
20472154
20482155 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
2156+ model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.NANOGPT);
20492157 $('#model_nanogpt_select').empty();
2158+
2159+ if (oai_settings.group_models) {
2160+ groupModelsByVendor(model_list, chat_completion_sources.NANOGPT).forEach((models, vendor) => {
2161+ const optgroup = $('<optgroup>').attr('label', vendor);
2162+ models.forEach((model) => {
2163+ optgroup.append($('<option>', { value: model.id, text: model.name || model.id }));
2164+ });
2165+ $('#model_nanogpt_select').append(optgroup);
2166+ });
2167+ } else {
20502168 model_list.forEach((model) => {
2051- $('#model_nanogpt_select').append(
2169+ $('#model_nanogpt_select').append($('<option>', { value: model.id, text: model.name || model.id }));
2052- $('<option>', {
2053- value: model.id,
2054- text: model.id,
2055- }));
20562170 });
2171+ }
20572172
20582173 const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model);
20592174 if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) {
@@ -2066,11 +2181,7 @@ function saveModelList(data) {
20662181 if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
20672182 $('#model_deepseek_select').empty();
20682183 model_list.forEach((model) => {
2069- $('#model_deepseek_select').append(
2184+ $('#model_deepseek_select').append($('<option>', { value: model.id, text: model.id }));
2070- $('<option>', {
2071- value: model.id,
2072- text: model.id,
2073- }));
20742185 });
20752186
20762187 const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);
@@ -2084,11 +2195,7 @@ function saveModelList(data) {
20842195 if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
20852196 $('#model_pollinations_select').empty();
20862197 model_list.forEach((model) => {
2087- $('#model_pollinations_select').append(
2198+ $('#model_pollinations_select').append($('<option>', { value: model.id, text: model.id }));
2088- $('<option>', {
2089- value: model.id,
2090- text: model.id,
2091- }));
20922199 });
20932200
20942201 const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model);
@@ -2193,6 +2300,24 @@ function saveModelList(data) {
21932300 $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change');
21942301 }
21952302
2303+ if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) {
2304+ $('#model_workers_ai_select').empty();
2305+ model_list.forEach((model) => {
2306+ $('#model_workers_ai_select').append(
2307+ $('<option>', {
2308+ value: model.id,
2309+ text: model.id,
2310+ }));
2311+ });
2312+
2313+ const selectedModel = model_list.find(model => model.id === oai_settings.workers_ai_model);
2314+ if (model_list.length > 0 && (!selectedModel || !oai_settings.workers_ai_model)) {
2315+ oai_settings.workers_ai_model = model_list[0].id;
2316+ }
2317+
2318+ $('#model_workers_ai_select').val(oai_settings.workers_ai_model).trigger('change');
2319+ }
2320+
21962321 if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) {
21972322 $('#model_cometapi_select').empty();
21982323
@@ -2259,181 +2384,134 @@ function saveModelList(data) {
22592384 }
22602385}
22612386
2262-function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
2387+/**
2263- $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` }));
2388+ * Sorts models by the specified property for the given source.
2264-
2389+ * @param {object[]} data - Array of model objects
2265- const appendOption = (model, parent = null) => {
2390+ * @param {string} property - Sort property ('alphabetically', 'context_length', 'pricing.prompt', 'pricing.completion')
2266- (parent || $('#model_openrouter_select')).append(
2391+ * @param {string} source - Chat Completion source (e.g., 'openrouter', 'chutes', 'electronhub', 'nanogpt')
2267- $('<option>', {
2392+ * @returns {object[]} Sorted array of model objects
2268- value: model.id,
2393+ */
2269- text: model.name,
2394+function sortModelsBy(data, property, source) {
2270- }));
2395+ switch (source) {
2271- };
2396+ case chat_completion_sources.OPENROUTER:
2272-
2273- if (groupModels) {
2274- model_list.forEach((models, vendor) => {
2275- const optgroup = $(`<optgroup label="${vendor}">`);
2276-
2277- models.forEach((model) => {
2278- appendOption(model, optgroup);
2279- });
2280-
2281- $('#model_openrouter_select').append(optgroup);
2282- });
2283- } else {
2284- model_list.forEach((model) => {
2285- appendOption(model);
2286- });
2287- }
2288-}
2289-
2290-const openRouterSortBy = (data, property = 'alphabetically') => {
22912397 return data.sort((a, b) => {
22922398 if (property === 'context_length') {
22932399 return (b.context_length || 0) - (a.context_length || 0);
22942400 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
22952401 return parseFloat(a.pricing?.prompt || 0) - parseFloat(b.pricing?.prompt || 0);
2402+ } else if (property === 'pricing.output' || property === 'pricing.completion') {
2403+ return parseFloat(a.pricing?.completion || 0) - parseFloat(b.pricing?.completion || 0);
22962404 } else {
2297- // Alphabetically
2405+ return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2298- return a?.name && b?.name && a.name.localeCompare(b.name);
22992406 }
23002407 });
2301-};
2408+ case chat_completion_sources.CHUTES:
2302-
2303-function openRouterGroupByVendor(array) {
2304- return array.reduce((acc, curr) => {
2305- const vendor = curr.id.split('/')[0];
2306-
2307- if (!acc.has(vendor)) {
2308- acc.set(vendor, []);
2309- }
2310-
2311- acc.get(vendor).push(curr);
2312-
2313- return acc;
2314- }, new Map());
2315-}
2316-
2317-function chutesSortBy(data, property = 'alphabetically') {
23182409 return data.sort((a, b) => {
23192410 if (property === 'context_length') {
23202411 return (b.context_length || 0) - (a.context_length || 0);
23212412 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
23222413 const return aPriceparseFloat(a.pricing?.input =|| 0) - parseFloat(ab.pricing?.input || 0);
2323- const bPrice = parseFloat(b.pricing?.input || 0);
2414+ } else if (property === 'pricing.output' || property === 'pricing.completion') {
2324- return aPrice - bPrice;
2415+ return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0);
2325- } else if (property === 'pricing.output') {
2326- const aPrice = parseFloat(a.pricing?.output || 0);
2327- const bPrice = parseFloat(b.pricing?.output || 0);
2328- return aPrice - bPrice;
23292416 } else {
23302417 return a?.id && b?.id &&? a.id.localeCompare(b.id) : 0;
23312418 }
23322419 });
2420+ case chat_completion_sources.ELECTRONHUB:
2421+ return data.sort((a, b) => {
2422+ if (property === 'context_length') {
2423+ return (b.tokens || 0) - (a.tokens || 0);
2424+ } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2425+ return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0);
2426+ } else if (property === 'pricing.output' || property === 'pricing.completion') {
2427+ return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0);
2428+ } else {
2429+ return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
23332430 }
2334-
2335-function appendElectronHubOptions(model_list, groupModels = false) {
2336- const appendOption = (model, parent = null) => {
2337- (parent || $('#model_electronhub_select')).append(
2338- $('<option>', {
2339- value: model.id,
2340- text: model.name,
2341- }));
2342- };
2343-
2344- if (groupModels) {
2345- model_list.forEach((models, vendor) => {
2346- const optgroup = $('<optgroup>').attr('label', vendor);
2347-
2348- models.forEach((model) => {
2349- appendOption(model, optgroup);
2350- });
2351-
2352- $('#model_electronhub_select').append(optgroup);
23532431 });
2432+ case chat_completion_sources.NANOGPT:
2433+ return data.sort((a, b) => {
2434+ if (property === 'context_length') {
2435+ return (b.context_length || 0) - (a.context_length || 0);
2436+ } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2437+ return parseFloat(a.pricing?.prompt || 0) - parseFloat(b.pricing?.prompt || 0);
2438+ } else if (property === 'pricing.output' || property === 'pricing.completion') {
2439+ return parseFloat(a.pricing?.completion || 0) - parseFloat(b.pricing?.completion || 0);
23542440 } else {
2355- model_list.forEach((model) => {
2441+ return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2356- appendOption(model);
2357- });
23582442 }
2359-}
2443+ });
2360-
2444+ case chat_completion_sources.AIMLAPI:
2361-function electronHubSortBy(data, property = 'alphabetically') {
23622445 return data.sort((a, b) => {
23632446 if (property === 'context_length') {
2364- return b.tokens - a.tokens;
2447+ return (b.info?.contextLength || 0) - (a.info?.contextLength || 0);
2365- } else if (property === 'pricing.input') {
2366- return parseFloat(a.pricing.input) - parseFloat(b.pricing.input);
2367- } else if (property === 'pricing.output') {
2368- return parseFloat(a.pricing.output) - parseFloat(b.pricing.output);
23692448 } else {
2370- return a?.name && b?.name && a.name.localeCompare(b.name);
2449+ // No pricing information on the API. Sort alphabetically by name.
2450+ return a?.info?.name && b?.info?.name ? a.info.name.localeCompare(b.info.name) : 0;
23712451 }
23722452 });
2453+ default:
2454+ return data;
2455+ }
23732456}
23742457
2375-function electronHubGroupByVendor(array) {
2458+/**
2459+ * Groups models by vendor for the given source. If not supported, returns a map with a single entry containing all models.
2460+ * @param {object[]} array Array of model objects
2461+ * @param {string} source Chat Completion source (e.g., 'openrouter')
2462+ * @returns {Map<string, object[]>} Map of vendor to array of models
2463+ */
2464+function groupModelsByVendor(array, source) {
2465+ switch (source) {
2466+ case chat_completion_sources.OPENROUTER:
23762467 return array.reduce((acc, curr) => {
23772468 const vendor = String(curr?.name || curr?.id || 'Other').split(':/')[0].trim() || 'Other';
2378-
23792469 if (!acc.has(vendor)) {
23802470 acc.set(vendor, []);
23812471 }
2382-
23832472 acc.get(vendor).push(curr);
2384-
23852473 return acc;
23862474 }, new Map());
2475+ case chat_completion_sources.ELECTRONHUB:
2476+ return array.reduce((acc, curr) => {
2477+ const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other';
2478+ if (!acc.has(vendor)) {
2479+ acc.set(vendor, []);
23872480 }
2388-
2481+ acc.get(vendor).push(curr);
2389-function aimlapiGroupByVendor(array) {
2482+ return acc;
2483+ }, new Map());
2484+ case chat_completion_sources.NANOGPT:
23902485 return array.reduce((acc, curr) => {
2391- const vendor = curr.info.developer;
2486+ const vendorPart = /\//.test(curr.id) ? curr.id.split('/')[0] : curr.id.split('-')[0];
2392-
2487+ const vendor = String(vendorPart?.trim()?.toLowerCase() || 'Other');
23932488 if (!acc.has(vendor)) {
23942489 acc.set(vendor, []);
23952490 }
2396-
23972491 acc.get(vendor).push(curr);
2398-
23992492 return acc;
24002493 }, new Map());
2494+ case chat_completion_sources.CHUTES:
2495+ return array.reduce((acc, curr) => {
2496+ const vendor = curr.id.split('/')[0];
2497+ if (!acc.has(vendor)) {
2498+ acc.set(vendor, []);
24012499 }
2402-
2500+ acc.get(vendor).push(curr);
2403-function appendAimlapiOptions(model_list) {
2501+ return acc;
2404- const appendOption = (model, parent = null) => {
2502+ }, new Map());
2405- (parent || $('#model_aimlapi_select')).append(
2503+ case chat_completion_sources.AIMLAPI:
2406- $('<option>', {
2504+ return array.reduce((acc, curr) => {
2407- value: model.id,
2505+ const vendor = curr.info?.developer || 'Other';
2408- text: model.info?.name || model.name || model.id,
2506+ if (!acc.has(vendor)) {
2409- }));
2507+ acc.set(vendor, []);
2410- };
2411-
2412- model_list.forEach((models, vendor) => {
2413- const optgroup = $(`<optgroup label="${vendor}">`);
2414-
2415- models.forEach((model) => {
2416- appendOption(model, optgroup);
2417- });
2418-
2419- $('#model_aimlapi_select').append(optgroup);
2420- });
24212508 }
2422-
2509+ acc.get(vendor).push(curr);
2423-function getAimlapiModelTemplate(option) {
2510+ return acc;
2424- const model = model_list.find(x => x.id === option?.element?.value);
2511+ }, new Map());
2425-
2512+ default:
2426- if (!option.id || !model) {
2513+ return new Map([['', array]]);
2427- return option.text;
24282514 }
2429-
2430- const vendor = model.id.split('/')[0];
2431-
2432- return $((`
2433- <div class="flex-container flexFlowColumn" title="${DOMPurify.sanitize(model.id)}">
2434- <div><strong>${DOMPurify.sanitize(model.info?.name || model.name || model.id)}</strong> | ${vendor}</div>
2435- </div>
2436- `));
24372515}
24382516
24392517/**
@@ -2459,6 +2537,7 @@ function getReasoningEffort(settings = null, model = null) {
24592537 chat_completion_sources.COMETAPI,
24602538 chat_completion_sources.ELECTRONHUB,
24612539 chat_completion_sources.CHUTES,
2540+ chat_completion_sources.DEEPSEEK,
24622541 ];
24632542
24642543 if (!reasoningEffortSources.includes(settings.chat_completion_source)) {
@@ -2466,6 +2545,36 @@ function getReasoningEffort(settings = null, model = null) {
24662545 }
24672546
24682547 function resolveReasoningEffort() {
2548+ if (settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
2549+ switch (settings.reasoning_effort) {
2550+ case reasoning_effort_types.auto:
2551+ return undefined;
2552+ case reasoning_effort_types.max:
2553+ return reasoning_effort_types.max;
2554+ default:
2555+ return reasoning_effort_types.high;
2556+ }
2557+ }
2558+
2559+ if (settings.chat_completion_source === chat_completion_sources.CUSTOM && /^koboldcpp\/(.+)$/.test(model)) {
2560+ switch (settings.reasoning_effort) {
2561+ case reasoning_effort_types.auto:
2562+ return undefined;
2563+ case reasoning_effort_types.min:
2564+ return 'minimal';
2565+ case reasoning_effort_types.low:
2566+ return 'low';
2567+ case reasoning_effort_types.medium:
2568+ return 'medium';
2569+ case reasoning_effort_types.high:
2570+ return 'high';
2571+ case reasoning_effort_types.max:
2572+ return 'xhigh';
2573+ default:
2574+ return settings.reasoning_effort;
2575+ }
2576+ }
2577+
24692578 switch (settings.reasoning_effort) {
24702579 case reasoning_effort_types.auto:
24712580 return undefined;
@@ -2474,9 +2583,16 @@ function getReasoningEffort(settings = null, model = null) {
24742583 return 'none';
24752584 }
24762585
24772586 returnif ([chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source) && /^gpt-5/.test(model) {
2478- ? reasoning_effort_types.min
2587+ if (/^gpt-5\.(4|5)/.test(model)) {
2479- : reasoning_effort_types.low;
2588+ return 'none';
2589+ }
2590+ if (/^gpt-5/.test(model)) {
2591+ return reasoning_effort_types.min;
2592+ }
2593+ }
2594+
2595+ return reasoning_effort_types.low;
24802596 case reasoning_effort_types.max:
24812597 return reasoning_effort_types.high;
24822598 default:
@@ -2604,7 +2720,8 @@ export async function createGenerationParameters(settings, model, type, messages
26042720 ];
26052721
26062722 const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model);
26072723 const streamisWorkersAIJsonMode = settings.stream_openai && typechat_completion_source !=== 'quiet'chat_completion_sources.WORKERS_AI && !isO1jsonSchema;
2724+ const stream = settings.stream_openai && type !== 'quiet' && !isO1 && !isWorkersAIJsonMode;
26082725
26092726 const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue'];
26102727 const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source);
@@ -2715,6 +2832,11 @@ export async function createGenerationParameters(settings, model, type, messages
27152832 generate_data.middleout = settings.openrouter_middleout;
27162833 }
27172834
2835+ if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
2836+ generate_data.nanogpt_provider = settings.nanogpt_provider;
2837+ generate_data.nanogpt_payg_override = settings.nanogpt_payg_override;
2838+ }
2839+
27182840 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {
27192841 const stopStringsLimit = 5;
27202842 generate_data.top_k = Number(settings.top_k_openai);
@@ -2815,6 +2937,24 @@ export async function createGenerationParameters(settings, model, type, messages
28152937 generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL;
28162938 }
28172939
2940+ if (settings.chat_completion_source === chat_completion_sources.MINIMAX) {
2941+ generate_data.minimax_endpoint = settings.minimax_endpoint || MINIMAX_ENDPOINT.GLOBAL;
2942+ // MiniMax requires temperature in (0.0, 1.0]; zero is rejected.
2943+ if (Number.isFinite(generate_data.temperature)) {
2944+ generate_data.temperature = clamp(generate_data.temperature, Number.EPSILON, 1.0);
2945+ }
2946+ }
2947+
2948+ if (settings.chat_completion_source === chat_completion_sources.WORKERS_AI) {
2949+ generate_data.workers_ai_account_id = settings.workers_ai_account_id;
2950+ generate_data.top_k = settings.top_k_openai > 0 ? Math.min(Number(settings.top_k_openai), 50) : undefined;
2951+ generate_data.repetition_penalty = Number(settings.repetition_penalty_openai);
2952+ generate_data.seed = settings.seed >= 1 ? Number(settings.seed) : undefined;
2953+ generate_data.top_p = Math.max(Number(settings.top_p_openai), 0.001);
2954+ delete generate_data.n;
2955+ delete generate_data.logit_bias;
2956+ }
2957+
28182958 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus
28192959 if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
28202960 generate_data.top_k = Number(settings.top_k_openai);
@@ -2870,7 +3010,7 @@ export async function createGenerationParameters(settings, model, type, messages
28703010 if (/gpt-5-chat-latest/.test(model)) {
28713011 delete generate_data.tools;
28723012 delete generate_data.tool_choice;
28733013 } else if (/gpt-5\.(1|2|3|4)/.test(model) && !/chat-latest/.test(model) && !generate_data.reasoning_effort) {
28743014 delete generate_data.frequency_penalty;
28753015 delete generate_data.presence_penalty;
28763016 delete generate_data.logit_bias;
@@ -3052,7 +3192,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
30523192 }
30533193 });
30543194 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
30553195 } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW, chat_completion_sources.CHUTES, chat_completion_sources.WORKERS_AI].includes(chat_completion_source)) {
30563196 if (show_thoughts) {
30573197 state.reasoning +=
30583198 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
@@ -4049,6 +4189,11 @@ function migrateChatCompletionSettings(settings) {
40494189 { oldKey: 'claude_use_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },
40504190 { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },
40514191 { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') },
4192+ { oldKey: 'deepseek_model', oldValue: /^deepseek-(chat|reasoner|coder)$/, newKey: 'deepseek_model', newValue: 'deepseek-v4-flash' },
4193+ { oldKey: 'openrouter_sort_models', oldValue: 'alphabetically', newKey: 'sort_models', newValue: 'alphabetically' },
4194+ { oldKey: 'openrouter_sort_models', oldValue: 'pricing.prompt', newKey: 'sort_models', newValue: 'pricing.prompt' },
4195+ { oldKey: 'openrouter_sort_models', oldValue: 'context_length', newKey: 'sort_models', newValue: 'context_length' },
4196+ { oldKey: 'openrouter_group_models', oldValue: true, newKey: 'group_models', newValue: true },
40524197 ];
40534198
40544199 for (const migration of migrateMap) {
@@ -4150,9 +4295,11 @@ function loadOpenAISettings(data, settings) {
41504295 setNamesBehaviorControls();
41514296 setContinuePostfixControls();
41524297 setToolReasoningControls();
4298+ ToolManager.RECURSE_LIMIT = oai_settings.tool_call_recurse_limit;
41534299
41544300 $('#openrouter_providers_chat').trigger('change');
41554301 $('#openrouter_quantizations_chat').trigger('change');
4302+ $('#nanogpt_provider').trigger('change');
41564303 $('#chat_completion_source').trigger('change');
41574304}
41584305
@@ -4215,6 +4362,7 @@ async function getStatusOpen() {
42154362 chat_completion_sources.VERTEXAI,
42164363 chat_completion_sources.PERPLEXITY,
42174364 chat_completion_sources.ZAI,
4365+ chat_completion_sources.MINIMAX,
42184366 ];
42194367 if (noValidateSources.includes(oai_settings.chat_completion_source)) {
42204368 let status = t`Key saved; press \"Test Message\" to verify.`;
@@ -4272,6 +4420,14 @@ async function getStatusOpen() {
42724420 data.siliconflow_endpoint = oai_settings.siliconflow_endpoint;
42734421 }
42744422
4423+ if (oai_settings.chat_completion_source === chat_completion_sources.MINIMAX) {
4424+ data.minimax_endpoint = oai_settings.minimax_endpoint;
4425+ }
4426+
4427+ if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) {
4428+ data.workers_ai_account_id = oai_settings.workers_ai_account_id;
4429+ }
4430+
42754431 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
42764432 if (canBypass) {
42774433 setOnlineStatus(t`Status check bypassed`);
@@ -4791,6 +4947,7 @@ function onSettingsPresetChange() {
47914947 $('#chat_completion_source').trigger('change');
47924948 $('#openrouter_providers_chat').trigger('change');
47934949 $('#openrouter_quantizations_chat').trigger('change');
4950+ $('#nanogpt_provider').trigger('change');
47944951 }
47954952
47964953 $('#openai_logit_bias_preset').trigger('change');
@@ -4801,41 +4958,102 @@ function onSettingsPresetChange() {
48014958 });
48024959}
48034960
4961+/**
4962+ * Get the maximum context size for the OpenAI model
4963+ * @param {string} value Model identifier
4964+ * @returns {number} Maximum context size in tokens
4965+ */
48044966function getMaxContextOpenAI(value) {
48054967 if (oai_settings.max_context_unlocked) {
48064968 return unlocked_max;
4807- } else if (value.startsWith('gpt-5.4')) {
4969+ }
4808- return max_1mil;
4970+
4809- } else if (value.startsWith('gpt-5')) {
4971+ /** @type {[RegExp, number][]} */
4810- return max_400k;
4972+ const contextMap = [
4811- } else if (value.includes('gpt-4.1')) {
4973+ [/^gpt-5\.[45]/, max_1mil],
4812- return max_1mil;
4974+ [/^gpt-5/, max_400k],
4813- } else if (value.includes('gpt-audio')) {
4975+ [/gpt-4\.1/, max_1mil],
4814- return max_128k;
4976+ [/gpt-audio/, max_128k],
4815- } else if (value.startsWith('o1')) {
4977+ [/^o1/, max_128k],
4978+ [/^o[34]/, max_200k],
4979+ [/chatgpt-4o-latest|gpt-4-turbo|gpt-4o|gpt-4-1106|gpt-4-0125|gpt-4-vision/, max_128k],
4980+ [/gpt-3\.5-turbo-1106/, max_16k],
4981+ [/^(gpt-4|gpt-4-0314|gpt-4-0613)$/, max_8k],
4982+ [/^(gpt-4-32k|gpt-4-32k-0314|gpt-4-32k-0613)$/, max_32k],
4983+ [/gpt-realtime/, max_32k],
4984+ [/^(gpt-3\.5-turbo-16k|gpt-3\.5-turbo-16k-0613)$/, max_16k],
4985+ [/^code-davinci-002$/, max_8k],
4986+ [/^(text-curie-001|text-babbage-001|text-ada-001)$/, max_2k],
4987+ [/gpt-3/, max_4k],
4988+ ];
4989+
4990+ for (const [regex, max] of contextMap) {
4991+ if (regex.test(value)) {
4992+ return max;
4993+ }
4994+ }
4995+
4996+ // Safe default for most modern models
48164997 return max_128k;
4817- } else if (value.startsWith('o4') || value.startsWith('o3')) {
4998+}
4818- return max_200k;
4999+
4819- } else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
5000+/**
5001+ * Get the maximum context size for Gemini models based on model identifier and optional model list.
5002+ * @param {string} model Model identifier
5003+ * @param {boolean} isUnlocked Whether context limits are unlocked
5004+ * @returns {number} Maximum context size in tokens
5005+ */
5006+function getGeminiMaxContext(model, isUnlocked) {
5007+ if (isUnlocked) {
5008+ return unlocked_max;
5009+ }
5010+
5011+ if (Array.isArray(model_list) && model_list.length > 0) {
5012+ const contextLength = model_list.find((record) => record.id === model)?.inputTokenLimit;
5013+ if (Number.isFinite(contextLength) && contextLength > 0) {
5014+ return contextLength;
5015+ }
5016+ }
5017+
5018+ /** @type {[RegExp, number][]} */
5019+ const contextMap = [
5020+ [/gemini-2\.5-flash-image/, max_32k],
5021+ [/gemini-3-pro-image/, max_64k],
5022+ [/gemini-(?:3[.\d]*|2\.(?:5|0))-(pro|flash)/, max_1mil],
5023+ [/(gemini-exp|learnlm-2\.0-flash|gemini-robotics)/, max_1mil],
5024+ [/gemma-3-27b-it/, max_128k],
5025+ [/gemma-3n-e4b-it/, max_8k],
5026+ [/gemma-3/, max_32k],
5027+ [/gemma-4/, max_256k],
5028+ ];
5029+
5030+ for (const [regex, max] of contextMap) {
5031+ if (regex.test(model)) {
5032+ return max;
5033+ }
5034+ }
5035+
48205036 return max_128k;
4821- } else if (value.includes('gpt-3.5-turbo-1106')) {
4822- return max_16k;
4823- } else if (['gpt-4', 'gpt-4-0314', 'gpt-4-0613'].includes(value)) {
4824- return max_8k;
4825- } else if (['gpt-4-32k', 'gpt-4-32k-0314', 'gpt-4-32k-0613'].includes(value)) {
4826- return max_32k;
4827- } else if (value.includes('gpt-realtime')) {
4828- return max_32k;
4829- } else if (['gpt-3.5-turbo-16k', 'gpt-3.5-turbo-16k-0613'].includes(value)) {
4830- return max_16k;
4831- } else if (value == 'code-davinci-002') {
4832- return max_8k;
4833- } else if (['text-curie-001', 'text-babbage-001', 'text-ada-001'].includes(value)) {
4834- return max_2k;
4835- } else {
4836- // default to gpt-3 (4095 tokens)
4837- return max_4k;
48385037}
5038+
5039+/**
5040+ * Get the maximum temperature for Gemini models based on model identifier and optional model list.
5041+ * @param {string} model Model identifier
5042+ * @returns {number} Maximum temperature for Gemini models
5043+ */
5044+function getGeminiMaxTemp(model) {
5045+ if (Array.isArray(model_list) && model_list.length > 0) {
5046+ const temp = model_list.find((record) => record.id === model)?.maxTemperature;
5047+ if (Number.isFinite(temp) && temp > 0) {
5048+ return temp;
5049+ }
5050+ }
5051+
5052+ if (/(vision|ultra|gemma)/.test(model)) {
5053+ return 1.0;
5054+ }
5055+
5056+ return 2.0;
48395057}
48405058
48415059/**
@@ -4921,6 +5139,7 @@ function getZaiMaxContext(model, isUnlocked) {
49215139 const contextMap = {
49225140 'glm-5.1': max_200k,
49235141 'glm-5-turbo': max_200k,
5142+ 'glm-5v-turbo': max_200k,
49245143 'glm-5': max_200k,
49255144 'glm-4.7': max_200k,
49265145 'glm-4.7-flash': max_200k,
@@ -5221,6 +5440,15 @@ async function onModelChange() {
52215440 oai_settings.siliconflow_model = value;
52225441 }
52235442
5443+ if ($(this).is('#model_minimax_select')) {
5444+ if (!value) {
5445+ console.debug('Null MiniMax model selected. Ignoring.');
5446+ return;
5447+ }
5448+ console.log('MiniMax model changed to', value);
5449+ oai_settings.minimax_model = value;
5450+ }
5451+
52245452 if ($(this).is('#model_electronhub_select')) {
52255453 if (!value || !hasModelsLoaded) {
52265454 console.debug('Null ElectronHub model selected. Ignoring.');
@@ -5247,6 +5475,7 @@ async function onModelChange() {
52475475
52485476 console.log('NanoGPT model changed to', value);
52495477 oai_settings.nanogpt_model = value;
5478+ syncNanoGptProvidersForModel(value, '#nanogpt_provider');
52505479 }
52515480
52525481 if ($(this).is('#model_deepseek_select')) {
@@ -5328,29 +5557,21 @@ async function onModelChange() {
53285557 oai_settings.zai_model = value;
53295558 }
53305559
5331- if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
5560+ if ($(this).is('#model_workers_ai_select')) {
53325561 if (oai_settings.max_context_unlocked!value || !hasModelsLoaded) {
5333- $('#openai_max_context').attr('max', max_2mil);
5562+ console.debug('Null Workers AI model selected. Ignoring.');
5334- } else if (value.includes('gemini-2.5-flash-image')) {
5563+ return;
5335- $('#openai_max_context').attr('max', max_32k);
5564+ }
5336- } else if (value.includes('gemini-3-pro-image')) {
5565+ console.log('Workers AI model changed to', value);
5337- $('#openai_max_context').attr('max', max_64k);
5566+ oai_settings.workers_ai_model = value;
5338- } else if (/gemini-3[.\d]*-(pro|flash)/.test(value) || /gemini-2.5-(pro|flash)/.test(value) || /gemini-2.0-(pro|flash)/.test(value)) {
5339- $('#openai_max_context').attr('max', max_1mil);
5340- } else if (value.includes('gemini-exp') || value.includes('learnlm-2.0-flash') || value.includes('gemini-robotics')) {
5341- $('#openai_max_context').attr('max', max_1mil);
5342- } else if (value.includes('gemma-3-27b-it')) {
5343- $('#openai_max_context').attr('max', max_128k);
5344- } else if (value.includes('gemma-3n-e4b-it')) {
5345- $('#openai_max_context').attr('max', max_8k);
5346- } else if (value.includes('gemma-3')) {
5347- $('#openai_max_context').attr('max', max_32k);
5348- } else {
5349- $('#openai_max_context').attr('max', max_32k);
53505567 }
5351- let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0;
5568+
5352- oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai);
5569+ if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
5353- $('#temp_openai').attr('max', makersuite_max_temp).val(oai_settings.temp_openai).trigger('input');
5570+ const contextSize = getGeminiMaxContext(value, oai_settings.max_context_unlocked);
5571+ const maxTemp = getGeminiMaxTemp(value);
5572+ $('#openai_max_context').attr('max', contextSize);
5573+ oai_settings.temp_openai = Math.min(maxTemp, oai_settings.temp_openai);
5574+ $('#temp_openai').attr('max', maxTemp).val(oai_settings.temp_openai).trigger('input');
53545575 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
53555576 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
53565577 }
@@ -5383,7 +5604,7 @@ async function onModelChange() {
53835604 if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
53845605 if (oai_settings.max_context_unlocked) {
53855606 $('#openai_max_context').attr('max', unlocked_max);
53865607 } else if (/^claude-(sonnet-4-5|sonnet-4-6|opus-4-6|opus-4-7)/.test(value)) {
53875608 $('#openai_max_context').attr('max', max_1mil);
53885609 } else if (/^claude-(3|opus|haiku|sonnet)/.test(value)) {
53895610 $('#openai_max_context').attr('max', max_200k);
@@ -5533,19 +5754,27 @@ async function onModelChange() {
55335754 }
55345755
55355756 if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
5757+ const maxContext = oai_settings.max_context_unlocked ? unlocked_max : max_1mil;
5758+ $('#openai_max_context').attr('max', maxContext);
5759+ oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5760+ $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5761+ $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5762+ }
5763+
5764+ if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) {
55365765 if (oai_settings.max_context_unlocked) {
55375766 $('#openai_max_context').attr('max', unlocked_max);
5538- } else if (['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) {
5539- $('#openai_max_context').attr('max', max_128k);
5540- } else if (oai_settings.deepseek_model == 'deepseek-coder') {
5541- $('#openai_max_context').attr('max', max_16k);
55425767 } else {
5543- $('#openai_max_context').attr('max', max_64k);
5768+ const model = model_list.find(m => m.id === oai_settings.workers_ai_model);
5769+ const ctxProp = Array.isArray(model?.properties) && model.properties.find(p => p.property_id === 'context_window');
5770+ const contextLength = ctxProp ? Number(ctxProp.value) : max_8k;
5771+ $('#openai_max_context').attr('max', contextLength || max_8k);
55445772 }
5545-
55465773 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
55475774 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5548- $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5775+ const workersAiMaxTemp = 5.0;
5776+ oai_settings.temp_openai = Math.min(workersAiMaxTemp, oai_settings.temp_openai);
5777+ $('#temp_openai').attr('max', workersAiMaxTemp).val(oai_settings.temp_openai).trigger('input');
55495778 }
55505779
55515780 if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) {
@@ -5637,6 +5866,15 @@ async function onModelChange() {
56375866 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
56385867 }
56395868
5869+ if (oai_settings.chat_completion_source === chat_completion_sources.MINIMAX) {
5870+ const maxContext = oai_settings.minimax_model === 'M2-her' ? 65536 : 204800;
5871+ $('#openai_max_context').attr('max', maxContext);
5872+ oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5873+ $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5874+ oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
5875+ $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
5876+ }
5877+
56405878 if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
56415879 const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked);
56425880 $('#openai_max_context').attr('max', maxContext);
@@ -5653,18 +5891,6 @@ async function onModelChange() {
56535891 eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);
56545892}
56555893
5656-async function onOpenrouterModelSortChange() {
5657- await getStatusOpen();
5658-}
5659-
5660-async function onChutesModelSortChange() {
5661- await getStatusOpen();
5662-}
5663-
5664-async function onElectronHubModelSortChange() {
5665- await getStatusOpen();
5666-}
5667-
56685894async function onNewPresetClick() {
56695895 const name = await Popup.show.input(t`Preset name:`, t`Hint: Use a character/group name to bind preset to a specific chat.`, oai_settings.preset_settings_openai);
56705896
@@ -5709,6 +5935,8 @@ async function onConnectButtonClick(e) {
57095935 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true },
57105936 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },
57115937 [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false },
5938+ [chat_completion_sources.WORKERS_AI]: { key: SECRET_KEYS.WORKERS_AI, selector: '#api_key_workers_ai', proxy: false },
5939+ [chat_completion_sources.MINIMAX]: { key: SECRET_KEYS.MINIMAX, selector: '#api_key_minimax', proxy: false },
57125940 };
57135941
57145942 // Vertex AI Express version - use API key
@@ -5774,6 +6002,8 @@ function toggleChatCompletionForms() {
57746002 $('#model_chutes_select').trigger('change');
57756003 } else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) {
57766004 $('#model_siliconflow_select').trigger('change');
6005+ } else if (oai_settings.chat_completion_source == chat_completion_sources.MINIMAX) {
6006+ $('#model_minimax_select').trigger('change');
57776007 } else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
57786008 $('#model_electronhub_select').trigger('change');
57796009 } else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
@@ -5798,6 +6028,8 @@ function toggleChatCompletionForms() {
57986028 $('#azure_openai_model').trigger('change');
57996029 } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
58006030 $('#model_zai_select').trigger('change');
6031+ } else if (oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI) {
6032+ $('#model_workers_ai_select').trigger('change');
58016033 }
58026034
58036035 $('[data-source]').each(function () {
@@ -5903,6 +6135,10 @@ export function isImageInliningSupported() {
59036135 'gemini-exp-1206',
59046136 'learnlm',
59056137 'gemini-robotics',
6138+ 'gemma-3-27b',
6139+ 'gemma-3-12b',
6140+ 'gemma-3-4b',
6141+ 'gemma-4',
59066142 // MistralAI
59076143 'mistral-small-2503',
59086144 'mistral-small-2506',
@@ -5923,6 +6159,7 @@ export function isImageInliningSupported() {
59236159 // Z.AI (GLM)
59246160 'glm-4.5v',
59256161 'glm-4.6v',
6162+ 'glm-5v-turbo',
59266163 'autoglm-phone',
59276164 // SiliconFlow
59286165 'Qwen/Qwen3-VL-32B-Instruct',
@@ -5978,6 +6215,10 @@ export function isImageInliningSupported() {
59786215 return visionSupportedModels.some(model => oai_settings.zai_model.includes(model));
59796216 case chat_completion_sources.SILICONFLOW:
59806217 return visionSupportedModels.some(model => oai_settings.siliconflow_model.includes(model));
6218+ case chat_completion_sources.WORKERS_AI: {
6219+ const waiModel = Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.workers_ai_model);
6220+ return Boolean(waiModel && Array.isArray(waiModel.properties) && waiModel.properties.some(p => p.property_id === 'vision' && p.value === 'true'));
6221+ }
59816222 default:
59826223 return false;
59836224 }
@@ -6002,9 +6243,11 @@ export function isVideoInliningSupported() {
60026243 'gemini-2.5',
60036244 'gemini-exp-1206',
60046245 'gemini-3',
6246+ 'gemma-4',
60056247 // Z.AI (GLM)
60066248 'glm-4.5v',
60076249 'glm-4.6v',
6250+ 'glm-5v-turbo',
60086251 ];
60096252
60106253 switch (oai_settings.chat_completion_source) {
@@ -6632,16 +6875,6 @@ export function initOpenAI() {
66326875 saveSettingsDebounced();
66336876 });
66346877
6635- $('#openrouter_group_models').on('input', function () {
6636- oai_settings.openrouter_group_models = !!$(this).prop('checked');
6637- saveSettingsDebounced();
6638- });
6639-
6640- $('#openrouter_sort_models').on('input', function () {
6641- oai_settings.openrouter_sort_models = String($(this).val());
6642- saveSettingsDebounced();
6643- });
6644-
66456878 $('#openrouter_allow_fallbacks').on('input', function () {
66466879 oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked');
66476880 updateOpenRouterProvidersWarning('#openrouter_providers_chat');
@@ -6653,21 +6886,6 @@ export function initOpenAI() {
66536886 saveSettingsDebounced();
66546887 });
66556888
6656- $('#electronhub_sort_models').on('input', function () {
6657- oai_settings.electronhub_sort_models = String($(this).val());
6658- saveSettingsDebounced();
6659- });
6660-
6661- $('#chutes_sort_models').on('input', function () {
6662- oai_settings.chutes_sort_models = String($(this).val());
6663- saveSettingsDebounced();
6664- });
6665-
6666- $('#electronhub_group_models').on('input', function () {
6667- oai_settings.electronhub_group_models = !!$(this).prop('checked');
6668- saveSettingsDebounced();
6669- });
6670-
66716889 $('#squash_system_messages').on('input', function () {
66726890 oai_settings.squash_system_messages = !!$(this).prop('checked');
66736891 saveSettingsDebounced();
@@ -6695,6 +6913,13 @@ export function initOpenAI() {
66956913 saveSettingsDebounced();
66966914 });
66976915
6916+ $('#tool_call_recurse_limit').on('input', function () {
6917+ oai_settings.tool_call_recurse_limit = Number($(this).val());
6918+ $('#tool_call_recurse_limit_counter').val(oai_settings.tool_call_recurse_limit);
6919+ ToolManager.RECURSE_LIMIT = oai_settings.tool_call_recurse_limit;
6920+ saveSettingsDebounced();
6921+ });
6922+
66986923 $('#tool_reasoning_mode').on('input', function () {
66996924 oai_settings.tool_reasoning_mode = getToolReasoningMode({
67006925 ...oai_settings,
@@ -6921,11 +7146,34 @@ export function initOpenAI() {
69217146 saveSettingsDebounced();
69227147 });
69237148
7149+ $('#nanogpt_provider').on('change', function () {
7150+ oai_settings.nanogpt_provider = String($(this).val() || '');
7151+ updateNanoGptProvidersWarning('#nanogpt_provider');
7152+ saveSettingsDebounced();
7153+ });
7154+
7155+ $('#nanogpt_payg_override').on('input', function () {
7156+ oai_settings.nanogpt_payg_override = !!$(this).prop('checked');
7157+ saveSettingsDebounced();
7158+ });
7159+
69247160 $('#bind_preset_to_connection').on('input', function () {
69257161 oai_settings.bind_preset_to_connection = !!$(this).prop('checked');
69267162 saveSettingsDebounced();
69277163 });
69287164
7165+ $('#cc_group_models').on('input', async () => {
7166+ oai_settings.group_models = $('#cc_group_models').prop('checked');
7167+ reconnectOpenAi();
7168+ saveSettingsDebounced();
7169+ });
7170+
7171+ $('#cc_sort_models').on('input', async () => {
7172+ oai_settings.sort_models = $('#cc_sort_models').val().toString();
7173+ reconnectOpenAi();
7174+ saveSettingsDebounced();
7175+ });
7176+
69297177 $('#api_button_openai').on('click', onConnectButtonClick);
69307178 $('#openai_reverse_proxy').on('input', onReverseProxyInput);
69317179 $('#model_openai_select').on('change', onModelChange);
@@ -6949,15 +7197,18 @@ export function initOpenAI() {
69497197 oai_settings.siliconflow_endpoint = String($(this).val());
69507198 saveSettingsDebounced();
69517199 });
7200+ $('#minimax_endpoint').on('input', function () {
7201+ oai_settings.minimax_endpoint = String($(this).val());
7202+ saveSettingsDebounced();
7203+ });
7204+ $('#workers_ai_account_id').on('input', function () {
7205+ oai_settings.workers_ai_account_id = String($(this).val());
7206+ saveSettingsDebounced();
7207+ });
69527208 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);
69537209 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
69547210 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
69557211 $('#model_openrouter_select').on('change', onModelChange);
6956- $('#openrouter_group_models').on('change', onOpenrouterModelSortChange);
6957- $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);
6958- $('#chutes_sort_models').on('change', onChutesModelSortChange);
6959- $('#electronhub_group_models').on('change', onElectronHubModelSortChange);
6960- $('#electronhub_sort_models').on('change', onElectronHubModelSortChange);
69617212 $('#model_ai21_select').on('change', onModelChange);
69627213 $('#model_mistralai_select').on('change', onModelChange);
69637214 $('#model_cohere_select').on('change', onModelChange);
@@ -6965,6 +7216,7 @@ export function initOpenAI() {
69657216 $('#model_groq_select').on('change', onModelChange);
69667217 $('#model_chutes_select').on('change', onModelChange);
69677218 $('#model_siliconflow_select').on('change', onModelChange);
7219+ $('#model_minimax_select').on('change', onModelChange);
69687220 $('#model_electronhub_select').on('change', onModelChange);
69697221 $('#model_nanogpt_select').on('change', onModelChange);
69707222 $('#model_deepseek_select').on('change', onModelChange);
@@ -6977,6 +7229,7 @@ export function initOpenAI() {
69777229 $('#model_fireworks_select').on('change', onModelChange);
69787230 $('#azure_openai_model').on('change', onModelChange);
69797231 $('#model_zai_select').on('change', onModelChange);
7232+ $('#model_workers_ai_select').on('change', onModelChange);
69807233 $('#settings_preset_openai').on('change', onSettingsPresetChange);
69817234 $('#new_oai_preset').on('click', onNewPresetClick);
69827235 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/personas.js+1030 -68
@@ -22,9 +22,35 @@ import {
2222 setUserName,
2323 this_chid,
2424} from '../script.js';
2525import { persona_description_positions, power_user } from './power-user.js';
2626import { getTokenCountAsync } from './tokenizers.js';
27-import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent } from './utils.js';
27+import {
28+ PAGINATION_TEMPLATE,
29+ clearInfoBlock,
30+ debounce,
31+ delay,
32+ download,
33+ ensureImageFormatSupported,
34+ flashHighlight,
35+ getBase64Async,
36+ getCharIndex,
37+ isFalseBoolean,
38+ isTrueBoolean,
39+ onlyUnique,
40+ parseJsonFile,
41+ setInfoBlock,
42+ localizePagination,
43+ renderPaginationDropdown,
44+ paginationDropdownChangeHandler,
45+ addLongPressEvent,
46+ stringToRange,
47+ sortIgnoreCaseAndAccents,
48+ equalsIgnoreCaseAndAccents,
49+ uuidv4,
50+ resolveAvatarData,
51+ findPersona,
52+ escapeHtml,
53+} from './utils.js';
2854import { debounce_timeout } from './constants.js';
2955import { FILTER_TYPES, FilterHelper } from './filters.js';
3056import { groups, selected_group } from './group-chats.js';
@@ -36,10 +62,11 @@ import { saveMetadataDebounced } from './extensions.js';
3662import { accountStorage } from './util/AccountStorage.js';
3763import { SlashCommand } from './slash-commands/SlashCommand.js';
3864import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
3965import { commonEnumMatchProviders, commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
4066import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
4167import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
4268import { isFirefox } from './browser-fixes.js';
69+import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
4370
4471/**
4572 * @typedef {object} PersonaConnection A connection between a character and a character or group entity
@@ -58,6 +85,18 @@ import { isFirefox } from './browser-fixes.js';
5885 * @property {boolean} locked.character - Whether the persona is locked to the currently open character or group
5986 */
6087
88+export const persona_description_positions = {
89+ IN_PROMPT: 0,
90+ /**
91+ * @deprecated Use persona_description_positions.IN_PROMPT instead.
92+ */
93+ AFTER_CHAR: 1,
94+ TOP_AN: 2,
95+ BOTTOM_AN: 3,
96+ AT_DEPTH: 4,
97+ NONE: 9,
98+};
99+
61100const USER_AVATAR_PATH = 'User Avatars/';
62101
63102let savePersonasPage = 0;
@@ -217,11 +256,12 @@ function getUserAvatarBlock(avatarId) {
217256/**
218257 * Initialize missing personas in the power user settings.
219258 * @param {string[]} avatarsList List of avatar file names
259+ * @returns {Promise<void>}
220260 */
221261async function addMissingPersonas(avatarsList) {
222262 for (const persona of avatarsList) {
223263 if (!power_user.personas[persona]) {
224264 await initPersona(persona, '[Unnamed Persona]', '', '', { silent: true });
225265 }
226266 }
227267}
@@ -249,7 +289,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
249289 }
250290
251291 // If any persona is missing from the power user settings, we add it
252292 await addMissingPersonas(allEntities);
253293 // Before printing the personas, we check if we should enable/disable search sorting
254294 verifyPersonaSearchSortRule();
255295
@@ -429,7 +469,7 @@ export async function createPersona(avatarId) {
429469
430470 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });
431471
432472 await initPersona(avatarId, personaName, personaDescription, '');
433473 if (power_user.persona_show_notifications) {
434474 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);
435475 }
@@ -454,7 +494,7 @@ async function createDummyPersona() {
454494
455495 // Date + name (only ASCII) to make it unique
456496 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
457497 await initPersona(avatarId, personaName, '', personaTitle);
458498 await uploadUserAvatar(default_user_avatar, avatarId);
459499}
460500
@@ -464,20 +504,36 @@ async function createDummyPersona() {
464504 * @param {string} personaName Name for the persona
465505 * @param {string} personaDescription Optional description for the persona
466506 * @param {string} personaTitle Optional title for the persona
467- * @returns {void}
507+ * @param {object} [options={}] Optional settings
508+ * @param {boolean} [options.silent=false] If true, no PERSONA_CREATED event is emitted (used for background migrations)
509+ * @param {number} [options.position=persona_description_positions.IN_PROMPT] Description position (defaults to IN_PROMPT)
510+ * @param {number} [options.depth=DEFAULT_DEPTH] Description depth (defaults to DEFAULT_DEPTH)
511+ * @param {number} [options.role=DEFAULT_ROLE] Description role (defaults to DEFAULT_ROLE)
512+ * @param {string} [options.lorebook=''] Attached lorebook name
513+ * @returns {Promise<void>}
468514 */
469515export async function initPersona(avatarId, personaName, personaDescription, personaTitle), {
516+ silent = false,
517+ position = persona_description_positions.IN_PROMPT,
518+ depth = DEFAULT_DEPTH,
519+ role = DEFAULT_ROLE,
520+ lorebook = '',
521+} = {}) {
470522 power_user.personas[avatarId] = personaName;
471523 power_user.persona_descriptions[avatarId] = {
472524 description: personaDescription || '',
473525 position: persona_description_positions.IN_PROMPTposition,
474526 depth: DEFAULT_DEPTHdepth,
475527 role: DEFAULT_ROLErole,
476528 lorebook: ''lorebook,
477529 title: personaTitle || '',
478530 };
479531
480532 saveSettingsDebounced();
533+
534+ if (!silent) {
535+ await eventSource.emit(event_types.PERSONA_CREATED, { avatarId, name: personaName, description: personaDescription || '', title: personaTitle || '' });
536+ }
481537}
482538
483539/**
@@ -540,6 +596,7 @@ export async function convertCharacterToPersona(characterId = null) {
540596 }
541597
542598 saveSettingsDebounced();
599+ await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: overwriteName, name, description, title: '' });
543600
544601 console.log('Persona for character created');
545602 toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`);
@@ -742,16 +799,17 @@ export async function askForPersonaSelection(title, text, personas, { okButton =
742799/**
743800 * Automatically selects a persona based on the given name if a matching persona exists.
744801 * @param {string} name - The name to search for
802+ * @param {Object} [options={}]
803+ * @param {string} [options.personaKey=null] - Optionally a persona avatar key to target (if multiple persona have the same name); must match the name
745804 * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise
746805 */
747806export async function autoSelectPersona(name, { personaKey = null } = {}) {
748- for (const [key, value] of Object.entries(power_user.personas)) {
807+ const persona = findPersona({ name: personaKey ?? name, allowAvatar: !!personaKey });
749808 if (value === namepersona) {
750809 console.log(`Auto-selecting persona ${keypersona.avatar} for name ${name}`);
751810 await setUserAvatar(keypersona.avatar);
752811 return true;
753812 }
754- }
755813 return false;
756814}
757815
@@ -778,6 +836,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
778836 delete power_user.persona_descriptions[avatarId].title;
779837 await getUserAvatars(true, avatarId);
780838 saveSettingsDebounced();
839+ await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
781840 return;
782841 }
783842
@@ -786,6 +845,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
786845 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);
787846 await getUserAvatars(true, avatarId);
788847 saveSettingsDebounced();
848+ await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
789849 return;
790850 }
791851}
@@ -821,6 +881,7 @@ async function renamePersona(avatarId) {
821881 }
822882
823883 saveSettingsDebounced();
884+ await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName: currentName, newName });
824885 await getUserAvatars(true, avatarId);
825886 updatePersonaUIStates();
826887 setPersonaDescription();
@@ -886,8 +947,9 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
886947 const temporary = getPersonaTemporaryLockInfo();
887948 if (temporary.isTemporary) {
888949 toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, {
889950 preventDuplicates: true, onclick: () => {
890- toastr.info(temporary.info.replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false });
951+ onclick: () => {
952+ toastr.info(escapeHtml(temporary.info).replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false });
891953 },
892954 });
893955 }
@@ -1013,6 +1075,7 @@ async function lockPersona(type = 'chat') {
10131075 connections: [],
10141076 title: '',
10151077 };
1078+ await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: user_avatar, name: name1, description: '', title: '' });
10161079 }
10171080
10181081 switch (type) {
@@ -1055,9 +1118,9 @@ async function lockPersona(type = 'chat') {
10551118 if (power_user.persona_show_notifications) {
10561119 let additional = '';
10571120 if (unlinkedCharacters.length)
10581121 additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.map(escapeHtml).join(', ')}`}`;
10591122 if (additional || !isPersonaPanelOpen()) {
10601123 toastr.success(t`User persona ${escapeHtml(name1)} is locked to character ${escapeHtml(name2)}${additional}`, t`Persona Locked`, { escapeHtml: false });
10611124 }
10621125 }
10631126 }
@@ -1071,21 +1134,37 @@ async function lockPersona(type = 'chat') {
10711134}
10721135
10731136
1137+/**
1138+ * Click handler for the delete persona button. Delegates to deletePersona with the current user avatar.
1139+ */
10741140async function deleteUserAvatar() {
1075- const avatarId = user_avatar;
1141+ await deletePersona(user_avatar);
1142+}
10761143
1144+/**
1145+ * Deletes a persona by avatar id.
1146+ * @param {string} avatarId The persona's avatar id to delete
1147+ * @param {object} [options] Options
1148+ * @param {boolean} [options.silent=false] If true, skips the confirmation popup and suppresses toast notifications
1149+ * @returns {Promise<boolean>} True if the persona was deleted
1150+ */
1151+async function deletePersona(avatarId, { silent = false } = {}) {
10771152 if (!avatarId) {
10781153 console.warn('No avatar id found');
10791154 return false;
10801155 }
1156+
10811157 const name = power_user.personas[avatarId] || '';
1158+
1159+ if (!silent) {
10821160 const confirm = await Popup.show.confirm(
10831161 t`Delete Persona` + `: ${name}`,
10841162 t`Are you sure you want to delete this avatar?` + '<br />' + t`All information associated with its linked persona will be lost.`);
10851163
10861164 if (!confirm) {
10871165 console.debug('User cancelled deleting avatar');
10881166 return false;
1167+ }
10891168 }
10901169
10911170 const request = await fetch('/api/avatars/delete', {
@@ -1102,24 +1181,29 @@ async function deleteUserAvatar() {
11021181 delete power_user.persona_descriptions[avatarId];
11031182
11041183 if (avatarId === power_user.default_persona) {
11051184 if (!silent) toastr.warning(t`The default persona was deleted. You will need to set a new default persona.`, t`Default Persona Deleted`);
11061185 power_user.default_persona = null;
11071186 }
11081187
11091188 if (avatarId === chat_metadata.persona) {
11101189 if (!silent) toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona Deleted`);
11111190 delete chat_metadata.persona;
11121191 await saveMetadata();
11131192 }
11141193
11151194 saveSettingsDebounced();
1195+ await eventSource.emit(event_types.PERSONA_DELETED, { avatarId, name });
11161196
11171197 // Use the existing mechanism to re-render the persona list and choose the next persona here
1198+ personaLastLoadedChatId = uuidv4(); // Force reload by making a dummy chat id
11181199 await loadPersonaForCurrentChat({ doRender: true });
1200+ return true;
11191201 }
1202+
1203+ return false;
11201204}
11211205
11221206async function onPersonaDescriptionInput() {
11231207 power_user.persona_description = String($('#persona_description').val());
11241208 countPersonaDescriptionTokens();
11251209
@@ -1145,25 +1229,35 @@ function onPersonaDescriptionInput() {
11451229 .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text'))
11461230 .toggleClass('text_muted', !power_user.persona_description);
11471231 saveSettingsDebounced();
1232+
1233+ if (power_user.personas[user_avatar]) {
1234+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1235+ }
11481236}
11491237
11501238async function onPersonaDescriptionDepthValueInput() {
11511239 power_user.persona_description_depth = Number($('#persona_depth_value').val());
11521240
11531241 if (power_user.personas[user_avatar]) {
11541242 const object = getOrCreatePersonaDescriptor();
11551243 object.depth = power_user.persona_description_depth;
1244+ saveSettingsDebounced();
1245+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1246+ return;
11561247 }
11571248
11581249 saveSettingsDebounced();
11591250}
11601251
11611252async function onPersonaDescriptionDepthRoleInput() {
11621253 power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val());
11631254
11641255 if (power_user.personas[user_avatar]) {
11651256 const object = getOrCreatePersonaDescriptor();
11661257 object.role = power_user.persona_description_role;
1258+ saveSettingsDebounced();
1259+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1260+ return;
11671261 }
11681262
11691263 saveSettingsDebounced();
@@ -1171,9 +1265,9 @@ function onPersonaDescriptionDepthRoleInput() {
11711265
11721266/**
11731267 * Opens a popup to set the lorebook for the current persona.
11741268 * @param {Pick<JQuery.ClickEvent, 'shiftKey' | 'altKey'>} event Click event
11751269 */
11761270async function onPersonaLoreButtonClick(event{ shiftKey, altKey }) {
11771271 const personaName = power_user.personas[user_avatar];
11781272 const selectedLorebook = power_user.persona_description_lorebook;
11791273
@@ -1182,7 +1276,7 @@ async function onPersonaLoreButtonClick(event) {
11821276 return;
11831277 }
11841278
11851279 if (selectedLorebook && !event.shiftKey && !event.altKey) {
11861280 openWorldInfoEditor(selectedLorebook);
11871281 return;
11881282 }
@@ -1200,7 +1294,7 @@ async function onPersonaLoreButtonClick(event) {
12001294 worldSelect.append(option);
12011295 }
12021296
12031297 worldSelect.on('change', async function () {
12041298 power_user.persona_description_lorebook = String($(this).val());
12051299
12061300 if (power_user.personas[user_avatar]) {
@@ -1210,12 +1304,16 @@ async function onPersonaLoreButtonClick(event) {
12101304
12111305 $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);
12121306 saveSettingsDebounced();
1307+
1308+ if (power_user.personas[user_avatar]) {
1309+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1310+ }
12131311 });
12141312
12151313 await callGenericPopup(template, POPUP_TYPE.TEXT);
12161314}
12171315
12181316async function onPersonaDescriptionPositionInput() {
12191317 power_user.persona_description_position = Number(
12201318 $('#persona_description_position').find(':selected').val(),
12211319 );
@@ -1223,6 +1321,10 @@ function onPersonaDescriptionPositionInput() {
12231321 if (power_user.personas[user_avatar]) {
12241322 const object = getOrCreatePersonaDescriptor();
12251323 object.position = power_user.persona_description_position;
1324+ saveSettingsDebounced();
1325+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1326+ $('#persona_depth_position_settings').toggle(power_user.persona_description_position === persona_description_positions.AT_DEPTH);
1327+ return;
12261328 }
12271329
12281330 saveSettingsDebounced();
@@ -1728,15 +1830,36 @@ async function onPersonasRestoreInput(e) {
17281830 $('#personas_restore_input').val('');
17291831}
17301832
1731-async function syncUserNameToPersona() {
1833+/**
1732- const confirmation = await Popup.show.confirm(t`Are you sure?`, t`All user-sent messages in this chat will be attributed to ${name1}.`);
1834+ * Synchronizes user-sent messages in the chat to the current persona.
1835+ * @param {object} [options={}] - Optional parameters
1836+ * @param {number} [options.start=0] - Start index of the message range (inclusive)
1837+ * @param {number} [options.end=chat.length - 1] - End index of the message range (inclusive)
1838+ * @param {boolean} [options.quiet=false] - If true, skips the confirmation popup
1839+ * @param {string} [options.nameFilter=''] - Filter messages by name (case-insensitive)
1840+ * @returns {Promise<void>}
1841+ */
1842+async function syncUserNameToPersona({ start = 0, end = chat.length - 1, quiet = false, nameFilter = '' } = {}) {
1843+ const isRangeAll = start === 0 && end === chat.length - 1;
1844+ const hasNameFilter = nameFilter?.trim();
1845+ const confirmMessage = isRangeAll && !hasNameFilter
1846+ ? t`All user-sent messages in this chat will be attributed to ${name1}.`
1847+ : isRangeAll && hasNameFilter
1848+ ? t`User-sent messages with name "${nameFilter}" will be attributed to ${name1}.`
1849+ : !isRangeAll && !hasNameFilter
1850+ ? t`User-sent messages in the specified range will be attributed to ${name1}.`
1851+ : t`User-sent messages with name "${nameFilter}" in the specified range will be attributed to ${name1}.`;
17331852
1853+ if (!quiet) {
1854+ const confirmation = await Popup.show.confirm(t`Are you sure?`, confirmMessage);
17341855 if (!confirmation) {
17351856 return;
17361857 }
1858+ }
17371859
1738- for (const mes of chat) {
1860+ for (let i = start; i <= end; i++) {
1739- if (mes.is_user) {
1861+ const mes = chat[i];
1862+ if (mes?.is_user && (!hasNameFilter || equalsIgnoreCaseAndAccents(mes.name, nameFilter))) {
17401863 mes.name = name1;
17411864 mes.force_avatar = getThumbnailUrl('persona', user_avatar);
17421865 }
@@ -1763,22 +1886,27 @@ export async function retriggerFirstMessageOnEmptyChat() {
17631886
17641887/**
17651888 * Duplicates a persona.
17661889 * @param {string} avatarId Source persona avatar id
17671890 * @returnsparam {Promise<void>object} [options] Options
1891+ * @param {boolean} [options.silent=false] If true, skips the confirmation popup
1892+ * @param {boolean} [options.select=false] If true, selects/activates the duplicated persona
1893+ * @returns {Promise<string>} The avatar id of the new persona, or empty string on failure/cancellation
17681894 */
1769-async function duplicatePersona(avatarId) {
1895+async function duplicatePersona(avatarId, { silent = false, select = false } = {}) {
17701896 const personaName = power_user.personas[avatarId];
17711897
17721898 if (!personaName) {
17731899 toastr.warning('t`Chosen avatar is not a persona'`, t`Persona Management`);
17741900 return '';
17751901 }
17761902
1903+ if (!silent) {
17771904 const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName);
17781905
17791906 if (!confirm) {
17801907 console.debug('User cancelled duplicating persona');
1781- return;
1908+ return '';
1909+ }
17821910 }
17831911
17841912 const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
@@ -1795,8 +1923,24 @@ async function duplicatePersona(avatarId) {
17951923 };
17961924
17971925 await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);
1926+
1927+ const eventData = {
1928+ avatarId: newAvatarId,
1929+ name: personaName,
1930+ description: descriptor?.description ?? '',
1931+ title: descriptor?.title ?? '',
1932+ duplicatedFromAvatarId: avatarId,
1933+ };
1934+ await eventSource.emit(event_types.PERSONA_CREATED, eventData);
1935+
17981936 await getUserAvatars(true, newAvatarId);
17991937 saveSettingsDebounced();
1938+
1939+ if (select) {
1940+ await setUserAvatar(newAvatarId);
1941+ }
1942+
1943+ return newAvatarId;
18001944}
18011945
18021946/**
@@ -1807,12 +1951,418 @@ async function migrateNonPersonaUser() {
18071951 return;
18081952 }
18091953
18101954 await initPersona(user_avatar, name1, '', '', { silent: true });
18111955 setPersonaDescription();
18121956 await getUserAvatars(true, user_avatar);
18131957}
18141958
18151959
1960+// #region Persona CRUD Slash Command Utilities
1961+
1962+/**
1963+ * Mapping of human-readable position names to persona_description_positions enum values.
1964+ * @type {Record<string, number>}
1965+ */
1966+const POSITION_NAME_MAP = Object.freeze({
1967+ 'inprompt': persona_description_positions.IN_PROMPT,
1968+ 'topan': persona_description_positions.TOP_AN,
1969+ 'bottoman': persona_description_positions.BOTTOM_AN,
1970+ 'atdepth': persona_description_positions.AT_DEPTH,
1971+ 'none': persona_description_positions.NONE,
1972+});
1973+
1974+/**
1975+ * Mapping of human-readable role names to numeric role values.
1976+ * @type {Record<string, number>}
1977+ */
1978+const ROLE_NAME_MAP = Object.freeze({
1979+ 'system': 0,
1980+ 'user': 1,
1981+ 'assistant': 2,
1982+});
1983+
1984+/**
1985+ * Parses a persona description position from a string or number value.
1986+ * @param {string|number|undefined} value Position value (name or number)
1987+ * @returns {number|null} Parsed position value, or null if invalid/undefined
1988+ */
1989+function parsePersonaPosition(value) {
1990+ if (value === undefined || value === null) return null;
1991+ const strValue = String(value).toLowerCase();
1992+ if (strValue in POSITION_NAME_MAP) return POSITION_NAME_MAP[strValue];
1993+ const numValue = Number(value);
1994+ if (!isNaN(numValue) && Object.values(persona_description_positions).includes(numValue)) return numValue;
1995+ return null;
1996+}
1997+
1998+/**
1999+ * Parses a persona description role from a string or number value.
2000+ * @param {string|number|undefined} value Role value (name or number)
2001+ * @returns {number|null} Parsed role value, or null if invalid/undefined
2002+ */
2003+function parsePersonaRole(value) {
2004+ if (value === undefined || value === null) return null;
2005+ const strValue = String(value).toLowerCase();
2006+ if (strValue in ROLE_NAME_MAP) return ROLE_NAME_MAP[strValue];
2007+ const numValue = Number(value);
2008+ if (!isNaN(numValue) && numValue >= 0 && numValue <= 2) return numValue;
2009+ return null;
2010+}
2011+
2012+/**
2013+ * Uploads base64 avatar data to a persona, optionally showing a crop dialog.
2014+ * @param {string} avatarId The persona's avatar file name
2015+ * @param {string} base64Data Base64 data URL of the image
2016+ * @param {object} [options] Options
2017+ * @param {boolean} [options.resizePrompt=false] Whether to show the crop dialog
2018+ * @returns {Promise<boolean>} True if upload was successful
2019+ */
2020+async function uploadPersonaAvatar(avatarId, base64Data, { resizePrompt = false } = {}) {
2021+ if (!base64Data || !avatarId) return false;
2022+
2023+ let finalImageData = base64Data;
2024+
2025+ if (resizePrompt && !power_user.never_resize_avatars) {
2026+ const dlg = new Popup(t`Set the crop position of the avatar image`, POPUP_TYPE.CROP, '', { cropImage: base64Data });
2027+ const croppedImage = await dlg.show();
2028+ if (!croppedImage) return false;
2029+ finalImageData = String(croppedImage);
2030+ }
2031+
2032+ try {
2033+ const response = await fetch(finalImageData);
2034+ const blob = await response.blob();
2035+ const file = new File([blob], 'avatar.png', { type: 'image/png' });
2036+ const formData = new FormData();
2037+ formData.append('avatar', file);
2038+ formData.append('overwrite_name', avatarId);
2039+
2040+ const uploadResponse = await fetch('/api/avatars/upload', {
2041+ method: 'POST',
2042+ headers: getRequestHeaders({ omitContentType: true }),
2043+ cache: 'no-cache',
2044+ body: formData,
2045+ });
2046+
2047+ if (!uploadResponse.ok) {
2048+ throw new Error(`Upload failed: ${uploadResponse.statusText}`);
2049+ }
2050+
2051+ // Cache bust for the updated avatar
2052+ await fetch(getUserAvatar(avatarId), { cache: 'reload' });
2053+ await fetch(getThumbnailUrl('persona', avatarId), { cache: 'reload' });
2054+ reloadUserAvatar(true);
2055+ return true;
2056+ } catch (error) {
2057+ console.error('Error uploading persona avatar:', error);
2058+ toastr.warning(t`Failed to upload avatar: ${error.message}`);
2059+ return false;
2060+ }
2061+}
2062+
2063+/**
2064+ * Resolves a persona from the given argument or falls back to the currently active persona.
2065+ * @param {string} [personaArg] Persona name or avatar key argument
2066+ * @returns {import('./utils.js').PersonaViewModel|null} The resolved persona, or null if not found
2067+ */
2068+function getTargetPersona(personaArg) {
2069+ if (personaArg) {
2070+ const persona = findPersona({ name: personaArg });
2071+ if (!persona) {
2072+ toastr.warning(t`Persona "${personaArg}" not found`);
2073+ return null;
2074+ }
2075+ return persona;
2076+ }
2077+
2078+ // Fall back to currently active persona
2079+ const persona = findPersona({ preferCurrentPersona: true });
2080+ if (!persona) {
2081+ toastr.warning(t`No persona selected and no persona argument provided`);
2082+ return null;
2083+ }
2084+ return persona;
2085+}
2086+
2087+// #endregion
2088+
2089+// #region Persona CRUD Slash Command Callbacks
2090+
2091+/**
2092+ * Creates a new persona with the specified attributes.
2093+ * @param {object} args Named arguments from the slash command
2094+ * @returns {Promise<string>} Avatar key of the created persona, or empty string on failure
2095+ */
2096+async function createPersonaCallback(args) {
2097+ const name = args.name;
2098+ if (!name || typeof name !== 'string' || !name.trim()) {
2099+ toastr.warning(t`Persona name is required`);
2100+ return '';
2101+ }
2102+
2103+ const trimmedName = name.trim();
2104+ const avatarId = `${Date.now()}-${trimmedName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
2105+
2106+ const description = args.description ?? '';
2107+ const title = args.title ?? '';
2108+ const position = parsePersonaPosition(args.descriptionPosition) ?? persona_description_positions.IN_PROMPT;
2109+ const role = parsePersonaRole(args.descriptionRole) ?? DEFAULT_ROLE;
2110+ const lorebook = args.lorebook ?? '';
2111+
2112+ let depth = args.descriptionDepth !== undefined ? Number(args.descriptionDepth) : DEFAULT_DEPTH;
2113+ if (isNaN(depth)) {
2114+ toastr.warning(t`Invalid description depth "${args.descriptionDepth}", defaulting to ${DEFAULT_DEPTH}`);
2115+ depth = DEFAULT_DEPTH;
2116+ }
2117+
2118+ // Initialize persona data with all fields
2119+ await initPersona(avatarId, trimmedName, description, title, {
2120+ position, depth, role, lorebook,
2121+ });
2122+
2123+ // Handle avatar upload
2124+ const avatarData = args.avatar ? await resolveAvatarData(args.avatar) : null;
2125+ if (avatarData) {
2126+ const resizePrompt = !isFalseBoolean(args.avatarPromptResize ?? 'true');
2127+ const uploaded = await uploadPersonaAvatar(avatarId, avatarData, { resizePrompt });
2128+ if (!uploaded) {
2129+ // Crop was cancelled or upload failed — use default avatar
2130+ await uploadUserAvatar(default_user_avatar, avatarId);
2131+ }
2132+ } else {
2133+ await uploadUserAvatar(default_user_avatar, avatarId);
2134+ }
2135+
2136+ saveSettingsDebounced();
2137+ await getUserAvatars(true, avatarId);
2138+
2139+ // Select/activate if requested (default: true)
2140+ if (!isFalseBoolean(args.select ?? 'true')) {
2141+ await setUserAvatar(avatarId);
2142+ }
2143+
2144+ toastr.success(t`Persona "${trimmedName}" created successfully`);
2145+ return avatarId;
2146+}
2147+
2148+/**
2149+ * Updates an existing persona's attributes.
2150+ * @param {object} args Named arguments from the slash command
2151+ * @returns {Promise<string>} Avatar key of the updated persona, or empty string on failure
2152+ */
2153+async function updatePersonaCallback(args) {
2154+ const persona = getTargetPersona(args.persona);
2155+ if (!persona) return '';
2156+
2157+ const avatarId = persona.avatar;
2158+ const descriptor = power_user.persona_descriptions[avatarId];
2159+
2160+ if (!descriptor) {
2161+ toastr.warning(t`Persona data not found for "${persona.name}"`);
2162+ return '';
2163+ }
2164+
2165+ let hasUpdates = false;
2166+
2167+ // Update name
2168+ if (args.name !== undefined) {
2169+ const newName = String(args.name).trim();
2170+ if (newName) {
2171+ const oldName = power_user.personas[avatarId];
2172+ power_user.personas[avatarId] = newName;
2173+ if (avatarId === user_avatar) {
2174+ setUserName(newName);
2175+ }
2176+ await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName, newName });
2177+ hasUpdates = true;
2178+ }
2179+ }
2180+
2181+ // Update description
2182+ if (args.description !== undefined) {
2183+ descriptor.description = args.description;
2184+ if (avatarId === user_avatar) {
2185+ power_user.persona_description = args.description;
2186+ }
2187+ hasUpdates = true;
2188+ }
2189+
2190+ // Update title
2191+ if (args.title !== undefined) {
2192+ descriptor.title = args.title;
2193+ hasUpdates = true;
2194+ }
2195+
2196+ // Update description position
2197+ if (args.descriptionPosition !== undefined) {
2198+ const position = parsePersonaPosition(args.descriptionPosition);
2199+ if (position !== null) {
2200+ descriptor.position = position;
2201+ if (avatarId === user_avatar) {
2202+ power_user.persona_description_position = position;
2203+ }
2204+ hasUpdates = true;
2205+ }
2206+ }
2207+
2208+ // Update description depth
2209+ if (args.descriptionDepth !== undefined) {
2210+ const depth = Number(args.descriptionDepth);
2211+ if (!isNaN(depth)) {
2212+ descriptor.depth = depth;
2213+ if (avatarId === user_avatar) {
2214+ power_user.persona_description_depth = depth;
2215+ }
2216+ hasUpdates = true;
2217+ }
2218+ }
2219+
2220+ // Update description role
2221+ if (args.descriptionRole !== undefined) {
2222+ const role = parsePersonaRole(args.descriptionRole);
2223+ if (role !== null) {
2224+ descriptor.role = role;
2225+ if (avatarId === user_avatar) {
2226+ power_user.persona_description_role = role;
2227+ }
2228+ hasUpdates = true;
2229+ }
2230+ }
2231+
2232+ // Update lorebook
2233+ if (args.lorebook !== undefined) {
2234+ descriptor.lorebook = args.lorebook;
2235+ if (avatarId === user_avatar) {
2236+ power_user.persona_description_lorebook = args.lorebook;
2237+ }
2238+ hasUpdates = true;
2239+ }
2240+
2241+ // Handle avatar
2242+ const avatarData = args.avatar ? await resolveAvatarData(args.avatar) : null;
2243+ if (avatarData) {
2244+ const resizePrompt = !isFalseBoolean(args.avatarPromptResize ?? 'true');
2245+ const uploaded = await uploadPersonaAvatar(avatarId, avatarData, { resizePrompt });
2246+ if (uploaded) {
2247+ hasUpdates = true;
2248+ }
2249+ }
2250+
2251+ if (!hasUpdates) {
2252+ toastr.info(t`No fields provided to update`);
2253+ return avatarId;
2254+ }
2255+
2256+ saveSettingsDebounced();
2257+ await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
2258+
2259+ // Refresh UI if the updated persona is the active one
2260+ if (avatarId === user_avatar) {
2261+ setPersonaDescription();
2262+ }
2263+ await getUserAvatars(true, avatarId);
2264+ updatePersonaUIStates();
2265+
2266+ toastr.success(t`Persona "${power_user.personas[avatarId]}" updated successfully`);
2267+ return avatarId;
2268+}
2269+
2270+/**
2271+ * Retrieves persona data or a specific field.
2272+ * @param {object} args Named arguments from the slash command
2273+ * @returns {Promise<string>} The persona data or field value
2274+ */
2275+async function getPersonaDataCallback(args) {
2276+ const persona = getTargetPersona(args.persona);
2277+ if (!persona) return '';
2278+
2279+ const avatarId = persona.avatar;
2280+ const descriptor = power_user.persona_descriptions[avatarId] ?? {};
2281+
2282+ if (args.field) {
2283+ /** @type {Record<string, unknown>} */
2284+ const fieldMap = {
2285+ name: power_user.personas[avatarId] ?? '',
2286+ description: descriptor.description ?? '',
2287+ title: descriptor.title ?? '',
2288+ position: descriptor.position ?? persona_description_positions.IN_PROMPT,
2289+ depth: descriptor.depth ?? DEFAULT_DEPTH,
2290+ role: descriptor.role ?? DEFAULT_ROLE,
2291+ lorebook: descriptor.lorebook ?? '',
2292+ avatar: avatarId,
2293+ default: power_user.default_persona === avatarId,
2294+ connections: descriptor.connections ?? [],
2295+ };
2296+
2297+ const value = fieldMap[args.field];
2298+ if (value === undefined) {
2299+ toastr.warning(t`Unknown persona field "${args.field}"`);
2300+ return '';
2301+ }
2302+
2303+ return await slashCommandReturnHelper.doReturn(
2304+ args.return ?? 'pipe', value,
2305+ { objectToStringFunc: x => typeof x === 'object' ? JSON.stringify(x) : String(x) },
2306+ );
2307+ }
2308+
2309+ // Return full persona data
2310+ const personaData = {
2311+ avatar: avatarId,
2312+ name: power_user.personas[avatarId] ?? '',
2313+ description: descriptor.description ?? '',
2314+ title: descriptor.title ?? '',
2315+ position: descriptor.position ?? persona_description_positions.IN_PROMPT,
2316+ depth: descriptor.depth ?? DEFAULT_DEPTH,
2317+ role: descriptor.role ?? DEFAULT_ROLE,
2318+ lorebook: descriptor.lorebook ?? '',
2319+ default: power_user.default_persona === avatarId,
2320+ connections: descriptor.connections ?? [],
2321+ };
2322+
2323+ return await slashCommandReturnHelper.doReturn(
2324+ args.return ?? 'pipe', personaData,
2325+ { objectToStringFunc: x => JSON.stringify(x, null, 2) },
2326+ );
2327+}
2328+
2329+/**
2330+ * Deletes a persona via slash command.
2331+ * @param {object} args Named arguments from the slash command
2332+ * @returns {Promise<string>} 'true' if deleted, 'false' otherwise
2333+ */
2334+async function deletePersonaCallback(args) {
2335+ const persona = getTargetPersona(args.persona);
2336+ if (!persona) return 'false';
2337+
2338+ const silent = isTrueBoolean(args.silent);
2339+ const success = await deletePersona(persona.avatar, { silent });
2340+ return String(success);
2341+}
2342+
2343+/**
2344+ * Duplicates a persona via slash command.
2345+ * @param {object} args Named arguments from the slash command
2346+ * @returns {Promise<string>} Avatar key of the duplicated persona, or empty string on failure
2347+ */
2348+async function duplicatePersonaCallback(args) {
2349+ const persona = getTargetPersona(args.persona);
2350+ if (!persona) return '';
2351+
2352+ const shouldSelect = isTrueBoolean(args.select);
2353+ const newAvatarId = await duplicatePersona(persona.avatar, { silent: true, select: shouldSelect });
2354+
2355+ if (!newAvatarId) {
2356+ toastr.error(t`Failed to duplicate persona`);
2357+ return '';
2358+ }
2359+
2360+ toastr.success(t`Persona "${power_user.personas[newAvatarId]}" duplicated successfully`);
2361+ return newAvatarId;
2362+}
2363+
2364+// #endregion
2365+
18162366/**
18172367 * Locks or unlocks the persona of the current chat.
18182368 * @param {{type: string}} _args Named arguments
@@ -1870,10 +2420,9 @@ async function setNameCallback({ mode = 'all' }, name) {
18702420
18712421 // If the name matches a persona avatar, or a name, auto-select it
18722422 if (['lookup', 'all'].includes(mode)) {
1873- let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1];
2423+ const persona = findPersona({ name });
1874- if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
18752424 if (persona) {
18762425 await autoSelectPersona(persona.name, { personaKey: persona.avatar });
18772426 return '';
18782427 } else if (mode === 'lookup') {
18792428 toastr.warning(`Persona ${name} not found`);
@@ -1889,28 +2438,375 @@ async function setNameCallback({ mode = 'all' }, name) {
18892438 return '';
18902439}
18912440
18922441async function syncCallback(args, value) {
1893- $('#sync_name_button').trigger('click');
2442+ const range = value ? stringToRange(value, 0, chat.length - 1) : null;
2443+
2444+ if (value && !range) {
2445+ console.warn(`WARN: Invalid range provided for /persona-sync command: ${value}`);
2446+ return '';
2447+ }
2448+
2449+ const quiet = !isFalseBoolean(args?.quiet);
2450+ const nameFilter = typeof args?.from === 'string' ? args.from.trim() : '';
2451+ const start = range ? range.start : 0;
2452+ const end = range ? range.end : chat.length - 1;
2453+
2454+ await syncUserNameToPersona({ start, end, quiet, nameFilter });
2455+
18942456 return '';
18952457}
18962458
2459+/**
2460+ * Returns all unique user message names in the current chat for enum autocomplete.
2461+ * @returns {SlashCommandEnumValue[]}
2462+ */
2463+function userMessageNamesEnumProvider() {
2464+ return chat
2465+ .filter(mes => mes.is_user)
2466+ .map(mes => mes.name)
2467+ .filter(onlyUnique)
2468+ .sort(sortIgnoreCaseAndAccents)
2469+ .map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.persona));
2470+}
2471+
18972472function registerPersonaSlashCommands() {
2473+ // Shared persona field definitions for persona CRUD commands
2474+ const getPersonaFieldArgs = ({ requiredFields = [] } = {}) => [
2475+ SlashCommandNamedArgument.fromProps({
2476+ name: 'name',
2477+ description: t`The name of the persona`,
2478+ typeList: [ARGUMENT_TYPE.STRING],
2479+ isRequired: requiredFields.includes('name'),
2480+ }),
2481+ SlashCommandNamedArgument.fromProps({
2482+ name: 'description',
2483+ description: t`The persona description (sent with messages for AI context)`,
2484+ typeList: [ARGUMENT_TYPE.STRING],
2485+ isRequired: requiredFields.includes('description'),
2486+ }),
2487+ SlashCommandNamedArgument.fromProps({
2488+ name: 'title',
2489+ description: t`A display title for the persona (not sent to the AI, display only)`,
2490+ typeList: [ARGUMENT_TYPE.STRING],
2491+ isRequired: requiredFields.includes('title'),
2492+ }),
2493+ SlashCommandNamedArgument.fromProps({
2494+ name: 'avatar',
2495+ description: t`Avatar image. Use "prompt" to open file picker, or provide a local ST file path or base64 data URL. Can also be the return value of /imagine.`,
2496+ typeList: [ARGUMENT_TYPE.STRING],
2497+ isRequired: requiredFields.includes('avatar'),
2498+ enumList: [
2499+ new SlashCommandEnumValue('prompt', 'Open file picker to select an image', enumTypes.enum, '📁'),
2500+ new SlashCommandEnumValue('characters/...', 'Character avatars path (e.g., characters/Name.png)', enumTypes.enum, '📄', (input) => commonEnumMatchProviders.folderEnum(input, 'characters/'), () => 'characters/'),
2501+ new SlashCommandEnumValue('backgrounds/...', 'Background image path', enumTypes.enum, '📄', (input) => commonEnumMatchProviders.folderEnum(input, 'backgrounds/'), () => 'backgrounds/'),
2502+ new SlashCommandEnumValue('User Avatars/...', 'User avatar path', enumTypes.enum, '📄', (input) => commonEnumMatchProviders.folderEnum(input, 'User Avatars/'), () => 'User Avatars/'),
2503+ new SlashCommandEnumValue('assets/...', 'Asset file path', enumTypes.enum, '📄', (input) => commonEnumMatchProviders.folderEnum(input, 'assets/'), () => 'assets/'),
2504+ new SlashCommandEnumValue('user/images/...', 'User image path', enumTypes.enum, '📄', (input) => commonEnumMatchProviders.folderEnum(input, 'user/images/'), () => 'user/images/'),
2505+ ],
2506+ }),
2507+ SlashCommandNamedArgument.fromProps({
2508+ name: 'avatarPromptResize',
2509+ description: t`Whether to show the avatar resize/crop dialog when uploading. Ignored if "Never resize avatars" is enabled in settings.`,
2510+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2511+ defaultValue: 'true',
2512+ enumProvider: commonEnumProviders.boolean('trueFalse'),
2513+ }),
2514+ SlashCommandNamedArgument.fromProps({
2515+ name: 'descriptionPosition',
2516+ description: t`Where to inject the persona description in the prompt`,
2517+ typeList: [ARGUMENT_TYPE.STRING],
2518+ enumList: [
2519+ new SlashCommandEnumValue('inPrompt', t`In Prompt (default)`, enumTypes.enum),
2520+ new SlashCommandEnumValue('topAN', t`Top of Author's Note`, enumTypes.enum),
2521+ new SlashCommandEnumValue('bottomAN', t`Bottom of Author's Note`, enumTypes.enum),
2522+ new SlashCommandEnumValue('atDepth', t`At a specific depth (uses descriptionDepth and descriptionRole)`, enumTypes.enum),
2523+ new SlashCommandEnumValue('none', t`None (don't inject)`, enumTypes.enum),
2524+ ],
2525+ }),
2526+ SlashCommandNamedArgument.fromProps({
2527+ name: 'descriptionDepth',
2528+ description: t`Depth for the persona description (when position is "atDepth")`,
2529+ typeList: [ARGUMENT_TYPE.NUMBER],
2530+ }),
2531+ SlashCommandNamedArgument.fromProps({
2532+ name: 'descriptionRole',
2533+ description: t`Role for the persona description (when position is "atDepth")`,
2534+ typeList: [ARGUMENT_TYPE.STRING],
2535+ enumList: commonEnumProviders.messageRoles(),
2536+ }),
2537+ SlashCommandNamedArgument.fromProps({
2538+ name: 'lorebook',
2539+ description: t`The name of the lorebook/world info to attach to this persona`,
2540+ typeList: [ARGUMENT_TYPE.STRING],
2541+ enumProvider: commonEnumProviders.worlds,
2542+ }),
2543+ ];
2544+
2545+ // Shared persona target argument (for commands that operate on an existing persona)
2546+ const personaTargetArg = SlashCommandNamedArgument.fromProps({
2547+ name: 'persona',
2548+ description: t`Persona name or avatar key. If not provided, uses the currently active persona.`,
2549+ typeList: [ARGUMENT_TYPE.STRING],
2550+ enumProvider: commonEnumProviders.personas({ allowPersonaKey: true }),
2551+ });
2552+
2553+ // ========================
2554+ // New CRUD commands
2555+ // ========================
2556+
2557+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2558+ name: 'persona-create',
2559+ callback: createPersonaCallback,
2560+ returns: t`the avatar key (unique identifier) of the created persona`,
2561+ namedArgumentList: [
2562+ ...getPersonaFieldArgs({ requiredFields: ['name'] }),
2563+ SlashCommandNamedArgument.fromProps({
2564+ name: 'select',
2565+ description: t`Whether to select/activate the persona after creation`,
2566+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2567+ defaultValue: 'true',
2568+ enumProvider: commonEnumProviders.boolean('trueFalse'),
2569+ }),
2570+ ],
2571+ helpString: `
2572+ <div>
2573+ ${t`Creates a new persona with the specified attributes. Returns the avatar key of the created persona.`}
2574+ </div>
2575+ <div>
2576+ <strong>${t`Required arguments:`}</strong>
2577+ <ul>
2578+ <li><code>name</code> – ${t`The persona's display name.`}</li>
2579+ </ul>
2580+ </div>
2581+ <div>
2582+ <strong>${t`Note on avatar:`}</strong>
2583+ ${t`The <code>avatar</code> argument accepts <code>prompt</code> to open a file picker, a local ST file path, or a base64 data URL. Can also be the return value of <code>/imagine</code>. If not provided, a default avatar will be used.`}
2584+ </div>
2585+ <div>
2586+ <strong>${t`Example:`}</strong>
2587+ <ul>
2588+ <li>
2589+ <pre><code>/persona-create name="Alice" description="A curious adventurer"</code></pre>
2590+ </li>
2591+ <li>
2592+ <pre><code>/persona-create name="Bob" avatar=prompt lorebook="detective_lore" select=false</code></pre>
2593+ </li>
2594+ <li>
2595+ <pre><code>/imagine portrait of an elf | /persona-create name="Elf" avatar="{{pipe}}"</code></pre>
2596+ </li>
2597+ </ul>
2598+ </div>
2599+ `,
2600+ }));
2601+
2602+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2603+ name: 'persona-update',
2604+ callback: updatePersonaCallback,
2605+ returns: t`the avatar key of the updated persona`,
2606+ namedArgumentList: [
2607+ personaTargetArg,
2608+ ...getPersonaFieldArgs(),
2609+ ],
2610+ helpString: `
2611+ <div>
2612+ ${t`Updates an existing persona's attributes. Only the provided fields are changed; others are left untouched.`}
2613+ </div>
2614+ <div>
2615+ ${t`If no <code>persona</code> argument is provided, updates the currently active persona.`}
2616+ </div>
2617+ <div>
2618+ <strong>${t`Example:`}</strong>
2619+ <ul>
2620+ <li>
2621+ <pre><code>/persona-update description="An updated description"</code></pre>
2622+ ${t`Updates the current persona's description.`}
2623+ </li>
2624+ <li>
2625+ <pre><code>/persona-update persona="Alice" name="Alice 2.0" descriptionPosition=atDepth descriptionDepth=3</code></pre>
2626+ ${t`Renames Alice and sets her description to inject at depth 3.`}
2627+ </li>
2628+ <li>
2629+ <pre><code>/imagine portrait | /persona-update avatar="{{pipe}}"</code></pre>
2630+ ${t`Generates an image and sets it as the current persona's avatar.`}
2631+ </li>
2632+ </ul>
2633+ </div>
2634+ `,
2635+ }));
2636+
2637+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2638+ name: 'persona-get',
2639+ aliases: ['persona-data'],
2640+ callback: getPersonaDataCallback,
2641+ returns: t`persona data as JSON or a specific field value`,
2642+ namedArgumentList: [
2643+ personaTargetArg,
2644+ SlashCommandNamedArgument.fromProps({
2645+ name: 'field',
2646+ description: t`Specific field to retrieve. If not provided, returns the entire persona data as JSON.`,
2647+ typeList: [ARGUMENT_TYPE.STRING],
2648+ enumList: [
2649+ new SlashCommandEnumValue('name', t`Persona name`, enumTypes.enum, enumIcons.persona),
2650+ new SlashCommandEnumValue('description', t`Persona description`, enumTypes.enum, enumIcons.default),
2651+ new SlashCommandEnumValue('title', t`Display title`, enumTypes.enum, enumIcons.default),
2652+ new SlashCommandEnumValue('position', t`Description position (numeric)`, enumTypes.enum, enumIcons.default),
2653+ new SlashCommandEnumValue('depth', t`Description depth`, enumTypes.enum, enumIcons.default),
2654+ new SlashCommandEnumValue('role', t`Description role (numeric)`, enumTypes.enum, enumIcons.default),
2655+ new SlashCommandEnumValue('lorebook', t`Attached lorebook name`, enumTypes.enum, enumIcons.world),
2656+ new SlashCommandEnumValue('avatar', t`Avatar filename (unique key)`, enumTypes.enum, enumIcons.persona),
2657+ new SlashCommandEnumValue('default', t`Whether this is the default persona`, enumTypes.enum, enumIcons.default),
2658+ new SlashCommandEnumValue('connections', t`Character/group connections (array)`, enumTypes.enum, enumIcons.character),
2659+ ],
2660+ }),
2661+ SlashCommandNamedArgument.fromProps({
2662+ name: 'return',
2663+ description: t`The way to return the result`,
2664+ typeList: [ARGUMENT_TYPE.STRING],
2665+ defaultValue: 'pipe',
2666+ enumList: slashCommandReturnHelper.enumList({ allowPipe: true, allowObject: true, allowPopup: true, allowTextVersion: false }),
2667+ }),
2668+ ],
2669+ helpString: `
2670+ <div>
2671+ ${t`Retrieves persona data. Can return all data as JSON or a specific field value.`}
2672+ </div>
2673+ <div>
2674+ ${t`If no <code>persona</code> argument is provided, uses the currently active persona.`}
2675+ </div>
2676+ <div>
2677+ <strong>${t`Example:`}</strong>
2678+ <ul>
2679+ <li>
2680+ <pre><code>/persona-get field=description | /echo</code></pre>
2681+ ${t`Outputs the current persona's description.`}
2682+ </li>
2683+ <li>
2684+ <pre><code>/persona-get persona="Alice" field=name</code></pre>
2685+ ${t`Returns Alice's persona name.`}
2686+ </li>
2687+ <li>
2688+ <pre><code>/persona-get return=object | /json-get key=avatar</code></pre>
2689+ ${t`Returns the current persona's full data as an object, then extracts the avatar key.`}
2690+ </li>
2691+ </ul>
2692+ </div>
2693+ `,
2694+ }));
2695+
2696+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2697+ name: 'persona-delete',
2698+ callback: deletePersonaCallback,
2699+ returns: t`true if the persona was deleted, false otherwise`,
2700+ namedArgumentList: [
2701+ personaTargetArg,
2702+ SlashCommandNamedArgument.fromProps({
2703+ name: 'silent',
2704+ description: t`Skip the confirmation popup`,
2705+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2706+ defaultValue: 'false',
2707+ enumProvider: commonEnumProviders.boolean('trueFalse'),
2708+ }),
2709+ ],
2710+ helpString: `
2711+ <div>
2712+ ${t`Deletes a persona and its avatar from the system.`}
2713+ </div>
2714+ <div>
2715+ ${t`If no <code>persona</code> argument is provided, deletes the currently active persona.`}
2716+ </div>
2717+ <div>
2718+ <strong>⚠️ ${t`Warning:`}</strong> ${t`This action is irreversible. All data associated with the persona will be lost.`}
2719+ </div>
2720+ <div>
2721+ <strong>${t`Example:`}</strong>
2722+ <ul>
2723+ <li>
2724+ <pre><code>/persona-delete</code></pre>
2725+ ${t`Deletes the current persona (shows confirmation popup).`}
2726+ </li>
2727+ <li>
2728+ <pre><code>/persona-delete persona="Bob" silent=true</code></pre>
2729+ ${t`Deletes Bob without confirmation.`}
2730+ </li>
2731+ </ul>
2732+ </div>
2733+ `,
2734+ }));
2735+
2736+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2737+ name: 'persona-duplicate',
2738+ callback: duplicatePersonaCallback,
2739+ returns: t`the avatar key (unique identifier) of the duplicated persona`,
2740+ namedArgumentList: [
2741+ personaTargetArg,
2742+ SlashCommandNamedArgument.fromProps({
2743+ name: 'select',
2744+ description: t`Whether to select/activate the duplicated persona after creation`,
2745+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2746+ defaultValue: 'false',
2747+ enumProvider: commonEnumProviders.boolean('trueFalse'),
2748+ }),
2749+ ],
2750+ helpString: `
2751+ <div>
2752+ ${t`Duplicates a persona including all its data and avatar. Returns the avatar key of the new persona.`}
2753+ </div>
2754+ <div>
2755+ ${t`Use <code>/persona-update</code> afterwards to rename or modify the duplicated persona's fields.`}
2756+ </div>
2757+ <div>
2758+ <strong>${t`Example:`}</strong>
2759+ <ul>
2760+ <li>
2761+ <pre><code>/persona-duplicate</code></pre>
2762+ ${t`Duplicates the currently active persona.`}
2763+ </li>
2764+ <li>
2765+ <pre><code>/persona-duplicate persona="Alice" select=true</code></pre>
2766+ ${t`Duplicates Alice and selects the new persona.`}
2767+ </li>
2768+ <li>
2769+ <pre><code>/persona-duplicate | /persona-update persona="{{pipe}}" name="Clone"</code></pre>
2770+ ${t`Duplicates the current persona, then renames the clone.`}
2771+ </li>
2772+ </ul>
2773+ </div>
2774+ `,
2775+ }));
2776+
2777+ // ========================
2778+ // Existing commands (enhanced help strings)
2779+ // ========================
2780+
18982781 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
18992782 name: 'persona-lock',
19002783 aliases: ['lock', 'bind'],
19012784 callback: lockPersonaCallback,
19022785 returns: 't`The current lock state for the given type'`,
1903- helpString: 'Locks/unlocks a persona (name and avatar) to the current chat. Gets the current lock state for the given type if no state is provided.',
2786+ helpString: `
2787+ <div>
2788+ ${t`Locks/unlocks the current persona to a chat, character, or as the default. Returns the lock state if no value is provided.`}
2789+ </div>
2790+ <div>
2791+ <strong>${t`Example:`}</strong>
2792+ <ul>
2793+ <li><pre><code>/persona-lock on</code></pre> ${t`Locks persona to this chat.`}</li>
2794+ <li><pre><code>/persona-lock type=character on</code></pre> ${t`Locks persona to the current character.`}</li>
2795+ <li><pre><code>/persona-lock type=default on</code></pre> ${t`Sets persona as the default for new chats.`}</li>
2796+ <li><pre><code>/persona-lock</code></pre> ${t`Returns whether the persona is locked to this chat.`}</li>
2797+ </ul>
2798+ </div>
2799+ `,
19042800 namedArgumentList: [
19052801 SlashCommandNamedArgument.fromProps({
19062802 name: 'type',
19072803 description: 't`The type of the lock, where it should apply to'`,
19082804 typeList: [ARGUMENT_TYPE.STRING],
19092805 defaultValue: 'chat',
19102806 enumList: [
19112807 new SlashCommandEnumValue('chat', 't`Lock the persona to the current chat.'`),
19122808 new SlashCommandEnumValue('character', 't`Lock this persona to the currently selected character. If the setting is enabled, multiple personas can be locked to the same character.'`),
19132809 new SlashCommandEnumValue('default', 't`Lock this persona as the default persona for all new chats.'`),
19142810 ],
19152811 }),
19162812 ],
@@ -1922,31 +2818,97 @@ function registerPersonaSlashCommands() {
19222818 }),
19232819 ],
19242820 }));
2821+
19252822 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
19262823 name: 'persona-set',
19272824 callback: setNameCallback,
19282825 aliases: ['persona', 'name'],
19292826 namedArgumentList: [
19302827 new SlashCommandNamedArgument.fromProps({
1931- 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)',
2828+ name: 'mode',
1932- [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'],
2829+ description: t`The mode for persona selection`,
1933- ),
2830+ typeList: [ARGUMENT_TYPE.STRING],
2831+ defaultValue: 'all',
2832+ enumList: [
2833+ new SlashCommandEnumValue('lookup', t`Search for an existing persona only`),
2834+ new SlashCommandEnumValue('temp', t`Set a temporary name only (no persona lookup)`),
2835+ new SlashCommandEnumValue('all', t`Try persona lookup first, fall back to temporary name`),
2836+ ],
2837+ }),
19342838 ],
19352839 unnamedArgumentList: [
19362840 SlashCommandArgument.fromProps({
19372841 description: 'persona name',
19382842 typeList: [ARGUMENT_TYPE.STRING],
19392843 isRequired: true,
19402844 enumProvider: commonEnumProviders.personas({ allowPersonaKey: true }),
19412845 }),
19422846 ],
1943- helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',
2847+ helpString: `
2848+ <div>
2849+ ${t`Selects an existing persona by name or avatar key, or sets a temporary user name.`}
2850+ </div>
2851+ <div>
2852+ ${t`If a matching persona exists, it will be selected with its name and avatar. Otherwise (in "all" or "temp" mode), only the display name is changed temporarily.`}
2853+ </div>
2854+ <div>
2855+ <strong>${t`Example:`}</strong>
2856+ <ul>
2857+ <li><pre><code>/persona-set Alice</code></pre> ${t`Selects persona "Alice", or sets name to "Alice" if not found.`}</li>
2858+ <li><pre><code>/persona-set mode=lookup Alice</code></pre> ${t`Only selects if persona "Alice" exists.`}</li>
2859+ </ul>
2860+ </div>
2861+ `,
19442862 }));
2863+
19452864 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
19462865 name: 'persona-sync',
19472866 aliases: ['sync'],
19482867 callback: syncCallback,
1949- helpString: 'Syncs the user persona in user-attributed messages in the current chat.',
2868+ namedArgumentList: [
2869+ SlashCommandNamedArgument.fromProps({
2870+ name: 'from',
2871+ description: t`only sync messages from a certain persona name`,
2872+ typeList: [ARGUMENT_TYPE.STRING],
2873+ enumProvider: userMessageNamesEnumProvider,
2874+ }),
2875+ SlashCommandNamedArgument.fromProps({
2876+ name: 'quiet',
2877+ description: t`suppress the confirmation popup`,
2878+ typeList: [ARGUMENT_TYPE.BOOLEAN],
2879+ enumList: commonEnumProviders.boolean('trueFalse')(),
2880+ defaultValue: 'true',
2881+ }),
2882+ ],
2883+ unnamedArgumentList: [
2884+ SlashCommandArgument.fromProps({
2885+ description: t`message index (starts with 0) or range, syncs all user messages if not provided`,
2886+ typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
2887+ defaultValue: '0-{{lastMessageId}}',
2888+ }),
2889+ ],
2890+ helpString: `
2891+ <div>
2892+ ${t`Syncs the user persona (name and avatar) in user-attributed messages in the current chat.`}
2893+ </div>
2894+ <div>
2895+ ${t`If <code>from</code> is set, only messages with that specific persona name will be synced. Useful when multiple personas have been used in the same chat.`}
2896+ </div>
2897+ <div>
2898+ ${t`If <code>quiet</code> is set to <code>false</code>, a confirmation popup will be shown before syncing.`}
2899+ </div>
2900+ <div>
2901+ <strong>${t`Examples:`}</strong>
2902+ <ul>
2903+ <li><pre><code>/persona-sync</code></pre> ${t`- Sync all user messages`}</li>
2904+ <li><pre><code>/persona-sync 5</code></pre> ${t`- Sync only message 5`}</li>
2905+ <li><pre><code>/persona-sync 0-10</code></pre> ${t`- Sync messages 0 through 10`}</li>
2906+ <li><pre><code>/persona-sync from=OldPersona 0-20</code></pre> ${t`- Sync only messages with name "OldPersona" in range 0-20`}</li>
2907+ <li><pre><code>/persona-sync quiet=false</code></pre> ${t`- Sync all with confirmation popup`}</li>
2908+ <li><pre><code>/persona-sync from=TempName quiet=false 5-15</code></pre> ${t`- Sync messages with name "TempName" in range 5-15 with confirmation`}</li>
2909+ </ul>
2910+ </div>
2911+ `,
19502912 }));
19512913}
19522914
@@ -2004,7 +2966,7 @@ export async function initPersonas() {
20042966 debouncedPersonaSearch(searchQuery);
20052967 });
20062968
20072969 $('#sync_name_button').on('click', async () => await syncUserNameToPersona());
20082970 $('#avatar_upload_file').on('change', changeUserAvatar);
20092971
20102972 $(document).on('click', '#user_avatar_block .avatar-container', async function () {
public/scripts/popup.js+54 -4
@@ -2,7 +2,7 @@ import dialogPolyfill from '../lib/dialog-polyfill.esm.js';
22import { shouldSendOnEnter } from './RossAscends-mods.js';
33import { t } from './i18n.js';
44import { power_user, toastPositionClasses } from './power-user.js';
55import { clamp, removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';
66
77/** @readonly */
88/** @enum {Number} */
@@ -79,8 +79,12 @@ export const POPUP_RESULT = {
7979 * @property {string} label - The label text for the input
8080 * @property {string?} [tooltip=null] - Optional tooltip to be displayed. Default placeholder in input controls, tooltip icon behind the checkbox for those.
8181 * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set)
8282 * @property {('checkbox'|'text'|'textarea'|'number')?} [type='checkbox'] - The type of the input (default is checkbox)
8383 * @property {number?} [rows=1] - The number of rows for the input field, if the input is 'textarea'
84+ * @property {number?} [min] - The minimum value for number inputs
85+ * @property {number?} [max] - The maximum value for number inputs
86+ * @property {number?} [step] - The step value for number inputs
87+ * @property {boolean?} [disabled=false] - Whether the input should be disabled
8488 */
8589
8690/**
@@ -331,6 +335,7 @@ export class Popup {
331335 inputElement.type = 'checkbox';
332336 inputElement.id = input.id;
333337 inputElement.checked = Boolean(input.defaultState ?? false);
338+ inputElement.disabled = Boolean(input.disabled ?? false);
334339 label.appendChild(inputElement);
335340 const labelText = document.createElement('span');
336341 labelText.innerText = input.label;
@@ -356,6 +361,7 @@ export class Popup {
356361 inputElement.id = input.id;
357362 inputElement.value = String(input.defaultState ?? '');
358363 inputElement.placeholder = input.tooltip ?? '';
364+ inputElement.disabled = Boolean(input.disabled ?? false);
359365 setTitleFromTooltip(inputElement, input.tooltip);
360366
361367 const labelText = document.createElement('span');
@@ -377,6 +383,7 @@ export class Popup {
377383 inputElement.value = String(input.defaultState ?? '');
378384 inputElement.rows = input.rows ?? 1;
379385 inputElement.placeholder = input.tooltip ?? '';
386+ inputElement.disabled = Boolean(input.disabled ?? false);
380387 setTitleFromTooltip(inputElement, input.tooltip);
381388
382389 const labelText = document.createElement('span');
@@ -387,8 +394,47 @@ export class Popup {
387394 label.appendChild(inputElement);
388395
389396 this.inputControls.appendChild(label);
397+ } else if (input.type === 'number') {
398+ const label = document.createElement('label');
399+ label.classList.add('text_label', 'justifyCenter');
400+ label.setAttribute('for', input.id);
401+
402+ const inputElement = document.createElement('input');
403+ inputElement.classList.add('text_pole', 'result-control');
404+ inputElement.type = 'number';
405+ inputElement.id = input.id;
406+ inputElement.value = String(input.defaultState ?? '');
407+ inputElement.placeholder = input.tooltip ?? '';
408+ inputElement.min = String(input.min ?? '');
409+ inputElement.max = String(input.max ?? '');
410+ inputElement.step = String(input.step ?? '');
411+ inputElement.disabled = Boolean(input.disabled ?? false);
412+ setTitleFromTooltip(inputElement, input.tooltip);
413+
414+ inputElement.addEventListener('change', () => {
415+ const value = parseFloat(inputElement.value);
416+ if (isNaN(value)) return;
417+
418+ const min = Number.isFinite(input.min) ? input.min : -Infinity;
419+ const max = Number.isFinite(input.max) ? input.max : Infinity;
420+ const clamped = clamp(value, min, max);
421+
422+ if (clamped !== value) {
423+ inputElement.value = String(clamped);
424+ toastr.warning(t`Value must be between ${min} and ${max}. Clamped to ${clamped}.`);
425+ }
426+ });
427+
428+ const labelText = document.createElement('span');
429+ labelText.innerText = input.label;
430+ labelText.dataset.i18n = input.label;
431+
432+ label.appendChild(labelText);
433+ label.appendChild(inputElement);
434+
435+ this.inputControls.appendChild(label);
390436 } else {
391437 console.warn('Unknown custom input type. Only checkbox, text, number and textarea are supported.', input);
392438 return;
393439 }
394440 });
@@ -672,6 +718,10 @@ export class Popup {
672718 }
673719 }
674720
721+ if (!control) {
722+ return;
723+ }
724+
675725 if (applyAutoFocus) {
676726 control.setAttribute('autofocus', '');
677727 // Manually enable tabindex too, as this might only be applied by the interactable functionality in the background, but too late for HTML autofocus
@@ -720,7 +770,7 @@ export class Popup {
720770 this.inputResults = new Map(this.customInputs.map(input => {
721771 /** @type {HTMLInputElement} */
722772 const inputControl = this.dlg.querySelector(`#${input.id}`);
723773 const value = ['text', 'textarea', 'number'].includes(input.type) ? inputControl.value : inputControl.checked;
724774 return [inputControl.id, value];
725775 }));
726776 }
public/scripts/power-user.js+2 -11
@@ -68,6 +68,7 @@ import { bindModelTemplates } from './chat-templates.js';
6868import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js';
6969import { t } from './i18n.js';
7070import { getBackgroundPath, isCustomBackgroundUrl } from './backgrounds.js';
71+import { persona_description_positions as _persona_description_positions } from './personas.js';
7172
7273export const toastPositionClasses = [
7374 'toast-top-left',
@@ -110,17 +111,7 @@ export const send_on_enter_options = {
110111 ENABLED: 1,
111112};
112113
113114export const persona_description_positions = {_persona_description_positions;
114- IN_PROMPT: 0,
115- /**
116- * @deprecated Use persona_description_positions.IN_PROMPT instead.
117- */
118- AFTER_CHAR: 1,
119- TOP_AN: 2,
120- BOTTOM_AN: 3,
121- AT_DEPTH: 4,
122- NONE: 9,
123-};
124115
125116export const power_user = {
126117 charListGrid: false,
public/scripts/reasoning.js+72 -3
@@ -134,6 +134,7 @@ export function extractReasoningFromData(data, {
134134 case chat_completion_sources.NANOGPT:
135135 case chat_completion_sources.SILICONFLOW:
136136 case chat_completion_sources.ZAI:
137+ case chat_completion_sources.WORKERS_AI:
137138 case chat_completion_sources.CUSTOM: {
138139 return data?.choices?.[0]?.message?.reasoning_content
139140 ?? data?.choices?.[0]?.message?.reasoning
@@ -1010,6 +1011,44 @@ function registerReasoningSlashCommands() {
10101011 },
10111012 }));
10121013 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1014+ name: 'reasoning-format',
1015+ aliases: ['format-reasoning'],
1016+ returns: 'formatted string',
1017+ helpString: t`Formats reasoning and content into a single string using Reasoning Formatting settings. Useful for preparing text that can be parsed with /reasoning-parse.`,
1018+ namedArgumentList: [
1019+ SlashCommandNamedArgument.fromProps({
1020+ name: 'reasoning',
1021+ description: 'The reasoning/thinking text to format',
1022+ typeList: [ARGUMENT_TYPE.STRING],
1023+ isRequired: true,
1024+ }),
1025+ ],
1026+ unnamedArgumentList: [
1027+ SlashCommandArgument.fromProps({
1028+ description: 'The main content text',
1029+ typeList: [ARGUMENT_TYPE.STRING],
1030+ isRequired: false,
1031+ }),
1032+ ],
1033+ callback: (args, value) => {
1034+ const reasoning = String(args?.reasoning ?? '');
1035+ const content = String(value ?? '');
1036+
1037+ if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
1038+ toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`, t`Reasoning Format`);
1039+ return '';
1040+ }
1041+
1042+ if (!reasoning) {
1043+ toastr.warning(t`Reasoning argument is required.`, t`Reasoning Format`);
1044+ return '';
1045+ }
1046+
1047+ const { formatted } = formatReasoning(reasoning, content);
1048+ return formatted;
1049+ },
1050+ }));
1051+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
10131052 name: 'reasoning-template',
10141053 aliases: ['reasoning-formatting', 'reasoning-preset'],
10151054 callback: selectReasoningTemplateCallback,
@@ -1082,7 +1121,7 @@ function registerReasoningSlashCommands() {
10821121 helpString: t`Collapse the reasoning block of a message or range of messages.`,
10831122 unnamedArgumentList: reasoningVisibilityArgs,
10841123 callback: (_args, value) => {
10851124 const details = getReasoningDetailsElements(value.toString());
10861125 if (details) details.removeAttr('open');
10871126 return '';
10881127 },
@@ -1094,7 +1133,7 @@ function registerReasoningSlashCommands() {
10941133 helpString: t`Expand the reasoning block of a message or range of messages.`,
10951134 unnamedArgumentList: reasoningVisibilityArgs,
10961135 callback: (_args, value) => {
10971136 const details = getReasoningDetailsElements(value.toString());
10981137 if (details) details.attr('open', '');
10991138 return '';
11001139 },
@@ -1106,7 +1145,7 @@ function registerReasoningSlashCommands() {
11061145 helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`,
11071146 unnamedArgumentList: reasoningVisibilityArgs,
11081147 callback: (_args, value) => {
11091148 const details = getReasoningDetailsElements(value.toString());
11101149 if (!details) return '';
11111150 details.each(function () {
11121151 const $el = $(this);
@@ -1411,6 +1450,36 @@ export function parseReasoningFromString(str, { strict = true } = {}, template =
14111450}
14121451
14131452/**
1453+ * Formats reasoning and content into a string using the reasoning template.
1454+ * This is the inverse of parseReasoningFromString.
1455+ * @typedef {Object} FormattedReasoning
1456+ * @property {string} formatted The formatted string with reasoning wrapped in prefix/suffix
1457+ * @property {string} contentOnly The content without reasoning
1458+ * @param {string} reasoning The reasoning/thinking text
1459+ * @param {string} content The main content/response text
1460+ * @param {ReasoningTemplate} [template=null] Optional template to use. Defaults to power_user.reasoning
1461+ * @returns {FormattedReasoning} Object containing both formatted (reasoning + content) and contentOnly
1462+ */
1463+export function formatReasoning(reasoning, content, template = null) {
1464+ template = template ?? power_user.reasoning;
1465+
1466+ // If no reasoning provided, return content only
1467+ if (!reasoning || !template.prefix || !template.suffix) {
1468+ return { formatted: content, contentOnly: content };
1469+ }
1470+
1471+ // Substitute macros in template parts
1472+ const prefix = substituteParams(template.prefix || '');
1473+ const suffix = substituteParams(template.suffix || '');
1474+ const separator = substituteParams(template.separator || '');
1475+
1476+ // Build the formatted string: prefix + reasoning + suffix + separator + content
1477+ const formatted = `${prefix}${reasoning}${suffix}${separator}${content}`;
1478+
1479+ return { formatted, contentOnly: content };
1480+}
1481+
1482+/**
14141483 * Parse reasoning in an array of swipe strings if auto-parsing is enabled.
14151484 * @param {string[]} swipes Array of swipe strings
14161485 * @param {{extra: Partial<ReasoningMessageExtra>}[]} swipeInfoArray Array of swipe info objects
public/scripts/secrets.js+186 -15
@@ -1,5 +1,5 @@
11import { DOMPurify, moment, sha256 } from '../lib.js';
22import { event_types, eventSource, getRequestHeaders, saveSettings } from '../script.js';
33import { t } from './i18n.js';
44import { chat_completion_sources } from './openai.js';
55import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
@@ -12,7 +12,9 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
1212import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
1313import { renderTemplateAsync } from './templates.js';
1414import { textgen_types } from './textgen-settings.js';
1515import { copyText, isTrueBooleangetCurrentUserHandle } from './utilsuser.js';
16+import { copyText, isTrueBoolean, uuidv4 } from './utils.js';
17+import { accountStorage } from './util/AccountStorage.js';
1618
1719export const SECRET_KEYS = {
1820 HORDE: 'api_key_horde',
@@ -76,6 +78,7 @@ export const SECRET_KEYS = {
7678 POLLINATIONS: 'api_key_pollinations',
7779 VOLCENGINE_APP_ID: 'volcengine_app_id',
7880 VOLCENGINE_ACCESS_KEY: 'volcengine_access_key',
81+ WORKERS_AI: 'api_key_workers_ai',
7982};
8083
8184const FRIENDLY_NAMES = {
@@ -129,7 +132,7 @@ const FRIENDLY_NAMES = {
129132 [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)',
130133 [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)',
131134 [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)',
132135 [SECRET_KEYS.MINIMAX]: 'MiniMax TTS',
133136 [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID',
134137 [SECRET_KEYS.MOONSHOT]: 'Moonshot AI',
135138 [SECRET_KEYS.COMETAPI]: 'CometAPI',
@@ -140,6 +143,7 @@ const FRIENDLY_NAMES = {
140143 [SECRET_KEYS.POLLINATIONS]: 'Pollinations',
141144 [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID',
142145 [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key',
146+ [SECRET_KEYS.WORKERS_AI]: 'Cloudflare Workers AI',
143147};
144148
145149const INPUT_MAP = {
@@ -182,8 +186,9 @@ const INPUT_MAP = {
182186 [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai',
183187 [SECRET_KEYS.ZAI]: '#api_key_zai',
184188 [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow',
185189 [SECRET_KEYS.COMFY_RUNPODMINIMAX]: '#api_key_comfy_runpodapi_key_minimax',
186190 [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations',
191+ [SECRET_KEYS.WORKERS_AI]: '#api_key_workers_ai',
187192};
188193
189194const getLabel = () => moment().format('L LT');
@@ -414,7 +419,6 @@ export async function readSecretState() {
414419 secret_state = await response.json();
415420 updateSecretDisplay();
416421 updateInputDataLists();
417- await checkOpenRouterAuth();
418422 }
419423 } catch {
420424 console.error('Could not read secrets file');
@@ -495,6 +499,25 @@ export async function renameSecret(key, id, label) {
495499}
496500
497501/**
502+ * Generates a storage key for the PKCE code verifier for a given source.
503+ * @param {string} source Source for which to generate the storage key (e.g. 'openrouter')
504+ * @returns {string} The storage key for the PKCE code verifier for a given source.
505+ */
506+const getVerifierKey = (source) => `${getCurrentUserHandle()}_${source}_code_verifier`;
507+
508+/**
509+ * Generates a code challenge for PKCE authentication flows.
510+ * @param {string} input Input secret string to generate the code challenge from.
511+ * @returns {string} S256 code challenge generated from the input string, encoded in base64url format.
512+ */
513+const generateChallenge = (input) => {
514+ const encoder = new TextEncoder();
515+ const data = encoder.encode(input);
516+ const hashBytes = sha256.array(data);
517+ return btoa(String.fromCharCode(...hashBytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
518+};
519+
520+/**
498521 * Redirects the user to authorize OpenRouter.
499522 */
500523async function authorizeOpenRouter() {
@@ -505,8 +528,15 @@ async function authorizeOpenRouter() {
505528 }
506529 }
507530
531+ // Generate a PKCE code verifier and code challenge
532+ const codeVerifier = uuidv4() + uuidv4();
533+ const codeChallenge = generateChallenge(codeVerifier);
534+ accountStorage.setItem(getVerifierKey('openrouter'), codeVerifier);
535+ await saveSettings();
536+
537+ // Redirect to OpenRouter authorization URL with the code challenge and callback URL
508538 const redirectUrl = new URL('/callback/openrouter', window.location.origin);
509539 const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(redirectUrl.toString())}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
510540 location.href = openRouterUrl;
511541}
512542
@@ -514,16 +544,32 @@ async function authorizeOpenRouter() {
514544 * Checks if the OpenRouter authorization code is present in the URL, and if so, exchanges it for an API key.
515545 * @returns {Promise<void>}
516546 */
517547export async function checkOpenRouterAuth() {
518548 const params = new URLSearchParams(location.search);
519549 const source = params.get('source');
520550 if (source === 'openrouter') {
521551 const query = new URLSearchParams(params.get('query'));
522- const code = query.get('code');
523552 try {
553+ const code = query.get('code');
554+ if (!code) {
555+ throw new Error('OpenRouter authorization code not found in URL');
556+ }
557+
558+ const codeVerifier = accountStorage.getItem(getVerifierKey('openrouter'));
559+ if (!codeVerifier) {
560+ throw new Error('OpenRouter code verifier not found in accountStorage');
561+ }
562+
524563 const response = await fetch('https://openrouter.ai/api/v1/auth/keys', {
525564 method: 'POST',
526- body: JSON.stringify({ code }),
565+ headers: {
566+ 'Content-Type': 'application/json',
567+ },
568+ body: JSON.stringify({
569+ code: code,
570+ code_verifier: codeVerifier,
571+ code_challenge_method: 'S256',
572+ }),
527573 });
528574
529575 if (!response.ok) {
@@ -539,18 +585,22 @@ async function checkOpenRouterAuth() {
539585
540586 if (secret_state[SECRET_KEYS.OPENROUTER]) {
541587 toastr.success('OpenRouter token saved');
542- // Remove the code from the URL
543- const currentUrl = window.location.href;
544- const urlWithoutSearchParams = currentUrl.split('?')[0];
545- window.history.pushState({}, '', urlWithoutSearchParams);
546588 } else {
547589 throw new Error('OpenRouter token not saved');
548590 }
549591 } catch (err) {
550592 toastr.error('Could not verify OpenRouter token. Please try again.');
551- return;
593+ console.error('OpenRouter OAuth error:', err);
594+ } finally {
595+ // Remove the code from the URL
596+ const currentUrl = window.location.href;
597+ const urlWithoutSearchParams = currentUrl.split('?')[0];
598+ window.history.pushState({}, '', urlWithoutSearchParams);
552599 }
553600 }
601+
602+ // Clean-up any code verifiers that might be left in accountStorage from abandoned auth flows
603+ accountStorage.removeItem(getVerifierKey('openrouter'));
554604}
555605
556606/**
@@ -1118,5 +1168,126 @@ export async function initSecrets() {
11181168 warningElement.toggle(value.length > 0);
11191169 });
11201170 $('.openrouter_authorize').on('click', authorizeOpenRouter);
1171+ $(document).on('click', '.openrouter_view_credits', async function (event) {
1172+ event.preventDefault();
1173+ const display = $(this).siblings('.openrouter_credits_display').first();
1174+ display.text(t`Loading…`);
1175+ try {
1176+ const response = await fetch('/api/openrouter/credits', {
1177+ method: 'POST',
1178+ headers: getRequestHeaders(),
1179+ });
1180+ if (!response.ok) {
1181+ throw new Error(`HTTP ${response.status}`);
1182+ }
1183+ const data = await response.json();
1184+ if (typeof data.remaining !== 'number') {
1185+ throw new Error('Invalid response');
1186+ }
1187+ display.text(`$${data.remaining.toFixed(2)}`);
1188+ } catch (error) {
1189+ console.error('Failed to fetch OpenRouter credits:', error);
1190+ display.text('');
1191+ toastr.error(t`Could not fetch OpenRouter credits. Please try again.`);
1192+ }
1193+ });
1194+
1195+ const formatNanoGptNumber = (num, decimals = null) => {
1196+ const number = Number(num);
1197+ if (!Number.isFinite(number)) return decimals === null ? '0' : (0).toFixed(decimals);
1198+ if (decimals !== null) return number.toFixed(decimals);
1199+ if (number >= 1000000) return (number / 1000000).toFixed(1) + 'M';
1200+ if (number >= 1000) return (number / 1000).toFixed(1) + 'K';
1201+ return number.toString();
1202+ };
1203+
1204+ const createNanoGptCreditsPopup = (credits) => {
1205+ const root = $('<div class="nanogpt-credits-popup"></div>');
1206+ root.append($('<h3></h3>').text(t`NanoGPT Credits & Usage`));
1207+
1208+ const rows = [
1209+ [t`USD`, `$${formatNanoGptNumber(credits.usdBalance, 2)}`],
1210+ [t`NANO`, formatNanoGptNumber(credits.nanoBalance, 3)],
1211+ ];
1212+
1213+ const addUsage = (label, usage, limit) => {
1214+ if (usage) {
1215+ rows.push([label, t`${formatNanoGptNumber(usage.used)} / ${formatNanoGptNumber(limit)} (${formatNanoGptNumber(usage.remaining)} left)`]);
1216+ }
1217+ };
1218+
1219+ if (credits.subscription?.active) {
1220+ const sub = credits.subscription;
1221+ const subEndDate = sub.period?.currentPeriodEnd ? moment(sub.period.currentPeriodEnd).format('LL') : t`Unknown`;
1222+ rows.push([t`Sub`, t`Active (until ${subEndDate})`]);
1223+ addUsage(t`Tokens/wk`, sub.weekly_tokens, sub.limits?.weeklyInputTokens);
1224+ addUsage(t`Tokens/day`, sub.daily_tokens, sub.limits?.dailyInputTokens);
1225+ addUsage(t`Images/day`, sub.daily_images, sub.limits?.dailyImages);
1226+ }
1227+
1228+ for (const [label, value] of rows) {
1229+ root.append($('<div></div>').text(label));
1230+ root.append($('<div></div>').text(value));
1231+ }
1232+
1233+ return root;
1234+ };
1235+
1236+ $(document).on('click', '.nanogpt_view_credits', async function (event) {
1237+ event.preventDefault();
1238+ const display = $(this).siblings('.nanogpt_credits_display').first();
1239+ display.empty().text(t`Loading…`);
1240+
1241+ try {
1242+ const response = await fetch('/api/nanogpt/credits', {
1243+ method: 'POST',
1244+ headers: getRequestHeaders(),
1245+ });
1246+
1247+ if (!response.ok) {
1248+ throw new Error(`HTTP ${response.status}`);
1249+ }
1250+
1251+ const data = await response.json();
1252+
1253+ const usdBalance = Number(data.usd_balance);
1254+ const nanoBalance = Number(data.nano_balance);
1255+ if (!Number.isFinite(usdBalance) || !Number.isFinite(nanoBalance)) {
1256+ throw new Error('Invalid response');
1257+ }
1258+
1259+ let balances = [`$${formatNanoGptNumber(usdBalance, 2)}`];
1260+ if (nanoBalance > 0) {
1261+ balances.push(`${formatNanoGptNumber(nanoBalance, 3)} NANO`);
1262+ }
1263+ let shortInlineText = balances.join(' | ');
1264+
1265+ if (data.subscription?.active) {
1266+ shortInlineText += ` | ${t`Sub Active`}`;
1267+ }
1268+
1269+ display.empty().text(shortInlineText + ' ');
1270+
1271+ const infoBtn = $('<i class="fa-solid fa-circle-info cursor-pointer nanogpt_info_btn"></i>');
1272+ infoBtn.attr('title', t`View details`);
1273+ infoBtn.data('credits', {
1274+ usdBalance,
1275+ nanoBalance,
1276+ subscription: data.subscription,
1277+ });
1278+ display.append(infoBtn);
1279+ } catch (error) {
1280+ console.error('Failed to fetch NanoGPT credits:', error);
1281+ display.empty().text('');
1282+ toastr.error(t`Could not fetch NanoGPT credits. Please try again.`);
1283+ }
1284+ });
1285+
1286+ $(document).on('click', '.nanogpt_info_btn', async function () {
1287+ const credits = $(this).data('credits');
1288+ if (credits) {
1289+ await callGenericPopup(createNanoGptCreditsPopup(credits), POPUP_TYPE.TEXT);
1290+ }
1291+ });
11211292 registerSecretSlashCommands();
11221293}
public/scripts/slash-commands.js+1 -1
@@ -1,5 +1,5 @@
11import { Fuse, DOMPurify } from '../lib.js';
22import { canUseNegativeLookbehind, copyText, findPersona, flashHighlight, getBase64Async, ensureImageFormatSupported, supportedImageMimeTypes, isExternalUrlresolveAvatarData } from './utils.js';
33
44import {
55 Generate,
@@ -70,7 +70,7 @@ import { hideChatMessageRange } from './chats.js';
7070import { getContext, saveMetadataDebounced } from './extensions.js';
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+0 -0
public/scripts/st-context.js+0 -0
public/scripts/streaming-display.js+0 -0
public/scripts/swipe-picker.js+0 -0
public/scripts/tags.js+0 -0
public/scripts/templates/thirdPartyExtensionWarning.html+0 -0
public/scripts/templates/welcomePanel.html+0 -0
public/scripts/textgen-models.js+0 -0
public/scripts/textgen-settings.js+0 -0
public/scripts/tokenizers.js+0 -0
public/scripts/tool-calling.js+0 -0
public/scripts/utils.js+0 -0
public/scripts/welcome-screen.js+0 -0
public/scripts/world-info.js+0 -0
public/style.css+0 -0
src/additional-headers.js+0 -0
src/command-line.js+0 -0
src/constants.js+0 -0
src/electron/Start.bat+0 -0
src/electron/package-lock.json+0 -0
src/electron/package.json+0 -0
src/endpoints/backends/chat-completions.js+0 -0
src/endpoints/backends/kobold.js+0 -0
src/endpoints/backends/text-completions.js+0 -0
src/endpoints/characters.js+0 -0
src/endpoints/content-manager.js+0 -0
src/endpoints/extensions.js+0 -0
src/endpoints/nanogpt.js+0 -0
src/endpoints/novelai.js+0 -0
src/endpoints/openai.js+0 -0
src/endpoints/openrouter.js+0 -0
src/endpoints/secrets.js+0 -0
src/endpoints/speech.js+0 -0
src/endpoints/stable-diffusion.js+0 -0
src/endpoints/tokenizers.js+0 -0
src/endpoints/users-private.js+0 -0
src/endpoints/users-public.js+0 -0
src/endpoints/vectors.js+0 -0
src/express-common.js+0 -0
src/middleware/accessLogWriter.js+0 -0
src/middleware/basicAuth.js+0 -0
src/middleware/corsProxy.js+0 -0
src/middleware/hostWhitelist.js+0 -0
src/middleware/userCss.js+0 -0
src/middleware/validateFileName.js+0 -0
src/middleware/whitelist.js+0 -0
src/private-request-filter.js+0 -0
src/prompt-converters.js+0 -0
src/request-proxy.js+0 -0
src/server-init.js+0 -0
src/server-main.js+0 -0
src/server-startup.js+0 -0
src/users.js+0 -0
src/util.js+0 -0
src/vectors/openai-vectors.js+0 -0
start.sh+0 -0
tests/package-lock.json+0 -0
tests/private-request-filter.test.js+0 -0
tests/prompt-converters.test.js+0 -0
tests/tavern-card-validator.test.js+0 -0
tests/util-pure.test.js+0 -0
tests/util.test.js+0 -0
Diff truncated