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:
19 - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v319 - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3
20 with:20 with:
21 node-version: 2421 node-version: 24
22 - run: npm ci22 - run: npm ci --omit=dev --ignore-scripts
2323
24 publish-npm:24 publish-npm:
25 needs: build25 needs: build
@@ -30,5 +30,5 @@ jobs:
30 with:30 with:
31 node-version: 2431 node-version: 24
32 registry-url: https://registry.npmjs.org/32 registry-url: https://registry.npmjs.org/
33 - run: npm ci33 - run: npm ci --omit=dev --ignore-scripts
34 - run: npm publish34 - run: npm publish
.npmrc+2 -0
@@ -0,0 +1,2 @@
1ignore-scripts=true
2min-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
20RUN \20RUN \
21 echo "*** Install npm packages ***" && \21 echo "*** Install npm packages ***" && \
22 npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force22 npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts && npm cache clean --force
2323
24# Create config directory and link config.yaml. Added hardcoded dirs(constants.js?)24# Create config directory and link config.yaml. Added hardcoded dirs(constants.js?)
25# that must be present for Non-Root Mode and volumeless docker runs.25# that must be present for Non-Root Mode and volumeless docker runs.
Start.bat+1 -1
@@ -1,7 +1,7 @@
1@echo off1@echo off
2pushd %~dp02pushd %~dp0
3set NODE_ENV=production3set NODE_ENV=production
4call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev4call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts
5node server.js %*5node server.js %*
6pause6pause
7popd7popd
UpdateAndStart.bat+1 -1
@@ -20,7 +20,7 @@ if %errorlevel% neq 0 (
20 )20 )
21)21)
22set NODE_ENV=production22set NODE_ENV=production
23call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev23call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts
24node server.js %*24node server.js %*
25:end25:end
26pause26pause
UpdateForkAndStart.bat+1 -1
@@ -102,7 +102,7 @@ if %errorlevel% neq 0 (
102102
103echo Installing npm packages and starting server103echo Installing npm packages and starting server
104set NODE_ENV=production104set NODE_ENV=production
105call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev105call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts
106node server.js %*106node server.js %*
107107
108:end108:end
default/config.yaml+48 -3
@@ -41,6 +41,9 @@ port: 8000
41# Interval in seconds to write a heartbeat file. Set to 0 to disable.41# Interval in seconds to write a heartbeat file. Set to 0 to disable.
42# This is used primarily for Docker healthchecks.42# This is used primarily for Docker healthchecks.
43heartbeatInterval: 043heartbeatInterval: 0
44# Enable HTTP/HTTPS keep-alive globally.
45# Disabling restores old Node 18 behavior, can help if ECONNRESET and other network errors occur.
46enableKeepAlive: false
44# -- SSL options --47# -- SSL options --
45ssl:48ssl:
46 # Enable SSL/TLS encryption49 # Enable SSL/TLS encryption
@@ -55,7 +58,7 @@ ssl:
55# -- SECURITY CONFIGURATION --58# -- SECURITY CONFIGURATION --
56# Toggle whitelist mode59# Toggle whitelist mode
57whitelistMode: true60whitelistMode: true
58# Whitelist will also verify IP in X-Forwarded-For / X-Real-IP headers61# When enabled, whitelist will also verify IP in headers enabled in `forwardedHeaders` section.
59enableForwardedWhitelist: true62enableForwardedWhitelist: true
60# Whitelist of allowed IP addresses63# Whitelist of allowed IP addresses
61whitelist:64whitelist:
@@ -127,6 +130,13 @@ sso:
127 # as that used for authentik. (Ensure the username in authentik130 # as that used for authentik. (Ensure the username in authentik
128 # is an exact match in lowercase with that in sillytavern).131 # is an exact match in lowercase with that in sillytavern).
129 authentikAuth: false132 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
131# Host whitelist configuration. Recommended if you're using a listen mode141# Host whitelist configuration. Recommended if you're using a listen mode
132hostWhitelist:142hostWhitelist:
@@ -141,6 +151,26 @@ hostWhitelist:
141 # - .trycloudflare.com151 # - .trycloudflare.com
142 hosts: []152 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.
157privateAddressWhitelist:
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
144# User session timeout *in seconds* (defaults to 24 hours).174# User session timeout *in seconds* (defaults to 24 hours).
145## Set to a positive number to expire session after a certain time of inactivity175## Set to a positive number to expire session after a certain time of inactivity
146## Set to 0 to expire session when the browser is closed176## Set to 0 to expire session when the browser is closed
@@ -159,9 +189,24 @@ logging:
159 minLogLevel: 0189 minLogLevel: 0
160# -- RATE LIMITING CONFIGURATION --190# -- RATE LIMITING CONFIGURATION --
161rateLimiting:191rateLimiting:
162 # Use X-Real-IP header instead of socket IP for rate limiting192 # Use any of the enabled headers 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.
164 preferRealIpHeader: false194 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.
203forwardedHeaders:
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
166## BACKUP CONFIGURATION211## BACKUP CONFIGURATION
167backups:212backups:
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 @@
2<html>2<html>
33
4<head>4<head>
5 <title>Not found</title>5 <title>Not Found</title>
6</head>6</head>
77
8<body>8<body>
9 <h1>Not found</h1>9 <h1>Not Found</h1>
10 <p>10 <p>
11 The requested URL was not found on this server.11 The requested URL was not found on this server.
12 </p>12 </p>
default/content/index.json+32 -0
@@ -642,5 +642,37 @@
642 {642 {
643 "filename": "presets/reasoning/Think XML.json",643 "filename": "presets/reasoning/Think XML.json",
644 "type": "reasoning"644 "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"
645 }677 }
646]678]
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 @@
10 "mistralai_model": "mistral-large-latest",10 "mistralai_model": "mistral-large-latest",
11 "chutes_model": "deepseek-ai/DeepSeek-V3-0324",11 "chutes_model": "deepseek-ai/DeepSeek-V3-0324",
12 "chutes_sort_models": "alphabetically",12 "chutes_sort_models": "alphabetically",
13 "minimax_model": "MiniMax-M2.7",
14 "minimax_endpoint": "global",
13 "electronhub_model": "gpt-4o-mini",15 "electronhub_model": "gpt-4o-mini",
14 "electronhub_sort_models": "alphabetically",16 "electronhub_sort_models": "alphabetically",
15 "electronhub_group_models": false,17 "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 @@
1#!/bin/sh1#!/bin/sh
22
3# Function to handle startup logic (Config check + Postinstall + Start)3# Function to handle startup logic (Config check + init + Start)
4start_sillytavern() {4start_sillytavern() {
5 local PREFIX="$1"5 local PREFIX="$1"
6 shift # Remove the first argument (PREFIX) so $@ contains the rest6 shift # Remove the first argument (PREFIX) so $@ contains the rest
@@ -11,8 +11,8 @@ start_sillytavern() {
11 $PREFIX cp "default/config.yaml" "config/config.yaml"11 $PREFIX cp "default/config.yaml" "config/config.yaml"
12 fi12 fi
1313
14 # Execute postinstall to auto-populate config.yaml with missing values14 # Execute init script to auto-populate config.yaml with missing values
15 $PREFIX npm run postinstall15 $PREFIX npm run init
1616
17 # Start the server17 # Start the server
18 exec $PREFIX node server.js --listen "$@"18 exec $PREFIX node server.js --listen "$@"
index.d.ts+4 -0
@@ -42,6 +42,10 @@ declare global {
42 */42 */
43 handle: string | null;43 handle: string | null;
44 /**44 /**
45 * Account version tag: shake256 derivative of password hash and salt.
46 */
47 version: string | null;
48 /**
45 * Last time the session was extended.49 * Last time the session was extended.
46 */50 */
47 touch: number;51 touch: number;
package-lock.json+80 -80
@@ -1,13 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.17.0",3 "version": "1.18.0",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.17.0",9 "version": "1.18.0",
10 "hasInstallScript": true,
11 "license": "AGPL-3.0",10 "license": "AGPL-3.0",
12 "dependencies": {11 "dependencies": {
13 "@adobe/css-tools": "^4.4.4",12 "@adobe/css-tools": "^4.4.4",
@@ -39,13 +38,14 @@
39 "@mozilla/readability": "^0.6.0",38 "@mozilla/readability": "^0.6.0",
40 "@popperjs/core": "^2.11.8",39 "@popperjs/core": "^2.11.8",
41 "@zeldafan0225/ai_horde": "^5.2.0",40 "@zeldafan0225/ai_horde": "^5.2.0",
41 "agent-base": "^7.1.3",
42 "archiver": "^7.0.1",42 "archiver": "^7.0.1",
43 "bing-translate-api": "^4.1.0",43 "bing-translate-api": "^4.1.0",
44 "body-parser": "^1.20.2",44 "body-parser": "^1.20.2",
45 "bowser": "^2.12.1",45 "bowser": "^2.12.1",
46 "bytes": "^3.1.2",46 "bytes": "^3.1.2",
47 "chalk": "^5.6.0",47 "chalk": "^5.6.0",
48 "chevrotain": "^11.1.1",48 "chevrotain": "^11.2.0",
49 "command-exists": "^1.2.9",49 "command-exists": "^1.2.9",
50 "compression": "^1.8.1",50 "compression": "^1.8.1",
51 "cookie-parser": "^1.4.6",51 "cookie-parser": "^1.4.6",
@@ -54,7 +54,7 @@
54 "crc": "^4.3.2",54 "crc": "^4.3.2",
55 "csrf-sync": "^4.2.1",55 "csrf-sync": "^4.2.1",
56 "diff-match-patch": "^1.0.5",56 "diff-match-patch": "^1.0.5",
57 "dompurify": "^3.2.6",57 "dompurify": "^3.4.2",
58 "droll": "^0.2.1",58 "droll": "^0.2.1",
59 "env-paths": "^3.0.0",59 "env-paths": "^3.0.0",
60 "express": "^4.21.0",60 "express": "^4.21.0",
@@ -74,8 +74,9 @@
74 "ipaddr.js": "^2.2.0",74 "ipaddr.js": "^2.2.0",
75 "is-docker": "^3.0.0",75 "is-docker": "^3.0.0",
76 "isomorphic-git": "^1.36.3",76 "isomorphic-git": "^1.36.3",
77 "js-sha256": "^0.11.1",
77 "localforage": "^1.10.0",78 "localforage": "^1.10.0",
78 "lodash": "^4.17.21",79 "lodash": "^4.18.1",
79 "mime-types": "^3.0.2",80 "mime-types": "^3.0.2",
80 "moment": "^2.30.1",81 "moment": "^2.30.1",
81 "morphdom": "^2.7.7",82 "morphdom": "^2.7.7",
@@ -109,7 +110,7 @@
109 "sillytavern": "src/server-global.js"110 "sillytavern": "src/server-global.js"
110 },111 },
111 "devDependencies": {112 "devDependencies": {
112 "@chevrotain/types": "^11.0.3",113 "@chevrotain/types": "^11.2.0",
113 "@types/archiver": "^6.0.3",114 "@types/archiver": "^6.0.3",
114 "@types/bytes": "^3.1.5",115 "@types/bytes": "^3.1.5",
115 "@types/command-exists": "^1.2.3",116 "@types/command-exists": "^1.2.3",
@@ -123,7 +124,7 @@
123 "@types/jquery-cropper": "^1.0.4",124 "@types/jquery-cropper": "^1.0.4",
124 "@types/jquery.transit": "^0.9.33",125 "@types/jquery.transit": "^0.9.33",
125 "@types/jqueryui": "^1.12.24",126 "@types/jqueryui": "^1.12.24",
126 "@types/lodash": "^4.17.20",127 "@types/lodash": "^4.17.24",
127 "@types/mime-types": "^3.0.1",128 "@types/mime-types": "^3.0.1",
128 "@types/multer": "^2.1.0",129 "@types/multer": "^2.1.0",
129 "@types/node": "^18.19.84",130 "@types/node": "^18.19.84",
@@ -139,7 +140,8 @@
139 "eslint": "^8.57.1",140 "eslint": "^8.57.1",
140 "eslint-plugin-jest": "^27.9.0",141 "eslint-plugin-jest": "^27.9.0",
141 "eslint-plugin-jsdoc": "^48.10.0",142 "eslint-plugin-jsdoc": "^48.10.0",
142 "eslint-plugin-playwright": "^2.3.0"143 "eslint-plugin-playwright": "^2.3.0",
144 "typescript": "^5.9.3"
143 },145 },
144 "engines": {146 "engines": {
145 "node": ">= 20"147 "node": ">= 20"
@@ -177,42 +179,42 @@
177 "license": "Apache-2.0"179 "license": "Apache-2.0"
178 },180 },
179 "node_modules/@chevrotain/cst-dts-gen": {181 "node_modules/@chevrotain/cst-dts-gen": {
180 "version": "11.1.1",182 "version": "11.2.0",
181 "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",183 "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.2.0.tgz",
182 "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",184 "integrity": "sha512-ssJFvn/UXhQQeICw3SR/fZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==",
183 "license": "Apache-2.0",185 "license": "Apache-2.0",
184 "dependencies": {186 "dependencies": {
185 "@chevrotain/gast": "11.1.1",187 "@chevrotain/gast": "11.2.0",
186 "@chevrotain/types": "11.1.1",188 "@chevrotain/types": "11.2.0",
187 "lodash-es": "4.17.23"189 "lodash-es": "4.17.23"
188 }190 }
189 },191 },
190 "node_modules/@chevrotain/gast": {192 "node_modules/@chevrotain/gast": {
191 "version": "11.1.1",193 "version": "11.2.0",
192 "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",194 "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.2.0.tgz",
193 "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",195 "integrity": "sha512-c+KoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==",
194 "license": "Apache-2.0",196 "license": "Apache-2.0",
195 "dependencies": {197 "dependencies": {
196 "@chevrotain/types": "11.1.1",198 "@chevrotain/types": "11.2.0",
197 "lodash-es": "4.17.23"199 "lodash-es": "4.17.23"
198 }200 }
199 },201 },
200 "node_modules/@chevrotain/regexp-to-ast": {202 "node_modules/@chevrotain/regexp-to-ast": {
201 "version": "11.1.1",203 "version": "11.2.0",
202 "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",204 "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.2.0.tgz",
203 "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",205 "integrity": "sha512-lG73pBFqbXODTbXhdZwv0oyUaI+3Irm+uOv5/W79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==",
204 "license": "Apache-2.0"206 "license": "Apache-2.0"
205 },207 },
206 "node_modules/@chevrotain/types": {208 "node_modules/@chevrotain/types": {
207 "version": "11.1.1",209 "version": "11.2.0",
208 "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",210 "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.2.0.tgz",
209 "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",211 "integrity": "sha512-vBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+kzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==",
210 "license": "Apache-2.0"212 "license": "Apache-2.0"
211 },213 },
212 "node_modules/@chevrotain/utils": {214 "node_modules/@chevrotain/utils": {
213 "version": "11.1.1",215 "version": "11.2.0",
214 "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",216 "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.2.0.tgz",
215 "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",217 "integrity": "sha512-+7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==",
216 "license": "Apache-2.0"218 "license": "Apache-2.0"
217 },219 },
218 "node_modules/@es-joy/jsdoccomment": {220 "node_modules/@es-joy/jsdoccomment": {
@@ -549,7 +551,6 @@
549 "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",551 "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz",
550 "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",552 "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==",
551 "license": "MIT",553 "license": "MIT",
552 "peer": true,
553 "dependencies": {554 "dependencies": {
554 "@jimp/core": "^0.22.12"555 "@jimp/core": "^0.22.12"
555 }556 }
@@ -1011,7 +1012,6 @@
1011 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",1012 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",
1012 "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",1013 "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",
1013 "license": "MIT",1014 "license": "MIT",
1014 "peer": true,
1015 "dependencies": {1015 "dependencies": {
1016 "@jimp/core": "1.6.0",1016 "@jimp/core": "1.6.0",
1017 "@jimp/types": "1.6.0",1017 "@jimp/types": "1.6.0",
@@ -1043,7 +1043,6 @@
1043 "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",1043 "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz",
1044 "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",1044 "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==",
1045 "license": "MIT",1045 "license": "MIT",
1046 "peer": true,
1047 "dependencies": {1046 "dependencies": {
1048 "@jimp/utils": "^0.22.12"1047 "@jimp/utils": "^0.22.12"
1049 },1048 },
@@ -1139,7 +1138,6 @@
1139 "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",1138 "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz",
1140 "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",1139 "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==",
1141 "license": "MIT",1140 "license": "MIT",
1142 "peer": true,
1143 "dependencies": {1141 "dependencies": {
1144 "@jimp/utils": "^0.22.12"1142 "@jimp/utils": "^0.22.12"
1145 },1143 },
@@ -1176,7 +1174,6 @@
1176 "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",1174 "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz",
1177 "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",1175 "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==",
1178 "license": "MIT",1176 "license": "MIT",
1179 "peer": true,
1180 "dependencies": {1177 "dependencies": {
1181 "@jimp/utils": "^0.22.12",1178 "@jimp/utils": "^0.22.12",
1182 "tinycolor2": "^1.6.0"1179 "tinycolor2": "^1.6.0"
@@ -1220,7 +1217,6 @@
1220 "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",1217 "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz",
1221 "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",1218 "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==",
1222 "license": "MIT",1219 "license": "MIT",
1223 "peer": true,
1224 "dependencies": {1220 "dependencies": {
1225 "@jimp/utils": "^0.22.12"1221 "@jimp/utils": "^0.22.12"
1226 },1222 },
@@ -1308,7 +1304,6 @@
1308 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",1304 "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz",
1309 "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",1305 "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==",
1310 "license": "MIT",1306 "license": "MIT",
1311 "peer": true,
1312 "dependencies": {1307 "dependencies": {
1313 "@jimp/utils": "^0.22.12"1308 "@jimp/utils": "^0.22.12"
1314 },1309 },
@@ -1321,7 +1316,6 @@
1321 "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",1316 "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz",
1322 "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",1317 "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==",
1323 "license": "MIT",1318 "license": "MIT",
1324 "peer": true,
1325 "dependencies": {1319 "dependencies": {
1326 "@jimp/utils": "^0.22.12"1320 "@jimp/utils": "^0.22.12"
1327 },1321 },
@@ -1922,7 +1916,6 @@
1922 "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",1916 "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
1923 "dev": true,1917 "dev": true,
1924 "license": "MIT",1918 "license": "MIT",
1925 "peer": true,
1926 "dependencies": {1919 "dependencies": {
1927 "@types/body-parser": "*",1920 "@types/body-parser": "*",
1928 "@types/express-serve-static-core": "^4.17.33",1921 "@types/express-serve-static-core": "^4.17.33",
@@ -2018,9 +2011,9 @@
2018 }2011 }
2019 },2012 },
2020 "node_modules/@types/lodash": {2013 "node_modules/@types/lodash": {
2021 "version": "4.17.20",2014 "version": "4.17.24",
2022 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",2015 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz",
2023 "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",2016 "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
2024 "dev": true,2017 "dev": true,
2025 "license": "MIT"2018 "license": "MIT"
2026 },2019 },
@@ -2612,7 +2605,6 @@
2612 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",2605 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
2613 "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",2606 "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
2614 "license": "MIT",2607 "license": "MIT",
2615 "peer": true,
2616 "bin": {2608 "bin": {
2617 "acorn": "bin/acorn"2609 "acorn": "bin/acorn"
2618 },2610 },
@@ -2799,9 +2791,9 @@
2799 }2791 }
2800 },2792 },
2801 "node_modules/archiver-utils/node_modules/brace-expansion": {2793 "node_modules/archiver-utils/node_modules/brace-expansion": {
2802 "version": "2.0.2",2794 "version": "2.1.0",
2803 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",2795 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
2804 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",2796 "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
2805 "license": "MIT",2797 "license": "MIT",
2806 "dependencies": {2798 "dependencies": {
2807 "balanced-match": "^1.0.0"2799 "balanced-match": "^1.0.0"
@@ -3021,14 +3013,23 @@
3021 }3013 }
3022 },3014 },
3023 "node_modules/axios": {3015 "node_modules/axios": {
3024 "version": "1.13.5",3016 "version": "1.15.2",
3025 "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",3017 "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz",
3026 "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",3018 "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==",
3027 "license": "MIT",3019 "license": "MIT",
3028 "dependencies": {3020 "dependencies": {
3029 "follow-redirects": "^1.15.11",3021 "follow-redirects": "^1.15.11",
3030 "form-data": "^4.0.5",3022 "form-data": "^4.0.5",
3031 "proxy-from-env": "^1.1.0"3023 "proxy-from-env": "^2.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"
3032 }3033 }
3033 },3034 },
3034 "node_modules/b4a": {3035 "node_modules/b4a": {
@@ -3083,9 +3084,9 @@
3083 }3084 }
3084 },3085 },
3085 "node_modules/basic-ftp": {3086 "node_modules/basic-ftp": {
3086 "version": "5.2.0",3087 "version": "5.3.1",
3087 "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",3088 "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
3088 "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",3089 "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
3089 "license": "MIT",3090 "license": "MIT",
3090 "engines": {3091 "engines": {
3091 "node": ">=10.0.0"3092 "node": ">=10.0.0"
@@ -3190,9 +3191,9 @@
3190 "license": "MIT"3191 "license": "MIT"
3191 },3192 },
3192 "node_modules/brace-expansion": {3193 "node_modules/brace-expansion": {
3193 "version": "1.1.12",3194 "version": "1.1.14",
3194 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",3195 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
3195 "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",3196 "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
3196 "dev": true,3197 "dev": true,
3197 "license": "MIT",3198 "license": "MIT",
3198 "dependencies": {3199 "dependencies": {
@@ -3232,7 +3233,6 @@
3232 }3233 }
3233 ],3234 ],
3234 "license": "MIT",3235 "license": "MIT",
3235 "peer": true,
3236 "dependencies": {3236 "dependencies": {
3237 "baseline-browser-mapping": "^2.9.0",3237 "baseline-browser-mapping": "^2.9.0",
3238 "caniuse-lite": "^1.0.30001759",3238 "caniuse-lite": "^1.0.30001759",
@@ -3503,16 +3503,16 @@
3503 }3503 }
3504 },3504 },
3505 "node_modules/chevrotain": {3505 "node_modules/chevrotain": {
3506 "version": "11.1.1",3506 "version": "11.2.0",
3507 "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",3507 "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.2.0.tgz",
3508 "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",3508 "integrity": "sha512-mHCHTxM51nCklUw9RzRVc0DLjAh/SAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==",
3509 "license": "Apache-2.0",3509 "license": "Apache-2.0",
3510 "dependencies": {3510 "dependencies": {
3511 "@chevrotain/cst-dts-gen": "11.1.1",3511 "@chevrotain/cst-dts-gen": "11.2.0",
3512 "@chevrotain/gast": "11.1.1",3512 "@chevrotain/gast": "11.2.0",
3513 "@chevrotain/regexp-to-ast": "11.1.1",3513 "@chevrotain/regexp-to-ast": "11.2.0",
3514 "@chevrotain/types": "11.1.1",3514 "@chevrotain/types": "11.2.0",
3515 "@chevrotain/utils": "11.1.1",3515 "@chevrotain/utils": "11.2.0",
3516 "lodash-es": "4.17.23"3516 "lodash-es": "4.17.23"
3517 }3517 }
3518 },3518 },
@@ -4322,13 +4322,10 @@
4322 }4322 }
4323 },4323 },
4324 "node_modules/dompurify": {4324 "node_modules/dompurify": {
4325 "version": "3.3.2",4325 "version": "3.4.2",
4326 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz",4326 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz",
4327 "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==",4327 "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==",
4328 "license": "(MPL-2.0 OR Apache-2.0)",4328 "license": "(MPL-2.0 OR Apache-2.0)",
4329 "engines": {
4330 "node": ">=20"
4331 },
4332 "optionalDependencies": {4329 "optionalDependencies": {
4333 "@types/trusted-types": "^2.0.7"4330 "@types/trusted-types": "^2.0.7"
4334 }4331 }
@@ -4567,7 +4564,6 @@
4567 "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",4564 "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
4568 "dev": true,4565 "dev": true,
4569 "license": "MIT",4566 "license": "MIT",
4570 "peer": true,
4571 "dependencies": {4567 "dependencies": {
4572 "@eslint-community/eslint-utils": "^4.2.0",4568 "@eslint-community/eslint-utils": "^4.2.0",
4573 "@eslint-community/regexpp": "^4.6.1",4569 "@eslint-community/regexpp": "^4.6.1",
@@ -5209,9 +5205,9 @@
5209 "license": "ISC"5205 "license": "ISC"
5210 },5206 },
5211 "node_modules/follow-redirects": {5207 "node_modules/follow-redirects": {
5212 "version": "1.15.11",5208 "version": "1.16.0",
5213 "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",5209 "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
5214 "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",5210 "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
5215 "funding": [5211 "funding": [
5216 {5212 {
5217 "type": "individual",5213 "type": "individual",
@@ -6378,6 +6374,12 @@
6378 "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",6374 "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
6379 "license": "BSD-3-Clause"6375 "license": "BSD-3-Clause"
6380 },6376 },
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 },
6381 "node_modules/js-yaml": {6383 "node_modules/js-yaml": {
6382 "version": "4.1.1",6384 "version": "4.1.1",
6383 "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",6385 "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
@@ -6614,9 +6616,9 @@
6614 }6616 }
6615 },6617 },
6616 "node_modules/lodash": {6618 "node_modules/lodash": {
6617 "version": "4.17.23",6619 "version": "4.18.1",
6618 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",6620 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
6619 "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",6621 "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
6620 "license": "MIT"6622 "license": "MIT"
6621 },6623 },
6622 "node_modules/lodash-es": {6624 "node_modules/lodash-es": {
@@ -7926,9 +7928,9 @@
7926 }7928 }
7927 },7929 },
7928 "node_modules/readdir-glob/node_modules/brace-expansion": {7930 "node_modules/readdir-glob/node_modules/brace-expansion": {
7929 "version": "2.0.2",7931 "version": "2.1.0",
7930 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",7932 "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
7931 "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",7933 "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
7932 "license": "MIT",7934 "license": "MIT",
7933 "dependencies": {7935 "dependencies": {
7934 "balanced-match": "^1.0.0"7936 "balanced-match": "^1.0.0"
@@ -8125,7 +8127,6 @@
8125 "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",8127 "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
8126 "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",8128 "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
8127 "license": "MIT",8129 "license": "MIT",
8128 "peer": true,
8129 "dependencies": {8130 "dependencies": {
8130 "fast-deep-equal": "^3.1.3",8131 "fast-deep-equal": "^3.1.3",
8131 "fast-uri": "^3.0.1",8132 "fast-uri": "^3.0.1",
@@ -9143,7 +9144,6 @@
9143 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",9144 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
9144 "dev": true,9145 "dev": true,
9145 "license": "Apache-2.0",9146 "license": "Apache-2.0",
9146 "peer": true,
9147 "bin": {9147 "bin": {
9148 "tsc": "bin/tsc",9148 "tsc": "bin/tsc",
9149 "tsserver": "bin/tsserver"9149 "tsserver": "bin/tsserver"
package.json+11 -8
@@ -29,13 +29,14 @@
29 "@mozilla/readability": "^0.6.0",29 "@mozilla/readability": "^0.6.0",
30 "@popperjs/core": "^2.11.8",30 "@popperjs/core": "^2.11.8",
31 "@zeldafan0225/ai_horde": "^5.2.0",31 "@zeldafan0225/ai_horde": "^5.2.0",
32 "agent-base": "^7.1.3",
32 "archiver": "^7.0.1",33 "archiver": "^7.0.1",
33 "bing-translate-api": "^4.1.0",34 "bing-translate-api": "^4.1.0",
34 "body-parser": "^1.20.2",35 "body-parser": "^1.20.2",
35 "bowser": "^2.12.1",36 "bowser": "^2.12.1",
36 "bytes": "^3.1.2",37 "bytes": "^3.1.2",
37 "chalk": "^5.6.0",38 "chalk": "^5.6.0",
38 "chevrotain": "^11.1.1",39 "chevrotain": "^11.2.0",
39 "command-exists": "^1.2.9",40 "command-exists": "^1.2.9",
40 "compression": "^1.8.1",41 "compression": "^1.8.1",
41 "cookie-parser": "^1.4.6",42 "cookie-parser": "^1.4.6",
@@ -44,7 +45,7 @@
44 "crc": "^4.3.2",45 "crc": "^4.3.2",
45 "csrf-sync": "^4.2.1",46 "csrf-sync": "^4.2.1",
46 "diff-match-patch": "^1.0.5",47 "diff-match-patch": "^1.0.5",
47 "dompurify": "^3.2.6",48 "dompurify": "^3.4.2",
48 "droll": "^0.2.1",49 "droll": "^0.2.1",
49 "env-paths": "^3.0.0",50 "env-paths": "^3.0.0",
50 "express": "^4.21.0",51 "express": "^4.21.0",
@@ -64,8 +65,9 @@
64 "ipaddr.js": "^2.2.0",65 "ipaddr.js": "^2.2.0",
65 "is-docker": "^3.0.0",66 "is-docker": "^3.0.0",
66 "isomorphic-git": "^1.36.3",67 "isomorphic-git": "^1.36.3",
68 "js-sha256": "^0.11.1",
67 "localforage": "^1.10.0",69 "localforage": "^1.10.0",
68 "lodash": "^4.17.21",70 "lodash": "^4.18.1",
69 "mime-types": "^3.0.2",71 "mime-types": "^3.0.2",
70 "moment": "^2.30.1",72 "moment": "^2.30.1",
71 "morphdom": "^2.7.7",73 "morphdom": "^2.7.7",
@@ -113,8 +115,9 @@
113 "type": "git",115 "type": "git",
114 "url": "https://github.com/SillyTavern/SillyTavern.git"116 "url": "https://github.com/SillyTavern/SillyTavern.git"
115 },117 },
116 "version": "1.17.0",118 "version": "1.18.0",
117 "scripts": {119 "scripts": {
120 "init": "node src/server-init.js",
118 "start": "node server.js",121 "start": "node server.js",
119 "debug": "node --inspect server.js",122 "debug": "node --inspect server.js",
120 "start:global": "node server.js --global",123 "start:global": "node server.js --global",
@@ -122,7 +125,6 @@
122 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",125 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
123 "start:bun": "bun server.js",126 "start:bun": "bun server.js",
124 "start:no-csrf": "node server.js --disableCsrf",127 "start:no-csrf": "node server.js --disableCsrf",
125 "postinstall": "node post-install.js",
126 "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",128 "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",
127 "lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix",129 "lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix",
128 "plugins:update": "node plugins update",130 "plugins:update": "node plugins update",
@@ -137,7 +139,7 @@
137 },139 },
138 "main": "server.js",140 "main": "server.js",
139 "devDependencies": {141 "devDependencies": {
140 "@chevrotain/types": "^11.0.3",142 "@chevrotain/types": "^11.2.0",
141 "@types/archiver": "^6.0.3",143 "@types/archiver": "^6.0.3",
142 "@types/bytes": "^3.1.5",144 "@types/bytes": "^3.1.5",
143 "@types/command-exists": "^1.2.3",145 "@types/command-exists": "^1.2.3",
@@ -151,7 +153,7 @@
151 "@types/jquery-cropper": "^1.0.4",153 "@types/jquery-cropper": "^1.0.4",
152 "@types/jquery.transit": "^0.9.33",154 "@types/jquery.transit": "^0.9.33",
153 "@types/jqueryui": "^1.12.24",155 "@types/jqueryui": "^1.12.24",
154 "@types/lodash": "^4.17.20",156 "@types/lodash": "^4.17.24",
155 "@types/mime-types": "^3.0.1",157 "@types/mime-types": "^3.0.1",
156 "@types/multer": "^2.1.0",158 "@types/multer": "^2.1.0",
157 "@types/node": "^18.19.84",159 "@types/node": "^18.19.84",
@@ -167,6 +169,7 @@
167 "eslint": "^8.57.1",169 "eslint": "^8.57.1",
168 "eslint-plugin-jest": "^27.9.0",170 "eslint-plugin-jest": "^27.9.0",
169 "eslint-plugin-jsdoc": "^48.10.0",171 "eslint-plugin-jsdoc": "^48.10.0",
170 "eslint-plugin-playwright": "^2.3.0"172 "eslint-plugin-playwright": "^2.3.0",
173 "typescript": "^5.9.3"
171 }174 }
172}175}
post-install.js+0 -114
@@ -1,114 +0,0 @@
1/**
2 * Scripts to be done before starting the server for the first time.
3 */
4import fs from 'node:fs';
5import path from 'node:path';
6import process from 'node:process';
7import yaml from 'yaml';
8import chalk from 'chalk';
9import { createRequire } from 'node:module';
10import { addMissingConfigValues } from './src/config-init.js';
11
12/**
13 * Colorizes console output.
14 */
15const color = chalk;
16
17/**
18 * Converts the old config.conf file to the new config.yaml format.
19 */
20function 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 */
46function 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
105try {
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
3user.css is now located under your data root directory in the "_css" folder.
4
5Example 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"] {
97 font-size: 1.05em;97 font-size: 1.05em;
98}98}
9999
100.extensions_info .extension_version {100.extensions_info :is(.extension_version, .extension_author) {
101 opacity: 0.8;101 opacity: 0.8;
102 font-size: 0.8em;102 font-size: 0.8em;
103 font-weight: normal;103 font-weight: normal;
104 margin-left: 2px;104 margin-left: 2px;
105}105}
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
107.extensions_info .extension_block a {117.extensions_info .extension_block a {
108 color: var(--SmartThemeBodyColor);118 color: var(--SmartThemeBodyColor);
109}119}
public/css/mobile-styles.css+5 -0
@@ -500,6 +500,11 @@
500 .horde_multiple_hint {500 .horde_multiple_hint {
501 display: none;501 display: none;
502 }502 }
503
504 select[multiple] {
505 max-height: 50px;
506 overflow-y: auto;
507 }
503}508}
504509
505/*landscape mode phones and ipads*/510/*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) {
565 display: none;565 display: none;
566}566}
567567
568#openai_settings:has(#openai_function_calling:not(:checked)) #tool_call_recurse_limit_block {
569 display: none;
570}
571
568#adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) {572#adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) {
569 display: none;573 display: none;
570}574}
public/css/welcome.css+1 -0
@@ -17,6 +17,7 @@
17.welcomePanel.recentHidden .welcomeRecent,17.welcomePanel.recentHidden .welcomeRecent,
18.welcomePanel.recentHidden .recentChatsTitle,18.welcomePanel.recentHidden .recentChatsTitle,
19.welcomePanel.recentHidden .hideRecentChats,19.welcomePanel.recentHidden .hideRecentChats,
20.welcomePanel.recentHidden .recentChatsSettings,
20.welcomePanel:not(.recentHidden) .showRecentChats {21.welcomePanel:not(.recentHidden) .showRecentChats {
21 display: none;22 display: none;
22}23}
public/img/kobold.svg+4 -76
@@ -1,78 +1,6 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>1<?xml version="1.0" encoding="UTF-8"?>
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">
33 <g id="g8" transform="translate(-12.7,-12.6)">
4<svg4 <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" />
77 </g>5 </g>
78</svg>6</svg>
public/img/koboldcpp.svg+4 -76
@@ -1,78 +1,6 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>1<?xml version="1.0" encoding="UTF-8"?>
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">
33 <g id="g8" transform="translate(-12.7,-12.6)">
4<svg4 <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" />
77 </g>5 </g>
78</svg>6</svg>
public/img/koboldhorde.svg+5 -46
@@ -1,48 +1,7 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>1<?xml version="1.0" encoding="UTF-8"?>
2<svg2<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" />
47 </g>6 </g>
48</svg>7</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>
\ No newline at end of file1 \ 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 \ 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 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>1<?xml version="1.0" encoding="UTF-8"?>
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 @@
1<?xml version="1.0" standalone="no"?>1<?xml version="1.0" encoding="UTF-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>
8Created 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
15c-54 27 -99 52 -99 55 0 3 35 46 77 95 131 154 287 370 387 537 77 130 96 168
1696 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
18165 195 248 64 86 132 166 159 186 95 72 107 84 129 129 13 25 33 91 45 146
1912 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
22120 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
2629 -60 11 2 31 -19 60 -62 60 -88 105 -118 205 -134 140 -22 189 -10 460 118
2749 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
29471 -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
340 21 -10 25 -37 4 -21 12 -49 18 -63 21 -43 179 -193 257 -243 41 -27 134 -87
35205 -133 209 -137 484 -268 770 -367 55 -19 163 -62 240 -94 77 -33 201 -81
36275 -107 74 -27 191 -68 260 -93 179 -65 382 -113 721 -172 271 -47 301 -50
37350 -40 36 8 116 10 239 6 276 -8 386 -41 584 -172 53 -35 153 -111 222 -169
38145 -121 277 -222 384 -293 143 -96 584 -356 659 -390 42 -19 87 -40 101 -48
3914 -7 41 -16 62 -20 45 -9 93 23 107 69 5 17 12 39 16 49 5 12 2 17 -8 17 -8
400 -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
4334 10 54 27 28 108 40 386 57 219 13 1193 2 1213 -13 27 -22 132 15 132 46 0
448 -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
4790 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
51157 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
5392 -411 135 -20 19 -45 37 -56 40 -21 7 -24 32 -6 56 22 29 245 92 467 133 47
548 213 36 370 61 476 76 694 134 917 244 131 65 236 136 357 244 45 40 140 120
55211 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
5812 193 21 493 52 979 180 1545 407 263 106 500 246 735 435 126 101 364 329
59502 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
6217 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
67371 42 26 99 58 125 71 114 55 208 178 193 251 -9 45 -55 100 -101 121 -162
6874 -676 -86 -1330 -413 -73 -37 -114 -53 -105 -42 8 11 56 66 105 124 50 58
69110 130 135 160 65 82 184 203 275 280 76 64 84 74 145 194 54 107 65 136 68
70190 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
72139 96 224 36 85 80 191 98 235 19 44 41 121 50 170 9 50 25 132 36 183 10 51
7319 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
77125 76 185 84 204 97 320 50 450 -40 112 -41 105 27 155 85 63 378 365 475
78490 101 130 244 343 354 526 46 76 124 201 172 278 49 76 100 166 114 200 28
7966 55 121 281 566 218 431 289 627 323 890 6 47 18 125 27 173 14 75 14 101 4
80165 -19 128 -101 273 -210 375 -28 26 -51 50 -51 52 0 3 14 5 30 5 17 0 30 3
8130 8 -1 22 -136 101 -289 167 -216 95 -278 115 -466 153 -304 63 -465 66 -712
8213 -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
84443 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"/>
87 </g>6 </g>
88</svg>7</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>
\ No newline at end of file9 \ No newline at end of file
public/index.html+147 -106
@@ -697,7 +697,7 @@
697 </span>697 </span>
698 </div>698 </div>
699 </div>699 </div>
700 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">700 <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">
701 <div class="range-block-title" data-i18n="Temperature">701 <div class="range-block-title" data-i18n="Temperature">
702 Temperature702 Temperature
703 </div>703 </div>
@@ -710,7 +710,7 @@
710 </div>710 </div>
711 </div>711 </div>
712 </div>712 </div>
713 <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">713 <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">
714 <div class="range-block-title" data-i18n="Frequency Penalty">714 <div class="range-block-title" data-i18n="Frequency Penalty">
715 Frequency Penalty715 Frequency Penalty
716 </div>716 </div>
@@ -723,7 +723,7 @@
723 </div>723 </div>
724 </div>724 </div>
725 </div>725 </div>
726 <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">726 <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">
727 <div class="range-block-title" data-i18n="Presence Penalty">727 <div class="range-block-title" data-i18n="Presence Penalty">
728 Presence Penalty728 Presence Penalty
729 </div>729 </div>
@@ -736,7 +736,7 @@
736 </div>736 </div>
737 </div>737 </div>
738 </div>738 </div>
739 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt">739 <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt,workers_ai">
740 <div class="range-block-title" data-i18n="Top K">740 <div class="range-block-title" data-i18n="Top K">
741 Top K741 Top K
742 </div>742 </div>
@@ -749,7 +749,7 @@
749 </div>749 </div>
750 </div>750 </div>
751 </div>751 </div>
752 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai">752 <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">
753 <div class="range-block-title" data-i18n="Top P">753 <div class="range-block-title" data-i18n="Top P">
754 Top P754 Top P
755 </div>755 </div>
@@ -762,7 +762,7 @@
762 </div>762 </div>
763 </div>763 </div>
764 </div>764 </div>
765 <div class="range-block" data-source="openrouter,nanogpt,chutes">765 <div class="range-block" data-source="openrouter,nanogpt,chutes,workers_ai">
766 <div class="range-block-title" data-i18n="Repetition Penalty">766 <div class="range-block-title" data-i18n="Repetition Penalty">
767 Repetition Penalty767 Repetition Penalty
768 </div>768 </div>
@@ -986,7 +986,7 @@
986 </div>986 </div>
987 </div>987 </div>
988 </div>988 </div>
989 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai">989 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai,workers_ai">
990 <div class="range-block-title justifyLeft" data-i18n="Seed">990 <div class="range-block-title justifyLeft" data-i18n="Seed">
991 Seed991 Seed
992 </div>992 </div>
@@ -1269,8 +1269,8 @@
1269 <span data-i18n="Top K">Top K</span>1269 <span data-i18n="Top K">Top K</span>
1270 <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>1270 <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>
1271 </small>1271 </small>
1272 <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="200" step="1">1272 <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="500" step="1">
1273 <input class="neo-range-input" type="number" min="-1" max="200" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui">1273 <input class="neo-range-input" type="number" min="-1" max="500" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui">
1274 </div>1274 </div>
1275 <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">1275 <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
1276 <small>1276 <small>
@@ -1400,7 +1400,7 @@
1400 <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">1400 <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">
1401 </div>1401 </div>
14021402
1403 <div data-tg-type="koboldcpp, llamacpp, tabby" id="adaptive_p_block" class="wide100p">1403 <div data-tg-type="koboldcpp, llamacpp, ooba, tabby" id="adaptive_p_block" class="wide100p">
1404 <h4 class="wide100p textAlignCenter">1404 <h4 class="wide100p textAlignCenter">
1405 <label data-i18n="Adaptive-P">Adaptive-P</label>1405 <label data-i18n="Adaptive-P">Adaptive-P</label>
1406 <a href="https://github.com/MrJackSpade/adaptive-p-docs" target="_blank">1406 <a href="https://github.com/MrJackSpade/adaptive-p-docs" target="_blank">
@@ -1831,6 +1831,7 @@
1831 <div data-name="tfs" draggable="true"><span>Tail Free Sampling</span><small></small></div>1831 <div data-name="tfs" draggable="true"><span>Tail Free Sampling</span><small></small></div>
1832 <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>1832 <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>
1833 <div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>1833 <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>
1834 <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div>1835 <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div>
1835 <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div>1836 <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div>
1836 <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div>1837 <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div>
@@ -1996,7 +1997,7 @@
1996 </b>1997 </b>
1997 </div>1998 </div>
1998 </div>1999 </div>
1999 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai,nanogpt">2000 <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">
2000 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">2001 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
2001 <input id="openai_function_calling" type="checkbox" />2002 <input id="openai_function_calling" type="checkbox" />
2002 <span data-i18n="Enable function calling">Enable function calling</span>2003 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -2010,8 +2011,22 @@
2010 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>2011 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
2011 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>2012 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
2012 </div>2013 </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>
2013 </div>2026 </div>
2014 <div class="range-block" data-source="openrouter">2027 </div>
2028 </div>
2029 <div class="range-block" data-source="openrouter,custom">
2015 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10">2030 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10">
2016 <div class="flex-container oneline-dropdown">2031 <div class="flex-container oneline-dropdown">
2017 <label for="tool_reasoning_mode" data-i18n="Interleaved Thinking">2032 <label for="tool_reasoning_mode" data-i18n="Interleaved Thinking">
@@ -2035,7 +2050,7 @@
2035 </em>2050 </em>
2036 </div>2051 </div>
2037 </div>2052 </div>
2038 <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">2053 <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">
2039 <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand">2054 <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand">
2040 <input id="openai_media_inlining" type="checkbox" />2055 <input id="openai_media_inlining" type="checkbox" />
2041 <span data-i18n="Send inline media">Send inline media</span>2056 <span data-i18n="Send inline media">Send inline media</span>
@@ -2059,7 +2074,7 @@
2059 <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong>2074 <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong>
2060 <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong>2075 <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong>
2061 </div>2076 </div>
2062 <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">2077 <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">
2063 <div class="flex-container oneline-dropdown">2078 <div class="flex-container oneline-dropdown">
2064 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">2079 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
2065 Inline Image Quality2080 Inline Image Quality
@@ -2120,7 +2135,7 @@
2120 </div>2135 </div>
2121 </div>2136 </div>
2122 </div>2137 </div>
2123 <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">2138 <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">
2124 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2139 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2125 <input id="openai_show_thoughts" type="checkbox" />2140 <input id="openai_show_thoughts" type="checkbox" />
2126 <span data-i18n="Request model reasoning">Request model reasoning</span>2141 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2129,12 +2144,12 @@
2129 <span data-i18n="Allows the model to return its thinking process.">2144 <span data-i18n="Allows the model to return its thinking process.">
2130 Allows the model to return its thinking process.2145 Allows the model to return its thinking process.
2131 </span>2146 </span>
2132 <strong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai,moonshot,openrouter">2147 <strong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai,moonshot,openrouter,deepseek">
2133 This setting affects visibility only.2148 This setting affects visibility only.
2134 </strong>2149 </strong>
2135 </div>2150 </div>
2136 </div>2151 </div>
2137 <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">2152 <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">
2138 <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.">2153 <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.">
2139 <label for="openai_reasoning_effort">2154 <label for="openai_reasoning_effort">
2140 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2155 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2445,7 +2460,8 @@
2445 <small data-i18n="Click Authorize below or get the key from">2460 <small data-i18n="Click Authorize below or get the key from">
2446 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.2461 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
2447 <br>2462 <br>
2448 <a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a>2463 <a href="https://openrouter.ai/settings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
2464 <span class="openrouter_credits_display marginLeft5"></span>
2449 </div>2465 </div>
2450 <div class="flex-container">2466 <div class="flex-container">
2451 <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">2467 <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 @@
2894 <option value="azure_openai">Azure OpenAI</option>2910 <option value="azure_openai">Azure OpenAI</option>
2895 <option value="chutes">Chutes</option>2911 <option value="chutes">Chutes</option>
2896 <option value="claude">Claude</option>2912 <option value="claude">Claude</option>
2913 <option value="workers_ai">Cloudflare Workers AI</option>
2897 <option value="cohere">Cohere</option>2914 <option value="cohere">Cohere</option>
2898 <!-- Temporarily disabled. -->2915 <!-- Temporarily disabled. -->
2899 <!-- <option value="cometapi">CometAPI</option> -->2916 <!-- <option value="cometapi">CometAPI</option> -->
@@ -2904,6 +2921,7 @@
2904 <option value="makersuite">Google AI Studio</option>2921 <option value="makersuite">Google AI Studio</option>
2905 <option value="vertexai">Google Vertex AI</option>2922 <option value="vertexai">Google Vertex AI</option>
2906 <option value="mistralai">MistralAI</option>2923 <option value="mistralai">MistralAI</option>
2924 <option value="minimax">MiniMax</option>
2907 <option value="moonshot">Moonshot AI</option>2925 <option value="moonshot">Moonshot AI</option>
2908 <option value="nanogpt">NanoGPT</option>2926 <option value="nanogpt">NanoGPT</option>
2909 <option value="openrouter">OpenRouter</option>2927 <option value="openrouter">OpenRouter</option>
@@ -3028,8 +3046,17 @@
3028 <div>3046 <div>
3029 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>3047 <h4 data-i18n="OpenAI Model">OpenAI Model</h4>
3030 <select id="model_openai_select">3048 <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>
3031 <optgroup label="GPT-5.4">3053 <optgroup label="GPT-5.4">
3032 <option value="gpt-5.4">gpt-5.4</option>3054 <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>
3033 </optgroup>3060 </optgroup>
3034 <optgroup label="GPT-5.3">3061 <optgroup label="GPT-5.3">
3035 <option value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option>3062 <option value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option>
@@ -3143,6 +3170,7 @@
3143 <h4 data-i18n="Claude Model">Claude Model</h4>3170 <h4 data-i18n="Claude Model">Claude Model</h4>
3144 <select id="model_claude_select">3171 <select id="model_claude_select">
3145 <optgroup label="Versions">3172 <optgroup label="Versions">
3173 <option value="claude-opus-4-7">claude-opus-4-7</option>
3146 <option value="claude-opus-4-6">claude-opus-4-6</option>3174 <option value="claude-opus-4-6">claude-opus-4-6</option>
3147 <option value="claude-opus-4-5">claude-opus-4-5</option>3175 <option value="claude-opus-4-5">claude-opus-4-5</option>
3148 <option value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option>3176 <option value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option>
@@ -3176,7 +3204,8 @@
3176 <small data-i18n="Click Authorize below or get the key from">3204 <small data-i18n="Click Authorize below or get the key from">
3177 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.3205 Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>.
3178 <br>3206 <br>
3179 <a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a>3207 <a href="https://openrouter.ai/settings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a>
3208 <span class="openrouter_credits_display marginLeft5"></span>
3180 </div>3209 </div>
3181 <div class="flex-container">3210 <div class="flex-container">
3182 <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off">3211 <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 @@
3195 <input id="openrouter_use_fallback" type="checkbox" />3224 <input id="openrouter_use_fallback" type="checkbox" />
3196 <span data-i18n="Allow fallback models">Allow fallback models</span>3225 <span data-i18n="Allow fallback models">Allow fallback models</span>
3197 </label>3226 </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>
3228 <div>3227 <div>
3229 <h4>3228 <h4>
3230 <span data-i18n="Model Providers">Model Providers</span>3229 <span data-i18n="Model Providers">Model Providers</span>
@@ -3344,6 +3343,8 @@
3344 <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>3343 <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>
3345 </optgroup>3344 </optgroup>
3346 <optgroup label="Gemma">3345 <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>
3347 <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>3348 <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>
3348 <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option>3349 <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option>
3349 <option value="gemma-3-27b-it">gemma-3-27b-it</option>3350 <option value="gemma-3-27b-it">gemma-3-27b-it</option>
@@ -3566,6 +3567,32 @@
3566 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>3567 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3567 </select>3568 </select>
3568 </div>3569 </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>
3569 <div id="electronhub_form" data-source="electronhub">3596 <div id="electronhub_form" data-source="electronhub">
3570 <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4>3597 <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4>
3571 <div>3598 <div>
@@ -3584,37 +3611,6 @@
3584 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>3611 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3585 </select>3612 </select>
3586 </div>3613 </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>
3618 </div>3614 </div>
3619 <div id="chutes_form" data-source="chutes">3615 <div id="chutes_form" data-source="chutes">
3620 <h4 data-i18n="Chutes API Key">Chutes API Key</h4>3616 <h4 data-i18n="Chutes API Key">Chutes API Key</h4>
@@ -3634,29 +3630,11 @@
3634 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>3630 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3635 </select>3631 </select>
3636 </div>3632 </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>
3657 </div>3633 </div>
3658 <div id="nanogpt_form" data-source="nanogpt">3634 <div id="nanogpt_form" data-source="nanogpt">
3659 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>3635 <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>
3660 <div class="flex-container">3638 <div class="flex-container">
3661 <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off">3639 <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off">
3662 <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>3640 <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 @@
3670 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>3648 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3671 </select>3649 </select>
3672 </div>3650 </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>
3673 </div>3684 </div>
3674 <div id="deepseek_form" data-source="deepseek">3685 <div id="deepseek_form" data-source="deepseek">
3675 <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>3686 <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4>
@@ -3683,9 +3694,7 @@
3683 <div>3694 <div>
3684 <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>3695 <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4>
3685 <select id="model_deepseek_select">3696 <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>
3689 </select>3698 </select>
3690 </div>3699 </div>
3691 </div>3700 </div>
@@ -3924,6 +3933,7 @@
3924 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>3933 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>
3925 <select id="model_zai_select">3934 <select id="model_zai_select">
3926 <option value="glm-5-turbo">glm-5-turbo</option>3935 <option value="glm-5-turbo">glm-5-turbo</option>
3936 <option value="glm-5v-turbo">glm-5v-turbo</option>
3927 <option value="glm-5.1">glm-5.1</option>3937 <option value="glm-5.1">glm-5.1</option>
3928 <option value="glm-5">glm-5</option>3938 <option value="glm-5">glm-5</option>
3929 <option value="glm-4.7">glm-4.7</option>3939 <option value="glm-4.7">glm-4.7</option>
@@ -3986,6 +3996,37 @@
3986 <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>3996 <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>
3987 </div>3997 </div>
3988 </div>3998 </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>
3989 <div id="prompt_post_processing_form">4030 <div id="prompt_post_processing_form">
3990 <h4>4031 <h4>
3991 <span data-i18n="Prompt Post-Processing">4032 <span data-i18n="Prompt Post-Processing">
@@ -4365,7 +4406,7 @@
4365 <small data-i18n="First User Prefix">First User Prefix</small>4406 <small data-i18n="First User Prefix">First User Prefix</small>
4366 <textarea id="instruct_first_input_sequence" data-macros class="text_pole textarea_compact autoSetHeight"></textarea>4407 <textarea id="instruct_first_input_sequence" data-macros class="text_pole textarea_compact autoSetHeight"></textarea>
4367 </div>4408 </div>
4368 <div class="flexAuto" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence">4409 <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">
4369 <small data-i18n="Last User Prefix">Last User Prefix</small>4410 <small data-i18n="Last User Prefix">Last User Prefix</small>
4370 <textarea id="instruct_last_input_sequence" data-macros class="text_pole wide100p textarea_compact autoSetHeight"></textarea>4411 <textarea id="instruct_last_input_sequence" data-macros class="text_pole wide100p textarea_compact autoSetHeight"></textarea>
4371 </div>4412 </div>
@@ -4775,7 +4816,7 @@
4775 <input type="file" id="world_import_file" accept=".json,.lorebook,.png" name="avatar" hidden>4816 <input type="file" id="world_import_file" accept=".json,.lorebook,.png" name="avatar" hidden>
4776 <div id="world_create_button" class="menu_button menu_button_icon">4817 <div id="world_create_button" class="menu_button menu_button_icon">
4777 <i class="fa-solid fa-globe"></i>4818 <i class="fa-solid fa-globe"></i>
4778 <span data-i18n="New">New</span>4819 <span data-i18n="Create">Create</span>
4779 </div>4820 </div>
4780 <small data-i18n="or">or</small>4821 <small data-i18n="or">or</small>
4781 <select id="world_editor_select">4822 <select id="world_editor_select">
@@ -6733,7 +6774,7 @@
6733 <small class="chat_file_size select_chat_block_filename_item"></small>6774 <small class="chat_file_size select_chat_block_filename_item"></small>
6734 <small class="chat_messages_num select_chat_block_filename_item"></small>6775 <small class="chat_messages_num select_chat_block_filename_item"></small>
6735 </div>6776 </div>
6736 <div class="flex-container gap10px">6777 <div class="select_chat_actions flex-container gap10px">
6737 <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>6778 <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>
6738 <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>6779 <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>
6739 <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div>6780 <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';
24import yaml from 'yaml';24import yaml from 'yaml';
25import * as chevrotain from 'chevrotain';25import * as chevrotain from 'chevrotain';
26import { gzipSync, gzip } from 'fflate';26import { gzipSync, gzip } from 'fflate';
27import { sha256 } from 'js-sha256';
2728
28/**29/**
29 * Expose the libraries to the 'window' object.30 * Expose the libraries to the 'window' object.
@@ -105,6 +106,7 @@ export default {
105 chevrotain,106 chevrotain,
106 gzipSync,107 gzipSync,
107 gzip,108 gzip,
109 sha256,
108};110};
109111
110export {112export {
@@ -132,4 +134,5 @@ export {
132 chevrotain,134 chevrotain,
133 gzipSync,135 gzipSync,
134 gzip,136 gzip,
137 sha256,
135};138};
public/script.js+84 -28
@@ -213,7 +213,7 @@ import {
213 tag_import_setting,213 tag_import_setting,
214 applyCharacterTagsToMessageDivs,214 applyCharacterTagsToMessageDivs,
215} from './scripts/tags.js';215} from './scripts/tags.js';
216import { initSecrets, readSecretState } from './scripts/secrets.js';216import { checkOpenRouterAuth, initSecrets, readSecretState } from './scripts/secrets.js';
217import { markdownExclusionExt } from './scripts/showdown-exclusion.js';217import { markdownExclusionExt } from './scripts/showdown-exclusion.js';
218import { markdownUnderscoreExt } from './scripts/showdown-underscore.js';218import { markdownUnderscoreExt } from './scripts/showdown-underscore.js';
219import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js';219import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js';
@@ -717,6 +717,7 @@ async function firstLoadInit() {
717 initLoaderOverlay.appendChild(splashMessage);717 initLoaderOverlay.appendChild(splashMessage);
718718
719 const initLoaderHandle = loader.show({719 const initLoaderHandle = loader.show({
720 slug: 'app-init',
720 toastMode: loader.ToastMode.NONE,721 toastMode: loader.ToastMode.NONE,
721 overlayContent: initLoaderOverlay,722 overlayContent: initLoaderOverlay,
722 });723 });
@@ -741,12 +742,13 @@ async function firstLoadInit() {
741 initKoboldSettings();742 initKoboldSettings();
742 initNovelAISettings();743 initNovelAISettings();
743 initSystemPrompts();744 initSystemPrompts();
744 initExtensions();745 await initExtensions();
745 initExtensionSlashCommands();746 initExtensionSlashCommands();
746 ToolManager.initToolSlashCommands();747 ToolManager.initToolSlashCommands();
747 await initPresetManager();748 await initPresetManager();
748 await initSystemMessages();749 await initSystemMessages();
749 await getSettings(initLoaderHandle);750 await getSettings(initLoaderHandle);
751 await checkOpenRouterAuth();
750 initKeyboard();752 initKeyboard();
751 initDynamicStyles();753 initDynamicStyles();
752 initTags();754 initTags();
@@ -1910,17 +1912,32 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
1910}1912}
19111913
1912/**1914/**
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 */
1923export 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/**
1913 * Inserts or replaces an SVG icon adjacent to the provided message's timestamp.1932 * Inserts or replaces an SVG icon adjacent to the provided message's timestamp.
1914 *1933 *
1915 * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.1934 * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.
1916 * @param {ChatMessageExtra} extra - Contains the API and model details.1935 * @param {ChatMessageExtra} extra - Contains the API and model details.
1917 */1936 */
1918function insertSVGIcon(mes, extra) {1937function insertSVGIcon(mes, extra) {
1919 // Determine the SVG filename1938 const apiName = extra?.api || '';
1920 let modelName = extra?.api || '';
19211939
1922 // If there's no API information, we can't determine which SVG to use1940 if (!apiName) {
1923 if (!modelName) {
1924 return;1941 return;
1925 }1942 }
19261943
@@ -1937,16 +1954,14 @@ function insertSVGIcon(mes, extra) {
1937 };1954 };
1938 };1955 };
19391956
1940 const createModelImage = (className, targetSelector, insertBefore) => {1957 const insertIcon = (className, targetSelector, insertBefore) => {
1941 const image = new Image();1958 const image = createModelIcon(apiName, extra?.model);
1942 image.classList.add('icon-svg', className);1959 image.classList.add(className);
1943 image.src = `/img/${modelName}.svg`;
1944 image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
1945 insertOrReplaceSVG(image, className, targetSelector, insertBefore);1960 insertOrReplaceSVG(image, className, targetSelector, insertBefore);
1946 };1961 };
19471962
1948 createModelImage('timestamp-icon', '.timestamp');1963 insertIcon('timestamp-icon', '.timestamp');
1949 createModelImage('thinking-icon', '.mes_reasoning_header_title', true);1964 insertIcon('thinking-icon', '.mes_reasoning_header_title', true);
1950}1965}
19511966
1952/**1967/**
@@ -2907,6 +2922,11 @@ export function substituteParamsLegacy(content, _name1, _name2, _original, _grou
2907export function substituteParams(content, options = {}) {2922export function substituteParams(content, options = {}) {
2908 if (!content) return '';2923 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
2910 // Handle legacy signature calls to substituteParams2930 // Handle legacy signature calls to substituteParams
2911 // 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.2931 // 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.
2912 const isOptionsObject = options && typeof options === 'object' && !Array.isArray(options);2932 const isOptionsObject = options && typeof options === 'object' && !Array.isArray(options);
@@ -3847,9 +3867,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst
38473867
3848 // If the prompt was given as a string, convert to a message-style object assuming user role3868 // If the prompt was given as a string, convert to a message-style object assuming user role
3849 if (typeof prompt === 'string') {3869 if (typeof prompt === 'string') {
3850 const message = api === 'openai'3870 const message = { role: 'user', content: prompt.trim() };
3851 ? { role: 'user', content: prompt.trim() }
3852 : { role: 'system', content: prompt };
3853 prompt = [message];3871 prompt = [message];
3854 } else { // checks for message-style object3872 } else { // checks for message-style object
3855 if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided');3873 if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided');
@@ -3876,7 +3894,12 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst
3876 // prepend system prompt, if provided3894 // prepend system prompt, if provided
3877 if (systemPrompt) {3895 if (systemPrompt) {
3878 systemPrompt = substituteParams(systemPrompt);3896 systemPrompt = substituteParams(systemPrompt);
3879 systemPrompt = isInstruct ? (formatInstructModeStoryString(systemPrompt) + '\n') : systemPrompt.trim();3897 systemPrompt = isInstruct ? formatInstructModeStoryString(systemPrompt) : 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 }
3880 prompt.unshift({ role: 'system', content: systemPrompt });3903 prompt.unshift({ role: 'system', content: systemPrompt });
3881 }3904 }
38823905
@@ -3906,7 +3929,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst
3906 * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used.3929 * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used.
3907 * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response.3930 * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response.
3908 * @prop {string} [prefill] An optional prefill for the prompt.3931 * @prop {string} [prefill] An optional prefill for the prompt.
3909 * @prop {object} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction.3932 * @prop {JsonSchema} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction.
3910 */3933 */
39113934
3912/**3935/**
@@ -4018,7 +4041,7 @@ export async function generateRawData({ prompt = '', api = null, instructOverrid
4018 }4041 }
40194042
4020 if (jsonSchema) {4043 if (jsonSchema) {
4021 return extractJsonFromData(data, { mainApi: api });4044 return extractJsonFromData(data, { mainApi: api, returnInvalidJson: jsonSchema.returnInvalid });
4022 }4045 }
40234046
4024 return data;4047 return data;
@@ -4181,6 +4204,7 @@ function removeLastMessage() {
4181 * @property {object} value JSON schema value.4204 * @property {object} value JSON schema value.
4182 * @property {string} [description] Description of the schema.4205 * @property {string} [description] Description of the schema.
4183 * @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.4206 * @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.
4184 *4208 *
4185 * @typedef {object} GenerateOptions4209 * @typedef {object} GenerateOptions
4186 * @property {boolean} [automatic_trigger] If the generation was triggered automatically (e.g. group auto mode).4210 * @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
4267 if (selected_group && !is_group_generating) {4291 if (selected_group && !is_group_generating) {
4268 if (!dryRun) {4292 if (!dryRun) {
4269 // Returns the promise that generateGroupWrapper returns; resolves when generation is done4293 // Returns the promise that generateGroupWrapper returns; resolves when generation is done
4270 return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage });4294 return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, jsonSchema });
4271 }4295 }
42724296
4273 const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index]));4297 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
4449 for (let i = coreChat.length - 1; i >= 0; i--) {4473 for (let i = coreChat.length - 1; i >= 0; i--) {
4450 const depth = coreChat.length - i - (isContinue ? 2 : 1);4474 const depth = coreChat.length - i - (isContinue ? 2 : 1);
4451 const isPrefix = isContinue && i === coreChat.length - 1;4475 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
4452 coreChat[i] = {4480 coreChat[i] = {
4453 ...coreChat[i],4481 ...coreChat[i],
4454 mes: promptReasoning.addToMessage(4482 mes: isOtherGroupMember
4483 ? coreChat[i].mes
4484 : promptReasoning.addToMessage(
4455 coreChat[i].mes,4485 coreChat[i].mes,
4456 getRegexedString(4486 getRegexedString(
4457 String(coreChat[i].extra?.reasoning ?? ''),4487 String(coreChat[i].extra?.reasoning ?? ''),
@@ -4697,7 +4727,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4697 chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);4727 chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
4698 }4728 }
46994729
4700 if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct) {4730 if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct && !isImpersonate) {
4701 // Reformat with the last input sequence (if any)4731 // Reformat with the last input sequence (if any)
4702 chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);4732 chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
4703 }4733 }
@@ -5301,7 +5331,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
5301 streamingProcessor.firstMessageText = '';5331 streamingProcessor.firstMessageText = '';
5302 }5332 }
53035333
5304 streamingProcessor.generator = await sendStreamingRequest(type, generate_data);5334 streamingProcessor.generator = await sendStreamingRequest(type, generate_data, { jsonSchema });
53055335
5306 hideSwipeButtons();5336 hideSwipeButtons();
5307 let getMessage = await streamingProcessor.generate();5337 let getMessage = await streamingProcessor.generate();
@@ -5390,7 +5420,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
53905420
5391 if (jsonSchema) {5421 if (jsonSchema) {
5392 unblockGeneration(type);5422 unblockGeneration(type);
5393 return extractJsonFromData(data);5423 return extractJsonFromData(data, { returnInvalidJson: jsonSchema.returnInvalid ?? false });
5394 }5424 }
53955425
5396 //const getData = await response.json();5426 //const getData = await response.json();
@@ -5823,11 +5853,11 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
5823 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt);5853 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt);
5824 } else {5854 } else {
5825 chat.push(message);5855 chat.push(message);
5856 await saveChatConditional();
5826 const chat_id = (chat.length - 1);5857 const chat_id = (chat.length - 1);
5827 await eventSource.emit(event_types.MESSAGE_SENT, chat_id);5858 await eventSource.emit(event_types.MESSAGE_SENT, chat_id);
5828 addOneMessage(message);5859 addOneMessage(message);
5829 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);5860 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
5830 await saveChatConditional();
5831 }5861 }
58325862
5833 return message;5863 return message;
@@ -6003,7 +6033,7 @@ function setInContextMessages(msgInContextCount, type) {
60036033
6004 if (lastMessageBlock.length === 0) {6034 if (lastMessageBlock.length === 0) {
6005 const firstMessageId = getFirstDisplayedMessageId();6035 const firstMessageId = getFirstDisplayedMessageId();
6006 chatElement.find(`.mes[mesid="${firstMessageId}"`).addClass('lastInContext');6036 chatElement.find(`.mes[mesid="${firstMessageId}"]`).addClass('lastInContext');
6007 }6037 }
60086038
6009 // Update last id to chat. No metadata save on purpose, gets hopefully saved via another call6039 // 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) {
6213/**6243/**
6214 * Extracts JSON from the response data.6244 * Extracts JSON from the response data.
6215 * @param {object} data Response data6245 * @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
6216 * @returns {string} Extracted JSON string from the response data6250 * @returns {string} Extracted JSON string from the response data
6217 */6251 */
6218export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null } = {}) {6252export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null, returnInvalidJson = false } = {}) {
6219 mainApi = mainApi ?? main_api;6253 mainApi = mainApi ?? main_api;
6220 chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source;6254 chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source;
62216255
@@ -6238,6 +6272,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
6238 break;6272 break;
6239 case chat_completion_sources.PERPLEXITY:6273 case chat_completion_sources.PERPLEXITY:
6240 result = tryParse(removeReasoningFromString(text));6274 result = tryParse(removeReasoningFromString(text));
6275 if (!result && returnInvalidJson) {
6276 return text;
6277 }
6241 break;6278 break;
6242 case chat_completion_sources.VERTEXAI:6279 case chat_completion_sources.VERTEXAI:
6243 case chat_completion_sources.MAKERSUITE:6280 case chat_completion_sources.MAKERSUITE:
@@ -6258,6 +6295,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
6258 case chat_completion_sources.ZAI:6295 case chat_completion_sources.ZAI:
6259 default:6296 default:
6260 result = tryParse(text);6297 result = tryParse(text);
6298 if (!result && returnInvalidJson) {
6299 return text;
6300 }
6261 break;6301 break;
6262 }6302 }
6263 } break;6303 } break;
@@ -7924,6 +7964,15 @@ export async function getSettings(initLoaderHandle = null) {
7924 const isVersionChanged = settings.currentVersion !== currentVersion;7964 const isVersionChanged = settings.currentVersion !== currentVersion;
7925 await loadExtensionSettings(settings, isVersionChanged, enableAutoUpdate);7965 await loadExtensionSettings(settings, isVersionChanged, enableAutoUpdate);
7926 await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);7966 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');
7927 }7976 }
79287977
7929 firstRun = !!settings.firstRun;7978 firstRun = !!settings.firstRun;
@@ -10565,6 +10614,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
10565 }10614 }
1056610615
10567 const loaderHandle = showLoader ? loader.show({10616 const loaderHandle = showLoader ? loader.show({
10617 slug: 'chat-rename',
10568 title: t`Rename Chat`,10618 title: t`Rename Chat`,
10569 message: t`Renaming chat…`,10619 message: t`Renaming chat…`,
10570 toastMode: loader.ToastMode.STATIC,10620 toastMode: loader.ToastMode.STATIC,
@@ -10602,6 +10652,9 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
10602 if (currentChatId) {10652 if (currentChatId) {
10603 await reloadCurrentChat();10653 await reloadCurrentChat();
10604 }10654 }
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);
10605 } catch {10658 } catch {
10606 await delay(500);10659 await delay(500);
10607 await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);10660 await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);
@@ -11166,6 +11219,7 @@ jQuery(async function () {
11166 $('#select_chat_cross').trigger('click');11219 $('#select_chat_cross').trigger('click');
1116711220
11168 const loaderHandle = loader.show({11221 const loaderHandle = loader.show({
11222 slug: 'chat-delete',
11169 title: t`Delete Chat`,11223 title: t`Delete Chat`,
11170 message: t`Deleting chat…`,11224 message: t`Deleting chat…`,
11171 toastMode: loader.ToastMode.STATIC,11225 toastMode: loader.ToastMode.STATIC,
@@ -12460,7 +12514,9 @@ jQuery(async function () {
12460 $('#avatar-and-name-block').slideToggle();12514 $('#avatar-and-name-block').slideToggle();
12461 });12515 });
1246212516
12463 $(document).on('mouseup touchend', '#show_more_messages', async function () {12517 $(document).on('click', '#show_more_messages', async function (event) {
12518 event.stopPropagation();
12519 event.preventDefault();
12464 await showMoreMessages();12520 await showMoreMessages();
12465 });12521 });
1246612522
public/scripts/BulkEditOverlay.js+1 -0
@@ -848,6 +848,7 @@ class BulkEditOverlay {
848 const deleteChats = checkbox.prop('checked') ?? false;848 const deleteChats = checkbox.prop('checked') ?? false;
849849
850 const loaderHandle = loader.show({850 const loaderHandle = loader.show({
851 slug: 'bulk-delete',
851 title: t`Bulk Delete`,852 title: t`Bulk Delete`,
852 message: t`Deleting ${characterIds.length} character(s)…`,853 message: t`Deleting ${characterIds.length} character(s)…`,
853 toastMode: loader.ToastMode.STATIC,854 toastMode: loader.ToastMode.STATIC,
public/scripts/RossAscends-mods.js+2 -0
@@ -407,6 +407,8 @@ function RA_autoconnect(PrevApi) {
407 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)407 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)
408 || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI)408 || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI)
409 || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)409 || (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)
410 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)412 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
411 || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)413 || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)
412 ) {414 ) {
public/scripts/a11y.js+1 -0
@@ -16,6 +16,7 @@ const buttonSelectors = [
16 '.jg-menu .jg-button',16 '.jg-menu .jg-button',
17 '.bg_example .mobile-only-menu-toggle',17 '.bg_example .mobile-only-menu-toggle',
18 '.paginationjs-pages li a',18 '.paginationjs-pages li a',
19 '#show_more_messages',
19].join(', ');20].join(', ');
2021
21const listSelectors = [22const listSelectors = [
public/scripts/action-loader-slashcommands.js+22 -7
@@ -1,4 +1,4 @@
1import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoader } from './action-loader.js';1import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, loader } from './action-loader.js';
2import { t } from './i18n.js';2import { t } from './i18n.js';
3import { SlashCommand } from './slash-commands/SlashCommand.js';3import { SlashCommand } from './slash-commands/SlashCommand.js';
4import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';4import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
@@ -7,6 +7,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
7import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';7import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
8import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';8import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
9import { isFalseBoolean } from './utils.js';9import { isFalseBoolean } from './utils.js';
10import { DOMPurify } from '../lib.js';
1011
11/**12/**
12 * Registers slash commands for the action loader module.13 * Registers slash commands for the action loader module.
@@ -116,6 +117,12 @@ export function registerActionLoaderSlashCommands() {
116 typeList: [ARGUMENT_TYPE.STRING],117 typeList: [ARGUMENT_TYPE.STRING],
117 }),118 }),
118 SlashCommandNamedArgument.fromProps({119 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({
119 name: 'stopTooltip',126 name: 'stopTooltip',
120 description: 'Tooltip text for the stop button (only used when toast=stoppable)',127 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
121 typeList: [ARGUMENT_TYPE.STRING],128 typeList: [ARGUMENT_TYPE.STRING],
@@ -148,7 +155,8 @@ export function registerActionLoaderSlashCommands() {
148 const title = args.title ? String(args.title) : '';155 const title = args.title ? String(args.title) : '';
149 const stopTooltip = String(args.stopTooltip ?? t`Stop`);156 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
150157
151 const loader = showActionLoader({158 const actionLoader = loader.show({
159 slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap',
152 blocking,160 blocking,
153 toastMode,161 toastMode,
154 message,162 message,
@@ -162,7 +170,7 @@ export function registerActionLoaderSlashCommands() {
162 const result = await closureCopy.execute();170 const result = await closureCopy.execute();
163 return result.pipe;171 return result.pipe;
164 } finally {172 } finally {
165 await loader.hide();173 await actionLoader.hide();
166 }174 }
167 },175 },
168 }));176 }));
@@ -232,6 +240,12 @@ export function registerActionLoaderSlashCommands() {
232 typeList: [ARGUMENT_TYPE.STRING],240 typeList: [ARGUMENT_TYPE.STRING],
233 }),241 }),
234 SlashCommandNamedArgument.fromProps({242 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({
235 name: 'stopTooltip',249 name: 'stopTooltip',
236 description: 'Tooltip text for the stop button (only used when toast=stoppable)',250 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
237 typeList: [ARGUMENT_TYPE.STRING],251 typeList: [ARGUMENT_TYPE.STRING],
@@ -258,11 +272,12 @@ export function registerActionLoaderSlashCommands() {
258 const title = args.title ? String(args.title) : '';272 const title = args.title ? String(args.title) : '';
259 const stopTooltip = String(args.stopTooltip ?? t`Stop`);273 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
260274
261 const handle = showActionLoader({275 const handle = loader.show({
276 slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
262 blocking,277 blocking,
263 toastMode,278 toastMode,
264 message,279 message: DOMPurify.sanitize(message),
265 title,280 title: DOMPurify.sanitize(title),
266 stopTooltip,281 stopTooltip,
267 onStop: createClosureHandler(args.onStop),282 onStop: createClosureHandler(args.onStop),
268 onHide: createClosureHandler(args.onHide, { argName: 'onHide' }),283 onHide: createClosureHandler(args.onHide, { argName: 'onHide' }),
@@ -307,7 +322,7 @@ export function registerActionLoaderSlashCommands() {
307 }322 }
308323
309 // No handle provided - hide all active loaders324 // No handle provided - hide all active loaders
310 const result = await hideActionLoader();325 const result = await loader.hide();
311 return result ? 'true' : 'false';326 return result ? 'true' : 'false';
312 },327 },
313 }));328 }));
public/scripts/action-loader.js+47 -2
@@ -33,6 +33,7 @@ export const ActionLoaderToastMode = {
33 * @typedef {object} ActionLoaderOptions33 * @typedef {object} ActionLoaderOptions
34 * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.34 * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.
35 * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode35 * @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
36 * @property {string} [message='Generating...'] - The message to display in the toast37 * @property {string} [message='Generating...'] - The message to display in the toast
37 * @property {string} [title] - Optional title for the toast notification38 * @property {string} [title] - Optional title for the toast notification
38 * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button39 * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button
@@ -73,8 +74,20 @@ function hasBlockingLoaders() {
73 * Manages its own toast, stop handler, and lifecycle.74 * Manages its own toast, stop handler, and lifecycle.
74 */75 */
75export class ActionLoaderHandle {76export 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
76 /** @type {string} Unique identifier for this handle */86 /** @type {string} Unique identifier for this handle */
77 id;87 #id;
88
89 /** @type {string|null} Unique slug for the loader */
90 #slug = null;
7891
79 /** @type {JQuery<HTMLElement>|null} The toast element for this loader */92 /** @type {JQuery<HTMLElement>|null} The toast element for this loader */
80 #toast = null;93 #toast = null;
@@ -96,9 +109,11 @@ export class ActionLoaderHandle {
96 * @param {object} options - Configuration options109 * @param {object} options - Configuration options
97 * @param {boolean} [options.blocking=true] - Whether to show blocking overlay110 * @param {boolean} [options.blocking=true] - Whether to show blocking overlay
98 * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode111 * @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)
99 * @param {string} [options.message='Generating...'] - Message to display in the toast113 * @param {string} [options.message='Generating...'] - Message to display in the toast
100 * @param {string} [options.title] - Title for the toast notification114 * @param {string} [options.title] - Title for the toast notification
101 * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button115 * @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)
102 * @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner)117 * @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner)
103 * @param {(() => void)|null} [options.onStop] - Custom stop handler118 * @param {(() => void)|null} [options.onStop] - Custom stop handler
104 * @param {(() => void)|null} [options.onHide] - Custom hide handler119 * @param {(() => void)|null} [options.onHide] - Custom hide handler
@@ -106,14 +121,22 @@ export class ActionLoaderHandle {
106 constructor({121 constructor({
107 blocking = true,122 blocking = true,
108 toastMode = ActionLoaderToastMode.STOPPABLE,123 toastMode = ActionLoaderToastMode.STOPPABLE,
124 slug = null,
109 message = t`Generating...`,125 message = t`Generating...`,
110 title = '',126 title = '',
111 stopTooltip = t`Stop`,127 stopTooltip = t`Stop`,
112 overlayContent = null,128 overlayContent = null,
113 onStop = null,129 onStop = null,
114 onHide = null,130 onHide = null,
131 predisposed = false,
115 } = {}) {132 } = {}) {
116 this.id = generateLoaderId();133 if (predisposed) {
134 this.#disposed = true;
135 return;
136 }
137
138 this.#id = generateLoaderId();
139 this.#slug = slug;
117 this.#blocking = blocking;140 this.#blocking = blocking;
118 this.#onStop = onStop;141 this.#onStop = onStop;
119 this.#onHide = onHide;142 this.#onHide = onHide;
@@ -148,6 +171,12 @@ export class ActionLoaderHandle {
148 const toastContent = document.createElement('div');171 const toastContent = document.createElement('div');
149 toastContent.className = 'action-loader-toast';172 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
151 const messageSpan = document.createElement('span');180 const messageSpan = document.createElement('span');
152 messageSpan.className = 'action-loader-message';181 messageSpan.className = 'action-loader-message';
153 messageSpan.textContent = message;182 messageSpan.textContent = message;
@@ -202,6 +231,22 @@ export class ActionLoaderHandle {
202 }231 }
203232
204 /**233 /**
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 /**
205 * Whether this handle is still active (not disposed).250 * Whether this handle is still active (not disposed).
206 * @returns {boolean}251 * @returns {boolean}
207 */252 */
public/scripts/bookmarks.js+1 -0
@@ -700,6 +700,7 @@ export function initBookmarks() {
700 }700 }
701701
702 const loaderHandle = loader.show({702 const loaderHandle = loader.show({
703 slug: 'chat-load',
703 title: t`Chat History`,704 title: t`Chat History`,
704 message: t`Loading chat…`,705 message: t`Loading chat…`,
705 toastMode: loader.ToastMode.STATIC,706 toastMode: loader.ToastMode.STATIC,
public/scripts/browser-fixes.js+8 -0
@@ -72,6 +72,14 @@ function applyBrowserFixes() {
7272
73 if (isMobile()) {73 if (isMobile()) {
74 const fixFunkyPositioning = () => {74 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 }
75 console.debug('[Mobile] Device viewport change detected.');83 console.debug('[Mobile] Device viewport change detected.');
76 document.documentElement.style.position = 'fixed';84 document.documentElement.style.position = 'fixed';
77 requestAnimationFrame(() => document.documentElement.style.position = '');85 requestAnimationFrame(() => document.documentElement.style.position = '');
public/scripts/chats.js+4 -1
@@ -2288,11 +2288,14 @@ export function initChatUtilities() {
2288 await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });2288 await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true });
2289 });2289 });
22902290
2291 $(document).on('click', 'body .mes .mes_text', function () {2291 $(document).on('click', 'body .mes .mes_text, body .mes .mes_reasoning', function (event) {
2292 if (!power_user.click_to_edit) return;2292 if (!power_user.click_to_edit) return;
2293 if (window.getSelection().toString()) return;2293 if (window.getSelection().toString()) return;
2294 if ($('.edit_textarea').length) return;2294 if ($('.edit_textarea').length) return;
2295 $(this).closest('.mes').find('.mes_edit').trigger('click');2295 $(this).closest('.mes').find('.mes_edit').trigger('click');
2296 if ($(event.target).closest('.mes_reasoning').length) {
2297 $('.reasoning_edit_textarea').trigger('focus');
2298 }
2296 });2299 });
22972300
2298 $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog);2301 $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog);
public/scripts/custom-request.js+3 -2
@@ -51,6 +51,7 @@ import EventSourceStream from './sse-stream.js';
51 * @property {string} [reverse_proxy] - Optional reverse proxy URL51 * @property {string} [reverse_proxy] - Optional reverse proxy URL
52 * @property {string} [proxy_password] - Optional proxy password52 * @property {string} [proxy_password] - Optional proxy password
53 * @property {string} [custom_prompt_post_processing] - Optional custom prompt post-processing53 * @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
54 */55 */
5556
56/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */57/** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */
@@ -505,7 +506,7 @@ export class ChatCompletionService {
505 return async function* streamData() {506 return async function* streamData() {
506 let text = '';507 let text = '';
507 const swipes = [];508 const swipes = [];
508 const state = { reasoning: '', image: '' };509 const state = { reasoning: '', images: [], signature: '', toolSignatures: {} };
509 while (true) {510 while (true) {
510 const { done, value } = await reader.read();511 const { done, value } = await reader.read();
511 if (done) return;512 if (done) return;
@@ -591,7 +592,7 @@ export class ChatCompletionService {
591 }592 }
592593
593 // Ensure api-url is properly applied for all sources that accept it594 // Ensure api-url is properly applied for all sources that accept it
594 ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint'].forEach(field => {595 ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint', 'minimax_endpoint'].forEach(field => {
595 // The order is: connection profile => CC preset => CC settings596 // The order is: connection profile => CC preset => CC settings
596 overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field];597 overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field];
597 });598 });
public/scripts/dynamic-styles.js+6 -0
@@ -126,6 +126,12 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) {
126 // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside,126 // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside,
127 // it should be manually defined in CSS.127 // it should be manually defined in CSS.
128 const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible');128 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 }
129 let focusRule = `${focusSelector} { ${rule.style.cssText} }`;135 let focusRule = `${focusSelector} { ${rule.style.cssText} }`;
130136
131 // Wrap the generated rule into the same @media/@supports/@container chain (if any)137 // 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 = {
50 FORCE_SET_BACKGROUND: 'force_set_background',50 FORCE_SET_BACKGROUND: 'force_set_background',
51 CHAT_DELETED: 'chat_deleted',51 CHAT_DELETED: 'chat_deleted',
52 CHAT_CREATED: 'chat_created',52 CHAT_CREATED: 'chat_created',
53 CHAT_RENAMED: 'chat_renamed',
53 GROUP_CHAT_DELETED: 'group_chat_deleted',54 GROUP_CHAT_DELETED: 'group_chat_deleted',
54 GROUP_CHAT_CREATED: 'group_chat_created',55 GROUP_CHAT_CREATED: 'group_chat_created',
55 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',56 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
@@ -97,9 +98,16 @@ export const event_types = {
97 WORLDINFO_SCAN_DONE: 'worldinfo_scan_done',98 WORLDINFO_SCAN_DONE: 'worldinfo_scan_done',
98 MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted',99 MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted',
99 PERSONA_CHANGED: 'persona_changed',100 PERSONA_CHANGED: 'persona_changed',
101 PERSONA_CREATED: 'persona_created',
102 PERSONA_UPDATED: 'persona_updated',
103 PERSONA_RENAMED: 'persona_renamed',
104 PERSONA_DELETED: 'persona_deleted',
100 TTS_JOB_STARTED: 'tts_job_started',105 TTS_JOB_STARTED: 'tts_job_started',
101 TTS_AUDIO_READY: 'tts_audio_ready',106 TTS_AUDIO_READY: 'tts_audio_ready',
102 TTS_JOB_COMPLETE: 'tts_job_complete',107 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',
103};111};
104112
105export const eventSource = new EventEmitter([event_types.APP_READY, event_types.APP_INITIALIZED]);113export const eventSource = new EventEmitter([event_types.APP_READY, event_types.APP_INITIALIZED]);
public/scripts/extensions.js+561 -123
@@ -1,9 +1,9 @@
1import { DOMPurify, Popper } from '../lib.js';1import { Popper } from '../lib.js';
22
3import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js';3import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js';
4import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';4import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
5import { renderTemplate, renderTemplateAsync } from './templates.js';5import { renderTemplate, renderTemplateAsync } from './templates.js';
6import { delay, equalsIgnoreCaseAndAccents, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js';6import { delay, deleteValueByPath, equalsIgnoreCaseAndAccents, escapeHtml, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js';
7import { getContext } from './st-context.js';7import { getContext } from './st-context.js';
8import { isAdmin } from './user.js';8import { isAdmin } from './user.js';
9import { addLocaleData, getCurrentLocale, t } from './i18n.js';9import { addLocaleData, getCurrentLocale, t } from './i18n.js';
@@ -61,6 +61,19 @@ let manifests = {};
61 */61 */
62const defaultUrl = 'http://localhost:5100';62const 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 */
69export 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
64let requiresReload = false;77let requiresReload = false;
65let stateChanged = false;78let stateChanged = false;
66let saveMetadataTimeout = null;79let saveMetadataTimeout = null;
@@ -267,6 +280,18 @@ export async function doExtrasFetch(endpoint, args = {}) {
267}280}
268281
269/**282/**
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 */
289function 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/**
270 * Discovers extensions from the API.295 * Discovers extensions from the API.
271 * @returns {Promise<{name: string, type: string}[]>}296 * @returns {Promise<{name: string, type: string}[]>}
272 */297 */
@@ -343,7 +368,7 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) {
343 }368 }
344369
345 toggleContainer370 toggleContainer
346 .find(`.extension_block[data-name="${name.replace('third-party', '')}"] .extension_toggle input`)371 .find(`.extension_block[data-name="${getNameSelector(name)}"] .extension_toggle input`)
347 .prop('checked', enable)372 .prop('checked', enable)
348 .toggleClass('toggle_enable', !enable)373 .toggleClass('toggle_enable', !enable)
349 .toggleClass('toggle_disable', enable)374 .toggleClass('toggle_disable', enable)
@@ -355,11 +380,27 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) {
355}380}
356381
357/**382/**
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 */
388function 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/**
358 * Calls a manifest hook for an extension.399 * Calls a manifest hook for an extension.
359 * Hooks are optional function names exported from the extension's JS entry point module.400 * Hooks are optional function names exported from the extension's JS entry point module.
360 * The hook function can optionally return a Promise that will be awaited.401 * The hook function can optionally return a Promise that will be awaited.
361 * @param {string} name Extension name402 * @param {string} name Extension name
362 * @param {'install' | 'update' | 'delete' | 'enable' | 'disable' | 'activate'} hookName The hook to call403 * @param {'install' | 'update' | 'delete' | 'clean' | 'enable' | 'disable' | 'activate'} hookName The hook to call
363 * @returns {Promise<void>}404 * @returns {Promise<void>}
364 */405 */
365async function callExtensionHook(name, hookName) {406async function callExtensionHook(name, hookName) {
@@ -474,6 +515,21 @@ export function findExtension(name) {
474}515}
475516
476/**517/**
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 */
524export 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/**
477 * Loads manifest.json files for extensions.533 * Loads manifest.json files for extensions.
478 * @param {string[]} names Array of extension names534 * @param {string[]} names Array of extension names
479 * @returns {Promise<Record<string, object>>} Object with extension names as keys and their manifests as values535 * @returns {Promise<Record<string, object>>} Object with extension names as keys and their manifests as values
@@ -821,7 +877,7 @@ function addExtensionLocale(name, manifest) {
821}877}
822878
823/**879/**
824 * Generates HTML string for displaying an extension in the UI.880 * Generates an element for displaying an extension in the UI.
825 *881 *
826 * @param {string} name - The name of the extension.882 * @param {string} name - The name of the extension.
827 * @param {object} manifest - The manifest of the extension.883 * @param {object} manifest - The manifest of the extension.
@@ -829,95 +885,180 @@ function addExtensionLocale(name, manifest) {
829 * @param {boolean} isDisabled - Whether the extension is disabled or not.885 * @param {boolean} isDisabled - Whether the extension is disabled or not.
830 * @param {boolean} isExternal - Whether the extension is external or not.886 * @param {boolean} isExternal - Whether the extension is external or not.
831 * @param {string} checkboxClass - The class for the checkbox HTML element.887 * @param {string} checkboxClass - The class for the checkbox HTML element.
832 * @return {string} - The HTML string that represents the extension.888 * @return {HTMLElement} - The element that represents the extension.
833 */889 */
834function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {890function generateExtensionElement(name, manifest, isActive, isDisabled, isExternal, checkboxClass) {
835 function getExtensionIcon() {891 function getExtensionIcon() {
836 const type = getExtensionType(name);892 const type = getExtensionType(name);
893 const icon = document.createElement('i');
894 icon.classList.add('fa-sm', 'fa-fw', 'fa-solid');
837 switch (type) {895 switch (type) {
838 case 'global':896 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;
840 case 'local':900 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;
842 case 'system':904 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;
844 default:908 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;
846 }912 }
913 return icon;
847 }914 }
848915
849 const isUserAdmin = isAdmin();916 const isUserAdmin = isAdmin();
850 const extensionIcon = getExtensionIcon();
851 const displayName = manifest.display_name;917 const displayName = manifest.display_name;
852 const displayVersion = manifest.version || '';918 const displayVersion = manifest.version || '';
853 const externalId = name.replace('third-party', '');919 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;
857 }942 }
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
869 if (isActive && Array.isArray(manifest.optional)) {972 if (isActive && Array.isArray(manifest.optional)) {
870 const optional = new Set(manifest.optional);973 const optional = new Set(manifest.optional);
871 modules.forEach(x => optional.delete(x));974 modules.forEach(x => optional.delete(x));
872 if (optional.size > 0) {975 if (optional.size > 0) {
873 const optionalString = DOMPurify.sanitize([...optional].join(', '));976 const modulesDiv = document.createElement('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 disabled979 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
877 const requirements = new Set(manifest.requires);986 const requirements = new Set(manifest.requires);
878 modules.forEach(x => requirements.delete(x));987 modules.forEach(x => requirements.delete(x));
879 if (requirements.size > 0) {988 if (requirements.size > 0) {
880 const requirementsString = DOMPurify.sanitize([...requirements].join(', '));989 const modulesDiv = document.createElement('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);
882 }996 }
883 }997 }
884998
885 // if external, wrap the name in a link to the repo999 // 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;
914}1055}
9151056
916/**1057/**
917 * Gets extension data and generates the corresponding HTML for displaying the extension.1058 * Gets extension data and generates the corresponding element for displaying the extension.
918 *1059 *
919 * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest.1060 * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest.
920 * @return {object} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string.1061 * @return {{isExternal: boolean, extensionElement: HTMLElement}} - An object with 'isExternal' indicating whether the extension is external, and 'extensionElement' for the extension's HTML element.
921 */1062 */
922function getExtensionData(extension) {1063function getExtensionData(extension) {
923 const name = extension[0];1064 const name = extension[0];
@@ -927,33 +1068,43 @@ function getExtensionData(extension) {
927 const isExternal = name.startsWith('third-party');1068 const isExternal = name.startsWith('third-party');
9281069
929 const checkboxClass = isDisabled ? 'checkbox_disabled' : '';1070 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 };
934}1074}
9351075
9361076
937/**1077/**
938 * Gets the module information to be displayed.1078 * Gets the module information to be displayed.
939 *1079 *
940 * @return {string} - The HTML string for the module information.1080 * @return {HTMLElement} - The element containing the module information.
941 */1081 */
942function getModuleInformation() {1082function 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;
948}1099}
9491100
950/**1101/**
951 * Generates HTML for the extension load errors.1102 * Generates HTMLElement for the extension load errors.
952 * @returns {string} HTML string containing the errors that occurred while loading extensions.1103 * @returns {HTMLElement} - The element containing the extension load errors.
953 */1104 */
954function getExtensionLoadErrorsHtml() {1105function getExtensionLoadErrors() {
955 if (extensionLoadErrors.size === 0) {1106 if (extensionLoadErrors.size === 0) {
956 return '';1107 return document.createElement('div');
957 }1108 }
9581109
959 const container = document.createElement('div');1110 const container = document.createElement('div');
@@ -965,7 +1116,7 @@ function getExtensionLoadErrorsHtml() {
965 container.appendChild(errorElement);1116 container.appendChild(errorElement);
966 }1117 }
9671118
968 return container.outerHTML;1119 return container;
969}1120}
9701121
971/**1122/**
@@ -982,22 +1133,35 @@ async function showExtensionsDetails() {
982 initialScrollTop = oldPopup.content.scrollTop;1133 initialScrollTop = oldPopup.content.scrollTop;
983 await oldPopup.completeCancelled();1134 await oldPopup.completeCancelled();
984 }1135 }
985 const htmlErrors = getExtensionLoadErrorsHtml();1136 const errors = getExtensionLoadErrors();
986 const htmlDefault = $('<div class="marginBot10"><h3>' + t`Built-in Extensions:` + '</h3></div>');1137
9871138 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');
9941145 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');
9991150 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
1002 const sortOrderKey = 'extensions_sortByName';1166 const sortOrderKey = 'extensions_sortByName';
1003 const sortByName = accountStorage.getItem(sortOrderKey) === 'true';1167 const sortByName = accountStorage.getItem(sortOrderKey) === 'true';
@@ -1006,16 +1170,16 @@ async function showExtensionsDetails() {
1006 let extensionsToToggle = [];1170 let extensionsToToggle = [];
10071171
1008 extensions.forEach(value => {1172 extensions.forEach(value => {
1009 const { isExternal, extensionHtml } = value;1173 const { isExternal, extensionElement } = value;
1010 const container = isExternal ? htmlExternal : htmlDefault;1174 const container = isExternal ? externalContainer : defaultContainer;
1011 container.append(extensionHtml);1175 container.appendChild(extensionElement);
1012 });1176 });
10131177
1014 const html = $('<div></div>')1178 const extensionsMenu = $('<div></div>')
1015 .addClass('extensions_info')1179 .addClass('extensions_info')
1016 .append(htmlErrors)1180 .append(errors)
1017 .append(htmlDefault)1181 .append(defaultContainer)
1018 .append(htmlExternal)1182 .append(externalContainer)
1019 .append(getModuleInformation());1183 .append(getModuleInformation());
10201184
1021 {1185 {
@@ -1041,23 +1205,24 @@ async function showExtensionsDetails() {
1041 const toggleAllExtensionsButton = document.createElement('div');1205 const toggleAllExtensionsButton = document.createElement('div');
1042 toggleAllExtensionsButton.classList.add('menu_button', 'menu_button_icon');1206 toggleAllExtensionsButton.classList.add('menu_button', 'menu_button_icon');
1043 toggleAllExtensionsButton.title = t`Bulk toggle third-party extensions.`;1207 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
1049 const restoreBulkToggledExtensionsButton = document.createElement('div');1214 const restoreBulkToggledExtensionsButton = document.createElement('div');
1050 restoreBulkToggledExtensionsButton.classList.add('menu_button', 'menu_button_icon', 'fa-solid', 'fa-arrow-right-rotate', 'displayNone');1215 restoreBulkToggledExtensionsButton.classList.add('menu_button', 'menu_button_icon', 'fa-solid', 'fa-arrow-right-rotate', 'displayNone');
1051 restoreBulkToggledExtensionsButton.title = t`Restore toggled extensions.\n\nIt does not restore extensions toggled individually.`;1216 restoreBulkToggledExtensionsButton.title = t`Restore toggled extensions.\n\nIt does not restore extensions toggled individually.`;
10521217
1053 toggleAllExtensionsButton.addEventListener('click', () => {1218 toggleAllExtensionsButton.addEventListener('click', () => {
1054 extensionsToToggle = onToggleAllExtensions(extensionsToToggle, htmlExternal);1219 extensionsToToggle = onToggleAllExtensions(extensionsToToggle, $(externalContainer));
10551220
1056 for (const extension of extensionsToToggle) {1221 for (const extension of extensionsToToggle) {
1057 const { name } = extension;1222 const { name } = extension;
10581223
1059 htmlExternal1224 $(externalContainer)
1060 .find(`.extension_block[data-name="${name.replace('third-party', '')}"] .extension_toggle input`)1225 .find(`.extension_block[data-name="${getNameSelector(name)}"] .extension_toggle input`)
1061 .off('click')1226 .off('click')
1062 .one('click', () => {1227 .one('click', () => {
1063 extensionsToToggle = extensionsToToggle.filter(ext => ext.name !== name);1228 extensionsToToggle = extensionsToToggle.filter(ext => ext.name !== name);
@@ -1074,8 +1239,8 @@ async function showExtensionsDetails() {
1074 const { name } = extension;1239 const { name } = extension;
1075 const isDisabled = extension_settings.disabledExtensions.includes(name);1240 const isDisabled = extension_settings.disabledExtensions.includes(name);
10761241
1077 htmlExternal1242 $(externalContainer)
1078 .find(`.extension_block[data-name="${name.replace('third-party', '')}"] .extension_toggle input`)1243 .find(`.extension_block[data-name="${getNameSelector(name)}"] .extension_toggle input`)
1079 .prop('checked', !isDisabled)1244 .prop('checked', !isDisabled)
1080 .toggleClass('toggle_enable', isDisabled)1245 .toggleClass('toggle_enable', isDisabled)
1081 .toggleClass('toggle_disable', !isDisabled)1246 .toggleClass('toggle_disable', !isDisabled)
@@ -1099,13 +1264,13 @@ async function showExtensionsDetails() {
1099 });1264 });
11001265
1101 toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton);1266 toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton);
1102 htmlExternal.find('.third_party_toolbar').append(restoreBulkToggledExtensionsButton, toggleAllExtensionsButton);1267 thirdPartyToolbar.append(restoreBulkToggledExtensionsButton, toggleAllExtensionsButton);
1103 html.prepend(toolbar);1268 extensionsMenu.prepend(toolbar);
1104 }1269 }
11051270
1106 let waitingForSave = false;1271 let waitingForSave = false;
11071272
1108 const popup = new Popup(html, POPUP_TYPE.TEXT, '', {1273 const popup = new Popup(extensionsMenu, POPUP_TYPE.TEXT, '', {
1109 okButton: t`Close`,1274 okButton: t`Close`,
1110 wide: true,1275 wide: true,
1111 large: true,1276 large: true,
@@ -1147,7 +1312,7 @@ async function showExtensionsDetails() {
1147 });1312 });
1148 popupPromise = popup.show();1313 popupPromise = popup.show();
1149 popup.content.scrollTop = initialScrollTop;1314 popup.content.scrollTop = initialScrollTop;
1150 checkForUpdatesManual(sortFn, abortController.signal).finally(() => htmlLoading.remove());1315 checkForUpdatesManual(sortFn, abortController.signal).finally(() => loadingEl.remove());
1151 } catch (error) {1316 } catch (error) {
1152 toastr.error(t`Error loading extensions. See browser console for details.`);1317 toastr.error(t`Error loading extensions. See browser console for details.`);
1153 console.error(error);1318 console.error(error);
@@ -1234,6 +1399,7 @@ async function updateExtension(extensionName, quiet, timeout = null) {
1234 * This function makes a POST request to '/api/extensions/delete' with the extension's name.1399 * This function makes a POST request to '/api/extensions/delete' with the extension's name.
1235 * If the extension is deleted, it displays a success message.1400 * If the extension is deleted, it displays a success message.
1236 * Creates a popup for the user to confirm before delete.1401 * 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.
1237 */1403 */
1238async function onDeleteClick() {1404async function onDeleteClick() {
1239 const extensionName = $(this).data('name');1405 const extensionName = $(this).data('name');
@@ -1244,11 +1410,48 @@ async function onDeleteClick() {
1244 return;1410 return;
1245 }1411 }
12461412
1247 // use callPopup to create a popup for the user to confirm before delete1413 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();
1249 if (confirmation === POPUP_RESULT.AFFIRMATIVE) {1420 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 */
1430async 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;
1251 }1436 }
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 */
1446async 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());
1252}1455}
12531456
1254async function onBranchClick() {1457async function onBranchClick() {
@@ -1303,8 +1506,8 @@ async function onMoveClick() {
13031506
1304 const confirmationHeader = t`Move extension`;1507 const confirmationHeader = t`Move extension`;
1305 const confirmationText = source == 'global'1508 const confirmationText = source == 'global'
1306 ? t`Are you sure you want to move ${extensionName} to your local extensions? This will make it available only for you.`1509 ? t`Are you sure you want to move ${escapeHtml(extensionName)} to your local extensions? This will make it available only for you.`
1307 : t`Are you sure you want to move ${extensionName} to the global extensions? This will make it available for all users.`;1510 : t`Are you sure you want to move ${escapeHtml(extensionName)} to the global extensions? This will make it available for all users.`;
13081511
1309 const confirmation = await Popup.show.confirm(confirmationHeader, confirmationText);1512 const confirmation = await Popup.show.confirm(confirmationHeader, confirmationText);
13101513
@@ -1353,9 +1556,16 @@ async function moveExtension(extensionName, source, destination) {
1353/**1556/**
1354 * Deletes an extension via the API.1557 * Deletes an extension via the API.
1355 * @param {string} extensionName Extension name to delete1558 * @param {string} extensionName Extension name to delete
1559 * @param {boolean} [shouldClean=false] Whether to also run the 'clean' hook before deleting
1356 */1560 */
1357export async function deleteExtension(extensionName) {1561export 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
1360 try {1570 try {
1361 await fetch('/api/extensions/delete', {1571 await fetch('/api/extensions/delete', {
@@ -1370,6 +1580,9 @@ export async function deleteExtension(extensionName) {
1370 console.error('Error:', error);1580 console.error('Error:', error);
1371 }1581 }
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
1373 toastr.success(t`Extension ${extensionName} deleted`);1586 toastr.success(t`Extension ${extensionName} deleted`);
1374 delay(1000).then(() => location.reload());1587 delay(1000).then(() => location.reload());
1375}1588}
@@ -1398,6 +1611,9 @@ async function getExtensionVersion(extensionName, abortSignal) {
1398 const data = await response.json();1611 const data = await response.json();
1399 return data;1612 return data;
1400 } catch (error) {1613 } catch (error) {
1614 if (error instanceof Error && error.name === 'AbortError') {
1615 return;
1616 }
1401 console.error('Error:', error);1617 console.error('Error:', error);
1402 }1618 }
1403}1619}
@@ -1476,9 +1692,53 @@ async function switchExtensionBranch(extensionName, isGlobal, branch) {
1476 * Installs a third-party extension via the API.1692 * Installs a third-party extension via the API.
1477 * @param {string} url Extension repository URL1693 * @param {string} url Extension repository URL
1478 * @param {boolean} global Is the extension global?1694 * @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
1480 */1697 */
1481export async function installExtension(url, global, branch = '') {1698export 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
1482 console.debug('Extension installation started', url);1742 console.debug('Extension installation started', url);
14831743
1484 toastr.info(t`Please wait...`, t`Installing extension`);1744 toastr.info(t`Please wait...`, t`Installing extension`);
@@ -1497,11 +1757,11 @@ export async function installExtension(url, global, branch = '') {
1497 const text = await request.text();1757 const text = await request.text();
1498 toastr.warning(text || request.statusText, t`Extension installation failed`, { timeOut: 5000 });1758 toastr.warning(text || request.statusText, t`Extension installation failed`, { timeOut: 5000 });
1499 console.error('Extension installation failed', request.status, request.statusText, text);1759 console.error('Extension installation failed', request.status, request.statusText, text);
1500 return;1760 return false;
1501 }1761 }
15021762
1503 const response = await request.json();1763 const response = await request.json();
1504 toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`);1764 toastr.success(t`Extension '${response.display_name}' has been installed successfully!`, t`Extension installation successful`);
1505 console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);1765 console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);
1506 await loadExtensionSettings({}, false, false);1766 await loadExtensionSettings({}, false, false);
1507 await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response);1767 await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response);
@@ -1510,6 +1770,8 @@ export async function installExtension(url, global, branch = '') {
1510 const extensionName = `third-party/${response.folderName}`;1770 const extensionName = `third-party/${response.folderName}`;
1511 await callExtensionHook(extensionName, 'install');1771 await callExtensionHook(extensionName, 'install');
1512 }1772 }
1773
1774 return true;
1513}1775}
15141776
1515/**1777/**
@@ -1589,7 +1851,11 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
1589 const promise = enqueueVersionCheck(async () => {1851 const promise = enqueueVersionCheck(async () => {
1590 try {1852 try {
1591 const data = await getExtensionVersion(externalId, abortSignal);1853 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}"]`);
1593 if (extensionBlock && data) {1859 if (extensionBlock && data) {
1594 if (data.isUpToDate === false) {1860 if (data.isUpToDate === false) {
1595 const buttonElement = extensionBlock.querySelector('.btn_update');1861 const buttonElement = extensionBlock.querySelector('.btn_update');
@@ -1620,6 +1886,18 @@ async function checkForUpdatesManual(sortFn, abortSignal) {
1620 }1886 }
1621 }1887 }
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
1623 const versionElement = extensionBlock.querySelector('.extension_version');1901 const versionElement = extensionBlock.querySelector('.extension_version');
1624 if (versionElement) {1902 if (versionElement) {
1625 versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`;1903 versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`;
@@ -1672,6 +1950,9 @@ async function checkForExtensionUpdates(force) {
1672 const promise = enqueueVersionCheck(async () => {1950 const promise = enqueueVersionCheck(async () => {
1673 try {1951 try {
1674 const data = await getExtensionVersion(id.replace('third-party', ''));1952 const data = await getExtensionVersion(id.replace('third-party', ''));
1953 if (!data) {
1954 return;
1955 }
1675 if (!data.isUpToDate) {1956 if (!data.isUpToDate) {
1676 updatesAvailable.push(manifest.display_name);1957 updatesAvailable.push(manifest.display_name);
1677 }1958 }
@@ -1759,6 +2040,18 @@ export async function runGenerationInterceptors(chat, contextSize, type) {
1759}2040}
17602041
1761/**2042/**
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 */
2052export const UNSET_VALUE = '__@@UNSET@@__';
2053
2054/**
1762 * Writes a field to the character's data extensions object.2055 * Writes a field to the character's data extensions object.
1763 * @param {number|string} characterId Index in the character array2056 * @param {number|string} characterId Index in the character array
1764 * @param {string} key Field name2057 * @param {string} key Field name
@@ -1772,13 +2065,23 @@ export async function writeExtensionField(characterId, key, value) {
1772 console.warn('Character not found', characterId);2065 console.warn('Character not found', characterId);
1773 return;2066 return;
1774 }2067 }
1775 const path = `data.extensions.${key}`;2068 const extensionPath = `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
1778 // Process JSON data2077 // Process JSON data
1779 if (character.json_data) {2078 if (character.json_data) {
1780 const jsonData = JSON.parse(character.json_data);2079 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 }
1782 character.json_data = JSON.stringify(jsonData);2085 character.json_data = JSON.stringify(jsonData);
17832086
1784 // Make sure the data doesn't get lost when saving the current character2087 // Make sure the data doesn't get lost when saving the current character
@@ -1808,6 +2111,107 @@ export async function writeExtensionField(characterId, key, value) {
1808}2111}
18092112
1810/**2113/**
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 */
2143export 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/**
1811 * Prompts the user to enter the Git URL of the extension to import.2215 * Prompts the user to enter the Git URL of the extension to import.
1812 * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.2216 * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
1813 * If the extension is imported successfully, a success message is displayed.2217 * If the extension is imported successfully, a success message is displayed.
@@ -1853,6 +2257,39 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
1853 await installExtension(url, global, branchName);2257 await installExtension(url, global, branchName);
1854}2258}
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 */
2264export 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 */
2274export 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
1856export async function initExtensions() {2293export async function initExtensions() {
1857 await addExtensionsButtonAndMenu();2294 await addExtensionsButtonAndMenu();
1858 $('#extensionsMenuButton').css('display', 'flex');2295 $('#extensionsMenuButton').css('display', 'flex');
@@ -1865,6 +2302,7 @@ export async function initExtensions() {
1865 $(document).on('click', '.extensions_info .extension_block .toggle_enable', onEnableExtensionClick);2302 $(document).on('click', '.extensions_info .extension_block .toggle_enable', onEnableExtensionClick);
1866 $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick);2303 $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick);
1867 $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick);2304 $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick);
2305 $(document).on('click', '.extensions_info .extension_block .btn_clean', onCleanClick);
1868 $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick);2306 $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick);
1869 $(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick);2307 $(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
6import { DOMPurify } from '../../../lib.js';6import { DOMPurify } from '../../../lib.js';
7import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js';7import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js';
8import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js';8import { deleteExtension, EMPTY_AUTHOR, extensionNames, getAuthorFromUrl, getContext, installExtension, renderExtensionTemplateAsync, isOfficialExtension } from '../../extensions.js';
9import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';9import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
10import { executeSlashCommandsWithOptions } from '../../slash-commands.js';
11import { accountStorage } from '../../util/AccountStorage.js';10import { accountStorage } from '../../util/AccountStorage.js';
12import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js';11import { escapeHtml, flashHighlight, getStringHash, isValidUrl } from '../../utils.js';
13import { t, translate } from '../../i18n.js';12import { t, translate } from '../../i18n.js';
13import { SlashCommandParser } from '/scripts/slash-commands/SlashCommandParser.js';
14export { MODULE_NAME };14export { MODULE_NAME };
1515
16const MODULE_NAME = 'assets';16const MODULE_NAME = 'assets';
@@ -60,100 +60,34 @@ const KNOWN_TYPES = {
60 'blip': t`Blip sounds`,60 'blip': t`Blip sounds`,
61};61};
6262
63const EMPTY_AUTHOR = {
64 name: '',
65 url: '',
66};
67
68/**63/**
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
72 */69 */
73function getAuthorFromUrl(url) {70function createAssetButton(asset, assetType, index) {
74 const result = structuredClone(EMPTY_AUTHOR);71 const elemId = `assets_install_${assetType}_${index}`;
7572 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
92async 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' });
144 const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');73 const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
145 element.append(label);74 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
150 console.debug(DEBUG_PREFIX, 'Checking asset', asset.id, asset.url);76 console.debug(DEBUG_PREFIX, 'Checking asset', asset.id, asset.url);
15177
152 const assetInstall = async function () {78 const assetInstall = async function () {
153 element.off('click');79 element.off('click');
154 label.removeClass('fa-download');80 label.removeClass('fa-download');
155 this.classList.add('asset-download-button-loading');81 this.classList.add('asset-download-button-loading');
156 await installAsset(asset.url, assetType, asset.id);82 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 }
157 label.addClass('fa-check');91 label.addClass('fa-check');
158 this.classList.remove('asset-download-button-loading');92 this.classList.remove('asset-download-button-loading');
159 element.on('click', assetDelete);93 element.on('click', assetDelete);
@@ -171,7 +105,7 @@ async function downloadAssetsList(url) {
171 const assetDelete = async function () {105 const assetDelete = async function () {
172 if (assetType === 'character') {106 if (assetType === 'character') {
173 toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');107 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);
175 return;109 return;
176 }110 }
177 element.off('click');111 element.off('click');
@@ -204,6 +138,17 @@ async function downloadAssetsList(url) {
204 element.on('click', assetInstall);138 element.on('click', assetInstall);
205 }139 }
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 */
151function createAssetBlock(asset, assetType, element) {
207 console.debug(DEBUG_PREFIX, 'Created element for ', asset.id);152 console.debug(DEBUG_PREFIX, 'Created element for ', asset.id);
208153
209 const displayName = DOMPurify.sanitize(asset.name || asset.id);154 const displayName = DOMPurify.sanitize(asset.name || asset.id);
@@ -214,23 +159,31 @@ async function downloadAssetsList(url) {
214 const toolTag = assetType === 'extension' && asset.tool;159 const toolTag = assetType === 'extension' && asset.tool;
215 const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR;160 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
235 assetBlock.find('.tag').on('click', function (e) {188 assetBlock.find('.tag').on('click', function (e) {
236 const a = document.createElement('a');189 const a = document.createElement('a');
@@ -241,38 +194,128 @@ async function downloadAssetsList(url) {
241194
242 if (assetType === 'character') {195 if (assetType === 'character') {
243 if (asset.highlight) {196 if (asset.highlight) {
244 assetBlock.find('.asset-name').append('<i class="fa-solid fa-sm fa-trophy"></i>');197 nameSpan.append($('<i>', { class: 'fa-solid fa-sm fa-trophy' }));
245 }198 }
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 })));
247 }200 }
248201
249 assetBlock.addClass('asset-block');202 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 */
211async 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 {
251 assetTypeMenu.append(assetBlock);231 assetTypeMenu.append(assetBlock);
252 }232 }
233 }
234
253 assetTypeMenu.appendTo('#assets_menu');235 assetTypeMenu.appendTo('#assets_menu');
254 assetTypeMenu.on('click', 'a.asset_preview', previewAsset);236 assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
255}237}
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 */
243async 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
257 filterAssets();280 filterAssets();
258 $('#assets_filters').show();281 $('#assets_filters').show();
259 $('#assets_menu').show();282 $('#assets_menu').show();
260 })283}
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 :3285/**
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 */
289async 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
263 const installButton = $('#third_party_extension_button');303 const installButton = $('#third_party_extension_button');
264 flashHighlight(installButton, 10_000);304 flashHighlight(installButton, 10_000);
265 toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 });305 toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 });
266306
267 // Error logged after, to appear on top307 // Error logged after, to appear on top
268 console.error(error);308 console.error(error);
269 toastr.error('Problem with assets URL', DEBUG_PREFIX + 'Cannot get assets list');309 toastr.error('Problem with assets URL', 'Cannot get assets list');
270 $('#assets-connect-button').addClass('fa-plug-circle-exclamation');310 $('#assets-connect-button').addClass('fa-plug-circle-exclamation');
271 $('#assets-connect-button').addClass('redOverlayGlow');311 $('#assets-connect-button').addClass('redOverlayGlow');
272 });312 }
273 });
274}313}
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 */
276function previewAsset(e) {319function previewAsset(e) {
277 const href = $(this).attr('href');320 const href = $(this).attr('href');
278 const audioExtensions = ['.mp3', '.ogg', '.wav'];321 const audioExtensions = ['.mp3', '.ogg', '.wav'];
@@ -295,6 +338,15 @@ function previewAsset(e) {
295 }338 }
296}339}
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 */
298function isAssetInstalled(assetType, filename) {350function isAssetInstalled(assetType, filename) {
299 let assetList = currentAssets[assetType];351 let assetList = currentAssets[assetType];
300352
@@ -316,15 +368,22 @@ function isAssetInstalled(assetType, filename) {
316 return false;368 return false;
317}369}
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 */
319async function installAsset(url, assetType, filename) {378async function installAsset(url, assetType, filename) {
320 console.debug(DEBUG_PREFIX, 'Downloading ', url);379 console.debug(DEBUG_PREFIX, 'Downloading ', url);
321 const category = assetType;380 const category = assetType;
322 try {381 try {
323 if (category === 'extension') {382 if (category === 'extension') {
324 console.debug(DEBUG_PREFIX, 'Installing extension ', url);383 console.debug(DEBUG_PREFIX, 'Installing extension ', url);
325 await installExtension(url, false);384 const result = await installExtension(url, false);
326 console.debug(DEBUG_PREFIX, 'Extension installed.');385 console.debug(DEBUG_PREFIX, 'Extension installed.');
327 return;386 return result;
328 }387 }
329388
330 const body = { url, category, filename };389 const body = { url, category, filename };
@@ -340,16 +399,25 @@ async function installAsset(url, assetType, filename) {
340 console.debug(DEBUG_PREFIX, 'Importing character ', filename);399 console.debug(DEBUG_PREFIX, 'Importing character ', filename);
341 const blob = await result.blob();400 const blob = await result.blob();
342 const file = new File([blob], filename, { type: blob.type });401 const file = new File([blob], filename, { type: blob.type });
343 await processDroppedFiles([file]);402 const fileNameMap = new Map([[file, filename]]);
403 await processDroppedFiles([file], fileNameMap);
344 console.debug(DEBUG_PREFIX, 'Character downloaded.');404 console.debug(DEBUG_PREFIX, 'Character downloaded.');
345 }405 }
406 return true;
346 }407 }
408 return false;
347 } catch (err) {409 } catch (err) {
348 console.log(err);410 console.log(err);
349 return [];411 return false;
350 }412 }
351}413}
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 */
353async function deleteAsset(assetType, filename) {421async function deleteAsset(assetType, filename) {
354 console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename);422 console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename);
355 const category = assetType;423 const category = assetType;
@@ -358,6 +426,7 @@ async function deleteAsset(assetType, filename) {
358 console.debug(DEBUG_PREFIX, 'Deleting extension ', filename);426 console.debug(DEBUG_PREFIX, 'Deleting extension ', filename);
359 await deleteExtension(filename);427 await deleteExtension(filename);
360 console.debug(DEBUG_PREFIX, 'Extension deleted.');428 console.debug(DEBUG_PREFIX, 'Extension deleted.');
429 return true;
361 }430 }
362431
363 const body = { category, filename };432 const body = { category, filename };
@@ -369,19 +438,37 @@ async function deleteAsset(assetType, filename) {
369 });438 });
370 if (result.ok) {439 if (result.ok) {
371 console.debug(DEBUG_PREFIX, 'Deletion success.');440 console.debug(DEBUG_PREFIX, 'Deletion success.');
441 return true;
372 }442 }
443 return false;
373 } catch (err) {444 } catch (err) {
374 console.log(err);445 console.log(err);
375 return [];446 return false;
376 }447 }
377}448}
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 */
379async function openCharacterBrowser(forceDefault) {455async function openCharacterBrowser(forceDefault) {
380 const url = forceDefault ? ASSETS_JSON_URL : String($('#assets-json-url-field').val());456 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 }
381 const fetchResult = await fetch(url, { cache: 'no-cache' });461 const fetchResult = await fetch(url, { cache: 'no-cache' });
462 if (!fetchResult.ok) {
463 toastr.error('Cannot download the assets list.');
464 return;
465 }
382 const json = await fetchResult.json();466 const json = await fetchResult.json();
383 const characters = json.filter(x => x.type === 'character');467 if (!Array.isArray(json)) {
384468 toastr.error('Assets list is not an array');
469 return;
470 }
471 const characters = json.filter(x => x && x.type === 'character');
385 if (!characters.length) {472 if (!characters.length) {
386 toastr.error('No characters found in the assets list', 'Character browser');473 toastr.error('No characters found in the assets list', 'Character browser');
387 return;474 return;
@@ -398,12 +485,19 @@ async function openCharacterBrowser(forceDefault) {
398485
399 downloadButton.toggle(!isInstalled).on('click', async () => {486 downloadButton.toggle(!isInstalled).on('click', async () => {
400 downloadButton.toggleClass('fa-download fa-spinner fa-spin');487 downloadButton.toggleClass('fa-download fa-spinner fa-spin');
401 await installAsset(character.url, 'character', character.id);488 const result = await installAsset(character.url, 'character', character.id);
489 if (result) {
402 downloadButton.hide();490 downloadButton.hide();
403 checkMark.show();491 checkMark.show();
492 } else {
493 downloadButton.toggleClass('fa-download fa-spinner fa-spin');
494 }
404 });495 });
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
408 listElement.append(characterElement);502 listElement.append(characterElement);
409 }503 }
@@ -435,7 +529,7 @@ async function updateCurrentAssets() {
435//#############################//529//#############################//
436530
437// This function is called when the extension is loaded531// This function is called when the extension is loaded
438jQuery(async () => {532export async function init() {
439 // This is an example of loading HTML from a file533 // This is an example of loading HTML from a file
440 const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {});534 const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {});
441 const windowHtml = $(windowTemplate);535 const windowHtml = $(windowTemplate);
@@ -457,11 +551,16 @@ jQuery(async () => {
457551
458 const connectButton = windowHtml.find('#assets-connect-button');552 const connectButton = windowHtml.find('#assets-connect-button');
459 connectButton.on('click', async function () {553 connectButton.on('click', async function () {
460 const url = DOMPurify.sanitize(String(assetsJsonUrl.val()));554 const urlString = 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)}`;
462 const skipConfirm = accountStorage.getItem(rememberKey) === 'true';561 const skipConfirm = accountStorage.getItem(rememberKey) === 'true';
463562
464 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>${url}</var>`, {563 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>`, {
465 customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }],564 customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }],
466 onClose: popup => {565 onClose: popup => {
467 if (popup.result) {566 if (popup.result) {
@@ -480,7 +579,7 @@ jQuery(async () => {
480 connectButton.addClass('fa-plug-circle-check');579 connectButton.addClass('fa-plug-circle-check');
481 } catch (error) {580 } catch (error) {
482 console.error('Error:', error);581 console.error('Error:', error);
483 toastr.error(`Cannot get assets list from ${url}`);582 toastr.error(`Cannot get assets list from ${url.href}`);
484 connectButton.removeClass('fa-plug-circle-check');583 connectButton.removeClass('fa-plug-circle-check');
485 connectButton.addClass('fa-plug-circle-exclamation');584 connectButton.addClass('fa-plug-circle-exclamation');
486 connectButton.removeClass('redOverlayGlow');585 connectButton.removeClass('redOverlayGlow');
@@ -496,4 +595,4 @@ jQuery(async () => {
496 eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {595 eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => {
497 openCharacterBrowser(forceDefault);596 openCharacterBrowser(forceDefault);
498 });597 });
499});598}
public/scripts/extensions/assets/installation.html+18 -0
@@ -2,3 +2,21 @@
2 <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>2 <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>
3 <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>3 <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>
4</div>4</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 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "Keij#6799",8 "author": "Keij#6799",
9 "version": "0.1.0",9 "version": "0.1.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/assets/style.css+16 -3
@@ -27,15 +27,20 @@
27 margin-bottom: 0.25em;27 margin-bottom: 0.25em;
28}28}
2929
30.assets-list-div h2 {
31 margin: 0;
32 font-size: 1.1em;
33}
34
30.assets-list-div h3 {35.assets-list-div h3 {
31 text-transform: capitalize;36 text-transform: capitalize;
32}37}
3338
34.assets-list-div i a {39.assets-list-div .asset-block a {
35 color: inherit;40 color: inherit;
36}41}
3742
38.assets-list-div>i {43.assets-list-div .asset-block {
39 display: flex;44 display: flex;
40 flex-direction: row;45 flex-direction: row;
41 align-items: center;46 align-items: center;
@@ -46,7 +51,7 @@
46 border-bottom: 1px solid var(--SmartThemeBorderColor);51 border-bottom: 1px solid var(--SmartThemeBorderColor);
47}52}
4853
49.assets-list-div i span:first-of-type {54.assets-list-div .asset-block span:first-of-type {
50 font-weight: bold;55 font-weight: bold;
51}56}
5257
@@ -198,3 +203,11 @@
198.asset-name>b {203.asset-name>b {
199 font-weight: 600;204 font-weight: 600;
200}205}
206
207div:is(.assets-list-extensions-official, .assets-list-extensions-community):has(.assets-list-extensions:empty) {
208 display: none;
209}
210
211div: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) {
243 }243 }
244}244}
245245
246jQuery(async () => {246export async function init() {
247 eventSource.on(event_types.APP_READY, cleanUpAttachments);247 eventSource.on(event_types.APP_READY, cleanUpAttachments);
248 eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments);248 eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments);
249 eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename);249 eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename);
@@ -407,4 +407,4 @@ jQuery(async () => {
407 }),407 }),
408 ],408 ],
409 }));409 }));
410});410}
public/scripts/extensions/attachments/manifest.json+4 -1
@@ -7,5 +7,8 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "Cohee1207",8 "author": "Cohee1207",
9 "version": "1.0.0",9 "version": "1.0.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/caption/index.js+12 -6
@@ -1,8 +1,9 @@
1import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js';1import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
2import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';2import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
3import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';3import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParams } from '../../../script.js';
4import { getMessageTimeStamp } from '../../RossAscends-mods.js';4import { getMessageTimeStamp } from '../../RossAscends-mods.js';
5import { SECRET_KEYS, secret_state } from '../../secrets.js';5import { SECRET_KEYS, secret_state } from '../../secrets.js';
6import { oai_settings } from '../../openai.js';
6import { getMultimodalCaption } from '../shared.js';7import { getMultimodalCaption } from '../shared.js';
7import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js';8import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js';
8import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
@@ -99,7 +100,7 @@ async function wrapCaptionTemplate(caption) {
99 template += ' {{caption}}';100 template += ' {{caption}}';
100 }101 }
101102
102 let messageText = substituteParamsExtended(template, { caption: caption });103 let messageText = substituteParams(template, { dynamicMacros: { caption: caption } });
103104
104 if (extension_settings.caption.refine_mode) {105 if (extension_settings.caption.refine_mode) {
105 messageText = await Popup.show.input(106 messageText = await Popup.show.input(
@@ -321,6 +322,8 @@ async function captionMultimodal(base64Img, externalPrompt) {
321 prompt = String(customPrompt).trim();322 prompt = String(customPrompt).trim();
322 }323 }
323324
325 prompt = substituteParams(prompt);
326
324 const caption = await getMultimodalCaption(base64Img, prompt);327 const caption = await getMultimodalCaption(base64Img, prompt);
325 return { caption };328 return { caption };
326}329}
@@ -454,7 +457,7 @@ function isVideoCaptioningAvailable() {
454 return ['google', 'vertexai', 'zai'].includes(extension_settings.caption.multimodal_api);457 return ['google', 'vertexai', 'zai'].includes(extension_settings.caption.multimodal_api);
455}458}
456459
457jQuery(async function () {460export async function init() {
458 function addSendPictureButton() {461 function addSendPictureButton() {
459 const sendButton = $(`462 const sendButton = $(`
460 <div id="send_picture" class="list-group-item flex-container flexGap5">463 <div id="send_picture" class="list-group-item flex-container flexGap5">
@@ -504,6 +507,7 @@ jQuery(async function () {
504 'chutes': SECRET_KEYS.CHUTES,507 'chutes': SECRET_KEYS.CHUTES,
505 'electronhub': SECRET_KEYS.ELECTRONHUB,508 'electronhub': SECRET_KEYS.ELECTRONHUB,
506 'pollinations': SECRET_KEYS.POLLINATIONS,509 'pollinations': SECRET_KEYS.POLLINATIONS,
510 'workers_ai': SECRET_KEYS.WORKERS_AI,
507 };511 };
508512
509 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {513 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
@@ -580,7 +584,7 @@ jQuery(async function () {
580 }584 }
581585
582 async function addRemoteEndpointModels() {586 async function addRemoteEndpointModels() {
583 async function processEndpoint(api, url) {587 async function processEndpoint(api, url, additionalParams = {}) {
584 const dropdown = document.getElementById('caption_multimodal_model');588 const dropdown = document.getElementById('caption_multimodal_model');
585 if (!(dropdown instanceof HTMLSelectElement)) {589 if (!(dropdown instanceof HTMLSelectElement)) {
586 return;590 return;
@@ -591,7 +595,8 @@ jQuery(async function () {
591 const options = Array.from(dropdown.options);595 const options = Array.from(dropdown.options);
592 const response = await fetch(url, {596 const response = await fetch(url, {
593 method: 'POST',597 method: 'POST',
594 headers: getRequestHeaders({ omitContentType: true }),598 headers: getRequestHeaders(),
599 body: JSON.stringify(additionalParams),
595 });600 });
596 if (!response.ok) {601 if (!response.ok) {
597 return;602 return;
@@ -620,6 +625,7 @@ jQuery(async function () {
620 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');625 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');
621 await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');626 await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');
622 await processEndpoint('moonshot', '/api/backends/chat-completions/multimodal-models/moonshot');627 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 });
623 }629 }
624630
625 await addSettings();631 await addSettings();
@@ -804,4 +810,4 @@ jQuery(async function () {
804 }));810 }));
805811
806 document.body.classList.add('caption');812 document.body.classList.add('caption');
807});813}
public/scripts/extensions/caption/manifest.json+4 -1
@@ -9,5 +9,8 @@
9 "css": "style.css",9 "css": "style.css",
10 "author": "Cohee#1207",10 "author": "Cohee#1207",
11 "version": "1.0.0",11 "version": "1.0.0",
12 "homePage": "https://github.com/SillyTavern/SillyTavern"12 "homePage": "https://github.com/SillyTavern/SillyTavern",
13 "hooks": {
14 "activate": "init"
15 }
13}16}
public/scripts/extensions/caption/settings.html+15 -0
@@ -20,6 +20,7 @@
20 <option value="aimlapi">AI/ML API</option>20 <option value="aimlapi">AI/ML API</option>
21 <option value="chutes">Chutes</option>21 <option value="chutes">Chutes</option>
22 <option value="anthropic">Claude</option>22 <option value="anthropic">Claude</option>
23 <option value="workers_ai">Cloudflare Workers AI</option>
23 <option value="cohere">Cohere</option>24 <option value="cohere">Cohere</option>
24 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>25 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
25 <option value="electronhub">Electron Hub</option>26 <option value="electronhub">Electron Hub</option>
@@ -53,7 +54,14 @@
53 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>54 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
54 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>55 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
55 <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option>56 <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>
56 <option data-type="openai" value="gpt-5.4">gpt-5.4</option>59 <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>
57 <option data-type="openai" value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option>65 <option data-type="openai" value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option>
58 <option data-type="openai" value="gpt-5.2">gpt-5.2</option>66 <option data-type="openai" value="gpt-5.2">gpt-5.2</option>
59 <option data-type="openai" value="gpt-5.2-2025-12-11">gpt-5.2-2025-12-11</option>67 <option data-type="openai" value="gpt-5.2-2025-12-11">gpt-5.2-2025-12-11</option>
@@ -88,6 +96,7 @@
88 <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>96 <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
89 <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>97 <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>
90 <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>98 <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>
91 <option data-type="anthropic" value="claude-opus-4-6">claude-opus-4-6</option>100 <option data-type="anthropic" value="claude-opus-4-6">claude-opus-4-6</option>
92 <option data-type="anthropic" value="claude-opus-4-5">claude-opus-4-5</option>101 <option data-type="anthropic" value="claude-opus-4-5">claude-opus-4-5</option>
93 <option data-type="anthropic" value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option>102 <option data-type="anthropic" value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option>
@@ -144,6 +153,11 @@
144 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>153 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
145 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>154 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
146 <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option>155 <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>
147 <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option>161 <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option>
148 <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option>162 <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option>
149 <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option>163 <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option>
@@ -174,6 +188,7 @@
174 <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option>188 <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option>
175 <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option>189 <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option>
176 <option data-type="ollama" value="llama4">llama4</option>190 <option data-type="ollama" value="llama4">llama4</option>
191 <option data-type="zai" value="glm-5v-turbo">glm-5v-turbo</option>
177 <option data-type="zai" value="glm-4.6v">glm-4.6v</option>192 <option data-type="zai" value="glm-4.6v">glm-4.6v</option>
178 <option data-type="zai" value="glm-4.6v-flashx">glm-4.6v-flashx</option>193 <option data-type="zai" value="glm-4.6v-flashx">glm-4.6v-flashx</option>
179 <option data-type="zai" value="glm-4.6v-flash">glm-4.6v-flash</option>194 <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 @@
1import { DOMPurify, Fuse } from '../../../lib.js';1import { DOMPurify, Fuse } from '../../../lib.js';
22
3import { event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js';3import { activateSendButtons, deactivateSendButtons, event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js';
4import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';4import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
5import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';5import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
6import { SlashCommand } from '../../slash-commands/SlashCommand.js';6import { SlashCommand } from '../../slash-commands/SlashCommand.js';
7import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';7import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';
@@ -9,11 +9,16 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
9import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';9import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
10import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js';10import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js';
11import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';11import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
12import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
12import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';13import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
13import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';14import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js';
14import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4, waitUntilCondition } from '../../utils.js';15import { collapseSpaces, getUniqueName, isFalseBoolean, isTrueBoolean, uuidv4, waitUntilCondition } from '../../utils.js';
15import { t } from '../../i18n.js';16import { t } from '../../i18n.js';
16import { getSecretLabelById } from '../../secrets.js';17import { getSecretLabelById } from '../../secrets.js';
18import { performFuzzySearch } from '/scripts/power-user.js';
19import { StreamingDisplay } from '/scripts/streaming-display.js';
20import { ConnectionManagerRequestService } from '../shared.js';
21import { formatReasoning } from '/scripts/reasoning.js';
1722
18const MODULE_NAME = 'connection-manager';23const MODULE_NAME = 'connection-manager';
19const NONE = '<None>';24const NONE = '<None>';
@@ -474,7 +479,223 @@ async function renderDetailsContent(detailsContent) {
474 }479 }
475}480}
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 */
489async 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
698export async function init() {
478 extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS);699 extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS);
479700
480 for (const key of Object.keys(DEFAULT_SETTINGS)) {701 for (const key of Object.keys(DEFAULT_SETTINGS)) {
@@ -824,4 +1045,114 @@ async function renderDetailsContent(detailsContent) {
824 return JSON.stringify(profile);1045 return JSON.stringify(profile);
825 },1046 },
826 }));1047 }));
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 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "Cohee1207",8 "author": "Cohee1207",
9 "version": "1.0.0",9 "version": "1.0.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/expressions/index.js+65 -3
@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateQuietPrompt, generateRaw,
4import { dragElement, isMobile } from '../../RossAscends-mods.js';4import { dragElement, isMobile } from '../../RossAscends-mods.js';
5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js';7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean, includesIgnoreCaseAndAccents } from '../../utils.js';
8import { hideMutedSprites, selected_group } from '../../group-chats.js';8import { hideMutedSprites, selected_group } from '../../group-chats.js';
9import { isJsonSchemaSupported } from '../../textgen-settings.js';9import { isJsonSchemaSupported } from '../../textgen-settings.js';
10import { debounce_timeout } from '../../constants.js';10import { debounce_timeout } from '../../constants.js';
@@ -787,6 +787,32 @@ async function setSpriteSlashCommand({ type }, searchTerm) {
787}787}
788788
789/**789/**
790 * @param {string} expressionName - Label of the expression to set as fallback
791 */
792function 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/**
790 * Returns the sprite folder name (including override) for a character.816 * Returns the sprite folder name (including override) for a character.
791 * @param {object} char Character object817 * @param {object} char Character object
792 * @param {string} char.avatar Avatar filename with extension818 * @param {string} char.avatar Avatar filename with extension
@@ -2140,7 +2166,7 @@ function migrateSettings() {
2140 }2166 }
2141}2167}
21422168
2143(async function () {2169export async function init() {
2144 function addExpressionImage() {2170 function addExpressionImage() {
2145 const html = `2171 const html = `
2146 <div id="expression-wrapper">2172 <div id="expression-wrapper">
@@ -2317,6 +2343,42 @@ function migrateSettings() {
2317 returns: 'The currently set expression label after setting it.',2343 returns: 'The currently set expression label after setting it.',
2318 }));2344 }));
2319 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2345 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({
2320 name: 'expression-folder-override',2382 name: 'expression-folder-override',
2321 aliases: ['spriteoverride', 'costume'],2383 aliases: ['spriteoverride', 'costume'],
2322 callback: setSpriteFolderCommand,2384 callback: setSpriteFolderCommand,
@@ -2511,4 +2573,4 @@ function migrateSettings() {
2511 </div>2573 </div>
2512 `,2574 `,
2513 }));2575 }));
2514})();2576}
public/scripts/extensions/expressions/manifest.json+4 -1
@@ -9,5 +9,8 @@
9 "css": "style.css",9 "css": "style.css",
10 "author": "Cohee#1207",10 "author": "Cohee#1207",
11 "version": "1.0.0",11 "version": "1.0.0",
12 "homePage": "https://github.com/SillyTavern/SillyTavern"12 "homePage": "https://github.com/SillyTavern/SillyTavern",
13 "hooks": {
14 "activate": "init"
15 }
13}16}
public/scripts/extensions/gallery/index.js+2 -2
@@ -818,7 +818,7 @@ function addGalleryWandButton() {
818}818}
819819
820// On extension load, ensure the settings are initialized820// On extension load, ensure the settings are initialized
821(function () {821export async function init() {
822 initSettings();822 initSettings();
823 eventSource.on(event_types.CHARACTER_RENAMED, (oldAvatar, newAvatar) => {823 eventSource.on(event_types.CHARACTER_RENAMED, (oldAvatar, newAvatar) => {
824 const context = SillyTavern.getContext();824 const context = SillyTavern.getContext();
@@ -850,4 +850,4 @@ function addGalleryWandButton() {
850 }),850 }),
851 );851 );
852 addGalleryWandButton();852 addGalleryWandButton();
853})();853}
public/scripts/extensions/gallery/manifest.json+4 -1
@@ -8,5 +8,8 @@
8 "css": "style.css",8 "css": "style.css",
9 "author": "City-Unit",9 "author": "City-Unit",
10 "version": "1.5.0",10 "version": "1.5.0",
11 "homePage": "https://github.com/SillyTavern/SillyTavern"11 "homePage": "https://github.com/SillyTavern/SillyTavern",
12 "hooks": {
13 "activate": "init"
14 }
12}15}
public/scripts/extensions/memory/index.js+2 -2
@@ -1063,7 +1063,7 @@ function setupListeners() {
1063 });1063 });
1064}1064}
10651065
1066jQuery(async function () {1066export async function init() {
1067 async function addExtensionControls() {1067 async function addExtensionControls() {
1068 const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings });1068 const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings });
1069 $('#summarize_container').append(settingsHtml);1069 $('#summarize_container').append(settingsHtml);
@@ -1128,4 +1128,4 @@ jQuery(async function () {
1128 () => summaryMacroHandler(),1128 () => summaryMacroHandler(),
1129 'Returns the latest memory/summary from the current chat.');1129 'Returns the latest memory/summary from the current chat.');
1130 }1130 }
1131});1131}
public/scripts/extensions/memory/manifest.json+4 -1
@@ -9,5 +9,8 @@
9 "css": "style.css",9 "css": "style.css",
10 "author": "Cohee#1207",10 "author": "Cohee#1207",
11 "version": "1.0.0",11 "version": "1.0.0",
12 "homePage": "https://github.com/SillyTavern/SillyTavern"12 "homePage": "https://github.com/SillyTavern/SillyTavern",
13 "hooks": {
14 "activate": "init"
15 }
13}16}
public/scripts/extensions/quick-reply/index.js+4 -3
@@ -169,7 +169,7 @@ const handleCharChange = () => {
169 settings.charConfig = charConfig;169 settings.charConfig = charConfig;
170};170};
171171
172const init = async () => {172export async function init() {
173 await loadSets();173 await loadSets();
174 await loadSettings();174 await loadSettings();
175 log('settings: ', settings);175 log('settings: ', settings);
@@ -214,7 +214,8 @@ const init = async () => {
214 eventSource.on(event_types.APP_READY, async () => await finalizeInit());214 eventSource.on(event_types.APP_READY, async () => await finalizeInit());
215215
216 globalThis.quickReplyApi = quickReplyApi;216 globalThis.quickReplyApi = quickReplyApi;
217};217}
218
218const finalizeInit = async () => {219const finalizeInit = async () => {
219 debug('executing startup');220 debug('executing startup');
220 await autoExec.handleStartup();221 await autoExec.handleStartup();
@@ -229,7 +230,7 @@ const finalizeInit = async () => {
229 isReady = true;230 isReady = true;
230 debug('READY');231 debug('READY');
231};232};
232await init();233
233234
234const purgeCharacterQuickReplySets = ({ character }) => {235const purgeCharacterQuickReplySets = ({ character }) => {
235 // Remove the character's Quick Reply Sets from the settings.236 // Remove the character's Quick Reply Sets from the settings.
public/scripts/extensions/quick-reply/manifest.json+4 -1
@@ -7,5 +7,8 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "RossAscends#1779",8 "author": "RossAscends#1779",
9 "version": "2.0.0",9 "version": "2.0.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/regex/index.js+34 -3
@@ -1639,7 +1639,7 @@ async function checkCharEmbeddedRegexScripts() {
1639function notifyReloadCurrentChat(presetName) {1639function notifyReloadCurrentChat(presetName) {
1640 toastr.info(1640 toastr.info(
1641 t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>',1641 t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>',
1642 t`Preset '${presetName}' contains enabled regex scripts`,1642 t`Preset '${escapeHtml(presetName)}' contains enabled regex scripts`,
1643 {1643 {
1644 timeOut: 5000,1644 timeOut: 5000,
1645 escapeHtml: false,1645 escapeHtml: false,
@@ -1709,7 +1709,7 @@ function onPresetRenamed({ apiId, oldName, newName }) {
17091709
1710// Workaround for loading in sequence with other extensions1710// Workaround for loading in sequence with other extensions
1711// NOTE: Always puts extension at the top of the list, but this is fine since it's static1711// NOTE: Always puts extension at the top of the list, but this is fine since it's static
1712jQuery(async () => {1712export async function init() {
1713 if (!Array.isArray(extension_settings.regex)) {1713 if (!Array.isArray(extension_settings.regex)) {
1714 extension_settings.regex = [];1714 extension_settings.regex = [];
1715 }1715 }
@@ -2069,6 +2069,37 @@ jQuery(async () => {
2069 helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.',2069 helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.',
2070 }));2070 }));
2071 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2071 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({
2072 name: 'regex-toggle',2103 name: 'regex-toggle',
2073 callback: toggleRegexCallback,2104 callback: toggleRegexCallback,
2074 returns: 'The name of the script that was toggled',2105 returns: 'The name of the script that was toggled',
@@ -2123,4 +2154,4 @@ jQuery(async () => {
21232154
2124 presetManager.setupEventListeners();2155 presetManager.setupEventListeners();
2125 presetManager.registerSlashCommands();2156 presetManager.registerSlashCommands();
2126});2157}
public/scripts/extensions/regex/manifest.json+4 -1
@@ -7,5 +7,8 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "kingbri",8 "author": "kingbri",
9 "version": "1.0.0",9 "version": "1.0.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/shared.js+35 -1
@@ -1,4 +1,4 @@
1import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js';1import { CONNECT_API_MAP, createModelIcon, getRequestHeaders } from '../../script.js';
2import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';2import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js';
3import { t } from '../i18n.js';3import { t } from '../i18n.js';
4import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js';4import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js';
@@ -126,6 +126,10 @@ export async function getMultimodalCaption(base64Img, prompt) {
126 requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;126 requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
127 }127 }
128128
129 if (extension_settings.caption.multimodal_api === 'workers_ai') {
130 requestBody.workers_ai_account_id = oai_settings.workers_ai_account_id;
131 }
132
129 function getEndpointUrl() {133 function getEndpointUrl() {
130 switch (extension_settings.caption.multimodal_api) {134 switch (extension_settings.caption.multimodal_api) {
131 case 'google':135 case 'google':
@@ -283,6 +287,10 @@ function throwIfInvalidModel(useReverseProxy) {
283 if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) {287 if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) {
284 throw new Error('Pollinations API key is not set.');288 throw new Error('Pollinations API key is not set.');
285 }289 }
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 }
286}294}
287295
288/**296/**
@@ -435,10 +443,12 @@ export class ConnectionManagerRequestService {
435 max_tokens: maxTokens,443 max_tokens: maxTokens,
436 model: profile.model,444 model: profile.model,
437 chat_completion_source: selectedApiMap.source,445 chat_completion_source: selectedApiMap.source,
446 secret_id: profile['secret-id'],
438 custom_url: profile['api-url'],447 custom_url: profile['api-url'],
439 vertexai_region: profile['api-url'],448 vertexai_region: profile['api-url'],
440 zai_endpoint: profile['api-url'],449 zai_endpoint: profile['api-url'],
441 siliconflow_endpoint: profile['api-url'],450 siliconflow_endpoint: profile['api-url'],
451 minimax_endpoint: profile['api-url'],
442 reverse_proxy: proxyPreset?.url,452 reverse_proxy: proxyPreset?.url,
443 proxy_password: proxyPreset?.password,453 proxy_password: proxyPreset?.password,
444 custom_prompt_post_processing: profile['prompt-post-processing'],454 custom_prompt_post_processing: profile['prompt-post-processing'],
@@ -459,6 +469,7 @@ export class ConnectionManagerRequestService {
459 model: profile.model,469 model: profile.model,
460 api_type: selectedApiMap.type,470 api_type: selectedApiMap.type,
461 api_server: profile['api-url'],471 api_server: profile['api-url'],
472 secret_id: profile['secret-id'],
462 ...overridePayload,473 ...overridePayload,
463 }, {474 }, {
464 instructName: includeInstruct ? profile.instruct : undefined,475 instructName: includeInstruct ? profile.instruct : undefined,
@@ -534,6 +545,29 @@ export class ConnectionManagerRequestService {
534 }545 }
535546
536 /**547 /**
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 /**
537 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]571 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
538 * @returns {boolean}572 * @returns {boolean}
539 */573 */
public/scripts/extensions/stable-diffusion/button.html+0 -4
@@ -2,7 +2,3 @@
2 <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div>2 <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div>
3 <span data-i18n="Generate Image">Generate Image</span>3 <span data-i18n="Generate Image">Generate Image</span>
4</div>4</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';
62import { oai_settings } from '../../openai.js';62import { oai_settings } from '../../openai.js';
63import { power_user } from '/scripts/power-user.js';63import { power_user } from '/scripts/power-user.js';
64import { MacrosParser } from '/scripts/macros.js';64import { MacrosParser } from '/scripts/macros.js';
65import { ActionLoaderHandle, loader } from '/scripts/action-loader.js';
6566
66export { MODULE_NAME };67export { MODULE_NAME };
6768
68const MODULE_NAME = 'sd';69const MODULE_NAME = 'sd';
69// This is a 1x1 transparent PNG70// This is a 1x1 transparent PNG
70const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';71const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
71const CUSTOM_STOP_EVENT = 'sd_stop_generation';
72
73// Generation tracking for status indicator
74let activeGenerations = 0;
75/** @type {JQuery<HTMLElement>|null} */
76let generationToast = null;
7772
78const sources = {73const sources = {
79 extras: 'extras',74 extras: 'extras',
@@ -99,6 +94,7 @@ const sources = {
99 google: 'google',94 google: 'google',
100 zai: 'zai',95 zai: 'zai',
101 openrouter: 'openrouter',96 openrouter: 'openrouter',
97 workersai: 'workersai',
102};98};
103const comfyTypes = {99const comfyTypes = {
104 standard: 'standard',100 standard: 'standard',
@@ -1747,6 +1743,9 @@ async function loadSamplers() {
1747 case sources.openrouter:1743 case sources.openrouter:
1748 samplers = ['N/A'];1744 samplers = ['N/A'];
1749 break;1745 break;
1746 case sources.workersai:
1747 samplers = ['N/A'];
1748 break;
1750 }1749 }
17511750
1752 for (const sampler of samplers) {1751 for (const sampler of samplers) {
@@ -1815,6 +1814,34 @@ async function loadAutoSamplers() {
1815 }1814 }
1816}1815}
18171816
1817async 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
1818async function loadSdcppSamplers() {1845async function loadSdcppSamplers() {
1819 // The sdcpp server does not provide an API for samplers, so we return the known list.1846 // The sdcpp server does not provide an API for samplers, so we return the known list.
1820 return ['euler', 'euler_a', 'heun', 'dpm2', 'dpm++2s_a', 'dpm++2m', 'dpm++2mv2', 'ipndm', 'ipndm_v', 'lcm', 'ddim_trailing', 'tcd'];1847 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() {
1910 models = await loadAutoModels();1937 models = await loadAutoModels();
1911 break;1938 break;
1912 case sources.sdcpp:1939 case sources.sdcpp:
1913 models = [{ value: '', text: 'N/A' }];1940 models = await loadSdcppModels();
1914 break;1941 break;
1915 case sources.drawthings:1942 case sources.drawthings:
1916 models = await loadDrawthingsModels();1943 models = await loadDrawthingsModels();
@@ -1969,6 +1996,9 @@ async function loadModels() {
1969 case sources.openrouter:1996 case sources.openrouter:
1970 models = await loadOpenRouterModels();1997 models = await loadOpenRouterModels();
1971 break;1998 break;
1999 case sources.workersai:
2000 models = await loadWorkersAIImageModels();
2001 break;
1972 }2002 }
19732003
1974 if (extension_settings.sd.source === sources.electronhub) {2004 if (extension_settings.sd.source === sources.electronhub) {
@@ -2096,6 +2126,33 @@ async function loadXAIModels() {
2096 ];2126 ];
2097}2127}
20982128
2129async 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
2099async function loadPollinationsModels() {2156async function loadPollinationsModels() {
2100 $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]);2157 $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]);
21012158
@@ -2325,6 +2382,8 @@ async function loadDrawthingsModels() {
23252382
2326async function loadOpenAiModels() {2383async function loadOpenAiModels() {
2327 return [2384 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' },
2328 { value: 'gpt-image-1.5', text: 'gpt-image-1.5' },2387 { value: 'gpt-image-1.5', text: 'gpt-image-1.5' },
2329 { value: 'gpt-image-1-mini', text: 'gpt-image-1-mini' },2388 { value: 'gpt-image-1-mini', text: 'gpt-image-1-mini' },
2330 { value: 'gpt-image-1', text: 'gpt-image-1' },2389 { value: 'gpt-image-1', text: 'gpt-image-1' },
@@ -2581,6 +2640,9 @@ async function loadSchedulers() {
2581 case sources.openrouter:2640 case sources.openrouter:
2582 schedulers = ['N/A'];2641 schedulers = ['N/A'];
2583 break;2642 break;
2643 case sources.workersai:
2644 schedulers = ['N/A'];
2645 break;
2584 }2646 }
25852647
2586 for (const scheduler of schedulers) {2648 for (const scheduler of schedulers) {
@@ -2701,6 +2763,9 @@ async function loadVaes() {
2701 case sources.openrouter:2763 case sources.openrouter:
2702 vaes = ['N/A'];2764 vaes = ['N/A'];
2703 break;2765 break;
2766 case sources.workersai:
2767 vaes = ['N/A'];
2768 break;
2704 }2769 }
27052770
2706 for (const vae of vaes) {2771 for (const vae of vaes) {
@@ -2913,62 +2978,6 @@ function ensureSelectionExists(setting, selector) {
2913}2978}
29142979
2915/**2980/**
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 */
2919function 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 */
2958function startGenerationTracking() {
2959 activeGenerations++;
2960 updateGenerationIndicator();
2961}
2962
2963/**
2964 * Decrements the active generation counter and updates indicators.
2965 */
2966function endGenerationTracking() {
2967 activeGenerations = Math.max(0, activeGenerations - 1);
2968 updateGenerationIndicator();
2969}
2970
2971/**
2972 * Generates an image based on the given trigger word.2981 * Generates an image based on the given trigger word.
2973 * @param {string} initiator The initiator of the image generation2982 * @param {string} initiator The initiator of the image generation
2974 * @param {Record<string, object>} args Command arguments2983 * @param {Record<string, object>} args Command arguments
@@ -3028,12 +3037,13 @@ async function generatePicture(initiator, args, trigger, message, callback) {
30283037
3029 const dimensions = setTypeSpecificDimensions(generationType);3038 const dimensions = setTypeSpecificDimensions(generationType);
3030 const abortController = new AbortController();3039 const abortController = new AbortController();
3031 const stopButton = document.getElementById('sd_stop_gen');
3032 let negativePromptPrefix = args?.negative || '';3040 let negativePromptPrefix = args?.negative || '';
3033 let imagePath = '';3041 let imagePath = '';
30343042
3035 const stopListener = () => abortController.abort('Aborted by user');3043 const stopListener = () => abortController.abort('Aborted by user');
30363044
3045 let loaderHandle = ActionLoaderHandle.EMPTY;
3046
3037 try {3047 try {
3038 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };3048 const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
30393049
@@ -3046,16 +3056,19 @@ async function generatePicture(initiator, args, trigger, message, callback) {
3046 await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData);3056 await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData);
3047 prompt = eventData.prompt; // Allow extensions to modify the prompt3057 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
3055 if (typeof args?._abortController?.addEventListener === 'function') {3059 if (typeof args?._abortController?.addEventListener === 'function') {
3056 args._abortController.addEventListener('abort', stopListener);3060 args._abortController.addEventListener('abort', stopListener);
3057 }3061 }
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
3059 // generate the image3072 // generate the image
3060 imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);3073 imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
3061 } catch (err) {3074 } catch (err) {
@@ -3074,10 +3087,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
3074 toastr.error(errorText, 'Image Generation');3087 toastr.error(errorText, 'Image Generation');
3075 throw new Error(errorText);3088 throw new Error(errorText);
3076 } finally {3089 } finally {
3077 $(stopButton).hide();
3078 restoreOriginalDimensions(dimensions);3090 restoreOriginalDimensions(dimensions);
3079 eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener);3091 await loaderHandle.hide();
3080 endGenerationTracking();
3081 }3092 }
30823093
3083 return imagePath;3094 return imagePath;
@@ -3404,6 +3415,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
3404 case sources.openrouter:3415 case sources.openrouter:
3405 result = await generateOpenRouterImage(prefixedPrompt, signal);3416 result = await generateOpenRouterImage(prefixedPrompt, signal);
3406 break;3417 break;
3418 case sources.workersai:
3419 result = await generateWorkersAIImage(prefixedPrompt, negativePrompt, signal);
3420 break;
3407 }3421 }
34083422
3409 if (!result.data) {3423 if (!result.data) {
@@ -3858,6 +3872,7 @@ async function generateAutoImage(prompt, negativePrompt, signal) {
3858async function generateSdcppImage(prompt, negativePrompt, signal) {3872async function generateSdcppImage(prompt, negativePrompt, signal) {
3859 const payload = {3873 const payload = {
3860 url: extension_settings.sd.sdcpp_url,3874 url: extension_settings.sd.sdcpp_url,
3875 model: extension_settings.sd.model || undefined,
3861 prompt: prompt,3876 prompt: prompt,
3862 negative_prompt: negativePrompt,3877 negative_prompt: negativePrompt,
3863 steps: extension_settings.sd.steps,3878 steps: extension_settings.sd.steps,
@@ -4062,7 +4077,7 @@ async function generateOpenAiImage(prompt, signal) {
40624077
4063 const isDalle2 = /dall-e-2/.test(extension_settings.sd.model);4078 const isDalle2 = /dall-e-2/.test(extension_settings.sd.model);
4064 const isDalle3 = /dall-e-3/.test(extension_settings.sd.model);4079 const isDalle3 = /dall-e-3/.test(extension_settings.sd.model);
4065 const isGptImg = /gpt-image-(1|latest)/.test(extension_settings.sd.model);4080 const isGptImg = /gpt-image-(1|2|latest)/.test(extension_settings.sd.model);
4066 const isSora2 = /sora-2/.test(extension_settings.sd.model);4081 const isSora2 = /sora-2/.test(extension_settings.sd.model);
40674082
4068 if (isDalle2 && prompt.length > dalle2PromptLimit) {4083 if (isDalle2 && prompt.length > dalle2PromptLimit) {
@@ -4719,6 +4734,33 @@ async function generateOpenRouterImage(prompt, signal) {
4719 throw new Error(text);4734 throw new Error(text);
4720}4735}
47214736
4737async 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
4722async function onComfyOpenWorkflowEditorClick() {4764async function onComfyOpenWorkflowEditorClick() {
4723 let workflow = await (await fetch('/api/sd/comfy/workflow', {4765 let workflow = await (await fetch('/api/sd/comfy/workflow', {
4724 method: 'POST',4766 method: 'POST',
@@ -5029,10 +5071,6 @@ async function addSDGenButtons() {
5029 generatePicture(initiators.wand, {}, param);5071 generatePicture(initiators.wand, {}, param);
5030 }5072 }
5031 });5073 });
5032
5033 const stopGenButton = $('#sd_stop_gen');
5034 stopGenButton.hide();
5035 stopGenButton.on('click', () => eventSource.emit(CUSTOM_STOP_EVENT));
5036}5074}
50375075
5038function isValidState() {5076function isValidState() {
@@ -5091,6 +5129,8 @@ function isValidState() {
5091 return secret_state[SECRET_KEYS.ZAI];5129 return secret_state[SECRET_KEYS.ZAI];
5092 case sources.openrouter:5130 case sources.openrouter:
5093 return secret_state[SECRET_KEYS.OPENROUTER];5131 return secret_state[SECRET_KEYS.OPENROUTER];
5132 case sources.workersai:
5133 return !!oai_settings.workers_ai_account_id && secret_state[SECRET_KEYS.WORKERS_AI];
5094 default:5134 default:
5095 return false;5135 return false;
5096 }5136 }
@@ -5115,10 +5155,6 @@ async function sdMessageButton($icon, { animate } = {}) {
5115 $icon.toggleClass(classes.idle, !isBusy);5155 $icon.toggleClass(classes.idle, !isBusy);
5116 $icon.toggleClass(classes.busy, isBusy);5156 $icon.toggleClass(classes.busy, isBusy);
5117 $media.toggleClass(classes.animation, isBusy);5157 $media.toggleClass(classes.animation, isBusy);
5118
5119 // Update generation counter toast
5120 const trackingFunction = isBusy ? startGenerationTracking : endGenerationTracking;
5121 trackingFunction();
5122 }5158 }
51235159
5124 let $media = jQuery();5160 let $media = jQuery();
@@ -5233,7 +5269,6 @@ async function writePromptFields(characterId) {
5233 * @returns {Promise<MediaAttachment|null>} - A promise that resolves to the newly generated media attachment, or null if generation failed or was aborted.5269 * @returns {Promise<MediaAttachment|null>} - A promise that resolves to the newly generated media attachment, or null if generation failed or was aborted.
5234 */5270 */
5235async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, abortController = new AbortController()) {5271async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, abortController = new AbortController()) {
5236 const stopButton = document.getElementById('sd_stop_gen');
5237 const stopListener = () => abortController.abort('Aborted by user');5272 const stopListener = () => abortController.abort('Aborted by user');
5238 const generationType = mediaAttachment.generation_type ?? message?.extra?.generationType ?? generationMode.FREE;5273 const generationType = mediaAttachment.generation_type ?? message?.extra?.generationType ?? generationMode.FREE;
5239 let dimensions = { width: extension_settings.sd.width, height: extension_settings.sd.height };5274 let dimensions = { width: extension_settings.sd.width, height: extension_settings.sd.height };
@@ -5247,9 +5282,9 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
5247 source: MEDIA_SOURCE.GENERATED,5282 source: MEDIA_SOURCE.GENERATED,
5248 };5283 };
52495284
5285 let loaderHandle = ActionLoaderHandle.EMPTY;
5286
5250 try {5287 try {
5251 $(stopButton).show();
5252 eventSource.once(CUSTOM_STOP_EVENT, stopListener);
5253 const callback = (_a, _b, _c, _d, _e, _f, format) => { result.type = isVideo(format) ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.IMAGE; };5288 const callback = (_a, _b, _c, _d, _e, _f, format) => { result.type = isVideo(format) ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.IMAGE; };
5254 const savedPrompt = mediaAttachment.title ?? message.extra.title ?? '';5289 const savedPrompt = mediaAttachment.title ?? message.extra.title ?? '';
5255 const savedNegative = mediaAttachment.negative ?? message.extra.negative ?? '';5290 const savedNegative = mediaAttachment.negative ?? message.extra.negative ?? '';
@@ -5265,6 +5300,15 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
5265 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()5300 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
5266 : context.characters[context.characterId]?.name;5301 : 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
5268 onStart();5312 onStart();
5269 result.url = await sendGenerationRequest(generationType, prompt, refineArgs.negative, characterName, callback, initiators.swipe, abortController.signal);5313 result.url = await sendGenerationRequest(generationType, prompt, refineArgs.negative, characterName, callback, initiators.swipe, abortController.signal);
5270 result.generation_type = generationType;5314 result.generation_type = generationType;
@@ -5276,11 +5320,10 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
5276 }5320 }
5277 } finally {5321 } finally {
5278 onComplete();5322 onComplete();
5279 $(stopButton).hide();
5280 eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener);
5281 restoreOriginalDimensions(dimensions);5323 restoreOriginalDimensions(dimensions);
5282 extension_settings.sd.seed = extension_settings.sd.original_seed;5324 extension_settings.sd.seed = extension_settings.sd.original_seed;
5283 delete extension_settings.sd.original_seed;5325 delete extension_settings.sd.original_seed;
5326 await loaderHandle.hide();
5284 }5327 }
52855328
5286 if (!result.url) {5329 if (!result.url) {
@@ -5441,7 +5484,7 @@ function registerFunctionTool() {
5441 });5484 });
5442}5485}
54435486
5444jQuery(async () => {5487export async function init() {
5445 await addSDGenButtons();5488 await addSDGenButtons();
54465489
5447 const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null));5490 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 () => {
5850 extension_settings.sd.google_duration = Number($(this).val());5893 extension_settings.sd.google_duration = Number($(this).val());
5851 saveSettingsDebounced();5894 saveSettingsDebounced();
5852 });5895 });
5896 $('#sd_models_refresh').on('click', async () => {
5897 await loadModels();
5898 });
5853 $('#sd_electronhub_quality').on('change', function () {5899 $('#sd_electronhub_quality').on('change', function () {
5854 extension_settings.sd.electronhub_quality = String($(this).val());5900 extension_settings.sd.electronhub_quality = String($(this).val());
5855 saveSettingsDebounced();5901 saveSettingsDebounced();
@@ -5893,6 +5939,7 @@ jQuery(async () => {
5893 [sources.aimlapi]: SECRET_KEYS.AIMLAPI,5939 [sources.aimlapi]: SECRET_KEYS.AIMLAPI,
5894 [sources.comfy]: SECRET_KEYS.COMFY_RUNPOD,5940 [sources.comfy]: SECRET_KEYS.COMFY_RUNPOD,
5895 [sources.pollinations]: SECRET_KEYS.POLLINATIONS,5941 [sources.pollinations]: SECRET_KEYS.POLLINATIONS,
5942 [sources.workersai]: SECRET_KEYS.WORKERS_AI,
5896 };5943 };
5897 const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key);5944 const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key);
5898 if (!shouldReloadOptions) {5945 if (!shouldReloadOptions) {
@@ -5948,4 +5995,4 @@ jQuery(async () => {
5948 t`Character's negative Image Generation prompt prefix`,5995 t`Character's negative Image Generation prompt prefix`,
5949 );5996 );
5950 }5997 }
5951});5998}
public/scripts/extensions/stable-diffusion/manifest.json+4 -1
@@ -10,5 +10,8 @@
10 "css": "style.css",10 "css": "style.css",
11 "author": "Cohee#1207",11 "author": "Cohee#1207",
12 "version": "1.0.0",12 "version": "1.0.0",
13 "homePage": "https://github.com/SillyTavern/SillyTavern"13 "homePage": "https://github.com/SillyTavern/SillyTavern",
14 "hooks": {
15 "activate": "init"
16 }
14}17}
public/scripts/extensions/stable-diffusion/settings.html+36 -16
@@ -40,10 +40,11 @@
40 <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span>40 <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span>
41 </label>41 </label>
42 <label for="sd_source" data-i18n="Source">Source</label>42 <label for="sd_source" data-i18n="Source">Source</label>
43 <select id="sd_source">43 <select id="sd_source" class="text_pole">
44 <option value="aimlapi">AI/ML API</option>44 <option value="aimlapi">AI/ML API</option>
45 <option value="bfl">BFL (Black Forest Labs)</option>45 <option value="bfl">BFL (Black Forest Labs)</option>
46 <option value="chutes">Chutes</option>46 <option value="chutes">Chutes</option>
47 <option value="workersai">Cloudflare Workers AI</option>
47 <option value="comfy">ComfyUI</option>48 <option value="comfy">ComfyUI</option>
48 <option value="drawthings">DrawThings HTTP API</option>49 <option value="drawthings">DrawThings HTTP API</option>
49 <option value="electronhub">Electron Hub</option>50 <option value="electronhub">Electron Hub</option>
@@ -123,7 +124,7 @@
123 <div class="flex-container" id="sd_electronhub_quality_row">124 <div class="flex-container" id="sd_electronhub_quality_row">
124 <div class="flex1">125 <div class="flex1">
125 <label for="sd_electronhub_quality" data-i18n="Image Quality">Image Quality</label>126 <label for="sd_electronhub_quality" data-i18n="Image Quality">Image Quality</label>
126 <select id="sd_electronhub_quality"></select>127 <select id="sd_electronhub_quality" class="text_pole"></select>
127 </div>128 </div>
128 </div>129 </div>
129 </div>130 </div>
@@ -191,14 +192,14 @@
191 <div class="flex-container">192 <div class="flex-container">
192 <div data-sd-model="dall-e-3" class="flex1">193 <div data-sd-model="dall-e-3" class="flex1">
193 <label for="sd_openai_style" data-i18n="Image Style">Image Style</label>194 <label for="sd_openai_style" data-i18n="Image Style">Image Style</label>
194 <select id="sd_openai_style">195 <select id="sd_openai_style" class="text_pole">
195 <option value="vivid">Vivid</option>196 <option value="vivid">Vivid</option>
196 <option value="natural">Natural</option>197 <option value="natural">Natural</option>
197 </select>198 </select>
198 </div>199 </div>
199 <div data-sd-model="gpt-image" class="flex1">200 <div data-sd-model="gpt-image" class="flex1">
200 <label for="sd_openai_quality_gpt" data-i18n="Image Quality">Image Quality</label>201 <label for="sd_openai_quality_gpt" data-i18n="Image Quality">Image Quality</label>
201 <select id="sd_openai_quality_gpt">202 <select id="sd_openai_quality_gpt" class="text_pole">
202 <option value="auto" data-i18n="Auto">Auto</option>203 <option value="auto" data-i18n="Auto">Auto</option>
203 <option value="low" data-i18n="Low">Low</option>204 <option value="low" data-i18n="Low">Low</option>
204 <option value="medium" data-i18n="Medium">Medium</option>205 <option value="medium" data-i18n="Medium">Medium</option>
@@ -207,7 +208,7 @@
207 </div>208 </div>
208 <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1">209 <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1">
209 <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>210 <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>
210 <select id="sd_openai_quality">211 <select id="sd_openai_quality" class="text_pole">
211 <option value="standard" data-i18n="Standard">Standard</option>212 <option value="standard" data-i18n="Standard">Standard</option>
212 <option value="hd" data-i18n="HD">HD</option>213 <option value="hd" data-i18n="HD">HD</option>
213 </select>214 </select>
@@ -216,7 +217,7 @@
216 <div data-sd-model="sora-2,sora-2-pro" class="flex-container">217 <div data-sd-model="sora-2,sora-2-pro" class="flex-container">
217 <div class="flex1">218 <div class="flex1">
218 <label for="sd_openai_duration" data-i18n="Duration">Duration</label>219 <label for="sd_openai_duration" data-i18n="Duration">Duration</label>
219 <select id="sd_openai_duration">220 <select id="sd_openai_duration" class="text_pole">
220 <option value="4" data-i18n="Short (4 seconds)">Short (4 seconds)</option>221 <option value="4" data-i18n="Short (4 seconds)">Short (4 seconds)</option>
221 <option value="8" data-i18n="Medium (8 seconds)">Medium (8 seconds)</option>222 <option value="8" data-i18n="Medium (8 seconds)">Medium (8 seconds)</option>
222 <option value="12" data-i18n="Long (16 seconds)">Long (12 seconds)</option>223 <option value="12" data-i18n="Long (16 seconds)">Long (12 seconds)</option>
@@ -226,7 +227,7 @@
226 </div>227 </div>
227 <div data-sd-source="comfy">228 <div data-sd-source="comfy">
228 <label for="sd_comfy_type">Server Type</label>229 <label for="sd_comfy_type">Server Type</label>
229 <select id="sd_comfy_type">230 <select id="sd_comfy_type" class="text_pole">
230 <option value="standard">Standard Server</option>231 <option value="standard">Standard Server</option>
231 <option value="runpod_serverless">RunPod Serverless Endpoint</option>232 <option value="runpod_serverless">RunPod Serverless Endpoint</option>
232 </select>233 </select>
@@ -318,7 +319,7 @@
318 <div class="flex-container">319 <div class="flex-container">
319 <div class="flex1">320 <div class="flex1">
320 <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label>321 <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label>
321 <select id="sd_stability_style_preset">322 <select id="sd_stability_style_preset" class="text_pole">
322 <option value="anime">Anime</option>323 <option value="anime">Anime</option>
323 <option value="3d-model">3D Model</option>324 <option value="3d-model">3D Model</option>
324 <option value="analog-film">Analog Film</option>325 <option value="analog-film">Analog Film</option>
@@ -375,6 +376,20 @@
375 </div>376 </div>
376 </div>377 </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
378 <div data-sd-source="google">393 <div data-sd-source="google">
379 <div class="flex-container">394 <div class="flex-container">
380 <div class="flex1">395 <div class="flex1">
@@ -394,7 +409,7 @@
394 </label>409 </label>
395 <div class="flex1">410 <div class="flex1">
396 <label for="sd_google_duration" data-i18n="Duration (Veo)">Duration (Veo)</label>411 <label for="sd_google_duration" data-i18n="Duration (Veo)">Duration (Veo)</label>
397 <select id="sd_google_duration">412 <select id="sd_google_duration" class="text_pole">
398 <option value="4">Short (4 seconds)</option>413 <option value="4">Short (4 seconds)</option>
399 <option value="6">Medium (6 seconds)</option>414 <option value="6">Medium (6 seconds)</option>
400 <option value="8">Long (8 seconds)</option>415 <option value="8">Long (8 seconds)</option>
@@ -405,37 +420,42 @@
405420
406 <div class="flex-container">421 <div class="flex-container">
407 <div class="flex1">422 <div class="flex1">
408 <label for="sd_model" data-i18n="Model">Model</label>423 <label for="sd_model" class="flex-container justifySpaceBetween">
409 <select id="sd_model"></select>424 <span data-i18n="Model">Model</span>
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>
410 </div>430 </div>
411431
412 <div class="flex1" data-sd-source="comfy,auto">432 <div class="flex1" data-sd-source="comfy,auto">
413 <label for="sd_vae">VAE</label>433 <label for="sd_vae">VAE</label>
414 <select id="sd_vae"></select>434 <select id="sd_vae" class="text_pole"></select>
415 </div>435 </div>
416 </div>436 </div>
417437
418 <div class="flex-container">438 <div class="flex-container">
419 <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp">439 <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp">
420 <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>440 <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label>
421 <select id="sd_sampler"></select>441 <select id="sd_sampler" class="text_pole"></select>
422 </div>442 </div>
423443
424 <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp">444 <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp">
425 <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>445 <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label>
426 <select id="sd_scheduler"></select>446 <select id="sd_scheduler" class="text_pole"></select>
427 </div>447 </div>
428 </div>448 </div>
429449
430 <div class="flex-container">450 <div class="flex-container">
431 <div class="flex1">451 <div class="flex1">
432 <label for="sd_resolution" data-i18n="Resolution">Resolution</label>452 <label for="sd_resolution" data-i18n="Resolution">Resolution</label>
433 <select id="sd_resolution"><!-- Populated in JS --></select>453 <select id="sd_resolution" class="text_pole"><!-- Populated in JS --></select>
434 </div>454 </div>
435455
436 <div class="flex1" data-sd-source="auto,vlad,drawthings">456 <div class="flex1" data-sd-source="auto,vlad,drawthings">
437 <label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label>457 <label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label>
438 <select id="sd_hr_upscaler"></select>458 <select id="sd_hr_upscaler" class="text_pole"></select>
439 </div>459 </div>
440 </div>460 </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
5#sd_dropdown {1#sd_dropdown {
6 z-index: 30000;2 z-index: 30000;
7 backdrop-filter: blur(var(--SmartThemeBlurStrength));3 backdrop-filter: blur(var(--SmartThemeBlurStrength));
public/scripts/extensions/token-counter/index.js+2 -2
@@ -101,7 +101,7 @@ async function doCount() {
101 return count;101 return count;
102}102}
103103
104jQuery(() => {104export function init() {
105 const buttonHtml = `105 const buttonHtml = `
106 <div id="token_counter" class="list-group-item flex-container flexGap5">106 <div id="token_counter" class="list-group-item flex-container flexGap5">
107 <div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>` +107 <div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>` +
@@ -115,4 +115,4 @@ jQuery(() => {
115 returns: 'number of tokens',115 returns: 'number of tokens',
116 helpString: 'Counts the number of tokens in the current chat.',116 helpString: 'Counts the number of tokens in the current chat.',
117 }));117 }));
118});118}
public/scripts/extensions/token-counter/manifest.json+4 -1
@@ -7,5 +7,8 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "Cohee#1207",8 "author": "Cohee#1207",
9 "version": "1.0.0",9 "version": "1.0.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/translate/index.js+2 -2
@@ -707,7 +707,7 @@ const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayTe
707707
708globalThis.translate = translate;708globalThis.translate = translate;
709709
710jQuery(async () => {710export async function init() {
711 const html = await renderExtensionTemplateAsync('translate', 'index');711 const html = await renderExtensionTemplateAsync('translate', 'index');
712 const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons');712 const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons');
713713
@@ -801,4 +801,4 @@ jQuery(async () => {
801 },801 },
802 returns: ARGUMENT_TYPE.STRING,802 returns: ARGUMENT_TYPE.STRING,
803 }));803 }));
804});804}
public/scripts/extensions/translate/manifest.json+4 -1
@@ -7,5 +7,8 @@
7 "css": "style.css",7 "css": "style.css",
8 "author": "Cohee#1207",8 "author": "Cohee#1207",
9 "version": "1.0.0",9 "version": "1.0.0",
10 "homePage": "https://github.com/SillyTavern/SillyTavern"10 "homePage": "https://github.com/SillyTavern/SillyTavern",
11 "hooks": {
12 "activate": "init"
13 }
11}14}
public/scripts/extensions/tts/index.js+39 -14
@@ -1,6 +1,7 @@
1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
2import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';2import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js';3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js';
4import { accountStorage } from '../../util/AccountStorage.js';
4import { EdgeTtsProvider } from './edge.js';5import { EdgeTtsProvider } from './edge.js';
5import { ElevenLabsTtsProvider } from './elevenlabs.js';6import { ElevenLabsTtsProvider } from './elevenlabs.js';
6import { SileroTtsProvider } from './silerotts.js';7import { SileroTtsProvider } from './silerotts.js';
@@ -165,7 +166,7 @@ async function onNarrateOneMessage() {
165 }166 }
166167
167 resetTtsPlayback();168 resetTtsPlayback();
168 processAndQueueTtsMessage(message, Number(id));169 processAndQueueTtsMessage(message, Number(id), { manual: true });
169 moduleWorker();170 moduleWorker();
170}171}
171172
@@ -186,13 +187,20 @@ async function onNarrateText(args, text) {
186 ? voiceMap[DEFAULT_VOICE_MARKER]187 ? voiceMap[DEFAULT_VOICE_MARKER]
187 : voiceMap[name];188 : voiceMap[name];
188189
189 if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {190 if (voiceMapEntry === DISABLED_VOICE_MARKER) {
191 toastr.info(`TTS voice for ${name} is disabled.`);
192 await initVoiceMap(false);
193 return;
194 }
195
196 if (!voiceMapEntry) {
190 toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`);197 toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`);
198 await initVoiceMap(false);
191 return;199 return;
192 }200 }
193201
194 resetTtsPlayback();202 resetTtsPlayback();
195 processAndQueueTtsMessage({ mes: text, name: name });203 processAndQueueTtsMessage({ mes: text, name: name }, null, { manual: true });
196 await moduleWorker();204 await moduleWorker();
197205
198 // Return back to the chat voices206 // Return back to the chat voices
@@ -245,7 +253,7 @@ function isTtsProcessing() {
245}253}
246254
247/**255/**
248 * @typedef {ChatMessage & { id?: number }} TtsMessage256 * @typedef {ChatMessage & { id?: number, manual?: boolean, segmentText?: string, segmentType?: string }} TtsMessage
249 */257 */
250258
251/**259/**
@@ -253,12 +261,15 @@ function isTtsProcessing() {
253 * (if enabled) and adds each part to the TTS job queue.261 * (if enabled) and adds each part to the TTS job queue.
254 * @param {ChatMessage} message - The message object to be processed.262 * @param {ChatMessage} message - The message object to be processed.
255 * @param {number|null} [messageId=null] - The chat message index to associate with TTS events.263 * @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.
256 * @returns {void}266 * @returns {void}
257 */267 */
258function processAndQueueTtsMessage(message, messageId = null) {268function processAndQueueTtsMessage(message, messageId = null, { manual = false } = {}) {
259 /** @type {TtsMessage} */269 /** @type {TtsMessage} */
260 const clone = structuredClone(message);270 const clone = structuredClone(message);
261 clone.id = messageId ?? null;271 clone.id = messageId ?? null;
272 clone.manual = manual ?? false;
262273
263 if (!extension_settings.tts.narrate_by_paragraphs) {274 if (!extension_settings.tts.narrate_by_paragraphs) {
264 ttsJobQueue.push(clone);275 ttsJobQueue.push(clone);
@@ -408,9 +419,10 @@ function onAudioControlClicked() {
408 // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful419 // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful
409 if (!audioElement.paused || isTtsProcessing()) {420 if (!audioElement.paused || isTtsProcessing()) {
410 resetTtsPlayback();421 resetTtsPlayback();
411 } else {422 } else if (context?.chat?.length > 0) {
412 // Default play behavior if not processing or playing is to play the last message.423 // Default play behavior if not processing or playing is to play the last message.
413 processAndQueueTtsMessage(context.chat[context.chat.length - 1]);424 const id = context.chat.length - 1;
425 processAndQueueTtsMessage(context.chat[id], id, { manual: true });
414 }426 }
415 updateUiAudioPlayState();427 updateUiAudioPlayState();
416}428}
@@ -481,8 +493,10 @@ async function processAudioJobQueue() {
481// TTS Control //493// TTS Control //
482//################//494//################//
483495
496/** @type {TtsMessage[]} */
484const ttsJobQueue = [];497const ttsJobQueue = [];
485let currentTtsJob; // Null if nothing is currently being processed498/** @type {TtsMessage|null} */
499let currentTtsJob = null; // Null if nothing is currently being processed
486500
487function completeTtsJob() {501function completeTtsJob() {
488 console.info(`Current TTS job for ${currentTtsJob?.name} completed.`);502 console.info(`Current TTS job for ${currentTtsJob?.name} completed.`);
@@ -621,7 +635,18 @@ async function processTtsQueue() {
621635
622 const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey];636 const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey];
623637
624 if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {638 if (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) {
625 throw `${char} not in voicemap. Configure character in extension settings voice map`;650 throw `${char} not in voicemap. Configure character in extension settings voice map`;
626 }651 }
627652
@@ -649,7 +674,6 @@ async function processTtsQueue() {
649 text = substituteParams(text);674 text = substituteParams(text);
650675
651 if (extension_settings.tts.skip_codeblocks) {676 if (extension_settings.tts.skip_codeblocks) {
652 text = text.replace(/^\s{4}.*$/gm, '').trim();
653 text = text.replace(/```.*?```/gs, '').trim();677 text = text.replace(/```.*?```/gs, '').trim();
654 text = text.replace(/~~~.*?~~~/gs, '').trim();678 text = text.replace(/~~~.*?~~~/gs, '').trim();
655 }679 }
@@ -724,6 +748,7 @@ async function processTtsQueue() {
724 mes: currentTtsJob.mes,748 mes: currentTtsJob.mes,
725 extra: currentTtsJob.extra,749 extra: currentTtsJob.extra,
726 id: currentTtsJob.id,750 id: currentTtsJob.id,
751 manual: currentTtsJob.manual,
727 };752 };
728 ttsJobQueue.unshift(segmentJob);753 ttsJobQueue.unshift(segmentJob);
729 }754 }
@@ -824,7 +849,7 @@ async function playFullConversation() {
824849
825 context.chat.forEach((msg, i) => {850 context.chat.forEach((msg, i) => {
826 if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') {851 if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') {
827 processAndQueueTtsMessage(msg, i);852 processAndQueueTtsMessage(msg, i, { manual: false });
828 }853 }
829 });854 });
830855
@@ -1164,7 +1189,7 @@ async function onMessageEvent(messageId, lastCharIndex) {
1164 message.id = messageId;1189 message.id = messageId;
1165 ttsJobQueue.push(message);1190 ttsJobQueue.push(message);
1166 } else {1191 } else {
1167 processAndQueueTtsMessage(message, messageId);1192 processAndQueueTtsMessage(message, messageId, { manual: false });
1168 }1193 }
1169}1194}
11701195
@@ -1506,7 +1531,7 @@ async function initVoiceMapInternal(unrestricted) {
1506 updateVoiceMap();1531 updateVoiceMap();
1507}1532}
15081533
1509jQuery(async function () {1534export async function init() {
1510 async function addExtensionControls() {1535 async function addExtensionControls() {
1511 const settingsHtml = $(await renderExtensionTemplateAsync('tts', 'settings'));1536 const settingsHtml = $(await renderExtensionTemplateAsync('tts', 'settings'));
1512 $('#tts_container').append(settingsHtml);1537 $('#tts_container').append(settingsHtml);
@@ -1594,4 +1619,4 @@ jQuery(async function () {
1594 }));1619 }));
15951620
1596 document.body.appendChild(audioElement);1621 document.body.appendChild(audioElement);
1597});1622}
public/scripts/extensions/tts/manifest.json+4 -1
@@ -11,5 +11,8 @@
11 "css": "style.css",11 "css": "style.css",
12 "author": "Ouoertheo#7264",12 "author": "Ouoertheo#7264",
13 "version": "1.0.0",13 "version": "1.0.0",
14 "homePage": "None"14 "homePage": "None",
15 "hooks": {
16 "activate": "init"
17 }
15}18}
public/scripts/extensions/vectors/index.js+258 -213
@@ -44,6 +44,7 @@ import { oai_settings } from '../../openai.js';
44 * @property {string} text - The hashed message text44 * @property {string} text - The hashed message text
45 * @property {number} hash - The hash used as the vector key45 * @property {number} hash - The hash used as the vector key
46 * @property {number} index - The index of the message in the chat46 * @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)
47 */48 */
4849
49const MODULE_NAME = 'vectors';50const MODULE_NAME = 'vectors';
@@ -77,10 +78,13 @@ const settings = {
77 summarize_sent: false,78 summarize_sent: false,
78 summary_source: 'main',79 summary_source: 'main',
79 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.',80 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,
80 force_chunk_delimiter: '',83 force_chunk_delimiter: '',
8184
82 // For chats85 // For chats
83 enabled_chats: false,86 enabled_chats: false,
87 keep_hidden: false,
84 template: 'Past events:\n{{text}}',88 template: 'Past events:\n{{text}}',
85 depth: 2,89 depth: 2,
86 position: extension_prompt_types.IN_PROMPT,90 position: extension_prompt_types.IN_PROMPT,
@@ -117,10 +121,77 @@ const settings = {
117121
118const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);122const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
119const webllmProvider = new WebLlmVectorProvider();123const webllmProvider = new WebLlmVectorProvider();
124/**
125 * Cache for storing summaries of messages by their hash.
126 * @type {Map<number, string>}
127 */
120const cachedSummaries = new Map();128const 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 */
133const skippedHashes = new Set();
134/**
135 * Error causes treated as fatal — abort Vectorize All rather than skip.
136 * @type {Set<string>}
137 */
138const 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']);
121const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];139const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];
122140
123/**141/**
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>} */
153const 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/**
124 * Gets the Collection ID for a file embedded in the chat.195 * Gets the Collection ID for a file embedded in the chat.
125 * @param {string} fileUrl URL of the file196 * @param {string} fileUrl URL of the file
126 * @returns {string} Collection ID197 * @returns {string} Collection ID
@@ -145,10 +216,12 @@ async function onVectorizeAllClick() {
145 // Clear all cached summaries to ensure that new ones are created216 // Clear all cached summaries to ensure that new ones are created
146 // upon request of a full vectorise217 // upon request of a full vectorise
147 cachedSummaries.clear();218 cachedSummaries.clear();
219 skippedHashes.clear();
148220
149 const batchSize = getBatchSize();221 const batchSize = getBatchSize();
150 const elapsedLog = [];222 const elapsedLog = [];
151 let finished = false;223 let finished = false;
224 let initialPending = null; // total items pending at the start of this run — set on first sync return
152 $('#vectorize_progress').show();225 $('#vectorize_progress').show();
153 $('#vectorize_progress_percent').text('0');226 $('#vectorize_progress_percent').text('0');
154 $('#vectorize_progress_eta').text('...');227 $('#vectorize_progress_eta').text('...');
@@ -162,16 +235,27 @@ async function onVectorizeAllClick() {
162 const startTime = Date.now();235 const startTime = Date.now();
163 const remaining = await synchronizeChat(batchSize);236 const remaining = await synchronizeChat(batchSize);
164 const elapsed = Date.now() - startTime;237 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
165 elapsedLog.push(elapsed);244 elapsedLog.push(elapsed);
166 finished = remaining <= 0;245 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 done249 }
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;
171 const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times255 const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times
172 const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item256 const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item
173 const pace = averageElapsed / batchSize; // time needed to process one item257 const pace = averageElapsed / batchSize; // time needed to process one item
174 const remainingTime = Math.round(pace * remaining / 1000);258 const remainingTime = Math.round(pace * pending / 1000);
175259
176 $('#vectorize_progress_percent').text(processedPercent);260 $('#vectorize_progress_percent').text(processedPercent);
177 $('#vectorize_progress_eta').text(remainingTime);261 $('#vectorize_progress_eta').text(remainingTime);
@@ -180,6 +264,9 @@ async function onVectorizeAllClick() {
180 throw new Error('Chat changed');264 throw new Error('Chat changed');
181 }265 }
182 }266 }
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 }
183 } catch (error) {270 } catch (error) {
184 console.error('Vectors: Failed to vectorize all', error);271 console.error('Vectors: Failed to vectorize all', error);
185 } finally {272 } finally {
@@ -250,7 +337,7 @@ async function summarizeExtra(element) {
250337
251 if (apiResult.ok) {338 if (apiResult.ok) {
252 const data = await apiResult.json();339 const data = await apiResult.json();
253 element.text = data.summary;340 element.text = removeReasoningFromString(data.summary);
254 }341 }
255 } catch (error) {342 } catch (error) {
256 console.log(error);343 console.log(error);
@@ -282,45 +369,70 @@ async function summarizeWebLLM(element) {
282 }369 }
283370
284 const messages = [{ role: 'system', content: settings.summary_prompt }, { role: 'user', content: element.text }];371 const messages = [{ role: 'system', content: settings.summary_prompt }, { role: 'user', content: element.text }];
285 element.text = await generateWebLlmChatPrompt(messages);372 element.text = removeReasoningFromString(await generateWebLlmChatPrompt(messages));
286373
287 return true;374 return true;
288}375}
289376
290/**377/**
291 * Summarizes messages using the chosen method.378 * Runs one summarization attempt for a single element via the chosen endpoint.
292 * @param {HashedMessage[]} hashedMessages Array of hashed messages379 * @param {HashedMessage} element
293 * @param {string} endpoint Type of endpoint to use380 * @param {string} endpoint
294 * @returns {Promise<HashedMessage[]>} Summarized messages381 * @returns {Promise<boolean>} Whether the attempt succeeded.
295 */382 */
296async function summarize(hashedMessages, endpoint = 'main') {383async function summarizeOne(element, endpoint) {
297 for (const element of hashedMessages) {
298 const cachedSummary = cachedSummaries.get(element.hash);
299 if (!cachedSummary) {
300 let success = true;
301 switch (endpoint) {384 switch (endpoint) {
302 case 'main':385 case 'main':
303 success = await summarizeMain(element);386 return await summarizeMain(element);
304 break;
305 case 'extras':387 case 'extras':
306 success = await summarizeExtra(element);388 return await summarizeExtra(element);
307 break;
308 case 'webllm':389 case 'webllm':
309 success = await summarizeWebLLM(element);390 return await summarizeWebLLM(element);
310 break;
311 default:391 default:
312 console.error('Unsupported endpoint', endpoint);392 throw new Error(`Unsupported summary endpoint: ${endpoint}`, { cause: 'summary_endpoint_invalid' });
313 success = false;
314 break;
315 }393 }
316 if (success) {
317 cachedSummaries.set(element.hash, element.text);
318 } else {
319 break;
320}394}
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 */
406async 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) {
322 element.text = cachedSummary;411 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' });
323 }434 }
435 cachedSummaries.set(element.hash, element.text);
324 }436 }
325 return hashedMessages;437 return hashedMessages;
326}438}
@@ -347,21 +459,43 @@ async function synchronizeChat(batchSize = 5) {
347 return -1;459 return -1;
348 }460 }
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) }));
351 const hashesInCollection = await getSavedHashes(chatId);464 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));
354 const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x));469 const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x));
355470
471 let batch = newVectorItems.slice(0, batchSize);
472
356 if (settings.summarize) {473 if (settings.summarize) {
357 newVectorItems = await summarize(newVectorItems, settings.summary_source);474 const minLength = Math.max(0, Number(settings.summary_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 }
358 }484 }
359485
360 if (newVectorItems.length > 0) {486 if (batch.length > 0) {
361 const chunkedBatch = splitByChunks(newVectorItems.slice(0, batchSize));487 const chunkedBatch = splitByChunks(batch);
362488
363 console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batchSize}...`);489 console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batch.length}...`);
490 try {
364 await insertVectorItems(chatId, chunkedBatch);491 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 }
365 }499 }
366500
367 if (deletedHashes.length > 0) {501 if (deletedHashes.length > 0) {
@@ -388,6 +522,12 @@ async function synchronizeChat(batchSize = 5) {
388 return 'Extras API must provide an "embeddings" module.';522 return 'Extras API must provide an "embeddings" module.';
389 case 'webllm_not_supported':523 case 'webllm_not_supported':
390 return 'WebLLM extension is not installed or the model is not set.';524 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.';
391 default:531 default:
392 return 'Check server console for more details';532 return 'Check server console for more details';
393 }533 }
@@ -397,7 +537,7 @@ async function synchronizeChat(batchSize = 5) {
397537
398 const message = getErrorMessage(error.cause);538 const message = getErrorMessage(error.cause);
399 toastr.error(message, 'Vectorization failed', { preventDuplicates: true });539 toastr.error(message, 'Vectorization failed', { preventDuplicates: true });
400 return -1;540 return null;
401 } finally {541 } finally {
402 syncBlocked = false;542 syncBlocked = false;
403 }543 }
@@ -771,7 +911,11 @@ async function getQueryText(chat, initiator) {
771 .slice(0, settings.query);911 .slice(0, settings.query);
772912
773 if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) {913 if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) {
774 hashedMessages = await summarize(hashedMessages, settings.summary_source);914 const minLength = Math.max(0, Number(settings.summary_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 }
775 }919 }
776920
777 const queryText = hashedMessages.map(x => x.text).join('\n');921 const queryText = hashedMessages.map(x => x.text).join('\n');
@@ -842,6 +986,10 @@ function getVectorsRequestBody(args = {}) {
842 body.model = extension_settings.vectors.siliconflow_model;986 body.model = extension_settings.vectors.siliconflow_model;
843 body.siliconflow_endpoint = oai_settings.siliconflow_endpoint;987 body.siliconflow_endpoint = oai_settings.siliconflow_endpoint;
844 break;988 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;
845 default:993 default:
846 break;994 break;
847 }995 }
@@ -935,6 +1083,7 @@ function throwIfSourceInvalid() {
935 settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] ||1083 settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] ||
936 settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] ||1084 settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] ||
937 settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE] ||1085 settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE] ||
1086 settings.source === 'workers_ai' && !secret_state[SECRET_KEYS.WORKERS_AI] ||
938 settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) {1087 settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) {
939 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });1088 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
940 }1089 }
@@ -963,6 +1112,10 @@ function throwIfSourceInvalid() {
963 if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) {1112 if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) {
964 throw new Error('Vectors: WebLLM is not supported', { cause: 'webllm_not_supported' });1113 throw new Error('Vectors: WebLLM is not supported', { cause: 'webllm_not_supported' });
965 }1114 }
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 }
966}1119}
9671120
968/**1121/**
@@ -972,11 +1125,12 @@ function throwIfSourceInvalid() {
972 * @returns {Promise<void>}1125 * @returns {Promise<void>}
973 */1126 */
974async function deleteVectorItems(collectionId, hashes) {1127async function deleteVectorItems(collectionId, hashes) {
1128 const args = await getAdditionalArgs([]);
975 const response = await fetch('/api/vector/delete', {1129 const response = await fetch('/api/vector/delete', {
976 method: 'POST',1130 method: 'POST',
977 headers: getRequestHeaders(),1131 headers: getRequestHeaders(),
978 body: JSON.stringify({1132 body: JSON.stringify({
979 ...getVectorsRequestBody(),1133 ...getVectorsRequestBody(args),
980 collectionId: collectionId,1134 collectionId: collectionId,
981 hashes: hashes,1135 hashes: hashes,
982 source: settings.source,1136 source: settings.source,
@@ -1154,211 +1308,77 @@ function toggleSettings() {
1154 $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');1308 $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');
1155 $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai');1309 $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai');
1156 $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow');1310 $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow');
1311 $('#workers_ai_vectorsModel').toggle(settings.source === 'workers_ai');
1157 $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));1312 $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));
1158 switch (settings.source) {1313 if (settings.source === 'webllm') {
1159 case 'webllm':
1160 loadWebLlmModels();1314 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
1180async 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
1199function 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
1214async 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
1233function 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
1248async 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([]);
1265 }1317 }
1266}1318}
12671319
1268/**1320/**
1269 * Populates the Electron Hub model select element.1321 * Loads models from a remote embedding endpoint and populates the corresponding select element.
1270 * @param {{ id: string, name: string }[]} models Electron Hub models1322 * @param {string} source - The source key matching a remoteEmbeddingEndpoints entry
1271 */1323 */
1272function populateElectronHubModelSelect(models) {1324async function loadRemoteEmbeddingModels(source) {
1273 const select = $('#vectors_electronhub_model');1325 const config = 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);
1285 }1328 }
12861329
1287async 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
1306 /**1334 /**
1307 * Populates the OpenRouter model select element.1335 * Populates the select element with the given models.
1308 * @param {{ id: string, name: string }[]} models OpenRouter models1336 * @param {any[]} models - Array of model objects
1309 */1337 */
1310function populateOpenRouterModelSelect(models) {1338 function populateSelect(models) {
1311 const select = $('#vectors_openrouter_model');1339 const select = $(`#${selectId}`);
1312 select.empty();1340 select.empty();
1313 for (const m of models) {1341 for (const m of models) {
1314 const option = document.createElement('option');1342 const option = document.createElement('option');
1315 option.value = m.id;1343 option.value = m[valueProperty];
1316 option.text = m.name || m.id;1344 option.text = textProperty ? (m[textProperty] || m[valueProperty]) : m[valueProperty];
1317 select.append(option);1345 select.append(option);
1318 }1346 }
1319 if (!settings.openrouter_model && models.length) {1347 if (!settings[settingsKey] && models.length) {
1320 settings.openrouter_model = models[0].id;1348 settings[settingsKey] = models[0][valueProperty];
1349 Object.assign(extension_settings.vectors, settings);
1350 saveSettingsDebounced();
1321 }1351 }
1322 $('#vectors_openrouter_model').val(settings.openrouter_model);1352 select.val(settings[settingsKey]);
1323 }1353 }
13241354
1325async function loadSiliconFlowModels() {
1326 try {1355 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 = {
1328 method: 'POST',1360 method: 'POST',
1329 headers: getRequestHeaders(),1361 headers: getRequestHeaders(),
1330 body: JSON.stringify({1362 body: JSON.stringify(body || {}),
1331 siliconflow_endpoint: oai_settings.siliconflow_endpoint,1363 };
1332 }),
1333 });
13341364
1365 const response = await fetch(url, fetchOptions);
1335 if (!response.ok) {1366 if (!response.ok) {
1336 throw new Error(`HTTP ${response.status}`);1367 throw new Error(`HTTP ${response.status}`);
1337 }1368 }
13381369
1339 /** @type {Array<any>} */1370 /** @type {Array<any>} */
1340 const data = await response.json();1371 const data = await response.json();
1341 const models = Array.isArray(data) ? data : [];1372 let 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 }
1347 }1375 }
13481376
1349function 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;
1360 }1381 }
1361 $('#vectors_siliconflow_model').val(settings.siliconflow_model);
1362}1382}
13631383
1364/**1384/**
@@ -1496,10 +1516,11 @@ async function onViewStatsClick() {
1496 { timeOut: 10000, escapeHtml: false },1516 { timeOut: 10000, escapeHtml: false },
1497 );1517 );
14981518
1519 $('#chat .mes.vectorized').removeClass('vectorized');
1499 const chat = getContext().chat;1520 const chat = getContext().chat;
1500 for (const message of chat) {1521 for (const message of chat) {
1501 if (hashesInCollection.includes(getStringHash(substituteParams(message.mes)))) {1522 if (hashesInCollection.includes(getStringHash(substituteParams(message.mes)))) {
1502 const messageElement = $(`.mes[mesid="${chat.indexOf(message)}"]`);1523 const messageElement = $(`#chat .mes[mesid="${chat.indexOf(message)}"]`);
1503 messageElement.addClass('vectorized');1524 messageElement.addClass('vectorized');
1504 }1525 }
1505 }1526 }
@@ -1704,7 +1725,7 @@ async function activateWorldInfo(chat) {
1704 await eventSource.emit(event_types.WORLDINFO_FORCE_ACTIVATE, activatedEntries);1725 await eventSource.emit(event_types.WORLDINFO_FORCE_ACTIVATE, activatedEntries);
1705}1726}
17061727
1707jQuery(async () => {1728export async function init() {
1708 if (!extension_settings.vectors) {1729 if (!extension_settings.vectors) {
1709 extension_settings.vectors = settings;1730 extension_settings.vectors = settings;
1710 }1731 }
@@ -1726,6 +1747,11 @@ jQuery(async () => {
1726 saveSettingsDebounced();1747 saveSettingsDebounced();
1727 toggleSettings();1748 toggleSettings();
1728 });1749 });
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 });
1729 $('#vectors_enabled_files').prop('checked', settings.enabled_files).on('input', () => {1755 $('#vectors_enabled_files').prop('checked', settings.enabled_files).on('input', () => {
1730 settings.enabled_files = $('#vectors_enabled_files').prop('checked');1756 settings.enabled_files = $('#vectors_enabled_files').prop('checked');
1731 Object.assign(extension_settings.vectors, settings);1757 Object.assign(extension_settings.vectors, settings);
@@ -1778,6 +1804,11 @@ jQuery(async () => {
1778 Object.assign(extension_settings.vectors, settings);1804 Object.assign(extension_settings.vectors, settings);
1779 saveSettingsDebounced();1805 saveSettingsDebounced();
1780 });1806 });
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 });
1781 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {1812 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {
1782 settings.openrouter_model = String($('#vectors_openrouter_model').val());1813 settings.openrouter_model = String($('#vectors_openrouter_model').val());
1783 Object.assign(extension_settings.vectors, settings);1814 Object.assign(extension_settings.vectors, settings);
@@ -1888,6 +1919,20 @@ jQuery(async () => {
1888 saveSettingsDebounced();1919 saveSettingsDebounced();
1889 });1920 });
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
1891 $('#vectors_message_chunk_size').val(settings.message_chunk_size).on('input', () => {1936 $('#vectors_message_chunk_size').val(settings.message_chunk_size).on('input', () => {
1892 settings.message_chunk_size = Number($('#vectors_message_chunk_size').val());1937 settings.message_chunk_size = Number($('#vectors_message_chunk_size').val());
1893 Object.assign(extension_settings.vectors, settings);1938 Object.assign(extension_settings.vectors, settings);
@@ -2310,4 +2355,4 @@ jQuery(async () => {
2310 }2355 }
2311 await purgeAllVectorIndexes();2356 await purgeAllVectorIndexes();
2312 });2357 });
2313});2358}
public/scripts/extensions/vectors/manifest.json+4 -1
@@ -10,5 +10,8 @@
10 "css": "style.css",10 "css": "style.css",
11 "author": "Cohee#1207",11 "author": "Cohee#1207",
12 "version": "1.0.0",12 "version": "1.0.0",
13 "homePage": "https://github.com/SillyTavern/SillyTavern"13 "homePage": "https://github.com/SillyTavern/SillyTavern",
14 "hooks": {
15 "activate": "init"
16 }
14}17}
public/scripts/extensions/vectors/settings.html+25 -0
@@ -11,6 +11,7 @@
11 </label>11 </label>
12 <select id="vectors_source" class="text_pole">12 <select id="vectors_source" class="text_pole">
13 <option value="chutes">Chutes</option>13 <option value="chutes">Chutes</option>
14 <option value="workers_ai">Cloudflare Workers AI</option>
14 <option value="cohere">Cohere</option>15 <option value="cohere">Cohere</option>
15 <option value="electronhub">Electron Hub</option>16 <option value="electronhub">Electron Hub</option>
16 <option value="extras">Extras (deprecated)</option>17 <option value="extras">Extras (deprecated)</option>
@@ -205,6 +206,16 @@
205 </i>206 </i>
206 </div>207 </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
208 <div class="flex-container marginTopBot5">219 <div class="flex-container marginTopBot5">
209 <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance.">220 <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance.">
210 <label for="vectors_query">221 <label for="vectors_query">
@@ -408,6 +419,10 @@
408 <hr>419 <hr>
409420
410 <div id="vectors_chats_settings">421 <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>
411 <div id="vectors_advanced_settings">426 <div id="vectors_advanced_settings">
412 <label for="vectors_template" data-i18n="Injection Template">427 <label for="vectors_template" data-i18n="Injection Template">
413 Injection Template428 Injection Template
@@ -478,6 +493,16 @@
478 <label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label>493 <label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label>
479 <small data-i18n="Only used when Main API or WebLLM Extension is selected.">Only used when Main API or WebLLM Extension is selected.</small>494 <small data-i18n="Only used when Main API or WebLLM Extension is selected.">Only used when Main API or WebLLM Extension is selected.</small>
480 <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>495 <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" />
481 </div>506 </div>
482 </div>507 </div>
483 <small data-i18n="Old messages are vectorized gradually as you chat. To process all previous messages, click the button below.">508 <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 {
289 return this.filterDataByState(data, state, isFolder);289 return this.filterDataByState(data, state, isFolder);
290 }290 }
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 */
292 filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) {302 filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) {
293 if (isFilterState(state, FILTER_STATES.SELECTED)) {303 if (isFilterState(state, FILTER_STATES.SELECTED)) {
294 return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag'));304 return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag'));
@@ -414,7 +424,7 @@ export class FilterHelper {
414 typeScores.set(uid, score);424 typeScores.set(uid, score);
415 }425 }
416 this.scoreCache.set(type, typeScores);426 this.scoreCache.set(type, typeScores);
417 console.debug('search scores chached', type, typeScores);427 console.debug('search scores cached', type, typeScores);
418 }428 }
419429
420 /**430 /**
public/scripts/i18n.js+2 -1
@@ -139,7 +139,8 @@ async function getLocaleData(language) {
139function findLang(language) {139function findLang(language) {
140 const supportedLang = langs.find(x => x.lang === language);140 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) {
143 console.warn(`Unsupported language: ${language}`);144 console.warn(`Unsupported language: ${language}`);
144 }145 }
145 return supportedLang;146 return supportedLang;
public/scripts/instruct-mode.js+1 -1
@@ -597,7 +597,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
597 function getSequence() {597 function getSequence() {
598 // User impersonation prompt598 // User impersonation prompt
599 if (isImpersonate) {599 if (isImpersonate) {
600 return instruct.input_sequence;600 return instruct.last_input_sequence || instruct.input_sequence;
601 }601 }
602602
603 // Neutral / system / quiet prompt603 // Neutral / system / quiet prompt
public/scripts/itemized-prompts.js+5 -0
@@ -31,6 +31,8 @@ export async function loadItemizedPrompts(chatId) {
31 if (!itemizedPrompts) {31 if (!itemizedPrompts) {
32 itemizedPrompts = [];32 itemizedPrompts = [];
33 }33 }
34
35 await eventSource.emit(event_types.ITEMIZED_PROMPTS_LOADED, { chatId: chatId });
34 } catch {36 } catch {
35 console.log('Error loading itemized prompts for chat', chatId);37 console.log('Error loading itemized prompts for chat', chatId);
36 itemizedPrompts = [];38 itemizedPrompts = [];
@@ -48,6 +50,7 @@ export async function saveItemizedPrompts(chatId) {
48 }50 }
4951
50 await promptStorage.setItem(chatId, itemizedPrompts);52 await promptStorage.setItem(chatId, itemizedPrompts);
53 await eventSource.emit(event_types.ITEMIZED_PROMPTS_SAVED, { chatId: chatId });
51 } catch {54 } catch {
52 console.log('Error saving itemized prompts for chat', chatId);55 console.log('Error saving itemized prompts for chat', chatId);
53 }56 }
@@ -84,6 +87,7 @@ export async function deleteItemizedPrompts(chatId) {
84 }87 }
8588
86 await promptStorage.removeItem(chatId);89 await promptStorage.removeItem(chatId);
90 await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { chatId: chatId, all: false });
87 } catch {91 } catch {
88 console.log('Error deleting itemized prompts for chat', chatId);92 console.log('Error deleting itemized prompts for chat', chatId);
89 }93 }
@@ -96,6 +100,7 @@ export async function clearItemizedPrompts() {
96 try {100 try {
97 await promptStorage.clear();101 await promptStorage.clear();
98 itemizedPrompts = [];102 itemizedPrompts = [];
103 await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { all: true });
99 } catch {104 } catch {
100 console.log('Error clearing itemized prompts');105 console.log('Error clearing itemized prompts');
101 }106 }
public/scripts/kai-settings.js+1 -0
@@ -202,6 +202,7 @@ export function getKoboldGenerationData(finalPrompt, settings, maxLength, maxCon
202 mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined,202 mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined,
203 use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined,203 use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined,
204 grammar: (kai_flags.can_use_grammar || isHorde) ? substituteParams(kai_settings.grammar) : undefined,204 grammar: (kai_flags.can_use_grammar || isHorde) ? substituteParams(kai_settings.grammar) : undefined,
205 grammar_retain_state: (kai_flags.can_use_grammar && !!isContinue) ? true : undefined,
205 sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined,206 sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined,
206 api_server: kai_settings.api_server,207 api_server: kai_settings.api_server,
207 };208 };
public/scripts/loader.js+1 -0
@@ -28,6 +28,7 @@ export function showLoader() {
2828
29 // Create a blocking loader with no toast (matches old behavior)29 // Create a blocking loader with no toast (matches old behavior)
30 legacyLoaderHandle = loader.show({30 legacyLoaderHandle = loader.show({
31 slug: 'legacy-loader',
31 blocking: true,32 blocking: true,
32 toastMode: loader.ToastMode.NONE,33 toastMode: loader.ToastMode.NONE,
33 });34 });
public/scripts/macros.js+7 -6
@@ -58,10 +58,11 @@ export class MacrosParser {
58 *58 *
59 * @param {string} method59 * @param {string} method
60 * @param {string} replacement60 * @param {string} replacement
61 * @param {IArguments} [methodArgs=null]
61 * @returns {void}62 * @returns {void}
62 */63 */
63 static #logDeprecated(method, replacement) {64 static #logDeprecated(method, replacement, methodArgs = null) {
64 console.warn(`[DEPRECATED] MacrosParser.${method} is deprecated and will be removed in a future version. Use ${replacement} instead.`);65 console.warn(`[DEPRECATED] MacrosParser.${method} is deprecated and will be removed in a future version. Use ${replacement} instead. Arguments:`, (methodArgs ?? 'none'));
65 }66 }
6667
67 /**68 /**
@@ -155,7 +156,7 @@ export class MacrosParser {
155 * @returns {string|MacroFunction|undefined} The macro value156 * @returns {string|MacroFunction|undefined} The macro value
156 */157 */
157 static get(key) {158 static get(key) {
158 MacrosParser.#logDeprecated('get', 'macros.registry.getMacro (from scripts/macros/macro-system.js)');159 MacrosParser.#logDeprecated('get', 'macros.registry.getMacro (from scripts/macros/macro-system.js)', arguments);
159 return MacrosParser.#macros.get(key);160 return MacrosParser.#macros.get(key);
160 }161 }
161162
@@ -165,7 +166,7 @@ export class MacrosParser {
165 * @returns {boolean} True if the macro is registered, false otherwise166 * @returns {boolean} True if the macro is registered, false otherwise
166 */167 */
167 static has(key) {168 static has(key) {
168 MacrosParser.#logDeprecated('has', 'macros.registry.hasMacro (from scripts/macros/macro-system.js)');169 MacrosParser.#logDeprecated('has', 'macros.registry.hasMacro (from scripts/macros/macro-system.js)', arguments);
169 if (power_user.experimental_macro_engine) {170 if (power_user.experimental_macro_engine) {
170 return macroSystem.registry.hasMacro(key);171 return macroSystem.registry.hasMacro(key);
171 }172 }
@@ -180,7 +181,7 @@ export class MacrosParser {
180 * @param {string} [description] Optional description of the macro181 * @param {string} [description] Optional description of the macro
181 */182 */
182 static registerMacro(key, value, description = '') {183 static registerMacro(key, value, description = '') {
183 MacrosParser.#logDeprecated('registerMacro', 'macros.registry.registerMacro (from scripts/macros/macro-system.js) or substituteParams({ dynamicMacros })');184 MacrosParser.#logDeprecated('registerMacro', 'macros.registry.registerMacro (from scripts/macros/macro-system.js) or substituteParams({ dynamicMacros })', arguments);
184 if (typeof key !== 'string') {185 if (typeof key !== 'string') {
185 throw new Error('Macro key must be a string');186 throw new Error('Macro key must be a string');
186 }187 }
@@ -223,7 +224,7 @@ export class MacrosParser {
223 * @param {string} key Macro name (key)224 * @param {string} key Macro name (key)
224 */225 */
225 static unregisterMacro(key) {226 static unregisterMacro(key) {
226 MacrosParser.#logDeprecated('unregisterMacro', 'macros.registry.unregisterMacro (from scripts/macros/macro-system.js)');227 MacrosParser.#logDeprecated('unregisterMacro', 'macros.registry.unregisterMacro (from scripts/macros/macro-system.js)', arguments);
227 if (typeof key !== 'string') {228 if (typeof key !== 'string') {
228 throw new Error('Macro key must be a string');229 throw new Error('Macro key must be a string');
229 }230 }
public/scripts/openai.js+573 -320
@@ -47,6 +47,7 @@ import { SECRET_KEYS, secret_state, writeSecret } from './secrets.js';
4747
48import { getEventSourceStream } from './sse-stream.js';48import { getEventSourceStream } from './sse-stream.js';
49import {49import {
50 clamp,
50 createThumbnail,51 createThumbnail,
51 delay,52 delay,
52 download,53 download,
@@ -79,7 +80,7 @@ import { t } from './i18n.js';
79import { ToolManager } from './tool-calling.js';80import { ToolManager } from './tool-calling.js';
80import { accountStorage } from './util/AccountStorage.js';81import { accountStorage } from './util/AccountStorage.js';
81import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js';82import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js';
82import { syncOpenRouterProvidersForModel, updateOpenRouterProvidersWarning } from './textgen-models.js';83import { syncNanoGptProvidersForModel, syncOpenRouterProvidersForModel, updateNanoGptProvidersWarning, updateOpenRouterProvidersWarning } from './textgen-models.js';
8384
84export {85export {
85 openai_messages_count,86 openai_messages_count,
@@ -196,6 +197,8 @@ export const chat_completion_sources = {
196 AZURE_OPENAI: 'azure_openai',197 AZURE_OPENAI: 'azure_openai',
197 ZAI: 'zai',198 ZAI: 'zai',
198 SILICONFLOW: 'siliconflow',199 SILICONFLOW: 'siliconflow',
200 WORKERS_AI: 'workers_ai',
201 MINIMAX: 'minimax',
199};202};
200203
201const character_names_behavior = {204const character_names_behavior = {
@@ -256,6 +259,7 @@ export const tool_reasoning_modes = {
256// Providers that support interleaved reasoning forwarding in tool-call chains.259// Providers that support interleaved reasoning forwarding in tool-call chains.
257const interleaved_reasoning_providers = [260const interleaved_reasoning_providers = [
258 chat_completion_sources.OPENROUTER,261 chat_completion_sources.OPENROUTER,
262 chat_completion_sources.CUSTOM,
259];263];
260264
261export const ZAI_ENDPOINT = {265export const ZAI_ENDPOINT = {
@@ -268,6 +272,11 @@ export const SILICONFLOW_ENDPOINT = {
268 CN: 'cn',272 CN: 'cn',
269};273};
270274
275export const MINIMAX_ENDPOINT = {
276 GLOBAL: 'global',
277 CN: 'cn',
278};
279
271const sensitiveFields = [280const sensitiveFields = [
272 'reverse_proxy',281 'reverse_proxy',
273 'proxy_password',282 'proxy_password',
@@ -279,6 +288,7 @@ const sensitiveFields = [
279 'vertexai_express_project_id',288 'vertexai_express_project_id',
280 'azure_base_url',289 'azure_base_url',
281 'azure_deployment_name',290 'azure_deployment_name',
291 'workers_ai_account_id',
282];292];
283293
284/**294/**
@@ -296,12 +306,12 @@ export const settingsToUpdate = {
296 min_p: ['#min_p_openai', 'min_p_openai', false, false],306 min_p: ['#min_p_openai', 'min_p_openai', false, false],
297 repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false],307 repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false],
298 max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false],308 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],
299 openai_model: ['#model_openai_select', 'openai_model', false, true],311 openai_model: ['#model_openai_select', 'openai_model', false, true],
300 claude_model: ['#model_claude_select', 'claude_model', false, true],312 claude_model: ['#model_claude_select', 'claude_model', false, true],
301 openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true],313 openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true],
302 openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true],314 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],
305 openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true],315 openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true],
306 openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true],316 openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true],
307 openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true],317 openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true],
@@ -313,13 +323,14 @@ export const settingsToUpdate = {
313 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],323 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
314 groq_model: ['#model_groq_select', 'groq_model', false, true],324 groq_model: ['#model_groq_select', 'groq_model', false, true],
315 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],325 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],
316 chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true],
317 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],326 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],
318 siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true],327 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],
319 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],330 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],
322 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],331 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],
323 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],334 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
324 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],335 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],
325 xai_model: ['#model_xai_select', 'xai_model', false, true],336 xai_model: ['#model_xai_select', 'xai_model', false, true],
@@ -337,6 +348,8 @@ export const settingsToUpdate = {
337 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],348 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],
338 zai_model: ['#model_zai_select', 'zai_model', false, true],349 zai_model: ['#model_zai_select', 'zai_model', false, true],
339 zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true],350 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],
340 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],353 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
341 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],354 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
342 names_behavior: ['#names_behavior', 'names_behavior', false, false],355 names_behavior: ['#names_behavior', 'names_behavior', false, false],
@@ -369,6 +382,7 @@ export const settingsToUpdate = {
369 continue_prefill: ['#continue_prefill', 'continue_prefill', true, false],382 continue_prefill: ['#continue_prefill', 'continue_prefill', true, false],
370 continue_postfix: ['#continue_postfix', 'continue_postfix', false, false],383 continue_postfix: ['#continue_postfix', 'continue_postfix', false, false],
371 function_calling: ['#openai_function_calling', 'function_calling', true, false],384 function_calling: ['#openai_function_calling', 'function_calling', true, false],
385 tool_call_recurse_limit: ['#tool_call_recurse_limit', 'tool_call_recurse_limit', false, false],
372 show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false],386 show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false],
373 reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false],387 reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false],
374 verbosity: ['#openai_verbosity', 'verbosity', false, false],388 verbosity: ['#openai_verbosity', 'verbosity', false, false],
@@ -413,6 +427,8 @@ const default_settings = {
413 group_nudge_prompt: default_group_nudge_prompt,427 group_nudge_prompt: default_group_nudge_prompt,
414 scenario_format: default_scenario_format,428 scenario_format: default_scenario_format,
415 personality_format: default_personality_format,429 personality_format: default_personality_format,
430 sort_models: 'alphabetically',
431 group_models: false,
416 openai_model: 'gpt-4-turbo',432 openai_model: 'gpt-4-turbo',
417 claude_model: 'claude-sonnet-4-5',433 claude_model: 'claude-sonnet-4-5',
418 google_model: 'gemini-2.5-pro',434 google_model: 'gemini-2.5-pro',
@@ -423,14 +439,15 @@ const default_settings = {
423 perplexity_model: 'sonar-pro',439 perplexity_model: 'sonar-pro',
424 groq_model: 'llama-3.3-70b-versatile',440 groq_model: 'llama-3.3-70b-versatile',
425 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',441 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',
426 chutes_sort_models: 'alphabetically',
427 siliconflow_model: 'deepseek-ai/DeepSeek-V3',442 siliconflow_model: 'deepseek-ai/DeepSeek-V3',
428 siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL,443 siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL,
444 minimax_model: 'MiniMax-M2.7',
445 minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL,
429 electronhub_model: 'gpt-4o-mini',446 electronhub_model: 'gpt-4o-mini',
430 electronhub_sort_models: 'alphabetically',
431 electronhub_group_models: false,
432 nanogpt_model: 'gpt-4o-mini',447 nanogpt_model: 'gpt-4o-mini',
433 deepseek_model: 'deepseek-chat',448 nanogpt_provider: '',
449 nanogpt_payg_override: false,
450 deepseek_model: 'deepseek-v4-flash',
434 aimlapi_model: 'chatgpt-4o-latest',451 aimlapi_model: 'chatgpt-4o-latest',
435 xai_model: 'grok-3-beta',452 xai_model: 'grok-3-beta',
436 pollinations_model: 'openai',453 pollinations_model: 'openai',
@@ -439,6 +456,8 @@ const default_settings = {
439 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',456 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
440 zai_model: 'glm-4.6',457 zai_model: 'glm-4.6',
441 zai_endpoint: ZAI_ENDPOINT.COMMON,458 zai_endpoint: ZAI_ENDPOINT.COMMON,
459 workers_ai_model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
460 workers_ai_account_id: '',
442 azure_base_url: '',461 azure_base_url: '',
443 azure_deployment_name: '',462 azure_deployment_name: '',
444 azure_api_version: '2024-02-15-preview',463 azure_api_version: '2024-02-15-preview',
@@ -450,8 +469,6 @@ const default_settings = {
450 custom_include_headers: '',469 custom_include_headers: '',
451 openrouter_model: openrouter_website_model,470 openrouter_model: openrouter_website_model,
452 openrouter_use_fallback: false,471 openrouter_use_fallback: false,
453 openrouter_group_models: false,
454 openrouter_sort_models: 'alphabetically',
455 openrouter_providers: [],472 openrouter_providers: [],
456 openrouter_quantizations: [],473 openrouter_quantizations: [],
457 openrouter_allow_fallbacks: true,474 openrouter_allow_fallbacks: true,
@@ -474,6 +491,7 @@ const default_settings = {
474 bypass_status_check: false,491 bypass_status_check: false,
475 continue_prefill: false,492 continue_prefill: false,
476 function_calling: false,493 function_calling: false,
494 tool_call_recurse_limit: 5,
477 names_behavior: character_names_behavior.DEFAULT,495 names_behavior: character_names_behavior.DEFAULT,
478 continue_postfix: continue_postfix_types.SPACE,496 continue_postfix: continue_postfix_types.SPACE,
479 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,497 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
@@ -597,8 +615,10 @@ function setOpenAIMessages(chat) {
597 const originApi = chat[j]?.extra?.api;615 const originApi = chat[j]?.extra?.api;
598 const originModel = chat[j]?.extra?.model;616 const originModel = chat[j]?.extra?.model;
599 const isSameModel = originApi === currentApi && originModel === currentModel;617 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
601 const reasoning = isSameModel ? String(chat[j]?.extra?.reasoning ?? '') : '';619 const isOtherGroupMember = selected_group && chat[j].name !== name2;
620 const signature = isSameModel && !isOtherGroupMember ? chat[j]?.extra?.reasoning_signature : null;
621 const reasoning = isSameModel && !isOtherGroupMember ? String(chat[j]?.extra?.reasoning ?? '') : '';
602622
603 // Remove reasoning metadata from invocations if the API/model don't match623 // Remove reasoning metadata from invocations if the API/model don't match
604 if (Array.isArray(invocations) && invocations.length > 0) {624 if (Array.isArray(invocations) && invocations.length > 0) {
@@ -1013,8 +1033,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
1013 const clone = structuredClone(invocation);1033 const clone = structuredClone(invocation);
1014 if (!reasoningIsEligible) {1034 if (!reasoningIsEligible) {
1015 delete clone.reasoning;1035 delete clone.reasoning;
1016 } else if (previousAssistantReasoning) {1036 } else if (previousAssistantReasoning && !clone.reasoning) {
1017 // Prefer currently editable assistant-text reasoning based on forwarding mode over invocation snapshot.1037 // Fall back to adjacent assistant-text reasoning only when the invocation has none of its own.
1018 clone.reasoning = previousAssistantReasoning;1038 clone.reasoning = previousAssistantReasoning;
1019 }1039 }
1020 return clone;1040 return clone;
@@ -1703,6 +1723,8 @@ export function getChatCompletionModel(settings = null) {
1703 return settings.groq_model;1723 return settings.groq_model;
1704 case chat_completion_sources.SILICONFLOW:1724 case chat_completion_sources.SILICONFLOW:
1705 return settings.siliconflow_model;1725 return settings.siliconflow_model;
1726 case chat_completion_sources.MINIMAX:
1727 return settings.minimax_model;
1706 case chat_completion_sources.ELECTRONHUB:1728 case chat_completion_sources.ELECTRONHUB:
1707 return settings.electronhub_model;1729 return settings.electronhub_model;
1708 case chat_completion_sources.CHUTES:1730 case chat_completion_sources.CHUTES:
@@ -1727,6 +1749,8 @@ export function getChatCompletionModel(settings = null) {
1727 return settings.azure_openai_model;1749 return settings.azure_openai_model;
1728 case chat_completion_sources.ZAI:1750 case chat_completion_sources.ZAI:
1729 return settings.zai_model;1751 return settings.zai_model;
1752 case chat_completion_sources.WORKERS_AI:
1753 return settings.workers_ai_model;
1730 default:1754 default:
1731 console.error(`Unknown chat completion source: ${source}`);1755 console.error(`Unknown chat completion source: ${source}`);
1732 return '';1756 return '';
@@ -1871,6 +1895,33 @@ function getChutesModelTemplate(option) {
1871 `));1895 `));
1872}1896}
18731897
1898function 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
1874function getNanoGptModelTemplate(option) {1925function getNanoGptModelTemplate(option) {
1875 const model = model_list.find(x => x.id === option?.element?.value);1926 const model = model_list.find(x => x.id === option?.element?.value);
18761927
@@ -1880,10 +1931,9 @@ function getNanoGptModelTemplate(option) {
18801931
1881 const inputPrice = model.pricing?.prompt;1932 const inputPrice = model.pricing?.prompt;
1882 const outputPrice = model.pricing?.completion;1933 const outputPrice = model.pricing?.completion;
1883
1884 let price = 'Unknown';1934 let price = 'Unknown';
1935
1885 if (inputPrice !== undefined && outputPrice !== undefined) {1936 if (inputPrice !== undefined && outputPrice !== undefined) {
1886 // Check if both prices are 0 (free model)
1887 if (inputPrice === 0 && outputPrice === 0) {1937 if (inputPrice === 0 && outputPrice === 0) {
1888 price = 'Free';1938 price = 'Free';
1889 } else {1939 } else {
@@ -1891,40 +1941,61 @@ function getNanoGptModelTemplate(option) {
1891 }1941 }
1892 }1942 }
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
1894 const contextLength = model.context_length || 'Unknown';1975 const contextLength = model.context_length || 'Unknown';
1976 const modelName = model.name || model.id;
18951977
1896 return $((`1978 return $((`
1897 <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}">1979 <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}">
1898 <strong>${DOMPurify.sanitize(model.id)}</strong> | ${contextLength} ctx | <small>${price}</small>1980 <strong>${DOMPurify.sanitize(modelName)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities}
1899 </div>1981 </div>
1900 `));1982 `));
1901}1983}
19021984
1903function calculateChutesCost() {1985function getAimlapiModelTemplate(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 }
1925 }1990 }
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 `));
1928}1999}
19292000
1930function saveModelList(data) {2001function saveModelList(data) {
@@ -1932,14 +2003,22 @@ function saveModelList(data) {
1932 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));2003 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));
19332004
1934 if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {2005 if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
1935 model_list = openRouterSortBy(model_list, oai_settings.openrouter_sort_models);2006 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.OPENROUTER);
1936
1937 $('#model_openrouter_select').empty();2007 $('#model_openrouter_select').empty();
2008 $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` }));
19382009
1939 if (true === oai_settings.openrouter_group_models) {2010 if (oai_settings.group_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 });
1941 } else {2018 } else {
1942 appendOpenRouterOptions(model_list);2019 model_list.forEach((model) => {
2020 $('#model_openrouter_select').append($('<option>', { value: model.id, text: model.name }));
2021 });
1943 }2022 }
19442023
1945 $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change');2024 $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change');
@@ -1979,13 +2058,26 @@ function saveModelList(data) {
1979 }2058 }
19802059
1981 if (oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI) {2060 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);
1982 $('#model_aimlapi_select').empty();2063 $('#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
1987 if (!oai_settings.aimlapi_model && chatModels.length > 0) {2079 if (!oai_settings.aimlapi_model && model_list.length > 0) {
1988 oai_settings.aimlapi_model = chatModels[0].id;2080 oai_settings.aimlapi_model = model_list[0].id;
1989 }2081 }
19902082
1991 $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change');2083 $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change');
@@ -2008,13 +2100,22 @@ function saveModelList(data) {
20082100
2009 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {2101 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
2010 model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions'));2102 model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions'));
20112103 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
2014 $('#model_electronhub_select').empty();2104 $('#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
2019 const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);2120 const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);
2020 if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {2121 if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {
@@ -2026,15 +2127,21 @@ function saveModelList(data) {
20262127
2027 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {2128 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {
2028 model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine'));2129 model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine'));
20292130 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
2032 $('#model_chutes_select').empty();2131 $('#model_chutes_select').empty();
20332132
2034 for (const model of model_list) {2133 if (oai_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 });
2038 }2145 }
20392146
2040 const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model);2147 const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model);
@@ -2046,14 +2153,22 @@ function saveModelList(data) {
2046 }2153 }
20472154
2048 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {2155 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
2156 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.NANOGPT);
2049 $('#model_nanogpt_select').empty();2157 $('#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 {
2050 model_list.forEach((model) => {2168 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 }));
2056 });2170 });
2171 }
20572172
2058 const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model);2173 const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model);
2059 if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) {2174 if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) {
@@ -2066,11 +2181,7 @@ function saveModelList(data) {
2066 if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {2181 if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
2067 $('#model_deepseek_select').empty();2182 $('#model_deepseek_select').empty();
2068 model_list.forEach((model) => {2183 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 }));
2074 });2185 });
20752186
2076 const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);2187 const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);
@@ -2084,11 +2195,7 @@ function saveModelList(data) {
2084 if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {2195 if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
2085 $('#model_pollinations_select').empty();2196 $('#model_pollinations_select').empty();
2086 model_list.forEach((model) => {2197 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 }));
2092 });2199 });
20932200
2094 const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model);2201 const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model);
@@ -2193,6 +2300,24 @@ function saveModelList(data) {
2193 $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change');2300 $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change');
2194 }2301 }
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
2196 if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) {2321 if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) {
2197 $('#model_cometapi_select').empty();2322 $('#model_cometapi_select').empty();
21982323
@@ -2259,181 +2384,134 @@ function saveModelList(data) {
2259 }2384 }
2260}2385}
22612386
2262function 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.
22642389 * @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,2394function 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
2290const openRouterSortBy = (data, property = 'alphabetically') => {
2291 return data.sort((a, b) => {2397 return data.sort((a, b) => {
2292 if (property === 'context_length') {2398 if (property === 'context_length') {
2293 return b.context_length - a.context_length;2399 return (b.context_length || 0) - (a.context_length || 0);
2294 } else if (property === 'pricing.prompt') {2400 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2295 return parseFloat(a.pricing.prompt) - parseFloat(b.pricing.prompt);2401 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);
2296 } else {2404 } else {
2297 // Alphabetically2405 return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2298 return a?.name && b?.name && a.name.localeCompare(b.name);
2299 }2406 }
2300 });2407 });
2301};2408 case chat_completion_sources.CHUTES:
2302
2303function 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
2317function chutesSortBy(data, property = 'alphabetically') {
2318 return data.sort((a, b) => {2409 return data.sort((a, b) => {
2319 if (property === 'context_length') {2410 if (property === 'context_length') {
2320 return b.context_length - a.context_length;2411 return (b.context_length || 0) - (a.context_length || 0);
2321 } else if (property === 'pricing.input') {2412 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2322 const aPrice = parseFloat(a.pricing?.input || 0);2413 return parseFloat(a.pricing?.input || 0) - parseFloat(b.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;
2329 } else {2416 } else {
2330 return a?.id && b?.id && a.id.localeCompare(b.id);2417 return a?.id && b?.id ? a.id.localeCompare(b.id) : 0;
2331 }2418 }
2332 });2419 });
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;
2333 }2430 }
2334
2335function 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);
2353 });2431 });
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);
2354 } else {2440 } else {
2355 model_list.forEach((model) => {2441 return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2356 appendOption(model);
2357 });
2358 }2442 }
2359}2443 });
23602444 case chat_completion_sources.AIMLAPI:
2361function electronHubSortBy(data, property = 'alphabetically') {
2362 return data.sort((a, b) => {2445 return data.sort((a, b) => {
2363 if (property === 'context_length') {2446 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);
2369 } else {2448 } 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;
2371 }2451 }
2372 });2452 });
2453 default:
2454 return data;
2455 }
2373}2456}
23742457
2375function 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 */
2464function groupModelsByVendor(array, source) {
2465 switch (source) {
2466 case chat_completion_sources.OPENROUTER:
2376 return array.reduce((acc, curr) => {2467 return array.reduce((acc, curr) => {
2377 const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other';2468 const vendor = curr.id.split('/')[0];
2378
2379 if (!acc.has(vendor)) {2469 if (!acc.has(vendor)) {
2380 acc.set(vendor, []);2470 acc.set(vendor, []);
2381 }2471 }
2382
2383 acc.get(vendor).push(curr);2472 acc.get(vendor).push(curr);
2384
2385 return acc;2473 return acc;
2386 }, new Map());2474 }, 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, []);
2387 }2480 }
23882481 acc.get(vendor).push(curr);
2389function aimlapiGroupByVendor(array) {2482 return acc;
2483 }, new Map());
2484 case chat_completion_sources.NANOGPT:
2390 return array.reduce((acc, curr) => {2485 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];
23922487 const vendor = String(vendorPart?.trim()?.toLowerCase() || 'Other');
2393 if (!acc.has(vendor)) {2488 if (!acc.has(vendor)) {
2394 acc.set(vendor, []);2489 acc.set(vendor, []);
2395 }2490 }
2396
2397 acc.get(vendor).push(curr);2491 acc.get(vendor).push(curr);
2398
2399 return acc;2492 return acc;
2400 }, new Map());2493 }, 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, []);
2401 }2499 }
24022500 acc.get(vendor).push(curr);
2403function 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 });
2421 }2508 }
24222509 acc.get(vendor).push(curr);
2423function getAimlapiModelTemplate(option) {2510 return acc;
2424 const model = model_list.find(x => x.id === option?.element?.value);2511 }, new Map());
24252512 default:
2426 if (!option.id || !model) {2513 return new Map([['', array]]);
2427 return option.text;
2428 }2514 }
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 `));
2437}2515}
24382516
2439/**2517/**
@@ -2459,6 +2537,7 @@ function getReasoningEffort(settings = null, model = null) {
2459 chat_completion_sources.COMETAPI,2537 chat_completion_sources.COMETAPI,
2460 chat_completion_sources.ELECTRONHUB,2538 chat_completion_sources.ELECTRONHUB,
2461 chat_completion_sources.CHUTES,2539 chat_completion_sources.CHUTES,
2540 chat_completion_sources.DEEPSEEK,
2462 ];2541 ];
24632542
2464 if (!reasoningEffortSources.includes(settings.chat_completion_source)) {2543 if (!reasoningEffortSources.includes(settings.chat_completion_source)) {
@@ -2466,6 +2545,36 @@ function getReasoningEffort(settings = null, model = null) {
2466 }2545 }
24672546
2468 function resolveReasoningEffort() {2547 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
2469 switch (settings.reasoning_effort) {2578 switch (settings.reasoning_effort) {
2470 case reasoning_effort_types.auto:2579 case reasoning_effort_types.auto:
2471 return undefined;2580 return undefined;
@@ -2474,9 +2583,16 @@ function getReasoningEffort(settings = null, model = null) {
2474 return 'none';2583 return 'none';
2475 }2584 }
24762585
2477 return [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source) && /^gpt-5/.test(model)2586 if ([chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source)) {
2478 ? reasoning_effort_types.min2587 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;
2480 case reasoning_effort_types.max:2596 case reasoning_effort_types.max:
2481 return reasoning_effort_types.high;2597 return reasoning_effort_types.high;
2482 default:2598 default:
@@ -2604,7 +2720,8 @@ export async function createGenerationParameters(settings, model, type, messages
2604 ];2720 ];
26052721
2606 const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model);2722 const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model);
2607 const stream = settings.stream_openai && type !== 'quiet' && !isO1;2723 const isWorkersAIJsonMode = settings.chat_completion_source === chat_completion_sources.WORKERS_AI && jsonSchema;
2724 const stream = settings.stream_openai && type !== 'quiet' && !isO1 && !isWorkersAIJsonMode;
26082725
2609 const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue'];2726 const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue'];
2610 const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source);2727 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
2715 generate_data.middleout = settings.openrouter_middleout;2832 generate_data.middleout = settings.openrouter_middleout;
2716 }2833 }
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
2718 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {2840 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {
2719 const stopStringsLimit = 5;2841 const stopStringsLimit = 5;
2720 generate_data.top_k = Number(settings.top_k_openai);2842 generate_data.top_k = Number(settings.top_k_openai);
@@ -2815,6 +2937,24 @@ export async function createGenerationParameters(settings, model, type, messages
2815 generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL;2937 generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL;
2816 }2938 }
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
2818 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus2958 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus
2819 if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {2959 if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
2820 generate_data.top_k = Number(settings.top_k_openai);2960 generate_data.top_k = Number(settings.top_k_openai);
@@ -2870,7 +3010,7 @@ export async function createGenerationParameters(settings, model, type, messages
2870 if (/gpt-5-chat-latest/.test(model)) {3010 if (/gpt-5-chat-latest/.test(model)) {
2871 delete generate_data.tools;3011 delete generate_data.tools;
2872 delete generate_data.tool_choice;3012 delete generate_data.tool_choice;
2873 } else if (/gpt-5\.(1|2|3|4)/.test(model) && !/chat-latest/.test(model)) {3013 } else if (/gpt-5\.(1|2|3|4)/.test(model) && !/chat-latest/.test(model) && !generate_data.reasoning_effort) {
2874 delete generate_data.frequency_penalty;3014 delete generate_data.frequency_penalty;
2875 delete generate_data.presence_penalty;3015 delete generate_data.presence_penalty;
2876 delete generate_data.logit_bias;3016 delete generate_data.logit_bias;
@@ -3052,7 +3192,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
3052 }3192 }
3053 });3193 });
3054 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';3194 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
3055 } 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].includes(chat_completion_source)) {3195 } 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)) {
3056 if (show_thoughts) {3196 if (show_thoughts) {
3057 state.reasoning +=3197 state.reasoning +=
3058 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??3198 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
@@ -4049,6 +4189,11 @@ function migrateChatCompletionSettings(settings) {
4049 { oldKey: 'claude_use_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },4189 { oldKey: 'claude_use_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },
4050 { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },4190 { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },
4051 { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') },4191 { 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 },
4052 ];4197 ];
40534198
4054 for (const migration of migrateMap) {4199 for (const migration of migrateMap) {
@@ -4150,9 +4295,11 @@ function loadOpenAISettings(data, settings) {
4150 setNamesBehaviorControls();4295 setNamesBehaviorControls();
4151 setContinuePostfixControls();4296 setContinuePostfixControls();
4152 setToolReasoningControls();4297 setToolReasoningControls();
4298 ToolManager.RECURSE_LIMIT = oai_settings.tool_call_recurse_limit;
41534299
4154 $('#openrouter_providers_chat').trigger('change');4300 $('#openrouter_providers_chat').trigger('change');
4155 $('#openrouter_quantizations_chat').trigger('change');4301 $('#openrouter_quantizations_chat').trigger('change');
4302 $('#nanogpt_provider').trigger('change');
4156 $('#chat_completion_source').trigger('change');4303 $('#chat_completion_source').trigger('change');
4157}4304}
41584305
@@ -4215,6 +4362,7 @@ async function getStatusOpen() {
4215 chat_completion_sources.VERTEXAI,4362 chat_completion_sources.VERTEXAI,
4216 chat_completion_sources.PERPLEXITY,4363 chat_completion_sources.PERPLEXITY,
4217 chat_completion_sources.ZAI,4364 chat_completion_sources.ZAI,
4365 chat_completion_sources.MINIMAX,
4218 ];4366 ];
4219 if (noValidateSources.includes(oai_settings.chat_completion_source)) {4367 if (noValidateSources.includes(oai_settings.chat_completion_source)) {
4220 let status = t`Key saved; press \"Test Message\" to verify.`;4368 let status = t`Key saved; press \"Test Message\" to verify.`;
@@ -4272,6 +4420,14 @@ async function getStatusOpen() {
4272 data.siliconflow_endpoint = oai_settings.siliconflow_endpoint;4420 data.siliconflow_endpoint = oai_settings.siliconflow_endpoint;
4273 }4421 }
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
4275 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;4431 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;
4276 if (canBypass) {4432 if (canBypass) {
4277 setOnlineStatus(t`Status check bypassed`);4433 setOnlineStatus(t`Status check bypassed`);
@@ -4791,6 +4947,7 @@ function onSettingsPresetChange() {
4791 $('#chat_completion_source').trigger('change');4947 $('#chat_completion_source').trigger('change');
4792 $('#openrouter_providers_chat').trigger('change');4948 $('#openrouter_providers_chat').trigger('change');
4793 $('#openrouter_quantizations_chat').trigger('change');4949 $('#openrouter_quantizations_chat').trigger('change');
4950 $('#nanogpt_provider').trigger('change');
4794 }4951 }
47954952
4796 $('#openai_logit_bias_preset').trigger('change');4953 $('#openai_logit_bias_preset').trigger('change');
@@ -4801,41 +4958,102 @@ function onSettingsPresetChange() {
4801 });4958 });
4802}4959}
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 */
4804function getMaxContextOpenAI(value) {4966function getMaxContextOpenAI(value) {
4805 if (oai_settings.max_context_unlocked) {4967 if (oai_settings.max_context_unlocked) {
4806 return unlocked_max;4968 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
4816 return max_128k;4997 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 */
5006function 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
4820 return max_128k;5036 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;
4838}5037}
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 */
5044function 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;
4839}5057}
48405058
4841/**5059/**
@@ -4921,6 +5139,7 @@ function getZaiMaxContext(model, isUnlocked) {
4921 const contextMap = {5139 const contextMap = {
4922 'glm-5.1': max_200k,5140 'glm-5.1': max_200k,
4923 'glm-5-turbo': max_200k,5141 'glm-5-turbo': max_200k,
5142 'glm-5v-turbo': max_200k,
4924 'glm-5': max_200k,5143 'glm-5': max_200k,
4925 'glm-4.7': max_200k,5144 'glm-4.7': max_200k,
4926 'glm-4.7-flash': max_200k,5145 'glm-4.7-flash': max_200k,
@@ -5221,6 +5440,15 @@ async function onModelChange() {
5221 oai_settings.siliconflow_model = value;5440 oai_settings.siliconflow_model = value;
5222 }5441 }
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
5224 if ($(this).is('#model_electronhub_select')) {5452 if ($(this).is('#model_electronhub_select')) {
5225 if (!value || !hasModelsLoaded) {5453 if (!value || !hasModelsLoaded) {
5226 console.debug('Null ElectronHub model selected. Ignoring.');5454 console.debug('Null ElectronHub model selected. Ignoring.');
@@ -5247,6 +5475,7 @@ async function onModelChange() {
52475475
5248 console.log('NanoGPT model changed to', value);5476 console.log('NanoGPT model changed to', value);
5249 oai_settings.nanogpt_model = value;5477 oai_settings.nanogpt_model = value;
5478 syncNanoGptProvidersForModel(value, '#nanogpt_provider');
5250 }5479 }
52515480
5252 if ($(this).is('#model_deepseek_select')) {5481 if ($(this).is('#model_deepseek_select')) {
@@ -5328,29 +5557,21 @@ async function onModelChange() {
5328 oai_settings.zai_model = value;5557 oai_settings.zai_model = value;
5329 }5558 }
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')) {
5332 if (oai_settings.max_context_unlocked) {5561 if (!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);
5350 }5567 }
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');
5354 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);5575 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5355 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');5576 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5356 }5577 }
@@ -5383,7 +5604,7 @@ async function onModelChange() {
5383 if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {5604 if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
5384 if (oai_settings.max_context_unlocked) {5605 if (oai_settings.max_context_unlocked) {
5385 $('#openai_max_context').attr('max', unlocked_max);5606 $('#openai_max_context').attr('max', unlocked_max);
5386 } else if (/^claude-(sonnet-4-5|sonnet-4-6|opus-4-6)/.test(value)) {5607 } else if (/^claude-(sonnet-4-5|sonnet-4-6|opus-4-6|opus-4-7)/.test(value)) {
5387 $('#openai_max_context').attr('max', max_1mil);5608 $('#openai_max_context').attr('max', max_1mil);
5388 } else if (/^claude-(3|opus|haiku|sonnet)/.test(value)) {5609 } else if (/^claude-(3|opus|haiku|sonnet)/.test(value)) {
5389 $('#openai_max_context').attr('max', max_200k);5610 $('#openai_max_context').attr('max', max_200k);
@@ -5533,19 +5754,27 @@ async function onModelChange() {
5533 }5754 }
55345755
5535 if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {5756 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) {
5536 if (oai_settings.max_context_unlocked) {5765 if (oai_settings.max_context_unlocked) {
5537 $('#openai_max_context').attr('max', unlocked_max);5766 $('#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);
5542 } else {5767 } 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);
5544 }5772 }
5545
5546 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);5773 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5547 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');5774 $('#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');
5549 }5778 }
55505779
5551 if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) {5780 if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) {
@@ -5637,6 +5866,15 @@ async function onModelChange() {
5637 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');5866 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5638 }5867 }
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
5640 if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {5878 if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
5641 const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked);5879 const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked);
5642 $('#openai_max_context').attr('max', maxContext);5880 $('#openai_max_context').attr('max', maxContext);
@@ -5653,18 +5891,6 @@ async function onModelChange() {
5653 eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);5891 eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);
5654}5892}
56555893
5656async function onOpenrouterModelSortChange() {
5657 await getStatusOpen();
5658}
5659
5660async function onChutesModelSortChange() {
5661 await getStatusOpen();
5662}
5663
5664async function onElectronHubModelSortChange() {
5665 await getStatusOpen();
5666}
5667
5668async function onNewPresetClick() {5894async function onNewPresetClick() {
5669 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);5895 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) {
5709 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true },5935 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true },
5710 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },5936 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },
5711 [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false },5937 [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 },
5712 };5940 };
57135941
5714 // Vertex AI Express version - use API key5942 // Vertex AI Express version - use API key
@@ -5774,6 +6002,8 @@ function toggleChatCompletionForms() {
5774 $('#model_chutes_select').trigger('change');6002 $('#model_chutes_select').trigger('change');
5775 } else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) {6003 } else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) {
5776 $('#model_siliconflow_select').trigger('change');6004 $('#model_siliconflow_select').trigger('change');
6005 } else if (oai_settings.chat_completion_source == chat_completion_sources.MINIMAX) {
6006 $('#model_minimax_select').trigger('change');
5777 } else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {6007 } else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
5778 $('#model_electronhub_select').trigger('change');6008 $('#model_electronhub_select').trigger('change');
5779 } else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {6009 } else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
@@ -5798,6 +6028,8 @@ function toggleChatCompletionForms() {
5798 $('#azure_openai_model').trigger('change');6028 $('#azure_openai_model').trigger('change');
5799 } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {6029 } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
5800 $('#model_zai_select').trigger('change');6030 $('#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');
5801 }6033 }
58026034
5803 $('[data-source]').each(function () {6035 $('[data-source]').each(function () {
@@ -5903,6 +6135,10 @@ export function isImageInliningSupported() {
5903 'gemini-exp-1206',6135 'gemini-exp-1206',
5904 'learnlm',6136 'learnlm',
5905 'gemini-robotics',6137 'gemini-robotics',
6138 'gemma-3-27b',
6139 'gemma-3-12b',
6140 'gemma-3-4b',
6141 'gemma-4',
5906 // MistralAI6142 // MistralAI
5907 'mistral-small-2503',6143 'mistral-small-2503',
5908 'mistral-small-2506',6144 'mistral-small-2506',
@@ -5923,6 +6159,7 @@ export function isImageInliningSupported() {
5923 // Z.AI (GLM)6159 // Z.AI (GLM)
5924 'glm-4.5v',6160 'glm-4.5v',
5925 'glm-4.6v',6161 'glm-4.6v',
6162 'glm-5v-turbo',
5926 'autoglm-phone',6163 'autoglm-phone',
5927 // SiliconFlow6164 // SiliconFlow
5928 'Qwen/Qwen3-VL-32B-Instruct',6165 'Qwen/Qwen3-VL-32B-Instruct',
@@ -5978,6 +6215,10 @@ export function isImageInliningSupported() {
5978 return visionSupportedModels.some(model => oai_settings.zai_model.includes(model));6215 return visionSupportedModels.some(model => oai_settings.zai_model.includes(model));
5979 case chat_completion_sources.SILICONFLOW:6216 case chat_completion_sources.SILICONFLOW:
5980 return visionSupportedModels.some(model => oai_settings.siliconflow_model.includes(model));6217 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 }
5981 default:6222 default:
5982 return false;6223 return false;
5983 }6224 }
@@ -6002,9 +6243,11 @@ export function isVideoInliningSupported() {
6002 'gemini-2.5',6243 'gemini-2.5',
6003 'gemini-exp-1206',6244 'gemini-exp-1206',
6004 'gemini-3',6245 'gemini-3',
6246 'gemma-4',
6005 // Z.AI (GLM)6247 // Z.AI (GLM)
6006 'glm-4.5v',6248 'glm-4.5v',
6007 'glm-4.6v',6249 'glm-4.6v',
6250 'glm-5v-turbo',
6008 ];6251 ];
60096252
6010 switch (oai_settings.chat_completion_source) {6253 switch (oai_settings.chat_completion_source) {
@@ -6632,16 +6875,6 @@ export function initOpenAI() {
6632 saveSettingsDebounced();6875 saveSettingsDebounced();
6633 });6876 });
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
6645 $('#openrouter_allow_fallbacks').on('input', function () {6878 $('#openrouter_allow_fallbacks').on('input', function () {
6646 oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked');6879 oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked');
6647 updateOpenRouterProvidersWarning('#openrouter_providers_chat');6880 updateOpenRouterProvidersWarning('#openrouter_providers_chat');
@@ -6653,21 +6886,6 @@ export function initOpenAI() {
6653 saveSettingsDebounced();6886 saveSettingsDebounced();
6654 });6887 });
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
6671 $('#squash_system_messages').on('input', function () {6889 $('#squash_system_messages').on('input', function () {
6672 oai_settings.squash_system_messages = !!$(this).prop('checked');6890 oai_settings.squash_system_messages = !!$(this).prop('checked');
6673 saveSettingsDebounced();6891 saveSettingsDebounced();
@@ -6695,6 +6913,13 @@ export function initOpenAI() {
6695 saveSettingsDebounced();6913 saveSettingsDebounced();
6696 });6914 });
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
6698 $('#tool_reasoning_mode').on('input', function () {6923 $('#tool_reasoning_mode').on('input', function () {
6699 oai_settings.tool_reasoning_mode = getToolReasoningMode({6924 oai_settings.tool_reasoning_mode = getToolReasoningMode({
6700 ...oai_settings,6925 ...oai_settings,
@@ -6921,11 +7146,34 @@ export function initOpenAI() {
6921 saveSettingsDebounced();7146 saveSettingsDebounced();
6922 });7147 });
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
6924 $('#bind_preset_to_connection').on('input', function () {7160 $('#bind_preset_to_connection').on('input', function () {
6925 oai_settings.bind_preset_to_connection = !!$(this).prop('checked');7161 oai_settings.bind_preset_to_connection = !!$(this).prop('checked');
6926 saveSettingsDebounced();7162 saveSettingsDebounced();
6927 });7163 });
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
6929 $('#api_button_openai').on('click', onConnectButtonClick);7177 $('#api_button_openai').on('click', onConnectButtonClick);
6930 $('#openai_reverse_proxy').on('input', onReverseProxyInput);7178 $('#openai_reverse_proxy').on('input', onReverseProxyInput);
6931 $('#model_openai_select').on('change', onModelChange);7179 $('#model_openai_select').on('change', onModelChange);
@@ -6949,15 +7197,18 @@ export function initOpenAI() {
6949 oai_settings.siliconflow_endpoint = String($(this).val());7197 oai_settings.siliconflow_endpoint = String($(this).val());
6950 saveSettingsDebounced();7198 saveSettingsDebounced();
6951 });7199 });
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 });
6952 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);7208 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);
6953 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);7209 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
6954 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);7210 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
6955 $('#model_openrouter_select').on('change', onModelChange);7211 $('#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);
6961 $('#model_ai21_select').on('change', onModelChange);7212 $('#model_ai21_select').on('change', onModelChange);
6962 $('#model_mistralai_select').on('change', onModelChange);7213 $('#model_mistralai_select').on('change', onModelChange);
6963 $('#model_cohere_select').on('change', onModelChange);7214 $('#model_cohere_select').on('change', onModelChange);
@@ -6965,6 +7216,7 @@ export function initOpenAI() {
6965 $('#model_groq_select').on('change', onModelChange);7216 $('#model_groq_select').on('change', onModelChange);
6966 $('#model_chutes_select').on('change', onModelChange);7217 $('#model_chutes_select').on('change', onModelChange);
6967 $('#model_siliconflow_select').on('change', onModelChange);7218 $('#model_siliconflow_select').on('change', onModelChange);
7219 $('#model_minimax_select').on('change', onModelChange);
6968 $('#model_electronhub_select').on('change', onModelChange);7220 $('#model_electronhub_select').on('change', onModelChange);
6969 $('#model_nanogpt_select').on('change', onModelChange);7221 $('#model_nanogpt_select').on('change', onModelChange);
6970 $('#model_deepseek_select').on('change', onModelChange);7222 $('#model_deepseek_select').on('change', onModelChange);
@@ -6977,6 +7229,7 @@ export function initOpenAI() {
6977 $('#model_fireworks_select').on('change', onModelChange);7229 $('#model_fireworks_select').on('change', onModelChange);
6978 $('#azure_openai_model').on('change', onModelChange);7230 $('#azure_openai_model').on('change', onModelChange);
6979 $('#model_zai_select').on('change', onModelChange);7231 $('#model_zai_select').on('change', onModelChange);
7232 $('#model_workers_ai_select').on('change', onModelChange);
6980 $('#settings_preset_openai').on('change', onSettingsPresetChange);7233 $('#settings_preset_openai').on('change', onSettingsPresetChange);
6981 $('#new_oai_preset').on('click', onNewPresetClick);7234 $('#new_oai_preset').on('click', onNewPresetClick);
6982 $('#delete_oai_preset').on('click', onDeletePresetClick);7235 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/personas.js+1030 -68
@@ -22,9 +22,35 @@ import {
22 setUserName,22 setUserName,
23 this_chid,23 this_chid,
24} from '../script.js';24} from '../script.js';
25import { persona_description_positions, power_user } from './power-user.js';25import { power_user } from './power-user.js';
26import { getTokenCountAsync } from './tokenizers.js';26import { getTokenCountAsync } from './tokenizers.js';
27import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent } from './utils.js';27import {
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';
28import { debounce_timeout } from './constants.js';54import { debounce_timeout } from './constants.js';
29import { FILTER_TYPES, FilterHelper } from './filters.js';55import { FILTER_TYPES, FilterHelper } from './filters.js';
30import { groups, selected_group } from './group-chats.js';56import { groups, selected_group } from './group-chats.js';
@@ -36,10 +62,11 @@ import { saveMetadataDebounced } from './extensions.js';
36import { accountStorage } from './util/AccountStorage.js';62import { accountStorage } from './util/AccountStorage.js';
37import { SlashCommand } from './slash-commands/SlashCommand.js';63import { SlashCommand } from './slash-commands/SlashCommand.js';
38import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';64import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
39import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';65import { commonEnumMatchProviders, commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
40import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';66import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
41import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';67import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
42import { isFirefox } from './browser-fixes.js';68import { isFirefox } from './browser-fixes.js';
69import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
4370
44/**71/**
45 * @typedef {object} PersonaConnection A connection between a character and a character or group entity72 * @typedef {object} PersonaConnection A connection between a character and a character or group entity
@@ -58,6 +85,18 @@ import { isFirefox } from './browser-fixes.js';
58 * @property {boolean} locked.character - Whether the persona is locked to the currently open character or group85 * @property {boolean} locked.character - Whether the persona is locked to the currently open character or group
59 */86 */
6087
88export 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
61const USER_AVATAR_PATH = 'User Avatars/';100const USER_AVATAR_PATH = 'User Avatars/';
62101
63let savePersonasPage = 0;102let savePersonasPage = 0;
@@ -217,11 +256,12 @@ function getUserAvatarBlock(avatarId) {
217/**256/**
218 * Initialize missing personas in the power user settings.257 * Initialize missing personas in the power user settings.
219 * @param {string[]} avatarsList List of avatar file names258 * @param {string[]} avatarsList List of avatar file names
259 * @returns {Promise<void>}
220 */260 */
221function addMissingPersonas(avatarsList) {261async function addMissingPersonas(avatarsList) {
222 for (const persona of avatarsList) {262 for (const persona of avatarsList) {
223 if (!power_user.personas[persona]) {263 if (!power_user.personas[persona]) {
224 initPersona(persona, '[Unnamed Persona]', '', '');264 await initPersona(persona, '[Unnamed Persona]', '', '', { silent: true });
225 }265 }
226 }266 }
227}267}
@@ -249,7 +289,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
249 }289 }
250290
251 // If any persona is missing from the power user settings, we add it291 // If any persona is missing from the power user settings, we add it
252 addMissingPersonas(allEntities);292 await addMissingPersonas(allEntities);
253 // Before printing the personas, we check if we should enable/disable search sorting293 // Before printing the personas, we check if we should enable/disable search sorting
254 verifyPersonaSearchSortRule();294 verifyPersonaSearchSortRule();
255295
@@ -429,7 +469,7 @@ export async function createPersona(avatarId) {
429469
430 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });470 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
432 initPersona(avatarId, personaName, personaDescription, '');472 await initPersona(avatarId, personaName, personaDescription, '');
433 if (power_user.persona_show_notifications) {473 if (power_user.persona_show_notifications) {
434 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);474 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);
435 }475 }
@@ -454,7 +494,7 @@ async function createDummyPersona() {
454494
455 // Date + name (only ASCII) to make it unique495 // Date + name (only ASCII) to make it unique
456 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;496 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
457 initPersona(avatarId, personaName, '', personaTitle);497 await initPersona(avatarId, personaName, '', personaTitle);
458 await uploadUserAvatar(default_user_avatar, avatarId);498 await uploadUserAvatar(default_user_avatar, avatarId);
459}499}
460500
@@ -464,20 +504,36 @@ async function createDummyPersona() {
464 * @param {string} personaName Name for the persona504 * @param {string} personaName Name for the persona
465 * @param {string} personaDescription Optional description for the persona505 * @param {string} personaDescription Optional description for the persona
466 * @param {string} personaTitle Optional title for the persona506 * @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>}
468 */514 */
469export function initPersona(avatarId, personaName, personaDescription, personaTitle) {515export 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} = {}) {
470 power_user.personas[avatarId] = personaName;522 power_user.personas[avatarId] = personaName;
471 power_user.persona_descriptions[avatarId] = {523 power_user.persona_descriptions[avatarId] = {
472 description: personaDescription || '',524 description: personaDescription || '',
473 position: persona_description_positions.IN_PROMPT,525 position: position,
474 depth: DEFAULT_DEPTH,526 depth: depth,
475 role: DEFAULT_ROLE,527 role: role,
476 lorebook: '',528 lorebook: lorebook,
477 title: personaTitle || '',529 title: personaTitle || '',
478 };530 };
479531
480 saveSettingsDebounced();532 saveSettingsDebounced();
533
534 if (!silent) {
535 await eventSource.emit(event_types.PERSONA_CREATED, { avatarId, name: personaName, description: personaDescription || '', title: personaTitle || '' });
536 }
481}537}
482538
483/**539/**
@@ -540,6 +596,7 @@ export async function convertCharacterToPersona(characterId = null) {
540 }596 }
541597
542 saveSettingsDebounced();598 saveSettingsDebounced();
599 await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: overwriteName, name, description, title: '' });
543600
544 console.log('Persona for character created');601 console.log('Persona for character created');
545 toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`);602 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 =
742/**799/**
743 * Automatically selects a persona based on the given name if a matching persona exists.800 * Automatically selects a persona based on the given name if a matching persona exists.
744 * @param {string} name - The name to search for801 * @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
745 * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise804 * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise
746 */805 */
747export async function autoSelectPersona(name) {806export 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 });
749 if (value === name) {808 if (persona) {
750 console.log(`Auto-selecting persona ${key} for name ${name}`);809 console.log(`Auto-selecting persona ${persona.avatar} for name ${name}`);
751 await setUserAvatar(key);810 await setUserAvatar(persona.avatar);
752 return true;811 return true;
753 }812 }
754 }
755 return false;813 return false;
756}814}
757815
@@ -778,6 +836,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
778 delete power_user.persona_descriptions[avatarId].title;836 delete power_user.persona_descriptions[avatarId].title;
779 await getUserAvatars(true, avatarId);837 await getUserAvatars(true, avatarId);
780 saveSettingsDebounced();838 saveSettingsDebounced();
839 await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
781 return;840 return;
782 }841 }
783842
@@ -786,6 +845,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
786 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);845 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);
787 await getUserAvatars(true, avatarId);846 await getUserAvatars(true, avatarId);
788 saveSettingsDebounced();847 saveSettingsDebounced();
848 await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
789 return;849 return;
790 }850 }
791}851}
@@ -821,6 +881,7 @@ async function renamePersona(avatarId) {
821 }881 }
822882
823 saveSettingsDebounced();883 saveSettingsDebounced();
884 await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName: currentName, newName });
824 await getUserAvatars(true, avatarId);885 await getUserAvatars(true, avatarId);
825 updatePersonaUIStates();886 updatePersonaUIStates();
826 setPersonaDescription();887 setPersonaDescription();
@@ -886,8 +947,9 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
886 const temporary = getPersonaTemporaryLockInfo();947 const temporary = getPersonaTemporaryLockInfo();
887 if (temporary.isTemporary) {948 if (temporary.isTemporary) {
888 toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, {949 toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, {
889 preventDuplicates: true, onclick: () => {950 preventDuplicates: true,
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 });
891 },953 },
892 });954 });
893 }955 }
@@ -1013,6 +1075,7 @@ async function lockPersona(type = 'chat') {
1013 connections: [],1075 connections: [],
1014 title: '',1076 title: '',
1015 };1077 };
1078 await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: user_avatar, name: name1, description: '', title: '' });
1016 }1079 }
10171080
1018 switch (type) {1081 switch (type) {
@@ -1055,9 +1118,9 @@ async function lockPersona(type = 'chat') {
1055 if (power_user.persona_show_notifications) {1118 if (power_user.persona_show_notifications) {
1056 let additional = '';1119 let additional = '';
1057 if (unlinkedCharacters.length)1120 if (unlinkedCharacters.length)
1058 additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.join(', ')}`}`;1121 additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.map(escapeHtml).join(', ')}`}`;
1059 if (additional || !isPersonaPanelOpen()) {1122 if (additional || !isPersonaPanelOpen()) {
1060 toastr.success(t`User persona ${name1} is locked to character ${name2}${additional}`, t`Persona Locked`, { escapeHtml: false });1123 toastr.success(t`User persona ${escapeHtml(name1)} is locked to character ${escapeHtml(name2)}${additional}`, t`Persona Locked`, { escapeHtml: false });
1061 }1124 }
1062 }1125 }
1063 }1126 }
@@ -1071,21 +1134,37 @@ async function lockPersona(type = 'chat') {
1071}1134}
10721135
10731136
1137/**
1138 * Click handler for the delete persona button. Delegates to deletePersona with the current user avatar.
1139 */
1074async function deleteUserAvatar() {1140async 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 */
1151async function deletePersona(avatarId, { silent = false } = {}) {
1077 if (!avatarId) {1152 if (!avatarId) {
1078 console.warn('No avatar id found');1153 console.warn('No avatar id found');
1079 return;1154 return false;
1080 }1155 }
1156
1081 const name = power_user.personas[avatarId] || '';1157 const name = power_user.personas[avatarId] || '';
1158
1159 if (!silent) {
1082 const confirm = await Popup.show.confirm(1160 const confirm = await Popup.show.confirm(
1083 t`Delete Persona` + `: ${name}`,1161 t`Delete Persona` + `: ${name}`,
1084 t`Are you sure you want to delete this avatar?` + '<br />' + t`All information associated with its linked persona will be lost.`);1162 t`Are you sure you want to delete this avatar?` + '<br />' + t`All information associated with its linked persona will be lost.`);
10851163
1086 if (!confirm) {1164 if (!confirm) {
1087 console.debug('User cancelled deleting avatar');1165 console.debug('User cancelled deleting avatar');
1088 return;1166 return false;
1167 }
1089 }1168 }
10901169
1091 const request = await fetch('/api/avatars/delete', {1170 const request = await fetch('/api/avatars/delete', {
@@ -1102,24 +1181,29 @@ async function deleteUserAvatar() {
1102 delete power_user.persona_descriptions[avatarId];1181 delete power_user.persona_descriptions[avatarId];
11031182
1104 if (avatarId === power_user.default_persona) {1183 if (avatarId === power_user.default_persona) {
1105 toastr.warning(t`The default persona was deleted. You will need to set a new default persona.`, t`Default Persona Deleted`);1184 if (!silent) toastr.warning(t`The default persona was deleted. You will need to set a new default persona.`, t`Default Persona Deleted`);
1106 power_user.default_persona = null;1185 power_user.default_persona = null;
1107 }1186 }
11081187
1109 if (avatarId === chat_metadata.persona) {1188 if (avatarId === chat_metadata.persona) {
1110 toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona Deleted`);1189 if (!silent) toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona Deleted`);
1111 delete chat_metadata.persona;1190 delete chat_metadata.persona;
1112 await saveMetadata();1191 await saveMetadata();
1113 }1192 }
11141193
1115 saveSettingsDebounced();1194 saveSettingsDebounced();
1195 await eventSource.emit(event_types.PERSONA_DELETED, { avatarId, name });
11161196
1117 // Use the existing mechanism to re-render the persona list and choose the next persona here1197 // 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
1118 await loadPersonaForCurrentChat({ doRender: true });1199 await loadPersonaForCurrentChat({ doRender: true });
1200 return true;
1119 }1201 }
1202
1203 return false;
1120}1204}
11211205
1122function onPersonaDescriptionInput() {1206async function onPersonaDescriptionInput() {
1123 power_user.persona_description = String($('#persona_description').val());1207 power_user.persona_description = String($('#persona_description').val());
1124 countPersonaDescriptionTokens();1208 countPersonaDescriptionTokens();
11251209
@@ -1145,25 +1229,35 @@ function onPersonaDescriptionInput() {
1145 .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text'))1229 .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text'))
1146 .toggleClass('text_muted', !power_user.persona_description);1230 .toggleClass('text_muted', !power_user.persona_description);
1147 saveSettingsDebounced();1231 saveSettingsDebounced();
1232
1233 if (power_user.personas[user_avatar]) {
1234 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1235 }
1148}1236}
11491237
1150function onPersonaDescriptionDepthValueInput() {1238async function onPersonaDescriptionDepthValueInput() {
1151 power_user.persona_description_depth = Number($('#persona_depth_value').val());1239 power_user.persona_description_depth = Number($('#persona_depth_value').val());
11521240
1153 if (power_user.personas[user_avatar]) {1241 if (power_user.personas[user_avatar]) {
1154 const object = getOrCreatePersonaDescriptor();1242 const object = getOrCreatePersonaDescriptor();
1155 object.depth = power_user.persona_description_depth;1243 object.depth = power_user.persona_description_depth;
1244 saveSettingsDebounced();
1245 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1246 return;
1156 }1247 }
11571248
1158 saveSettingsDebounced();1249 saveSettingsDebounced();
1159}1250}
11601251
1161function onPersonaDescriptionDepthRoleInput() {1252async function onPersonaDescriptionDepthRoleInput() {
1162 power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val());1253 power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val());
11631254
1164 if (power_user.personas[user_avatar]) {1255 if (power_user.personas[user_avatar]) {
1165 const object = getOrCreatePersonaDescriptor();1256 const object = getOrCreatePersonaDescriptor();
1166 object.role = power_user.persona_description_role;1257 object.role = power_user.persona_description_role;
1258 saveSettingsDebounced();
1259 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1260 return;
1167 }1261 }
11681262
1169 saveSettingsDebounced();1263 saveSettingsDebounced();
@@ -1171,9 +1265,9 @@ function onPersonaDescriptionDepthRoleInput() {
11711265
1172/**1266/**
1173 * Opens a popup to set the lorebook for the current persona.1267 * Opens a popup to set the lorebook for the current persona.
1174 * @param {JQuery.ClickEvent} event Click event1268 * @param {Pick<JQuery.ClickEvent, 'shiftKey' | 'altKey'>} event Click event
1175 */1269 */
1176async function onPersonaLoreButtonClick(event) {1270async function onPersonaLoreButtonClick({ shiftKey, altKey }) {
1177 const personaName = power_user.personas[user_avatar];1271 const personaName = power_user.personas[user_avatar];
1178 const selectedLorebook = power_user.persona_description_lorebook;1272 const selectedLorebook = power_user.persona_description_lorebook;
11791273
@@ -1182,7 +1276,7 @@ async function onPersonaLoreButtonClick(event) {
1182 return;1276 return;
1183 }1277 }
11841278
1185 if (selectedLorebook && !event.shiftKey && !event.altKey) {1279 if (selectedLorebook && !shiftKey && !altKey) {
1186 openWorldInfoEditor(selectedLorebook);1280 openWorldInfoEditor(selectedLorebook);
1187 return;1281 return;
1188 }1282 }
@@ -1200,7 +1294,7 @@ async function onPersonaLoreButtonClick(event) {
1200 worldSelect.append(option);1294 worldSelect.append(option);
1201 }1295 }
12021296
1203 worldSelect.on('change', function () {1297 worldSelect.on('change', async function () {
1204 power_user.persona_description_lorebook = String($(this).val());1298 power_user.persona_description_lorebook = String($(this).val());
12051299
1206 if (power_user.personas[user_avatar]) {1300 if (power_user.personas[user_avatar]) {
@@ -1210,12 +1304,16 @@ async function onPersonaLoreButtonClick(event) {
12101304
1211 $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);1305 $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);
1212 saveSettingsDebounced();1306 saveSettingsDebounced();
1307
1308 if (power_user.personas[user_avatar]) {
1309 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1310 }
1213 });1311 });
12141312
1215 await callGenericPopup(template, POPUP_TYPE.TEXT);1313 await callGenericPopup(template, POPUP_TYPE.TEXT);
1216}1314}
12171315
1218function onPersonaDescriptionPositionInput() {1316async function onPersonaDescriptionPositionInput() {
1219 power_user.persona_description_position = Number(1317 power_user.persona_description_position = Number(
1220 $('#persona_description_position').find(':selected').val(),1318 $('#persona_description_position').find(':selected').val(),
1221 );1319 );
@@ -1223,6 +1321,10 @@ function onPersonaDescriptionPositionInput() {
1223 if (power_user.personas[user_avatar]) {1321 if (power_user.personas[user_avatar]) {
1224 const object = getOrCreatePersonaDescriptor();1322 const object = getOrCreatePersonaDescriptor();
1225 object.position = power_user.persona_description_position;1323 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;
1226 }1328 }
12271329
1228 saveSettingsDebounced();1330 saveSettingsDebounced();
@@ -1728,15 +1830,36 @@ async function onPersonasRestoreInput(e) {
1728 $('#personas_restore_input').val('');1830 $('#personas_restore_input').val('');
1729}1831}
17301832
1731async 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 */
1842async 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);
1734 if (!confirmation) {1855 if (!confirmation) {
1735 return;1856 return;
1736 }1857 }
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))) {
1740 mes.name = name1;1863 mes.name = name1;
1741 mes.force_avatar = getThumbnailUrl('persona', user_avatar);1864 mes.force_avatar = getThumbnailUrl('persona', user_avatar);
1742 }1865 }
@@ -1763,22 +1886,27 @@ export async function retriggerFirstMessageOnEmptyChat() {
17631886
1764/**1887/**
1765 * Duplicates a persona.1888 * Duplicates a persona.
1766 * @param {string} avatarId1889 * @param {string} avatarId Source persona avatar id
1767 * @returns {Promise<void>}1890 * @param {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
1768 */1894 */
1769async function duplicatePersona(avatarId) {1895async function duplicatePersona(avatarId, { silent = false, select = false } = {}) {
1770 const personaName = power_user.personas[avatarId];1896 const personaName = power_user.personas[avatarId];
17711897
1772 if (!personaName) {1898 if (!personaName) {
1773 toastr.warning('Chosen avatar is not a persona', t`Persona Management`);1899 toastr.warning(t`Chosen avatar is not a persona`, t`Persona Management`);
1774 return;1900 return '';
1775 }1901 }
17761902
1903 if (!silent) {
1777 const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName);1904 const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName);
17781905
1779 if (!confirm) {1906 if (!confirm) {
1780 console.debug('User cancelled duplicating persona');1907 console.debug('User cancelled duplicating persona');
1781 return;1908 return '';
1909 }
1782 }1910 }
17831911
1784 const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;1912 const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
@@ -1795,8 +1923,24 @@ async function duplicatePersona(avatarId) {
1795 };1923 };
17961924
1797 await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);1925 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
1798 await getUserAvatars(true, newAvatarId);1936 await getUserAvatars(true, newAvatarId);
1799 saveSettingsDebounced();1937 saveSettingsDebounced();
1938
1939 if (select) {
1940 await setUserAvatar(newAvatarId);
1941 }
1942
1943 return newAvatarId;
1800}1944}
18011945
1802/**1946/**
@@ -1807,12 +1951,418 @@ async function migrateNonPersonaUser() {
1807 return;1951 return;
1808 }1952 }
18091953
1810 initPersona(user_avatar, name1, '', '');1954 await initPersona(user_avatar, name1, '', '', { silent: true });
1811 setPersonaDescription();1955 setPersonaDescription();
1812 await getUserAvatars(true, user_avatar);1956 await getUserAvatars(true, user_avatar);
1813}1957}
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 */
1966const 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 */
1978const 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 */
1989function 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 */
2003function 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 */
2020async 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 */
2068function 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 */
2096async 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 */
2153async 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 */
2275async 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 */
2334async 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 */
2348async 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
1816/**2366/**
1817 * Locks or unlocks the persona of the current chat.2367 * Locks or unlocks the persona of the current chat.
1818 * @param {{type: string}} _args Named arguments2368 * @param {{type: string}} _args Named arguments
@@ -1870,10 +2420,9 @@ async function setNameCallback({ mode = 'all' }, name) {
18702420
1871 // If the name matches a persona avatar, or a name, auto-select it2421 // If the name matches a persona avatar, or a name, auto-select it
1872 if (['lookup', 'all'].includes(mode)) {2422 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];
1875 if (persona) {2424 if (persona) {
1876 await autoSelectPersona(persona);2425 await autoSelectPersona(persona.name, { personaKey: persona.avatar });
1877 return '';2426 return '';
1878 } else if (mode === 'lookup') {2427 } else if (mode === 'lookup') {
1879 toastr.warning(`Persona ${name} not found`);2428 toastr.warning(`Persona ${name} not found`);
@@ -1889,28 +2438,375 @@ async function setNameCallback({ mode = 'all' }, name) {
1889 return '';2438 return '';
1890}2439}
18912440
1892function syncCallback() {2441async 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
1894 return '';2456 return '';
1895}2457}
18962458
2459/**
2460 * Returns all unique user message names in the current chat for enum autocomplete.
2461 * @returns {SlashCommandEnumValue[]}
2462 */
2463function 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
1897function registerPersonaSlashCommands() {2472function 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
1898 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2781 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1899 name: 'persona-lock',2782 name: 'persona-lock',
1900 aliases: ['lock', 'bind'],2783 aliases: ['lock', 'bind'],
1901 callback: lockPersonaCallback,2784 callback: lockPersonaCallback,
1902 returns: 'The current lock state for the given type',2785 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 `,
1904 namedArgumentList: [2800 namedArgumentList: [
1905 SlashCommandNamedArgument.fromProps({2801 SlashCommandNamedArgument.fromProps({
1906 name: 'type',2802 name: 'type',
1907 description: 'The type of the lock, where it should apply to',2803 description: t`The type of the lock, where it should apply to`,
1908 typeList: [ARGUMENT_TYPE.STRING],2804 typeList: [ARGUMENT_TYPE.STRING],
1909 defaultValue: 'chat',2805 defaultValue: 'chat',
1910 enumList: [2806 enumList: [
1911 new SlashCommandEnumValue('chat', 'Lock the persona to the current chat.'),2807 new SlashCommandEnumValue('chat', t`Lock the persona to the current chat.`),
1912 new SlashCommandEnumValue('character', 'Lock this persona to the currently selected character. If the setting is enabled, multiple personas can be locked to the same character.'),2808 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.`),
1913 new SlashCommandEnumValue('default', 'Lock this persona as the default persona for all new chats.'),2809 new SlashCommandEnumValue('default', t`Lock this persona as the default persona for all new chats.`),
1914 ],2810 ],
1915 }),2811 }),
1916 ],2812 ],
@@ -1922,31 +2818,97 @@ function registerPersonaSlashCommands() {
1922 }),2818 }),
1923 ],2819 ],
1924 }));2820 }));
2821
1925 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2822 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1926 name: 'persona-set',2823 name: 'persona-set',
1927 callback: setNameCallback,2824 callback: setNameCallback,
1928 aliases: ['persona', 'name'],2825 aliases: ['persona', 'name'],
1929 namedArgumentList: [2826 namedArgumentList: [
1930 new SlashCommandNamedArgument(2827 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 }),
1934 ],2838 ],
1935 unnamedArgumentList: [2839 unnamedArgumentList: [
1936 SlashCommandArgument.fromProps({2840 SlashCommandArgument.fromProps({
1937 description: 'persona name',2841 description: 'persona name',
1938 typeList: [ARGUMENT_TYPE.STRING],2842 typeList: [ARGUMENT_TYPE.STRING],
1939 isRequired: true,2843 isRequired: true,
1940 enumProvider: commonEnumProviders.personas,2844 enumProvider: commonEnumProviders.personas({ allowPersonaKey: true }),
1941 }),2845 }),
1942 ],2846 ],
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 `,
1944 }));2862 }));
2863
1945 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2864 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1946 name: 'persona-sync',2865 name: 'persona-sync',
1947 aliases: ['sync'],2866 aliases: ['sync'],
1948 callback: syncCallback,2867 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 `,
1950 }));2912 }));
1951}2913}
19522914
@@ -2004,7 +2966,7 @@ export async function initPersonas() {
2004 debouncedPersonaSearch(searchQuery);2966 debouncedPersonaSearch(searchQuery);
2005 });2967 });
20062968
2007 $('#sync_name_button').on('click', syncUserNameToPersona);2969 $('#sync_name_button').on('click', async () => await syncUserNameToPersona());
2008 $('#avatar_upload_file').on('change', changeUserAvatar);2970 $('#avatar_upload_file').on('change', changeUserAvatar);
20092971
2010 $(document).on('click', '#user_avatar_block .avatar-container', async function () {2972 $(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';
2import { shouldSendOnEnter } from './RossAscends-mods.js';2import { shouldSendOnEnter } from './RossAscends-mods.js';
3import { t } from './i18n.js';3import { t } from './i18n.js';
4import { power_user, toastPositionClasses } from './power-user.js';4import { power_user, toastPositionClasses } from './power-user.js';
5import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';5import { clamp, removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';
66
7/** @readonly */7/** @readonly */
8/** @enum {Number} */8/** @enum {Number} */
@@ -79,8 +79,12 @@ export const POPUP_RESULT = {
79 * @property {string} label - The label text for the input79 * @property {string} label - The label text for the input
80 * @property {string?} [tooltip=null] - Optional tooltip to be displayed. Default placeholder in input controls, tooltip icon behind the checkbox for those.80 * @property {string?} [tooltip=null] - Optional tooltip to be displayed. Default placeholder in input controls, tooltip icon behind the checkbox for those.
81 * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set)81 * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set)
82 * @property {('checkbox'|'text'|'textarea')?} [type='checkbox'] - The type of the input (default is checkbox)82 * @property {('checkbox'|'text'|'textarea'|'number')?} [type='checkbox'] - The type of the input (default is checkbox)
83 * @property {number?} [rows=1] - The number of rows for the input field, if the input is 'textarea'83 * @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
84 */88 */
8589
86/**90/**
@@ -331,6 +335,7 @@ export class Popup {
331 inputElement.type = 'checkbox';335 inputElement.type = 'checkbox';
332 inputElement.id = input.id;336 inputElement.id = input.id;
333 inputElement.checked = Boolean(input.defaultState ?? false);337 inputElement.checked = Boolean(input.defaultState ?? false);
338 inputElement.disabled = Boolean(input.disabled ?? false);
334 label.appendChild(inputElement);339 label.appendChild(inputElement);
335 const labelText = document.createElement('span');340 const labelText = document.createElement('span');
336 labelText.innerText = input.label;341 labelText.innerText = input.label;
@@ -356,6 +361,7 @@ export class Popup {
356 inputElement.id = input.id;361 inputElement.id = input.id;
357 inputElement.value = String(input.defaultState ?? '');362 inputElement.value = String(input.defaultState ?? '');
358 inputElement.placeholder = input.tooltip ?? '';363 inputElement.placeholder = input.tooltip ?? '';
364 inputElement.disabled = Boolean(input.disabled ?? false);
359 setTitleFromTooltip(inputElement, input.tooltip);365 setTitleFromTooltip(inputElement, input.tooltip);
360366
361 const labelText = document.createElement('span');367 const labelText = document.createElement('span');
@@ -377,6 +383,7 @@ export class Popup {
377 inputElement.value = String(input.defaultState ?? '');383 inputElement.value = String(input.defaultState ?? '');
378 inputElement.rows = input.rows ?? 1;384 inputElement.rows = input.rows ?? 1;
379 inputElement.placeholder = input.tooltip ?? '';385 inputElement.placeholder = input.tooltip ?? '';
386 inputElement.disabled = Boolean(input.disabled ?? false);
380 setTitleFromTooltip(inputElement, input.tooltip);387 setTitleFromTooltip(inputElement, input.tooltip);
381388
382 const labelText = document.createElement('span');389 const labelText = document.createElement('span');
@@ -387,8 +394,47 @@ export class Popup {
387 label.appendChild(inputElement);394 label.appendChild(inputElement);
388395
389 this.inputControls.appendChild(label);396 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);
390 } else {436 } else {
391 console.warn('Unknown custom input type. Only checkbox, text and textarea are supported.', input);437 console.warn('Unknown custom input type. Only checkbox, text, number and textarea are supported.', input);
392 return;438 return;
393 }439 }
394 });440 });
@@ -672,6 +718,10 @@ export class Popup {
672 }718 }
673 }719 }
674720
721 if (!control) {
722 return;
723 }
724
675 if (applyAutoFocus) {725 if (applyAutoFocus) {
676 control.setAttribute('autofocus', '');726 control.setAttribute('autofocus', '');
677 // Manually enable tabindex too, as this might only be applied by the interactable functionality in the background, but too late for HTML autofocus727 // 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 {
720 this.inputResults = new Map(this.customInputs.map(input => {770 this.inputResults = new Map(this.customInputs.map(input => {
721 /** @type {HTMLInputElement} */771 /** @type {HTMLInputElement} */
722 const inputControl = this.dlg.querySelector(`#${input.id}`);772 const inputControl = this.dlg.querySelector(`#${input.id}`);
723 const value = ['text', 'textarea'].includes(input.type) ? inputControl.value : inputControl.checked;773 const value = ['text', 'textarea', 'number'].includes(input.type) ? inputControl.value : inputControl.checked;
724 return [inputControl.id, value];774 return [inputControl.id, value];
725 }));775 }));
726 }776 }
public/scripts/power-user.js+2 -11
@@ -68,6 +68,7 @@ import { bindModelTemplates } from './chat-templates.js';
68import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js';68import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js';
69import { t } from './i18n.js';69import { t } from './i18n.js';
70import { getBackgroundPath, isCustomBackgroundUrl } from './backgrounds.js';70import { getBackgroundPath, isCustomBackgroundUrl } from './backgrounds.js';
71import { persona_description_positions as _persona_description_positions } from './personas.js';
7172
72export const toastPositionClasses = [73export const toastPositionClasses = [
73 'toast-top-left',74 'toast-top-left',
@@ -110,17 +111,7 @@ export const send_on_enter_options = {
110 ENABLED: 1,111 ENABLED: 1,
111};112};
112113
113export const persona_description_positions = {114export 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
125export const power_user = {116export const power_user = {
126 charListGrid: false,117 charListGrid: false,
public/scripts/reasoning.js+72 -3
@@ -134,6 +134,7 @@ export function extractReasoningFromData(data, {
134 case chat_completion_sources.NANOGPT:134 case chat_completion_sources.NANOGPT:
135 case chat_completion_sources.SILICONFLOW:135 case chat_completion_sources.SILICONFLOW:
136 case chat_completion_sources.ZAI:136 case chat_completion_sources.ZAI:
137 case chat_completion_sources.WORKERS_AI:
137 case chat_completion_sources.CUSTOM: {138 case chat_completion_sources.CUSTOM: {
138 return data?.choices?.[0]?.message?.reasoning_content139 return data?.choices?.[0]?.message?.reasoning_content
139 ?? data?.choices?.[0]?.message?.reasoning140 ?? data?.choices?.[0]?.message?.reasoning
@@ -1010,6 +1011,44 @@ function registerReasoningSlashCommands() {
1010 },1011 },
1011 }));1012 }));
1012 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1013 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({
1013 name: 'reasoning-template',1052 name: 'reasoning-template',
1014 aliases: ['reasoning-formatting', 'reasoning-preset'],1053 aliases: ['reasoning-formatting', 'reasoning-preset'],
1015 callback: selectReasoningTemplateCallback,1054 callback: selectReasoningTemplateCallback,
@@ -1082,7 +1121,7 @@ function registerReasoningSlashCommands() {
1082 helpString: t`Collapse the reasoning block of a message or range of messages.`,1121 helpString: t`Collapse the reasoning block of a message or range of messages.`,
1083 unnamedArgumentList: reasoningVisibilityArgs,1122 unnamedArgumentList: reasoningVisibilityArgs,
1084 callback: (_args, value) => {1123 callback: (_args, value) => {
1085 const details = getReasoningDetailsElements(value);1124 const details = getReasoningDetailsElements(value.toString());
1086 if (details) details.removeAttr('open');1125 if (details) details.removeAttr('open');
1087 return '';1126 return '';
1088 },1127 },
@@ -1094,7 +1133,7 @@ function registerReasoningSlashCommands() {
1094 helpString: t`Expand the reasoning block of a message or range of messages.`,1133 helpString: t`Expand the reasoning block of a message or range of messages.`,
1095 unnamedArgumentList: reasoningVisibilityArgs,1134 unnamedArgumentList: reasoningVisibilityArgs,
1096 callback: (_args, value) => {1135 callback: (_args, value) => {
1097 const details = getReasoningDetailsElements(value);1136 const details = getReasoningDetailsElements(value.toString());
1098 if (details) details.attr('open', '');1137 if (details) details.attr('open', '');
1099 return '';1138 return '';
1100 },1139 },
@@ -1106,7 +1145,7 @@ function registerReasoningSlashCommands() {
1106 helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`,1145 helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`,
1107 unnamedArgumentList: reasoningVisibilityArgs,1146 unnamedArgumentList: reasoningVisibilityArgs,
1108 callback: (_args, value) => {1147 callback: (_args, value) => {
1109 const details = getReasoningDetailsElements(value);1148 const details = getReasoningDetailsElements(value.toString());
1110 if (!details) return '';1149 if (!details) return '';
1111 details.each(function () {1150 details.each(function () {
1112 const $el = $(this);1151 const $el = $(this);
@@ -1411,6 +1450,36 @@ export function parseReasoningFromString(str, { strict = true } = {}, template =
1411}1450}
14121451
1413/**1452/**
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 */
1463export 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/**
1414 * Parse reasoning in an array of swipe strings if auto-parsing is enabled.1483 * Parse reasoning in an array of swipe strings if auto-parsing is enabled.
1415 * @param {string[]} swipes Array of swipe strings1484 * @param {string[]} swipes Array of swipe strings
1416 * @param {{extra: Partial<ReasoningMessageExtra>}[]} swipeInfoArray Array of swipe info objects1485 * @param {{extra: Partial<ReasoningMessageExtra>}[]} swipeInfoArray Array of swipe info objects
public/scripts/secrets.js+186 -15
@@ -1,5 +1,5 @@
1import { DOMPurify, moment } from '../lib.js';1import { DOMPurify, moment, sha256 } from '../lib.js';
2import { event_types, eventSource, getRequestHeaders } from '../script.js';2import { event_types, eventSource, getRequestHeaders, saveSettings } from '../script.js';
3import { t } from './i18n.js';3import { t } from './i18n.js';
4import { chat_completion_sources } from './openai.js';4import { chat_completion_sources } from './openai.js';
5import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';5import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
@@ -12,7 +12,9 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
12import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';12import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
13import { renderTemplateAsync } from './templates.js';13import { renderTemplateAsync } from './templates.js';
14import { textgen_types } from './textgen-settings.js';14import { textgen_types } from './textgen-settings.js';
15import { copyText, isTrueBoolean } from './utils.js';15import { getCurrentUserHandle } from './user.js';
16import { copyText, isTrueBoolean, uuidv4 } from './utils.js';
17import { accountStorage } from './util/AccountStorage.js';
1618
17export const SECRET_KEYS = {19export const SECRET_KEYS = {
18 HORDE: 'api_key_horde',20 HORDE: 'api_key_horde',
@@ -76,6 +78,7 @@ export const SECRET_KEYS = {
76 POLLINATIONS: 'api_key_pollinations',78 POLLINATIONS: 'api_key_pollinations',
77 VOLCENGINE_APP_ID: 'volcengine_app_id',79 VOLCENGINE_APP_ID: 'volcengine_app_id',
78 VOLCENGINE_ACCESS_KEY: 'volcengine_access_key',80 VOLCENGINE_ACCESS_KEY: 'volcengine_access_key',
81 WORKERS_AI: 'api_key_workers_ai',
79};82};
8083
81const FRIENDLY_NAMES = {84const FRIENDLY_NAMES = {
@@ -129,7 +132,7 @@ const FRIENDLY_NAMES = {
129 [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)',132 [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)',
130 [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)',133 [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)',
131 [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)',134 [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)',
132 [SECRET_KEYS.MINIMAX]: 'MiniMax TTS',135 [SECRET_KEYS.MINIMAX]: 'MiniMax',
133 [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID',136 [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID',
134 [SECRET_KEYS.MOONSHOT]: 'Moonshot AI',137 [SECRET_KEYS.MOONSHOT]: 'Moonshot AI',
135 [SECRET_KEYS.COMETAPI]: 'CometAPI',138 [SECRET_KEYS.COMETAPI]: 'CometAPI',
@@ -140,6 +143,7 @@ const FRIENDLY_NAMES = {
140 [SECRET_KEYS.POLLINATIONS]: 'Pollinations',143 [SECRET_KEYS.POLLINATIONS]: 'Pollinations',
141 [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID',144 [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID',
142 [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key',145 [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key',
146 [SECRET_KEYS.WORKERS_AI]: 'Cloudflare Workers AI',
143};147};
144148
145const INPUT_MAP = {149const INPUT_MAP = {
@@ -182,8 +186,9 @@ const INPUT_MAP = {
182 [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai',186 [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai',
183 [SECRET_KEYS.ZAI]: '#api_key_zai',187 [SECRET_KEYS.ZAI]: '#api_key_zai',
184 [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow',188 [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow',
185 [SECRET_KEYS.COMFY_RUNPOD]: '#api_key_comfy_runpod',189 [SECRET_KEYS.MINIMAX]: '#api_key_minimax',
186 [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations',190 [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations',
191 [SECRET_KEYS.WORKERS_AI]: '#api_key_workers_ai',
187};192};
188193
189const getLabel = () => moment().format('L LT');194const getLabel = () => moment().format('L LT');
@@ -414,7 +419,6 @@ export async function readSecretState() {
414 secret_state = await response.json();419 secret_state = await response.json();
415 updateSecretDisplay();420 updateSecretDisplay();
416 updateInputDataLists();421 updateInputDataLists();
417 await checkOpenRouterAuth();
418 }422 }
419 } catch {423 } catch {
420 console.error('Could not read secrets file');424 console.error('Could not read secrets file');
@@ -495,6 +499,25 @@ export async function renameSecret(key, id, label) {
495}499}
496500
497/**501/**
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 */
506const 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 */
513const 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/**
498 * Redirects the user to authorize OpenRouter.521 * Redirects the user to authorize OpenRouter.
499 */522 */
500async function authorizeOpenRouter() {523async function authorizeOpenRouter() {
@@ -505,8 +528,15 @@ async function authorizeOpenRouter() {
505 }528 }
506 }529 }
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
508 const redirectUrl = new URL('/callback/openrouter', window.location.origin);538 const redirectUrl = new URL('/callback/openrouter', window.location.origin);
509 const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(redirectUrl.toString())}`;539 const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(redirectUrl.toString())}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
510 location.href = openRouterUrl;540 location.href = openRouterUrl;
511}541}
512542
@@ -514,16 +544,32 @@ async function authorizeOpenRouter() {
514 * Checks if the OpenRouter authorization code is present in the URL, and if so, exchanges it for an API key.544 * Checks if the OpenRouter authorization code is present in the URL, and if so, exchanges it for an API key.
515 * @returns {Promise<void>}545 * @returns {Promise<void>}
516 */546 */
517async function checkOpenRouterAuth() {547export async function checkOpenRouterAuth() {
518 const params = new URLSearchParams(location.search);548 const params = new URLSearchParams(location.search);
519 const source = params.get('source');549 const source = params.get('source');
520 if (source === 'openrouter') {550 if (source === 'openrouter') {
521 const query = new URLSearchParams(params.get('query'));551 const query = new URLSearchParams(params.get('query'));
522 const code = query.get('code');
523 try {552 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
524 const response = await fetch('https://openrouter.ai/api/v1/auth/keys', {563 const response = await fetch('https://openrouter.ai/api/v1/auth/keys', {
525 method: 'POST',564 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 }),
527 });573 });
528574
529 if (!response.ok) {575 if (!response.ok) {
@@ -539,18 +585,22 @@ async function checkOpenRouterAuth() {
539585
540 if (secret_state[SECRET_KEYS.OPENROUTER]) {586 if (secret_state[SECRET_KEYS.OPENROUTER]) {
541 toastr.success('OpenRouter token saved');587 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);
546 } else {588 } else {
547 throw new Error('OpenRouter token not saved');589 throw new Error('OpenRouter token not saved');
548 }590 }
549 } catch (err) {591 } catch (err) {
550 toastr.error('Could not verify OpenRouter token. Please try again.');592 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);
552 }599 }
553 }600 }
601
602 // Clean-up any code verifiers that might be left in accountStorage from abandoned auth flows
603 accountStorage.removeItem(getVerifierKey('openrouter'));
554}604}
555605
556/**606/**
@@ -1118,5 +1168,126 @@ export async function initSecrets() {
1118 warningElement.toggle(value.length > 0);1168 warningElement.toggle(value.length > 0);
1119 });1169 });
1120 $('.openrouter_authorize').on('click', authorizeOpenRouter);1170 $('.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 });
1121 registerSecretSlashCommands();1292 registerSecretSlashCommands();
1122}1293}
public/scripts/slash-commands.js+1 -1
@@ -1,5 +1,5 @@
1import { Fuse, DOMPurify } from '../lib.js';1import { Fuse, DOMPurify } from '../lib.js';
2import { canUseNegativeLookbehind, copyText, findPersona, flashHighlight, getBase64Async, ensureImageFormatSupported, supportedImageMimeTypes, isExternalUrl } from './utils.js';2import { canUseNegativeLookbehind, copyText, findPersona, flashHighlight, resolveAvatarData } from './utils.js';
33
4import {4import {
5 Generate,5 Generate,
@@ -70,7 +70,7 @@ import { hideChatMessageRange } from './chats.js';
70import { getContext, saveMetadataDebounced } from './extensions.js';70import { 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