Merge pull request #5591 from SillyTavern/staging Staging
Signed| @@ -19,7 +19,7 @@ jobs: | ||
| 19 | 19 | - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 |
| 20 | 20 | with: |
| 21 | 21 | node-version: 24 |
| 22 | 22 | - run: npm ci --omit=dev --ignore-scripts |
| 23 | 23 | |
| 24 | 24 | publish-npm: |
| 25 | 25 | needs: build |
| @@ -30,5 +30,5 @@ jobs: | ||
| 30 | 30 | with: |
| 31 | 31 | node-version: 24 |
| 32 | 32 | registry-url: https://registry.npmjs.org/ |
| 33 | 33 | - run: npm ci --omit=dev --ignore-scripts |
| 34 | 34 | - run: npm publish |
| @@ -0,0 +1,2 @@ | ||
| 1 | +ignore-scripts=true | |
| 2 | +min-release-age=7 | |
| @@ -0,0 +1,4 @@ | ||
| 1 | +{ | |
| 2 | + "js/ts.tsdk.promptToUseWorkspaceVersion": true, | |
| 3 | + "js/ts.tsdk.path": "./node_modules/typescript/lib" | |
| 4 | +} | |
| @@ -19,7 +19,7 @@ COPY --chown=node:node . ./ | ||
| 19 | 19 | |
| 20 | 20 | RUN \ |
| 21 | 21 | echo "*** Install npm packages ***" && \ |
| 22 | 22 | npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts && npm cache clean --force |
| 23 | 23 | |
| 24 | 24 | # Create config directory and link config.yaml. Added hardcoded dirs(constants.js?) |
| 25 | 25 | # that must be present for Non-Root Mode and volumeless docker runs. |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | @echo off |
| 2 | 2 | pushd %~dp0 |
| 3 | 3 | set NODE_ENV=production |
| 4 | 4 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts |
| 5 | 5 | node server.js %* |
| 6 | 6 | pause |
| 7 | 7 | popd |
| @@ -20,7 +20,7 @@ if %errorlevel% neq 0 ( | ||
| 20 | 20 | ) |
| 21 | 21 | ) |
| 22 | 22 | set NODE_ENV=production |
| 23 | 23 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts |
| 24 | 24 | node server.js %* |
| 25 | 25 | :end |
| 26 | 26 | pause |
| @@ -102,7 +102,7 @@ if %errorlevel% neq 0 ( | ||
| 102 | 102 | |
| 103 | 103 | echo Installing npm packages and starting server |
| 104 | 104 | set NODE_ENV=production |
| 105 | 105 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts |
| 106 | 106 | node server.js %* |
| 107 | 107 | |
| 108 | 108 | :end |
| @@ -41,6 +41,9 @@ port: 8000 | ||
| 41 | 41 | # Interval in seconds to write a heartbeat file. Set to 0 to disable. |
| 42 | 42 | # This is used primarily for Docker healthchecks. |
| 43 | 43 | heartbeatInterval: 0 |
| 44 | +# Enable HTTP/HTTPS keep-alive globally. | |
| 45 | +# Disabling restores old Node 18 behavior, can help if ECONNRESET and other network errors occur. | |
| 46 | +enableKeepAlive: false | |
| 44 | 47 | # -- SSL options -- |
| 45 | 48 | ssl: |
| 46 | 49 | # Enable SSL/TLS encryption |
| @@ -55,7 +58,7 @@ ssl: | ||
| 55 | 58 | # -- SECURITY CONFIGURATION -- |
| 56 | 59 | # Toggle whitelist mode |
| 57 | 60 | whitelistMode: true |
| 58 | 61 | # WhitelistWhen enabled, whitelist will also verify IP in X-Forwarded-Forheaders /enabled X-Real-IPin headers`forwardedHeaders` section. |
| 59 | 62 | enableForwardedWhitelist: true |
| 60 | 63 | # Whitelist of allowed IP addresses |
| 61 | 64 | whitelist: |
| @@ -127,6 +130,13 @@ sso: | ||
| 127 | 130 | # as that used for authentik. (Ensure the username in authentik |
| 128 | 131 | # is an exact match in lowercase with that in sillytavern). |
| 129 | 132 | 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 | |
| 130 | 140 | |
| 131 | 141 | # Host whitelist configuration. Recommended if you're using a listen mode |
| 132 | 142 | hostWhitelist: |
| @@ -141,6 +151,26 @@ hostWhitelist: | ||
| 141 | 151 | # - .trycloudflare.com |
| 142 | 152 | hosts: [] |
| 143 | 153 | |
| 154 | +# Perform whitelist checks against server-side HTTP requests that resolve to private IP addresses. | |
| 155 | +# This is an additional layer of security to prevent Server-Side Request Forgery (SSRF) attacks. | |
| 156 | +# Recommended when listen mode is enabled, or if your server is accessible by untrusted users. | |
| 157 | +privateAddressWhitelist: | |
| 158 | + # Enable private address whitelist to block requests to private IP ranges. | |
| 159 | + enabled: false | |
| 160 | + # If true, requests to hosts that cannot be resolved will be allowed instead of blocked. | |
| 161 | + allowUnresolvedHosts: false | |
| 162 | + # Log blocked and allowed requests to the console. | |
| 163 | + log: | |
| 164 | + # Log blocked requests to the console with a warning message | |
| 165 | + blockedRequests: true | |
| 166 | + # Log allowed requests to the console with an info message | |
| 167 | + allowedRequests: false | |
| 168 | + # List of allowed private IP ranges (in CIDR notation or wildcard format). | |
| 169 | + # Allows loopback IP ranges by default, but you can customize this list to fit your needs. | |
| 170 | + allowedRanges: | |
| 171 | + - '127.0.0.0/8' # Loopback (IPv4) | |
| 172 | + - '::1/128' # Loopback (IPv6) | |
| 173 | + | |
| 144 | 174 | # User session timeout *in seconds* (defaults to 24 hours). |
| 145 | 175 | ## Set to a positive number to expire session after a certain time of inactivity |
| 146 | 176 | ## Set to 0 to expire session when the browser is closed |
| @@ -159,9 +189,24 @@ logging: | ||
| 159 | 189 | minLogLevel: 0 |
| 160 | 190 | # -- RATE LIMITING CONFIGURATION -- |
| 161 | 191 | rateLimiting: |
| 162 | 192 | # Use X-Real-IPany headerof insteadthe ofenabled socketheaders in the `forwardedHeaders` section to identify the client IP for rate limiting. |
| 163 | - # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy) | |
| 193 | + # If disabled, only the socket IP will be used, which may not work correctly if you are behind a reverse proxy. | |
| 164 | 194 | preferRealIpHeader: false |
| 195 | + # Set the maximum number of allowed failed basic authentication attempts before rate limiting is applied. Set to 0 to disable rate limiting for basic auth. | |
| 196 | + basicAuthMaxAttempts: 5 | |
| 197 | + # Set the maximum number of allowed failed account login attempts before rate limiting is applied. Set to 0 to disable rate limiting for account logins. | |
| 198 | + accountsLoginMaxAttempts: 5 | |
| 199 | + # Set the maximum number of allowed failed account recovery attempts before rate limiting is applied. Set to 0 to disable rate limiting for account recovery. | |
| 200 | + accountsRecoverMaxAttempts: 5 | |
| 201 | +# Set to true to enable support for real IPs in certain request headers for features like IP whitelisting, rate limiting and access logging. | |
| 202 | +# Only change if you are sure that you use a correctly configured reverse proxy, otherwise this may lead to IP spoofing. | |
| 203 | +forwardedHeaders: | |
| 204 | + # X-Real-IP header (common with Nginx and Caddy) | |
| 205 | + xRealIp: true | |
| 206 | + # X-Forwarded-For header (common with many proxies, but may contain multiple IPs - only the first one will be used) | |
| 207 | + xForwardedFor: true | |
| 208 | + # CF-Connecting-IP header (used by Cloudflare Tunnels) | |
| 209 | + cfConnectingIp: false | |
| 165 | 210 | |
| 166 | 211 | ## BACKUP CONFIGURATION |
| 167 | 212 | backups: |
| @@ -2,11 +2,11 @@ | ||
| 2 | 2 | <html> |
| 3 | 3 | |
| 4 | 4 | <head> |
| 5 | 5 | <title>Not foundFound</title> |
| 6 | 6 | </head> |
| 7 | 7 | |
| 8 | 8 | <body> |
| 9 | 9 | <h1>Not foundFound</h1> |
| 10 | 10 | <p> |
| 11 | 11 | The requested URL was not found on this server. |
| 12 | 12 | </p> |
| @@ -642,5 +642,37 @@ | ||
| 642 | 642 | { |
| 643 | 643 | "filename": "presets/reasoning/Think XML.json", |
| 644 | 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 | ] |
| @@ -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 | +} | |
| @@ -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 | +} | |
| @@ -10,6 +10,8 @@ | ||
| 10 | 10 | "mistralai_model": "mistral-large-latest", |
| 11 | 11 | "chutes_model": "deepseek-ai/DeepSeek-V3-0324", |
| 12 | 12 | "chutes_sort_models": "alphabetically", |
| 13 | + "minimax_model": "MiniMax-M2.7", | |
| 14 | + "minimax_endpoint": "global", | |
| 13 | 15 | "electronhub_model": "gpt-4o-mini", |
| 14 | 16 | "electronhub_sort_models": "alphabetically", |
| 15 | 17 | "electronhub_group_models": false, |
| @@ -0,0 +1,6 @@ | ||
| 1 | +{ | |
| 2 | + "name": "Gemma 4", | |
| 3 | + "prefix": "<|channel>thought\n", | |
| 4 | + "suffix": "<channel|>", | |
| 5 | + "separator": "\n\n" | |
| 6 | +} | |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | #!/bin/sh |
| 2 | 2 | |
| 3 | 3 | # Function to handle startup logic (Config check + Postinstallinit + Start) |
| 4 | 4 | start_sillytavern() { |
| 5 | 5 | local PREFIX="$1" |
| 6 | 6 | shift # Remove the first argument (PREFIX) so $@ contains the rest |
| @@ -11,8 +11,8 @@ start_sillytavern() { | ||
| 11 | 11 | $PREFIX cp "default/config.yaml" "config/config.yaml" |
| 12 | 12 | fi |
| 13 | 13 | |
| 14 | 14 | # Execute postinstallinit script to auto-populate config.yaml with missing values |
| 15 | 15 | $PREFIX npm run postinstallinit |
| 16 | 16 | |
| 17 | 17 | # Start the server |
| 18 | 18 | exec $PREFIX node server.js --listen "$@" |
| @@ -42,6 +42,10 @@ declare global { | ||
| 42 | 42 | */ |
| 43 | 43 | handle: string | null; |
| 44 | 44 | /** |
| 45 | + * Account version tag: shake256 derivative of password hash and salt. | |
| 46 | + */ | |
| 47 | + version: string | null; | |
| 48 | + /** | |
| 45 | 49 | * Last time the session was extended. |
| 46 | 50 | */ |
| 47 | 51 | touch: number; |
| @@ -1,13 +1,12 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "name": "sillytavern", |
| 3 | 3 | "version": "1.1718.0", |
| 4 | 4 | "lockfileVersion": 3, |
| 5 | 5 | "requires": true, |
| 6 | 6 | "packages": { |
| 7 | 7 | "": { |
| 8 | 8 | "name": "sillytavern", |
| 9 | 9 | "version": "1.1718.0", |
| 10 | - "hasInstallScript": true, | |
| 11 | 10 | "license": "AGPL-3.0", |
| 12 | 11 | "dependencies": { |
| 13 | 12 | "@adobe/css-tools": "^4.4.4", |
| @@ -39,13 +38,14 @@ | ||
| 39 | 38 | "@mozilla/readability": "^0.6.0", |
| 40 | 39 | "@popperjs/core": "^2.11.8", |
| 41 | 40 | "@zeldafan0225/ai_horde": "^5.2.0", |
| 41 | + "agent-base": "^7.1.3", | |
| 42 | 42 | "archiver": "^7.0.1", |
| 43 | 43 | "bing-translate-api": "^4.1.0", |
| 44 | 44 | "body-parser": "^1.20.2", |
| 45 | 45 | "bowser": "^2.12.1", |
| 46 | 46 | "bytes": "^3.1.2", |
| 47 | 47 | "chalk": "^5.6.0", |
| 48 | 48 | "chevrotain": "^11.12.10", |
| 49 | 49 | "command-exists": "^1.2.9", |
| 50 | 50 | "compression": "^1.8.1", |
| 51 | 51 | "cookie-parser": "^1.4.6", |
| @@ -54,7 +54,7 @@ | ||
| 54 | 54 | "crc": "^4.3.2", |
| 55 | 55 | "csrf-sync": "^4.2.1", |
| 56 | 56 | "diff-match-patch": "^1.0.5", |
| 57 | 57 | "dompurify": "^3.24.62", |
| 58 | 58 | "droll": "^0.2.1", |
| 59 | 59 | "env-paths": "^3.0.0", |
| 60 | 60 | "express": "^4.21.0", |
| @@ -74,8 +74,9 @@ | ||
| 74 | 74 | "ipaddr.js": "^2.2.0", |
| 75 | 75 | "is-docker": "^3.0.0", |
| 76 | 76 | "isomorphic-git": "^1.36.3", |
| 77 | + "js-sha256": "^0.11.1", | |
| 77 | 78 | "localforage": "^1.10.0", |
| 78 | 79 | "lodash": "^4.1718.211", |
| 79 | 80 | "mime-types": "^3.0.2", |
| 80 | 81 | "moment": "^2.30.1", |
| 81 | 82 | "morphdom": "^2.7.7", |
| @@ -109,7 +110,7 @@ | ||
| 109 | 110 | "sillytavern": "src/server-global.js" |
| 110 | 111 | }, |
| 111 | 112 | "devDependencies": { |
| 112 | 113 | "@chevrotain/types": "^11.02.30", |
| 113 | 114 | "@types/archiver": "^6.0.3", |
| 114 | 115 | "@types/bytes": "^3.1.5", |
| 115 | 116 | "@types/command-exists": "^1.2.3", |
| @@ -123,7 +124,7 @@ | ||
| 123 | 124 | "@types/jquery-cropper": "^1.0.4", |
| 124 | 125 | "@types/jquery.transit": "^0.9.33", |
| 125 | 126 | "@types/jqueryui": "^1.12.24", |
| 126 | 127 | "@types/lodash": "^4.17.2024", |
| 127 | 128 | "@types/mime-types": "^3.0.1", |
| 128 | 129 | "@types/multer": "^2.1.0", |
| 129 | 130 | "@types/node": "^18.19.84", |
| @@ -139,7 +140,8 @@ | ||
| 139 | 140 | "eslint": "^8.57.1", |
| 140 | 141 | "eslint-plugin-jest": "^27.9.0", |
| 141 | 142 | "eslint-plugin-jsdoc": "^48.10.0", |
| 142 | 143 | "eslint-plugin-playwright": "^2.3.0", |
| 144 | + "typescript": "^5.9.3" | |
| 143 | 145 | }, |
| 144 | 146 | "engines": { |
| 145 | 147 | "node": ">= 20" |
| @@ -177,42 +179,42 @@ | ||
| 177 | 179 | "license": "Apache-2.0" |
| 178 | 180 | }, |
| 179 | 181 | "node_modules/@chevrotain/cst-dts-gen": { |
| 180 | 182 | "version": "11.12.10", |
| 181 | 183 | "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.12.10.tgz", |
| 182 | 184 | "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1KssJFvn/i9gSd5NNO0ds03ek0CboUXhQQeICw3SR/4uVKmOBcwfZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==", |
| 183 | 185 | "license": "Apache-2.0", |
| 184 | 186 | "dependencies": { |
| 185 | 187 | "@chevrotain/gast": "11.12.10", |
| 186 | 188 | "@chevrotain/types": "11.12.10", |
| 187 | 189 | "lodash-es": "4.17.23" |
| 188 | 190 | } |
| 189 | 191 | }, |
| 190 | 192 | "node_modules/@chevrotain/gast": { |
| 191 | 193 | "version": "11.12.10", |
| 192 | 194 | "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.12.10.tgz", |
| 193 | 195 | "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGmc+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvgKoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==", |
| 194 | 196 | "license": "Apache-2.0", |
| 195 | 197 | "dependencies": { |
| 196 | 198 | "@chevrotain/types": "11.12.10", |
| 197 | 199 | "lodash-es": "4.17.23" |
| 198 | 200 | } |
| 199 | 201 | }, |
| 200 | 202 | "node_modules/@chevrotain/regexp-to-ast": { |
| 201 | 203 | "version": "11.12.10", |
| 202 | 204 | "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.12.10.tgz", |
| 203 | 205 | "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKwlG73pBFqbXODTbXhdZwv0oyUaI+82C2uylg3Irm+uOv5/TEwFRgwLmbhlln4qkmDytegW79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==", |
| 204 | 206 | "license": "Apache-2.0" |
| 205 | 207 | }, |
| 206 | 208 | "node_modules/@chevrotain/types": { |
| 207 | 209 | "version": "11.12.10", |
| 208 | 210 | "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.12.10.tgz", |
| 209 | 211 | "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5yvBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqwkzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==", |
| 210 | 212 | "license": "Apache-2.0" |
| 211 | 213 | }, |
| 212 | 214 | "node_modules/@chevrotain/utils": { |
| 213 | 215 | "version": "11.12.10", |
| 214 | 216 | "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.12.10.tgz", |
| 215 | 217 | "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==", |
| 216 | 218 | "license": "Apache-2.0" |
| 217 | 219 | }, |
| 218 | 220 | "node_modules/@es-joy/jsdoccomment": { |
| @@ -549,7 +551,6 @@ | ||
| 549 | 551 | "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", |
| 550 | 552 | "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", |
| 551 | 553 | "license": "MIT", |
| 552 | - "peer": true, | |
| 553 | 554 | "dependencies": { |
| 554 | 555 | "@jimp/core": "^0.22.12" |
| 555 | 556 | } |
| @@ -1011,7 +1012,6 @@ | ||
| 1011 | 1012 | "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", |
| 1012 | 1013 | "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", |
| 1013 | 1014 | "license": "MIT", |
| 1014 | - "peer": true, | |
| 1015 | 1015 | "dependencies": { |
| 1016 | 1016 | "@jimp/core": "1.6.0", |
| 1017 | 1017 | "@jimp/types": "1.6.0", |
| @@ -1043,7 +1043,6 @@ | ||
| 1043 | 1043 | "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", |
| 1044 | 1044 | "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", |
| 1045 | 1045 | "license": "MIT", |
| 1046 | - "peer": true, | |
| 1047 | 1046 | "dependencies": { |
| 1048 | 1047 | "@jimp/utils": "^0.22.12" |
| 1049 | 1048 | }, |
| @@ -1139,7 +1138,6 @@ | ||
| 1139 | 1138 | "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", |
| 1140 | 1139 | "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", |
| 1141 | 1140 | "license": "MIT", |
| 1142 | - "peer": true, | |
| 1143 | 1141 | "dependencies": { |
| 1144 | 1142 | "@jimp/utils": "^0.22.12" |
| 1145 | 1143 | }, |
| @@ -1176,7 +1174,6 @@ | ||
| 1176 | 1174 | "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", |
| 1177 | 1175 | "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", |
| 1178 | 1176 | "license": "MIT", |
| 1179 | - "peer": true, | |
| 1180 | 1177 | "dependencies": { |
| 1181 | 1178 | "@jimp/utils": "^0.22.12", |
| 1182 | 1179 | "tinycolor2": "^1.6.0" |
| @@ -1220,7 +1217,6 @@ | ||
| 1220 | 1217 | "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", |
| 1221 | 1218 | "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", |
| 1222 | 1219 | "license": "MIT", |
| 1223 | - "peer": true, | |
| 1224 | 1220 | "dependencies": { |
| 1225 | 1221 | "@jimp/utils": "^0.22.12" |
| 1226 | 1222 | }, |
| @@ -1308,7 +1304,6 @@ | ||
| 1308 | 1304 | "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", |
| 1309 | 1305 | "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", |
| 1310 | 1306 | "license": "MIT", |
| 1311 | - "peer": true, | |
| 1312 | 1307 | "dependencies": { |
| 1313 | 1308 | "@jimp/utils": "^0.22.12" |
| 1314 | 1309 | }, |
| @@ -1321,7 +1316,6 @@ | ||
| 1321 | 1316 | "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", |
| 1322 | 1317 | "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", |
| 1323 | 1318 | "license": "MIT", |
| 1324 | - "peer": true, | |
| 1325 | 1319 | "dependencies": { |
| 1326 | 1320 | "@jimp/utils": "^0.22.12" |
| 1327 | 1321 | }, |
| @@ -1922,7 +1916,6 @@ | ||
| 1922 | 1916 | "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", |
| 1923 | 1917 | "dev": true, |
| 1924 | 1918 | "license": "MIT", |
| 1925 | - "peer": true, | |
| 1926 | 1919 | "dependencies": { |
| 1927 | 1920 | "@types/body-parser": "*", |
| 1928 | 1921 | "@types/express-serve-static-core": "^4.17.33", |
| @@ -2018,9 +2011,9 @@ | ||
| 2018 | 2011 | } |
| 2019 | 2012 | }, |
| 2020 | 2013 | "node_modules/@types/lodash": { |
| 2021 | 2014 | "version": "4.17.2024", |
| 2022 | 2015 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.2024.tgz", |
| 2023 | 2016 | "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXAgIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGAFizimb35b7k8atwsQ==", |
| 2024 | 2017 | "dev": true, |
| 2025 | 2018 | "license": "MIT" |
| 2026 | 2019 | }, |
| @@ -2612,7 +2605,6 @@ | ||
| 2612 | 2605 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", |
| 2613 | 2606 | "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", |
| 2614 | 2607 | "license": "MIT", |
| 2615 | - "peer": true, | |
| 2616 | 2608 | "bin": { |
| 2617 | 2609 | "acorn": "bin/acorn" |
| 2618 | 2610 | }, |
| @@ -2799,9 +2791,9 @@ | ||
| 2799 | 2791 | } |
| 2800 | 2792 | }, |
| 2801 | 2793 | "node_modules/archiver-utils/node_modules/brace-expansion": { |
| 2802 | 2794 | "version": "2.01.20", |
| 2803 | 2795 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.01.20.tgz", |
| 2804 | 2796 | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQTN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", |
| 2805 | 2797 | "license": "MIT", |
| 2806 | 2798 | "dependencies": { |
| 2807 | 2799 | "balanced-match": "^1.0.0" |
| @@ -3021,14 +3013,23 @@ | ||
| 3021 | 3013 | } |
| 3022 | 3014 | }, |
| 3023 | 3015 | "node_modules/axios": { |
| 3024 | 3016 | "version": "1.1315.52", |
| 3025 | 3017 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.1315.52.tgz", |
| 3026 | 3018 | "integrity": "sha512-cz4ur7Vb0xS4wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/KUN0tPWe44eqxrIu31mexEo0gHN+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8QD5A==", |
| 3027 | 3019 | "license": "MIT", |
| 3028 | 3020 | "dependencies": { |
| 3029 | 3021 | "follow-redirects": "^1.15.11", |
| 3030 | 3022 | "form-data": "^4.0.5", |
| 3031 | 3023 | "proxy-from-env": "^12.1.0" |
| 3024 | + } | |
| 3025 | + }, | |
| 3026 | + "node_modules/axios/node_modules/proxy-from-env": { | |
| 3027 | + "version": "2.1.0", | |
| 3028 | + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", | |
| 3029 | + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", | |
| 3030 | + "license": "MIT", | |
| 3031 | + "engines": { | |
| 3032 | + "node": ">=10" | |
| 3032 | 3033 | } |
| 3033 | 3034 | }, |
| 3034 | 3035 | "node_modules/b4a": { |
| @@ -3083,9 +3084,9 @@ | ||
| 3083 | 3084 | } |
| 3084 | 3085 | }, |
| 3085 | 3086 | "node_modules/basic-ftp": { |
| 3086 | 3087 | "version": "5.23.01", |
| 3087 | 3088 | "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.23.01.tgz", |
| 3088 | 3089 | "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXwGttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", |
| 3089 | 3090 | "license": "MIT", |
| 3090 | 3091 | "engines": { |
| 3091 | 3092 | "node": ">=10.0.0" |
| @@ -3190,9 +3191,9 @@ | ||
| 3190 | 3191 | "license": "MIT" |
| 3191 | 3192 | }, |
| 3192 | 3193 | "node_modules/brace-expansion": { |
| 3193 | 3194 | "version": "1.1.1214", |
| 3194 | 3195 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1214.tgz", |
| 3195 | 3196 | "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBPMWPGfDxnyzKU7rNOW9SP/W1ZEyZ1twGWom5c50vi3xrmrua/56TF4lPcqjnDHcg+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", |
| 3196 | 3197 | "dev": true, |
| 3197 | 3198 | "license": "MIT", |
| 3198 | 3199 | "dependencies": { |
| @@ -3232,7 +3233,6 @@ | ||
| 3232 | 3233 | } |
| 3233 | 3234 | ], |
| 3234 | 3235 | "license": "MIT", |
| 3235 | - "peer": true, | |
| 3236 | 3236 | "dependencies": { |
| 3237 | 3237 | "baseline-browser-mapping": "^2.9.0", |
| 3238 | 3238 | "caniuse-lite": "^1.0.30001759", |
| @@ -3503,16 +3503,16 @@ | ||
| 3503 | 3503 | } |
| 3504 | 3504 | }, |
| 3505 | 3505 | "node_modules/chevrotain": { |
| 3506 | 3506 | "version": "11.12.10", |
| 3507 | 3507 | "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.12.10.tgz", |
| 3508 | 3508 | "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1mHCHTxM51nCklUw9RzRVc0DLjAh/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQSAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==", |
| 3509 | 3509 | "license": "Apache-2.0", |
| 3510 | 3510 | "dependencies": { |
| 3511 | 3511 | "@chevrotain/cst-dts-gen": "11.12.10", |
| 3512 | 3512 | "@chevrotain/gast": "11.12.10", |
| 3513 | 3513 | "@chevrotain/regexp-to-ast": "11.12.10", |
| 3514 | 3514 | "@chevrotain/types": "11.12.10", |
| 3515 | 3515 | "@chevrotain/utils": "11.12.10", |
| 3516 | 3516 | "lodash-es": "4.17.23" |
| 3517 | 3517 | } |
| 3518 | 3518 | }, |
| @@ -4322,13 +4322,10 @@ | ||
| 4322 | 4322 | } |
| 4323 | 4323 | }, |
| 4324 | 4324 | "node_modules/dompurify": { |
| 4325 | 4325 | "version": "3.34.2", |
| 4326 | 4326 | "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.34.2.tgz", |
| 4327 | 4327 | "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSRlHeS9SA/UPU5fq3OXClEHLJNGvbxKpQIKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", |
| 4328 | 4328 | "license": "(MPL-2.0 OR Apache-2.0)", |
| 4329 | - "engines": { | |
| 4330 | - "node": ">=20" | |
| 4331 | - }, | |
| 4332 | 4329 | "optionalDependencies": { |
| 4333 | 4330 | "@types/trusted-types": "^2.0.7" |
| 4334 | 4331 | } |
| @@ -4567,7 +4564,6 @@ | ||
| 4567 | 4564 | "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", |
| 4568 | 4565 | "dev": true, |
| 4569 | 4566 | "license": "MIT", |
| 4570 | - "peer": true, | |
| 4571 | 4567 | "dependencies": { |
| 4572 | 4568 | "@eslint-community/eslint-utils": "^4.2.0", |
| 4573 | 4569 | "@eslint-community/regexpp": "^4.6.1", |
| @@ -5209,9 +5205,9 @@ | ||
| 5209 | 5205 | "license": "ISC" |
| 5210 | 5206 | }, |
| 5211 | 5207 | "node_modules/follow-redirects": { |
| 5212 | 5208 | "version": "1.1516.110", |
| 5213 | 5209 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.1516.110.tgz", |
| 5214 | 5210 | "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FOy5rN/0gfIES8CuOsadFT/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQJfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", |
| 5215 | 5211 | "funding": [ |
| 5216 | 5212 | { |
| 5217 | 5213 | "type": "individual", |
| @@ -6378,6 +6374,12 @@ | ||
| 6378 | 6374 | "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", |
| 6379 | 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 | 6383 | "node_modules/js-yaml": { |
| 6382 | 6384 | "version": "4.1.1", |
| 6383 | 6385 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", |
| @@ -6614,9 +6616,9 @@ | ||
| 6614 | 6616 | } |
| 6615 | 6617 | }, |
| 6616 | 6618 | "node_modules/lodash": { |
| 6617 | 6619 | "version": "4.1718.231", |
| 6618 | 6620 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.1718.231.tgz", |
| 6619 | 6621 | "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEcdMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/LCaj/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnGgfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+w0Q==", |
| 6620 | 6622 | "license": "MIT" |
| 6621 | 6623 | }, |
| 6622 | 6624 | "node_modules/lodash-es": { |
| @@ -7926,9 +7928,9 @@ | ||
| 7926 | 7928 | } |
| 7927 | 7929 | }, |
| 7928 | 7930 | "node_modules/readdir-glob/node_modules/brace-expansion": { |
| 7929 | 7931 | "version": "2.01.20", |
| 7930 | 7932 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.01.20.tgz", |
| 7931 | 7933 | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQTN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", |
| 7932 | 7934 | "license": "MIT", |
| 7933 | 7935 | "dependencies": { |
| 7934 | 7936 | "balanced-match": "^1.0.0" |
| @@ -8125,7 +8127,6 @@ | ||
| 8125 | 8127 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", |
| 8126 | 8128 | "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", |
| 8127 | 8129 | "license": "MIT", |
| 8128 | - "peer": true, | |
| 8129 | 8130 | "dependencies": { |
| 8130 | 8131 | "fast-deep-equal": "^3.1.3", |
| 8131 | 8132 | "fast-uri": "^3.0.1", |
| @@ -9143,7 +9144,6 @@ | ||
| 9143 | 9144 | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", |
| 9144 | 9145 | "dev": true, |
| 9145 | 9146 | "license": "Apache-2.0", |
| 9146 | - "peer": true, | |
| 9147 | 9147 | "bin": { |
| 9148 | 9148 | "tsc": "bin/tsc", |
| 9149 | 9149 | "tsserver": "bin/tsserver" |
| @@ -29,13 +29,14 @@ | ||
| 29 | 29 | "@mozilla/readability": "^0.6.0", |
| 30 | 30 | "@popperjs/core": "^2.11.8", |
| 31 | 31 | "@zeldafan0225/ai_horde": "^5.2.0", |
| 32 | + "agent-base": "^7.1.3", | |
| 32 | 33 | "archiver": "^7.0.1", |
| 33 | 34 | "bing-translate-api": "^4.1.0", |
| 34 | 35 | "body-parser": "^1.20.2", |
| 35 | 36 | "bowser": "^2.12.1", |
| 36 | 37 | "bytes": "^3.1.2", |
| 37 | 38 | "chalk": "^5.6.0", |
| 38 | 39 | "chevrotain": "^11.12.10", |
| 39 | 40 | "command-exists": "^1.2.9", |
| 40 | 41 | "compression": "^1.8.1", |
| 41 | 42 | "cookie-parser": "^1.4.6", |
| @@ -44,7 +45,7 @@ | ||
| 44 | 45 | "crc": "^4.3.2", |
| 45 | 46 | "csrf-sync": "^4.2.1", |
| 46 | 47 | "diff-match-patch": "^1.0.5", |
| 47 | 48 | "dompurify": "^3.24.62", |
| 48 | 49 | "droll": "^0.2.1", |
| 49 | 50 | "env-paths": "^3.0.0", |
| 50 | 51 | "express": "^4.21.0", |
| @@ -64,8 +65,9 @@ | ||
| 64 | 65 | "ipaddr.js": "^2.2.0", |
| 65 | 66 | "is-docker": "^3.0.0", |
| 66 | 67 | "isomorphic-git": "^1.36.3", |
| 68 | + "js-sha256": "^0.11.1", | |
| 67 | 69 | "localforage": "^1.10.0", |
| 68 | 70 | "lodash": "^4.1718.211", |
| 69 | 71 | "mime-types": "^3.0.2", |
| 70 | 72 | "moment": "^2.30.1", |
| 71 | 73 | "morphdom": "^2.7.7", |
| @@ -113,8 +115,9 @@ | ||
| 113 | 115 | "type": "git", |
| 114 | 116 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 115 | 117 | }, |
| 116 | 118 | "version": "1.1718.0", |
| 117 | 119 | "scripts": { |
| 120 | + "init": "node src/server-init.js", | |
| 118 | 121 | "start": "node server.js", |
| 119 | 122 | "debug": "node --inspect server.js", |
| 120 | 123 | "start:global": "node server.js --global", |
| @@ -122,7 +125,6 @@ | ||
| 122 | 125 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", |
| 123 | 126 | "start:bun": "bun server.js", |
| 124 | 127 | "start:no-csrf": "node server.js --disableCsrf", |
| 125 | - "postinstall": "node post-install.js", | |
| 126 | 128 | "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js", |
| 127 | 129 | "lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix", |
| 128 | 130 | "plugins:update": "node plugins update", |
| @@ -137,7 +139,7 @@ | ||
| 137 | 139 | }, |
| 138 | 140 | "main": "server.js", |
| 139 | 141 | "devDependencies": { |
| 140 | 142 | "@chevrotain/types": "^11.02.30", |
| 141 | 143 | "@types/archiver": "^6.0.3", |
| 142 | 144 | "@types/bytes": "^3.1.5", |
| 143 | 145 | "@types/command-exists": "^1.2.3", |
| @@ -151,7 +153,7 @@ | ||
| 151 | 153 | "@types/jquery-cropper": "^1.0.4", |
| 152 | 154 | "@types/jquery.transit": "^0.9.33", |
| 153 | 155 | "@types/jqueryui": "^1.12.24", |
| 154 | 156 | "@types/lodash": "^4.17.2024", |
| 155 | 157 | "@types/mime-types": "^3.0.1", |
| 156 | 158 | "@types/multer": "^2.1.0", |
| 157 | 159 | "@types/node": "^18.19.84", |
| @@ -167,6 +169,7 @@ | ||
| 167 | 169 | "eslint": "^8.57.1", |
| 168 | 170 | "eslint-plugin-jest": "^27.9.0", |
| 169 | 171 | "eslint-plugin-jsdoc": "^48.10.0", |
| 170 | 172 | "eslint-plugin-playwright": "^2.3.0", |
| 173 | + "typescript": "^5.9.3" | |
| 171 | 174 | } |
| 172 | 175 | } |
| @@ -1,114 +0,0 @@ | ||
| 1 | -/** | |
| 2 | - * Scripts to be done before starting the server for the first time. | |
| 3 | - */ | |
| 4 | -import fs from 'node:fs'; | |
| 5 | -import path from 'node:path'; | |
| 6 | -import process from 'node:process'; | |
| 7 | -import yaml from 'yaml'; | |
| 8 | -import chalk from 'chalk'; | |
| 9 | -import { createRequire } from 'node:module'; | |
| 10 | -import { addMissingConfigValues } from './src/config-init.js'; | |
| 11 | - | |
| 12 | -/** | |
| 13 | - * Colorizes console output. | |
| 14 | - */ | |
| 15 | -const color = chalk; | |
| 16 | - | |
| 17 | -/** | |
| 18 | - * Converts the old config.conf file to the new config.yaml format. | |
| 19 | - */ | |
| 20 | -function convertConfig() { | |
| 21 | - if (fs.existsSync('./config.conf')) { | |
| 22 | - if (fs.existsSync('./config.yaml')) { | |
| 23 | - console.log(color.yellow('Both config.conf and config.yaml exist. Please delete config.conf manually.')); | |
| 24 | - return; | |
| 25 | - } | |
| 26 | - | |
| 27 | - try { | |
| 28 | - console.log(color.blue('Converting config.conf to config.yaml. Your old config.conf will be renamed to config.conf.bak')); | |
| 29 | - fs.renameSync('./config.conf', './config.conf.cjs'); // Force loading as CommonJS | |
| 30 | - const require = createRequire(import.meta.url); | |
| 31 | - const config = require(path.join(process.cwd(), './config.conf.cjs')); | |
| 32 | - fs.copyFileSync('./config.conf.cjs', './config.conf.bak'); | |
| 33 | - fs.rmSync('./config.conf.cjs'); | |
| 34 | - fs.writeFileSync('./config.yaml', yaml.stringify(config)); | |
| 35 | - console.log(color.green('Conversion successful. Please check your config.yaml and fix it if necessary.')); | |
| 36 | - } catch (error) { | |
| 37 | - console.error(color.red('FATAL: Config conversion failed. Please check your config.conf file and try again.'), error); | |
| 38 | - return; | |
| 39 | - } | |
| 40 | - } | |
| 41 | -} | |
| 42 | - | |
| 43 | -/** | |
| 44 | - * Creates the default config files if they don't exist yet. | |
| 45 | - */ | |
| 46 | -function createDefaultFiles() { | |
| 47 | - /** | |
| 48 | - * @typedef DefaultItem | |
| 49 | - * @type {object} | |
| 50 | - * @property {'file' | 'directory'} type - Whether the item should be copied as a single file or merged into a directory structure. | |
| 51 | - * @property {string} defaultPath - The path to the default item (typically in `default/`). | |
| 52 | - * @property {string} productionPath - The path to the copied item for production use. | |
| 53 | - */ | |
| 54 | - | |
| 55 | - /** @type {DefaultItem[]} */ | |
| 56 | - const defaultItems = [ | |
| 57 | - { | |
| 58 | - type: 'file', | |
| 59 | - defaultPath: './default/config.yaml', | |
| 60 | - productionPath: './config.yaml', | |
| 61 | - }, | |
| 62 | - { | |
| 63 | - type: 'directory', | |
| 64 | - defaultPath: './default/public/', | |
| 65 | - productionPath: './public/', | |
| 66 | - }, | |
| 67 | - ]; | |
| 68 | - | |
| 69 | - for (const defaultItem of defaultItems) { | |
| 70 | - try { | |
| 71 | - if (defaultItem.type === 'file') { | |
| 72 | - if (!fs.existsSync(defaultItem.productionPath)) { | |
| 73 | - fs.copyFileSync( | |
| 74 | - defaultItem.defaultPath, | |
| 75 | - defaultItem.productionPath, | |
| 76 | - ); | |
| 77 | - console.log( | |
| 78 | - color.green(`Created default file: ${defaultItem.productionPath}`), | |
| 79 | - ); | |
| 80 | - } | |
| 81 | - } else if (defaultItem.type === 'directory') { | |
| 82 | - fs.cpSync(defaultItem.defaultPath, defaultItem.productionPath, { | |
| 83 | - force: false, // Don't overwrite existing files! | |
| 84 | - recursive: true, | |
| 85 | - }); | |
| 86 | - console.log( | |
| 87 | - color.green(`Synchronized missing files: ${defaultItem.productionPath}`), | |
| 88 | - ); | |
| 89 | - } else { | |
| 90 | - throw new Error( | |
| 91 | - 'FATAL: Unexpected default file format in `post-install.js#createDefaultFiles()`.', | |
| 92 | - ); | |
| 93 | - } | |
| 94 | - } catch (error) { | |
| 95 | - console.error( | |
| 96 | - color.red( | |
| 97 | - `FATAL: Could not write default ${defaultItem.type}: ${defaultItem.productionPath}`, | |
| 98 | - ), | |
| 99 | - error, | |
| 100 | - ); | |
| 101 | - } | |
| 102 | - } | |
| 103 | -} | |
| 104 | - | |
| 105 | -try { | |
| 106 | - // 0. Convert config.conf to config.yaml | |
| 107 | - convertConfig(); | |
| 108 | - // 1. Create default config files | |
| 109 | - createDefaultFiles(); | |
| 110 | - // 2. Add missing config values | |
| 111 | - addMissingConfigValues(path.join(process.cwd(), './config.yaml')); | |
| 112 | -} catch (error) { | |
| 113 | - console.error(error); | |
| 114 | -} | |
| @@ -0,0 +1,7 @@ | ||
| 1 | +# Looking for user.css? | |
| 2 | + | |
| 3 | +user.css is now located under your data root directory in the "_css" folder. | |
| 4 | + | |
| 5 | +Example for the default data root: | |
| 6 | + | |
| 7 | +/data/_css/user.css | |
| @@ -97,13 +97,23 @@ label[for="extensions_autoconnect"] { | ||
| 97 | 97 | font-size: 1.05em; |
| 98 | 98 | } |
| 99 | 99 | |
| 100 | 100 | .extensions_info :is(.extension_version, .extension_author) { |
| 101 | 101 | opacity: 0.8; |
| 102 | 102 | font-size: 0.8em; |
| 103 | 103 | font-weight: normal; |
| 104 | 104 | margin-left: 2px; |
| 105 | 105 | } |
| 106 | 106 | |
| 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 | 117 | .extensions_info .extension_block a { |
| 108 | 118 | color: var(--SmartThemeBodyColor); |
| 109 | 119 | } |
| @@ -500,6 +500,11 @@ | ||
| 500 | 500 | .horde_multiple_hint { |
| 501 | 501 | display: none; |
| 502 | 502 | } |
| 503 | + | |
| 504 | + select[multiple] { | |
| 505 | + max-height: 50px; | |
| 506 | + overflow-y: auto; | |
| 507 | + } | |
| 503 | 508 | } |
| 504 | 509 | |
| 505 | 510 | /*landscape mode phones and ipads*/ |
| @@ -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 | +} | |
| @@ -565,6 +565,10 @@ label[for="bind_preset_to_connection"]:has(input:checked) { | ||
| 565 | 565 | display: none; |
| 566 | 566 | } |
| 567 | 567 | |
| 568 | +#openai_settings:has(#openai_function_calling:not(:checked)) #tool_call_recurse_limit_block { | |
| 569 | + display: none; | |
| 570 | +} | |
| 571 | + | |
| 568 | 572 | #adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) { |
| 569 | 573 | display: none; |
| 570 | 574 | } |
| @@ -17,6 +17,7 @@ | ||
| 17 | 17 | .welcomePanel.recentHidden .welcomeRecent, |
| 18 | 18 | .welcomePanel.recentHidden .recentChatsTitle, |
| 19 | 19 | .welcomePanel.recentHidden .hideRecentChats, |
| 20 | +.welcomePanel.recentHidden .recentChatsSettings, | |
| 20 | 21 | .welcomePanel:not(.recentHidden) .showRecentChats { |
| 21 | 22 | display: none; |
| 22 | 23 | } |
| @@ -1,78 +1,6 @@ | ||
| 1 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
| 2 | -<!-- Created with Inkscape (http://www.inkscape.org/) --> | |
| 2 | +<svg id="svg2" width="660.14" height="621.01" version="1.1" viewBox="0 0 660.14 621.01" xmlns="http://www.w3.org/2000/svg"> | |
| 3 | - | |
| 3 | + <g id="g8" transform="translate(-12.7,-12.6)"> | |
| 4 | -<svg | |
| 4 | + <path id="path972" d="m171.3 47.3c5.5 33.5 12.9 56.6-16 78-15.6 13.6-27.3 32.5-43.1 45.3-12.7 6.5-49.7 1.9-63.5-0.6-15.9 28-27.4 57-36 88 13.7 10.8 35 39.1 53.3 38.7 0 0-3.8 77.9-8.1 90.5-10.8 15-30.6 29.8-45.2 41.5l25.3 69.3c7.3 3.1 54.7 9.3 54.7 9.3 42.8 3.6 33.4 58.2 89.3 46.7-0.6 3.6-2 9.1 1.2 11.9 4.9 4.2 11.7-3.7 17.4-1.8 14.2 4.8 19.7 29 37.4 16.5 3.7 14.9 13.7 53.6 34.5 53 20.5-0.5 4.5-62.6 42.8-63.7 20.7-0.6 53.1-5.3 69 10.9 11.9 12.1 12.1 52 28.2 50.8 7.5-0.6 21.5-24 22.8-55.1 21.4 13.6 26.8-8.6 45.3-15 5.7-2 13.9 4.9 18.7 1.1 3.4-2.8 0-8.5-1.3-11.4 42.4 4.4 54.3-1.8 78.7-45.3 21.7 0 43.4 2.3 65.2 2 25.1-43.5 31.5-72.2 30.9-76.7-2.7-18-34.1-27.2-42.5-42.8 1-27.9-1.7-56.9-1.7-85.3 0.5-18.7 33.7-29.7 42.8-44.6 4-6.5-1.4-16.9-3.5-23.4-6.5-19.7-14.1-42.9-26.5-59.7-9.7-13.1-44.3 5.1-63-2.8-19.3-20.2-39.2-39.3-60-58.5-15.9-14.8 2.9-45.4-3.6-63.8-3.8-10.8-75.4-40.2-86.8-35-16.2 9.4-22.6 33.9-39.6 41.8-28.2-5.2-60.9 7-88.8 0.6-14.8-10.2-30.4-30.8-41.5-45.1-31.1 6.9-58 21.6-86.7 34.7m400 218.7c-58.8-77.3-132.9-36.3-148 72-5.7-38.5-2.8-80.8-2.7-120 0-12.8 0.2-30.4 10.8-39.4 27.2-23 139.8-3.8 139.8 87.4m-301.3 53.3c-17.7-99.9-99.2-127.2-144-54.7-19.7-65 99.7-95.5 123.9-85 24 10.4 30.5 86.9 21.4 139.8m-46.6-96.1c5 13.9 18 23.4 25.5 36 7.4 12.5 9.7 32.1 22.5 40-3.9-24.8-18.5-72.1-48-76m200 77.3c10.9-7.5 13.7-23.5 19.9-34.7 7.7-13.8 22.1-25 26.8-40-27.9 4.5-44.2 50.2-46.7 74.7m-265.3-45.3c5.2-20.6 30.4-14.3 45-8.2m340.4 21.6c-18.3-11-31.3-18.6-53.3-18.7 19-16.3 44.5-1 53.3 18.7m-358.9-1.6c65.8-8.8 89.9 90 78.5 104.8-12.5 16.3-80.6 23.8-103.4 2.9-13.3-12.2-18.5-33.7-24.2-50.2l-4 1.3-5.3-17.3c24.3-2.8 31-32.7 54.4-40.6m333.6 2.3c32.6 19 36.5 42.5 55 39.9l-4.3 25-5.3-9.3c-8.7 20.9-12.7 50.6-37.3 58.5-40.9 13.1-102.3 2-98-29.1 13.4-39 40.6-87.6 86-86.8m-86.6 302.8c0 17.8-2.3 32.1-10.7 48-9.4-5.8-23.3-33.8-13.4-43.9 5.5-5.7 16.9-4.1 24.1-4.1m-145.3 4-8 46.7c-16.3-9.2-22.8-30.3-24-48l32 1.3"/> | |
| 5 | - version="1.1" | |
| 6 | - id="svg2" | |
| 7 | - width="682.66669" | |
| 8 | - height="682.66669" | |
| 9 | - viewBox="0 0 682.66669 682.66669" | |
| 10 | - sodipodi:docname="kobold.svg" | |
| 11 | - inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | |
| 12 | - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 13 | - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 14 | - xmlns="http://www.w3.org/2000/svg" | |
| 15 | - xmlns:svg="http://www.w3.org/2000/svg"> | |
| 16 | - <defs | |
| 17 | - id="defs6" /> | |
| 18 | - <sodipodi:namedview | |
| 19 | - id="namedview4" | |
| 20 | - pagecolor="#ffffff" | |
| 21 | - bordercolor="#000000" | |
| 22 | - borderopacity="0.25" | |
| 23 | - inkscape:showpageshadow="2" | |
| 24 | - inkscape:pageopacity="0.0" | |
| 25 | - inkscape:pagecheckerboard="0" | |
| 26 | - inkscape:deskcolor="#d1d1d1" | |
| 27 | - showgrid="false" | |
| 28 | - inkscape:zoom="1.5043945" | |
| 29 | - inkscape:cx="460.98281" | |
| 30 | - inkscape:cy="311.08862" | |
| 31 | - inkscape:window-width="2560" | |
| 32 | - inkscape:window-height="1377" | |
| 33 | - inkscape:window-x="2552" | |
| 34 | - inkscape:window-y="-8" | |
| 35 | - inkscape:window-maximized="1" | |
| 36 | - inkscape:current-layer="g8" /> | |
| 37 | - <g | |
| 38 | - inkscape:groupmode="layer" | |
| 39 | - inkscape:label="Image" | |
| 40 | - id="g8"> | |
| 41 | - <path | |
| 42 | - style="stroke:none;stroke-width:1.33333" | |
| 43 | - d="m 171.32749,47.331392 v 4 c 12.46,9.0412 4.032,24.8744 4.91067,37.33333 0.60933,8.61773 4.48133,15.097198 3.08933,23.999998 -9.768,-1.0508 -17.05467,6.59294 -24,12.62774 -15.61067,13.5656 -27.25574,32.5056 -43.14654,45.2576 -6.2576,5.02133 -14.829587,3.284 -22.186787,2.55733 -13.6492,-1.34666 -27.88494,-0.35466 -41.33334,-3.10933 -15.8956,27.97867 -27.41493,57.04 -36,88 13.74,10.83867 35.03,39.1 53.33334,38.66667 l -1.33334,6.66667 -5.33333,-2.66667 2.66667,14.66667 -4,-2.66667 -1.33334,6.66667 h 6.66667 l -1.33333,25.33333 h -1.33334 l -5.33333,-8 c 1.2756,13.55867 6.9132,37.864 2.5692,50.54 -2.02707,5.91467 -8.7172,10.132 -13.23613,14.05067 -10.5992,9.19067 -20.984,18.72 -31.99974,27.40933 l 25.33334,69.33334 h 1.33333 l 1.33333,-10.66667 17.33334,28 -2.66667,-5.33333 30.66667,4 1.33333,-6.66667 8,5.33334 -2.66667,-5.33334 c 33.997197,-5.85866 39.588927,35.29201 66.666667,47.46401 6.416,2.88533 16.03067,0.56933 22.66667,-0.79734 -0.58267,3.59734 -2.02267,9.11867 1.24133,11.92667 4.892,4.20667 11.732,-3.67467 17.35467,-1.764 14.21733,4.83467 19.67333,28.956 37.404,16.504 3.70133,14.85067 13.68667,53.584 34.496,53.04533 8.328,-0.216 10.988,-14.944 12.73867,-21.04533 5.44933,-18.99333 5.108,-41.92533 30.09866,-42.62667 20.65067,-0.58 53.08801,-5.31333 69.00801,10.92534 10.052,10.25333 8.66666,27.47066 15.73333,39.69333 2.42933,4.20133 6.73733,11.54 12.48667,11.09867 5.58133,-0.42934 9.27466,-8.18267 11.60533,-12.424 7.32533,-13.32667 10.54,-27.56534 11.16667,-42.66667 21.444,13.56267 26.752,-8.56 45.33333,-14.99333 5.74,-1.98667 13.94134,4.92 18.68,1.088 3.448,-2.788 0.0213,-8.492 -1.34666,-11.428 11.49466,1.196 36.74933,2.12 46.66667,-3.89067 13.856,-8.39867 22.12,-24.84267 33.33333,-36.10934 l -1.3333,-5.33333 41.3333,1.33333 c 1.3253,7.79467 6.2707,7.924 13.3333,8 v -6.66666 l 5.3334,5.33333 h 2.6666 c 10.9174,-18.93867 20.5987,-39.24933 27.5454,-60 2.1786,-6.504 6.8373,-15.692 5.808,-22.64934 -2.6627,-18.00933 -34.1347,-27.24133 -42.4814,-42.756 -4.5173,-8.39333 0.028,-24.07866 0.8147,-33.26133 1.5,-17.472 -3,-34.648 -2.5173,-51.99734 0.5226,-18.74133 33.68,-29.71733 42.796,-44.60933 3.9746,-6.49467 -1.3827,-16.932 -3.5227,-23.39334 -6.516,-19.676 -14.056,-42.93333 -26.5373,-59.73733 -4.7547,-6.40133 -10.9067,-4.772 -17.9054,-4.38667 -12.3666,0.68134 -33.6346,6.424 -45.12,1.564 -7.93997,-3.36133 -13.2693,-13.912 -18.93463,-20.06666 -12.87067,-13.98401 -27.26667,-25.57214 -41.10534,-38.47241 -15.90933,-14.829598 2.85333,-45.371998 -3.62133,-63.770668 -3.06134,-8.700397 -20.756,-14.264397 -28.552,-17.694131 -13.49867,-5.9384 -27.244,-11.378134 -41.33334,-15.735334 -5.22,-1.6144 -11.668,-4.60664 -16.88933,-1.585333 -16.16,9.350267 -22.56667,33.947998 -39.556,41.756398 -9.47467,4.3548 -27.90934,-1.2064 -38.22134,-1.7456 -15.03333,-0.786 -36.49067,6.88534 -50.54,2.33574 -5.92267,-1.9176 -10.304,-8.63254 -14.20267,-13.12894 -9.148,-10.549598 -18.88133,-20.827998 -27.25733,-31.999732 -31.076,6.852134 -58.048,21.650001 -86.66667,34.666672 m 297.33334,37.33333 v 1.33333 h -4 l 4,-1.33333 m 4,0 -1.33333,1.33333 1.33333,-1.33333 m -253.33334,1.33333 -1.33333,1.33334 1.33333,-1.33334 m 192.00001,44.000008 -1.33334,1.33333 1.33334,-1.33333 m 159.99997,136 h -1.3333 c -6.54533,-9.72 -13.10133,-19.16666 -22.67333,-26.17733 -6.84667,-5.01334 -14.52267,-8.54934 -22.66001,-10.896 -64.19333,-18.52 -94.28267,58.57733 -101.33333,109.07333 -4.65867,-12.744 -2.66667,-27.976 -2.66667,-41.33333 0,-26.22133 -0.096,-52.44534 0.003,-78.66667 0.048,-12.81733 0.236,-30.37867 10.848,-39.352 11.536,-9.75467 35,-9.336 49.14933,-7.74 50.02934,5.64267 90.66531,43.30267 90.66671,95.092 M 271.32782,319.3314 h -1.33333 c -8.73067,-49.15734 -41.592,-109.81467 -100,-92.31201 -8.24267,2.46934 -15.92267,6.46267 -22.66401,11.82 -8.93866,7.10267 -15.3944,16.20534 -21.336,25.82534 -14.76986,-48.75467 53.38134,-80.68134 90.66667,-85.05067 10.04267,-1.176 23.616,-4.14533 33.25467,0.0387 11.71067,5.08533 16.88267,22.98533 19.47067,34.34533 7.42133,32.56134 7.61067,72.44534 1.94133,105.33334 m -46.66667,-96.00001 c 4.99867,13.94667 17.988,23.35334 25.46001,36.00001 7.37733,12.484 9.68933,32.056 22.54,40 -3.912,-24.796 -18.472,-72.13734 -48.00001,-76.00001 m 200.00001,77.33334 c 10.88133,-7.50267 13.66933,-23.524 19.86667,-34.66667 7.652,-13.75733 22.06133,-25.04533 26.8,-40 -27.912,4.53067 -44.212,50.15067 -46.66667,74.66667 m -220.00034,-54.6667 v 1.33334 l -45.33333,8 c 5.248,-20.64801 30.736,-15.41734 45.33333,-9.33334 m 340.00002,22.66667 c -18.28667,-11.04667 -31.34001,-18.64267 -53.33334,-18.66667 19.048,-16.336 44.54933,-0.956 53.33334,18.66667 m -62.66667,-21.33333 -5.33334,8 5.33334,-8 m -330.66668,9.33333 v 9.33333 h -2.66667 l 2.66667,-9.33333 m 33.33333,10.66667 c -10.14666,28.372 -20.33733,64.79466 -3.732,93.332 23.412,40.23467 69.13467,19.88133 79.73201,-17.332 l -37.33334,12 c 7.55333,-29.95734 6.71333,-51.172 -5.33333,-80 l -4,1.33333 2.66666,-9.33333 c 25.18534,13.21066 33.43067,41.44266 45.02534,65.33333 5.99867,12.36134 12.33733,26.51467 2.584,39.25867 -4.64,6.06267 -15.18,9.508 -22.276,11.536 -21.07067,6.024 -63.98134,7.04267 -81.088,-8.61333 -13.28934,-12.164 -18.45467,-33.68534 -24.24534,-50.18134 l -4,1.33333 -5.33333,-17.33333 c 24.27867,-2.78133 33.91467,-33.52133 57.33333,-41.33333 m 328.00001,1.33333 c 13.32267,7.76533 22.768,16.98133 33.40401,27.99867 6.94666,7.19466 13.224,14.832 23.92933,13.33466 l 1.3333,1.33334 -5.3333,24 h -1.33333 l -2.66667,-9.33334 h -1.33333 c -8.71867,20.86401 -12.74534,50.58534 -37.33334,58.46534 -21.72,6.96133 -56.508,7.996 -77.33333,-1.67467 -10.91334,-5.068 -22.63334,-13.23333 -20.69734,-27.45733 0.92267,-6.78667 6.69067,-14.096 10.03067,-20 h 1.33333 c 0,8.11867 -0.77733,16.57467 3.116,24 22.06534,42.08667 74.49067,26.58933 82.21734,-16 l -33.33334,8 c 2.796,-20.556 10.612,-60.17867 -10.66666,-73.33334 l -2.66667,6.66667 h -1.33333 c 3.22666,-14.35067 15.708,-19.72133 27.70266,-8.79467 6.95467,6.336 9.74534,15.98934 12.044,24.79334 0.59067,2.264 2.49067,13.96 7.14667,10.308 8.94933,-7.02 -8.35467,-33.472 -8.22667,-42.30667 M 215.32749,311.3314 c -13.772,4.168 -8.14267,19.588 -8,30.66666 -7.48933,-6.38666 -6.66267,-16.27333 -6.66667,-25.33333 -18.06266,-3.052 -5.47733,-24.708 8.75467,-13.59333 2.832,2.212 4.33867,5.13333 5.912,8.26 m 261.33334,1.33333 v 2.66667 c -12.91733,5.19066 -8.896,15.32533 -6.66666,26.66666 -5.38134,-3.632 -15.92667,-20.34133 -17.05334,-26.78266 -1.472,-8.40667 8.44,-10.26267 14.36134,-7.90534 3.31866,1.32267 6.33733,3.48134 9.35866,5.35467 m -413.333337,2.66667 -1.33333,1.33333 1.33333,-1.33333 m -6.66667,13.33333 4,2.66667 v -1.33334 l -4,-1.33333 m 18.66667,180.00001 v 4 h -1.33333 l 1.33333,-4 m 349.333337,62.66667 c 0,17.77733 -2.26,32.136 -10.66667,48 -9.4,-5.81067 -23.30933,-33.76267 -13.44266,-43.90267 5.54,-5.69333 16.94133,-4.096 24.10933,-4.09733 m -145.33334,4 -8,46.66666 c -16.29333,-9.21866 -22.78666,-30.32 -24,-48 l 32,1.33334 m 124.00001,17.33333 v 10.66667 h 1.33333 z" | |
| 44 | - id="path972" /> | |
| 45 | - <path | |
| 46 | - style="fill:#ffffff" | |
| 47 | - id="path1031" | |
| 48 | - d="m 171.07018,314.24465 c 1.43533,2.55362 3.43647,4.86204 6.02366,6.31063 15.51628,8.6877 9.94595,7.11918 18.45758,9.09921 2.9676,0.68502 6.00726,0.33711 9.00575,0.4851 0.51777,0.0256 1.97943,0.5002 1.53847,0.22762 -12.3291,-7.62122 -8.46459,-9.40075 -13.47067,-6.74985 -0.81161,0.51959 -1.60672,1.06589 -2.43482,1.55876 -4.52165,2.69122 -9.22362,5.08101 -13.79908,7.67767 -5.00626,2.84116 -5.79872,3.37745 -10.6807,6.35696 -3.21994,2.00581 -5.34709,3.29487 -8.43672,5.34334 -0.93485,0.61982 -1.86152,1.25236 -2.77524,1.90291 -0.53177,0.3786 -1.12785,0.69454 -1.56007,1.18372 -0.23297,0.26368 -0.67179,0.8249 -0.36192,0.99157 4.80104,2.58231 9.91238,4.5407 14.86857,6.81105 12.64758,-5.66419 24.4458,-13.03247 36.15953,-20.38753 8.83007,-5.82628 17.49966,-11.90043 26.13449,-18.01368 0.12258,-0.10977 0.51254,-0.25112 0.36775,-0.32931 -4.67223,-2.52289 -9.13452,-5.66802 -14.23142,-7.15676 -1.20748,-0.35269 -2.10539,1.37742 -3.14865,2.08026 -4.89213,3.29582 -9.54755,6.82945 -14.1897,10.46637 -9.49744,7.36804 -17.9992,15.83825 -26.01509,24.76432 -6.30592,7.59525 -8.14218,8.23949 10.2995,13.47521 1.78331,0.50629 3.3167,-1.66017 4.9328,-2.56834 5.73793,-3.22441 9.53881,-5.73893 15.19815,-9.3143 13.24893,-8.54994 25.96694,-17.90399 37.94901,-28.15332 1.04824,-0.91395 2.1079,-1.81498 3.14472,-2.74187 0.79084,-0.70699 1.56569,-1.43176 2.33769,-2.15926 0.53118,-0.50056 1.12786,-0.94513 1.57156,-1.52464 0.198,-0.25861 0.59124,-0.77034 0.30618,-0.92788 -14.71815,-8.13451 -10.1663,-9.48815 -17.93001,-5.21701 -11.41265,6.6473 -22.20287,14.27124 -32.75212,22.19736 -10.66088,8.29117 -20.89797,17.10296 -30.98403,26.07851 -0.98028,0.86051 -2.03371,1.64422 -2.94083,2.58153 -5.39678,5.57637 8.13193,7.60867 14.30926,9.09646 2.00094,0.48192 3.79185,-1.6031 5.66556,-2.45469 5.07424,-2.30622 9.60823,-4.57573 14.62099,-7.03268 11.14435,-5.70338 22.44245,-11.13252 33.38489,-17.22113 5.46451,-0.58006 14.20537,-1.41277 -14.20952,-6.74516 -1.73723,-0.32601 -2.8735,2.0593 -4.29587,3.10863 -5.86769,4.32876 -11.61624,8.8152 -17.40775,13.24456 -0.88721,0.84917 -1.97162,1.53157 -2.66162,2.54751 -0.28753,0.42334 -0.56545,1.30065 -0.10868,1.53142 5.12688,2.59017 10.33852,5.33462 15.9671,6.48047 1.71551,0.34924 3.17757,-1.47245 4.73846,-2.26529 4.90381,-2.49085 6.99171,-3.77194 11.81268,-6.53639 1.94264,-1.1845 3.88822,-2.3642 5.82792,-3.55351 4.47851,-2.74595 8.99301,-5.47352 13.30943,-8.47306 0.65181,-0.45295 2.58101,-1.00805 1.90658,-1.4266 -4.79779,-2.9775 -10.29254,-4.64766 -15.43882,-6.97148 -11.72135,5.99277 -22.91179,12.94688 -34.23961,19.6353 -6.60264,3.95287 -13.30201,7.72362 -20.18434,11.16672 -9.33663,4.8581 -9.99423,3.7481 8.12157,11.42393 0.83219,0.3526 1.70531,-0.59973 2.5527,-0.91405 1.02537,-0.38033 2.04352,-0.77986 3.06527,-1.1698 6.88738,-3.16305 13.93717,-5.90731 21.04709,-8.52021 4.06204,-1.52457 8.09972,-3.2178 12.32549,-4.25606 1.68407,-0.41377 2.88582,-0.56931 4.57605,-0.83571 -1.4108,-2.79602 3.18988,0.90932 2.44908,0.52434 -4.45476,-2.31509 -8.49787,-5.5392 -13.24768,-7.16519 -0.90867,-0.31106 -1.4379,1.27434 -2.1802,1.8838 -2.94314,2.41647 -3.50484,2.75165 -6.63769,5.0276 -6.26617,4.40862 -12.714,8.5456 -19.11454,12.75349 -4.45769,4.93043 -2.50115,2.00475 11.93444,10.09798 0.63232,0.3545 1.41442,-0.31883 2.11844,-0.49163 2.98621,-0.73295 5.93525,-1.60178 8.89459,-2.4344 5.27098,-1.7298 10.76117,-2.53812 16.20623,-3.48887 4.60406,-0.82453 9.1585,-1.85176 13.62925,-3.22747 3.11796,-1.15071 6.24676,-2.27099 9.18927,-3.82602 1.66601,-0.96562 3.51589,-1.59353 5.16478,-2.58761 0.59316,-0.3576 1.14403,-0.78107 1.71659,-1.1708 1.74687,-1.35545 2.65746,-2.35985 3.62271,-4.37357 0.84126,-2.07386 1.06124,-4.30847 1.19133,-6.52137 0.0789,-1.7605 0.0646,-3.52332 0.0576,-5.28505 0.42359,-6.7192 3.03353,-1.77248 -13.2615,-9.57841 -0.44856,-0.21487 -0.88179,0.46044 -1.3207,0.69441 -0.54734,0.29178 -1.09201,0.58855 -1.63801,0.88282 -3.72432,2.34742 -7.18224,5.08426 -10.82303,7.54849 -3.13256,2.27465 -6.59511,3.9843 -10.07352,5.64923 -2.68913,1.31556 -5.50527,2.2938 -8.12552,3.76052 4.28068,2.88035 8.26686,6.25599 12.84202,8.64104 0.64153,0.33443 1.30296,-0.62985 1.9647,-0.92223 4.26541,-1.8846 1.57695,-0.53142 6.36978,-2.80944 1.13947,-0.54159 6.36523,-3.15514 7.52569,-3.73405 2.81628,-1.45312 5.68135,-2.8072 8.50641,-4.24271 0.33246,-0.16893 1.29943,-0.30491 0.99237,-0.51652 -12.71423,-8.76181 -8.74434,-10.10314 -15.04117,-6.92273 -6.79382,3.53204 -13.74745,6.74262 -20.52354,10.31127 -7.5434,3.95521 -15.43438,7.12357 -23.44161,9.9938 -4.93666,1.93963 -10.00951,3.44204 -15.13808,4.76283 -0.61961,0.37578 -4.11298,0.37957 -4.53103,0.75257 -0.23898,0.21323 -0.7467,0.65064 -0.48212,0.83114 4.26858,2.91201 8.88087,5.28514 13.32131,7.92771 3.06017,-0.26036 6.1004,-0.66469 9.16565,-0.88282 3.5349,-0.27157 7.08772,-0.15372 10.62077,-0.41487 2.34311,-0.20178 4.68416,-0.42837 7.03502,-0.51429 1.98063,0.0101 3.95881,-0.2504 5.90142,-0.62775 1.98537,-0.26974 3.8929,-0.84394 5.80038,-1.43236 1.70419,-0.58082 3.50678,-0.50524 5.26603,-0.74464 2.53961,-0.30944 5.00901,-1.04243 7.50092,-1.60707 1.79673,-0.39005 3.62652,-0.69197 5.40419,-1.16894 0.77169,-0.20705 0.73152,-0.48877 0.6584,0.0322 -15.02092,-10.25008 -8.42834,-9.62098 -17.23578,-6.65309 -1.91185,0.77525 -3.84198,1.48065 -5.76047,2.23372 -2.1782,0.94213 -4.52971,1.33577 -6.77528,2.06463 -1.92286,0.62993 -3.95096,0.6523 -5.85054,1.34594 -1.43592,0.6249 -2.95828,0.80537 -4.50244,0.90949 -1.69973,0.41292 -3.45038,0.54237 -5.19138,0.64517 -1.88828,0.13475 -3.82163,0.27962 -5.61684,0.9172 -0.0211,-0.0295 -0.0943,-0.10741 -0.0634,-0.0885 4.4075,2.68861 8.5688,5.82092 13.20029,8.10215 0.50696,0.2497 0.95543,-0.60426 1.44328,-0.88951 2.47696,-1.44829 4.88574,-2.95444 7.29325,-4.51881 4.43353,-2.70197 8.70382,-5.65959 12.75546,-8.90627 1.32906,-0.98398 2.49335,-2.14207 3.62575,-3.33846 0,0 -12.90598,-7.87335 -12.90598,-7.87335 v 0 c -1.10475,1.13152 -2.24531,2.21955 -3.51853,3.16535 -4.0487,3.17935 -8.28306,6.10197 -12.67783,8.78408 -1.64902,1.06545 -3.39232,2.21147 -5.07695,3.22299 -0.72249,0.43381 -1.46465,0.83405 -2.19248,1.25885 -0.47497,0.27721 -1.81706,0.47105 -1.41578,0.8471 10.42477,9.76936 7.5619,11.33669 12.0127,8.76713 1.78097,-0.40206 3.61469,-0.5307 5.43243,-0.65354 1.74871,-0.15261 3.47842,-0.41518 5.21287,-0.6753 1.60693,-0.20434 3.15791,-0.59502 4.6777,-1.16108 1.93936,-0.48218 3.95267,-0.655 5.84512,-1.338 2.30159,-0.64532 4.63,-1.21698 6.8368,-2.15547 1.91913,-0.72053 3.81715,-1.49554 5.74076,-2.20447 8.62643,-3.25519 5.68409,-1.80659 -7.41768,-10.83793 -0.57951,0.42283 -1.36317,0.47509 -2.05091,0.62728 -1.43747,0.31811 -2.87464,0.63797 -4.31681,0.93429 -2.44658,0.58032 -4.88995,1.23651 -7.40174,1.46555 -1.83742,0.16771 -3.67129,0.3449 -5.42332,0.97071 -1.86505,0.56113 -3.76192,0.9855 -5.68941,1.27195 -1.92476,0.2718 -3.85014,0.48183 -5.79717,0.48098 -2.35121,0.13789 -4.69492,0.37057 -7.04479,0.52789 -3.54525,0.15356 -7.09771,0.15487 -10.63737,0.43806 -3.05823,0.25769 -6.10045,0.66447 -9.16265,0.87861 4.10095,2.6678 8.35865,5.10888 12.30286,8.00339 0.22243,0.16323 -0.50266,0.42091 -0.45351,0.69239 0.0141,0.0781 4.37054,-0.75142 4.58956,-0.83694 5.16506,-1.33006 10.23089,-2.98806 15.21856,-4.88087 8.04727,-2.92727 15.97342,-6.16211 23.55564,-10.16528 6.79245,-3.54284 13.74759,-6.75919 20.5298,-10.32082 6.1694,-3.46088 3.5939,-1.2335 -10.25015,-9.64727 -0.31437,-0.19106 -0.65421,0.33667 -0.98284,0.50202 -3.77307,1.89844 -0.71516,0.34579 -5.12115,2.54028 -1.12963,0.56264 -2.25547,1.13287 -3.38321,1.69931 -3.64669,1.81381 -7.27715,3.65196 -11.01842,5.26635 -0.92249,0.39807 -1.84114,0.80968 -2.78853,1.14419 -0.5191,0.18329 -2.01711,0.0453 -1.60091,0.40566 3.71695,3.21784 7.99353,5.72576 11.99029,8.58864 2.63436,-1.37598 5.4429,-2.34498 8.08645,-3.70104 3.53151,-1.71393 7.01231,-3.53031 10.19911,-5.84269 3.562,-2.48006 7.00535,-5.14327 10.72295,-7.3938 0.5256,-0.26341 1.03573,-0.56029 1.57681,-0.79022 0.34553,-0.14683 1.37754,-0.11453 1.07482,-0.33657 -13.82664,-10.14164 -11.55582,-12.30727 -12.54762,-6.01864 0.009,1.73726 0.0275,3.47508 -0.0202,5.21196 -0.0821,2.02459 -0.20817,4.07984 -0.90984,6.00091 -0.81376,1.75352 -1.64737,2.5454 -3.20333,3.67747 -2.14295,1.42279 -4.50639,2.42113 -6.78341,3.60728 -2.91093,1.43792 -5.96998,2.53986 -9.03631,3.59764 -4.44558,1.29448 -8.96449,2.28607 -13.52651,3.07895 -5.48707,0.94315 -10.98138,1.90216 -16.29401,3.61552 -1.12137,0.3136 -4.98063,1.40492 -6.11919,1.67531 -0.86475,0.20537 -1.72806,0.45221 -2.61364,0.5278 -0.54137,0.0462 -2.04418,-0.50768 -1.62151,-0.16625 11.42704,9.23077 8.40436,11.68934 12.60187,7.50083 6.42684,-4.20992 12.88092,-8.38087 19.1554,-12.81757 2.49735,-1.83513 4.3837,-3.14306 6.72175,-5.16166 0.83724,-0.72285 3.09224,-1.40127 2.41497,-2.27578 -6.33848,-8.18436 -7.96984,-10.4425 -14.66541,-9.60946 -5.89378,1.07124 -11.51294,3.08172 -17.0598,5.32199 -7.10879,2.62566 -14.12733,5.44994 -21.05306,8.52907 -1.01221,0.37581 -2.01669,0.77314 -3.03664,1.12742 -0.81369,0.28264 -3.03652,0.14035 -2.46272,0.78279 11.80339,13.21539 6.99615,10.9296 11.92284,8.12062 1.31947,-0.7523 3.74865,-1.97655 5.01135,-2.62453 6.89121,-3.49042 13.61336,-7.28283 20.24024,-11.25236 11.27734,-6.67828 22.43811,-13.59569 34.1506,-19.49953 -3.31035,-3.15832 -6.16118,-6.88237 -9.93104,-9.47496 -0.63445,-0.43632 -1.23051,0.92684 -1.86559,1.36225 -1.15761,0.79367 -2.33243,1.56218 -3.51117,2.32412 -5.12389,3.31211 -10.35273,6.45766 -15.57115,9.61718 -4.38295,2.49441 -7.24864,4.21481 -11.74375,6.43131 -1.51803,0.74853 -5.44101,0.61334 -4.62479,2.09606 2.12153,3.85393 7.02265,5.3155 10.31252,8.23619 0.20792,0.18458 -0.98326,0.41742 -0.80971,0.20021 0.73463,-0.91941 1.71836,-1.60849 2.57754,-2.41273 5.78153,-4.42323 11.52997,-8.88866 17.38712,-13.21198 1.42972,-1.05531 5.0733,-1.52866 4.31223,-3.13445 -5.81826,-12.27593 -6.3744,-11.75565 -11.23511,-9.58703 -10.94552,6.03635 -22.20646,11.4862 -33.34269,17.15676 -4.22937,2.06502 -10.22531,5.0476 -14.57292,6.96022 -1.81778,0.79967 -6.27975,0.40599 -5.52019,2.24089 1.70084,4.10876 6.3992,6.24034 10.07003,8.75027 1.49441,1.02179 0.88047,-9.44405 3.83393,-3.84665 10.06538,-8.93869 20.2702,-17.72751 30.91727,-25.97198 10.50559,-7.86626 21.24325,-15.45041 32.62871,-22.00546 7.36761,-3.8532 3.96829,-0.86418 -8.13522,-10.03306 -0.061,-0.0462 0.28008,-0.0735 0.22901,-0.0165 -0.47916,0.5348 -1.02792,1.00305 -1.55236,1.49352 -0.76503,0.71545 -1.53338,1.4276 -2.31581,2.12398 -1.03519,0.92135 -2.09105,1.81921 -3.13658,2.72881 -11.95628,10.17483 -24.62464,19.47989 -37.83252,27.9695 -4.36546,2.74513 -10.44576,6.69692 -15.10959,9.17839 -1.49209,0.79389 -5.49167,0.65909 -4.61365,2.10329 2.38531,3.9234 6.89231,6.21587 10.96295,8.34018 1.93356,1.00905 0.87576,-8.92908 3.97258,-5.19905 7.97197,-8.85114 16.43325,-17.24046 25.87613,-24.53933 4.62049,-3.60275 9.25775,-7.09819 14.12437,-10.36353 1.0398,-0.69767 3.88038,-1.05567 3.13843,-2.06435 -8.3794,-11.39171 -7.52997,-11.87727 -11.60778,-8.80552 -8.62141,6.08752 -17.25943,12.16238 -26.08925,17.94303 -11.65263,7.29252 -23.38273,14.63386 -36.00522,20.15284 3.76123,2.73954 7.65708,5.30322 11.28369,8.21863 0.17317,0.13921 -0.64943,0.598 -0.45933,0.48299 0.54064,-0.3271 1.0066,-0.76466 1.52415,-1.12721 3.62633,-2.54031 7.40811,-4.8487 11.16951,-7.18018 4.99607,-3.03314 5.5246,-3.4057 10.64708,-6.30516 4.6037,-2.6058 9.38129,-4.97264 13.86718,-7.78308 0.88771,-0.55616 1.72092,-1.19491 2.58138,-1.79236 0.50182,-0.59773 2.09084,-1.27701 1.50547,-1.79319 -3.87871,-3.42027 -8.57482,-5.79813 -13.05103,-8.38775 -0.47089,-0.27242 -1.08498,-0.086 -1.62866,-0.10489 -2.94034,-0.10204 -5.89671,-0.0548 -8.77687,-0.79164 -6.84974,-2.01396 -3.16404,-1.19536 7.6358,5.56497 0.6805,0.42597 -1.44013,-0.72013 -2.10585,-1.16884 -1.12844,-0.76059 -2.34725,-1.94327 -3.22517,-2.98418 -0.37019,-0.43891 -0.67583,-0.92844 -1.01374,-1.39266 0,0 -13.77387,-6.23173 -13.77387,-6.23173 z" /> | |
| 49 | - <path | |
| 50 | - style="fill:#ffffff" | |
| 51 | - id="path1033" | |
| 52 | - d="m 232.73967,343.98425 c -0.16239,-4.21792 -0.38636,-8.43294 -0.53272,-12.65164 -0.17653,-6.95973 -1.85631,-4.87751 -13.29234,-10.63359 -0.04,-0.0201 -3.53227,3.33793 -3.78645,3.58181 -1.99028,1.91133 -4.00381,3.86285 -6.2969,5.4174 -1.06135,0.71952 -1.93388,1.15314 -3.0558,1.75665 -1.95113,0.78708 -4.0813,1.77702 -6.2486,1.72206 -0.41992,-0.0106 -0.83194,-0.11686 -1.24792,-0.1753 -0.48134,-0.10515 -0.97296,-0.17113 -1.44404,-0.31546 -1.88325,-0.57698 -3.69696,-1.49191 -5.50071,-2.26758 -2.1798,-0.9374 -2.2769,-0.95062 -4.52683,-1.82148 -4.44489,-1.59935 -9.05198,-2.51641 -13.72931,-3.06968 -0.51119,-0.087 -1.04311,-0.0927 -1.53355,-0.26109 -0.28832,-0.099 -1.01299,-0.67747 -0.75025,-0.5229 4.24182,2.49551 8.37529,5.17918 12.68969,7.54702 0.17035,0.0935 0.32998,-0.20543 0.49213,-0.31253 0.47218,-0.31192 0.93292,-0.64129 1.41076,-0.94447 0.64704,-0.41054 1.30876,-0.79748 1.96314,-1.19621 5.32786,-2.88263 10.80956,-5.47437 16.22858,-8.18209 4.78718,-1.95989 9.10461,-4.78507 13.46258,-7.52548 2.62671,-1.56346 5.19283,-3.22119 7.80177,-4.81406 1.1227,-0.93327 3.56593,-1.49092 4.53824,-2.47123 0.16716,-0.16853 0.24952,-0.40389 0.37428,-0.60583 -4.23667,-2.89754 -8.32988,-6.01688 -12.71002,-8.69261 -0.29187,-0.1783 -0.64392,0.2312 -0.96946,0.33612 -2.15826,0.69562 -4.30644,1.33446 -6.42473,2.15203 -4.29152,1.63988 -8.41307,3.66717 -12.6077,5.53018 -4.82936,2.0624 -9.56932,4.30241 -14.24575,6.68873 -3.90778,2.11644 -7.73683,4.3642 -11.5033,6.72178 -1.61315,0.98308 -3.112,2.13675 -4.70891,3.14424 -0.37871,0.23893 -0.78785,0.42969 -1.15472,0.68643 -0.77674,0.54356 -1.56314,1.30623 -2.2582,1.94248 -1.3674,1.53824 -3.49987,3.52541 -3.14491,5.83895 0.0542,0.35357 0.23681,0.67509 0.35521,1.01264 4.611,2.65737 9.10124,5.53626 13.833,7.9721 0.39531,0.2035 0.88276,-0.1075 1.32296,-0.17003 2.35331,-0.33431 4.41661,-1.16882 6.57287,-2.14245 4.66944,-2.08131 9.10602,-4.67138 13.44997,-7.35827 4.57957,-2.99451 9.14835,-6.0171 13.558,-9.25944 2.82993,-2.279 5.77344,-4.40642 8.73187,-6.51307 1.93406,-1.45357 3.91458,-2.83753 5.84743,-4.29188 1.50721,-1.13725 2.80699,-2.51181 4.20715,-3.77294 0.53578,-0.55497 1.54467,-0.85008 1.93694,-1.55022 2.9445,-5.25545 -8.98814,-6.22301 -12.95355,-8.48667 -2.46378,1.36153 -4.50197,3.20247 -6.39761,5.26251 -0.39857,0.48953 -5.58042,6.12839 -4.84005,6.67322 4.2012,3.09163 8.7864,5.62424 13.1796,8.43636 3.68207,-1.12239 6.2577,-3.8927 8.89656,-6.52432 0.0606,-0.0662 1.62195,-1.787 1.8513,-1.9885 0.12384,-0.10881 0.28737,-0.16161 0.43105,-0.24242 -4.39526,-2.2892 -8.63135,-4.91417 -13.18578,-6.86759 -0.34145,-0.14645 -0.2515,0.70012 -0.40601,1.03799 -0.22397,0.48975 -0.48546,0.96145 -0.72819,1.44218 -0.86092,1.6686 -2.0156,3.14343 -3.23225,4.56183 -0.24595,0.28673 -0.47983,0.58649 -0.7558,0.84446 -0.0265,0.0247 -0.071,-0.11872 -0.0392,-0.10134 4.40101,2.40648 8.77951,4.85392 13.16926,7.28089 1.46759,-2.4309 3.48191,-4.44214 4.9583,-6.86589 1.06137,-2.32718 0.93692,-4.83266 0.0823,-7.19189 -2.80578,-4.83762 -12.6773,-7.80282 -14.85026,-8.84928 -0.31655,-0.15245 -3.53109,1.16694 -3.87595,1.30947 -4.47781,1.99153 -8.79044,4.33462 -13.22011,6.43083 -4.21998,2.0963 -8.51473,4.03792 -12.87256,5.82964 -2.77042,1.16583 -5.6843,1.88722 -8.58772,2.62102 -2.80453,0.92069 -5.70723,1.44571 -8.56832,2.15075 -2.29694,0.62103 -4.58347,1.319 -6.66594,2.49128 -0.12684,0.0518 -0.49398,0.0785 -0.38052,0.15532 4.23064,2.86275 8.3752,5.89525 12.88037,8.30297 0.43136,0.23053 0.84219,-0.49762 1.26527,-0.74304 2.97907,-1.72813 5.83126,-3.60518 8.70066,-5.51101 5.75823,-3.98884 11.70446,-7.70199 17.74697,-11.24239 2.80788,-1.4419 5.6684,-2.82242 8.61115,-3.96542 2.70305,-1.02336 5.36367,-2.15739 8.10939,-3.06346 1.86988,-0.63702 3.74282,-1.3128 5.66466,-1.77909 1.44074,-0.18382 2.82028,-0.62291 4.25689,-0.83578 0.96481,-0.11847 1.939,-0.1301 2.9096,-0.15324 0.20012,-0.005 0.40765,-0.0685 0.60035,-0.0143 0.0912,0.0256 0.11119,0.15329 0.16679,0.22994 -12.44464,-7.68644 -11.33159,-11.89752 -15.32577,-3.63822 -0.92044,2.08126 -1.91653,4.06144 -3.30388,5.86997 -1.67673,1.99068 -4.14201,2.87169 -6.44358,3.91089 -5.85047,2.07862 -6.41927,-0.30481 11.67311,7.81678 0.39119,0.1756 0.56205,-0.64848 0.86246,-0.95446 0.8056,-0.82054 1.65794,-1.59387 2.48089,-2.39702 2.13255,-1.90308 3.73829,-3.81663 4.58765,-6.56018 0.51997,-2.17666 0.50849,-4.42627 0.51403,-6.65016 -0.0365,-1.98098 0.0282,-3.96596 -0.0874,-5.94446 -0.0469,-1.31854 -0.54398,-2.4502 -1.18941,-3.56455 -11.86794,-7.43097 -10.44561,-9.61528 -19.54019,-6.64195 -2.90504,1.06197 -5.64842,2.5092 -8.47323,3.76296 -2.82972,1.2514 -5.55757,2.71314 -8.35105,4.03951 -2.73802,1.46585 -5.62337,2.6416 -8.46532,3.89092 -2.93432,1.44932 -5.95304,2.71572 -8.93136,4.07021 -2.61697,1.18126 -5.17726,2.46451 -7.68626,3.85548 -0.43106,0.29155 -1.18643,0.74935 -1.55054,1.17364 -3.52989,4.11327 7.88695,6.33505 13.16143,8.67144 0.33506,0.14842 0.66895,-0.29947 1.00343,-0.44921 1.5692,-0.85195 3.27529,-1.39841 4.82461,-2.29156 0.80789,-0.46573 1.57825,-0.99384 2.37385,-1.48028 2.34583,-1.58221 4.30424,-3.63646 6.41685,-5.49806 1.46344,-1.30423 2.65707,-2.81893 3.34364,-4.6575 0.25683,-1.25844 0.81728,-2.81572 0.29801,-4.09133 -0.10581,-0.25993 -0.31332,-0.46569 -0.46998,-0.69854 -4.55348,-2.53758 -9.0634,-5.15497 -13.66045,-7.61274 -0.17399,-0.093 -0.39472,-0.015 -0.59186,-0.007 -0.12411,0.005 -0.47422,-0.0219 -0.36984,0.0455 4.24259,2.73583 8.50906,5.4383 12.85552,8.0059 0.073,0.0431 0.4994,-0.37586 0.75985,-0.61595 1.05025,-1.01942 1.86431,-2.28026 2.42797,-3.62806 0.31654,-0.52272 0.40322,-1.30266 0.96533,-1.64806 0.0979,-0.0602 0.21352,-0.0853 0.32028,-0.12794 0.13562,-0.0966 0.27123,-0.19324 0.40684,-0.28986 5.19693,-2.57523 10.31369,3.11931 -12.05308,-8.24825 -0.31398,-0.15958 -0.57926,0.4008 -0.86889,0.60121 -2.16543,1.33756 -4.16339,2.92389 -6.13481,4.52914 -1.66623,1.5478 -3.16695,3.26179 -4.80545,4.83609 -4.54417,4.78544 2.02375,3.79484 12.61783,9.20028 0.0449,0.0229 3.73072,-1.71529 3.99793,-1.84236 2.68136,-1.48749 5.35558,-2.96967 8.08898,-4.35972 1.77149,-0.97442 3.61795,-1.83865 5.50693,-2.55921 1.83273,-0.61603 3.6719,-1.20408 5.51169,-1.79859 1.74554,-0.54089 3.55893,-0.73243 5.34083,-1.11531 1.46277,-0.3253 2.95799,-0.3803 4.44952,-0.41387 1.62889,-0.0231 3.25805,-0.0161 4.88705,-0.0128 0.7882,0.19692 0.44532,0.0265 1.04462,0.47783 0,0 -11.91856,-9.30063 -11.91856,-9.30063 v 0 c -0.87451,-0.0778 -0.42265,-0.0541 -1.35592,-0.0645 -1.64103,8e-5 -3.28289,-0.005 -4.92286,0.0623 -1.54681,0.0866 -3.08424,0.26499 -4.59518,0.61963 -1.81956,0.37014 -3.67292,0.59739 -5.42732,1.241 -1.87635,0.58373 -3.73576,1.22084 -5.57977,1.8995 -1.8899,0.82 -3.77812,1.64933 -5.57518,2.66212 -2.72347,1.37718 -5.34241,2.93712 -8.06232,4.31931 -0.6843,0.3143 -2.00098,0.94025 -2.73657,1.19988 -0.32907,0.11614 -1.28367,0.058 -1.01018,0.27479 13.7453,10.89314 8.53254,11.61904 12.61923,7.42493 1.58097,-1.56706 3.0709,-3.22812 4.73039,-4.71521 1.96252,-1.57136 3.95718,-3.10421 6.08414,-4.44895 0.30082,-0.21807 1.18637,-0.41454 0.90245,-0.65421 -7.7916,-6.57739 -8.31092,-11.32876 -13.4836,-7.94085 -1.05349,0.71525 -1.69754,1.6303 -2.14331,2.82068 -0.43797,1.15738 -1.16584,2.19887 -2.07857,3.03463 -0.0545,0.0481 -0.92528,0.80376 -0.89054,0.83075 3.96643,3.08211 8.18217,5.82945 12.29744,8.70984 0.21262,0.14882 0.55613,0.0335 0.73193,0.26553 -4.11791,-2.3947 -8.23583,-4.78939 -12.35375,-7.18409 0.11825,0.0189 1.04835,0.11235 1.20019,0.31114 0.361,0.47262 -0.13437,2.13833 -0.19015,2.6962 -0.51158,1.63225 -1.70596,2.86555 -2.95481,3.99408 -2.02917,1.82599 -3.95843,3.79249 -6.27205,5.26938 -2.2578,1.37928 -4.6055,2.54372 -7.05381,3.5521 -5.58612,1.85709 -0.0223,-0.24329 11.68112,7.64689 0.40851,0.27541 -1.63103,1.30748 0.076,0.15975 0.2753,-0.18511 0.5646,-0.3485 0.84691,-0.52274 2.4821,-1.36587 5.04373,-2.57855 7.62031,-3.75523 2.97736,-1.34282 5.97456,-2.64164 8.92032,-4.05358 2.86232,-1.24381 5.72956,-2.48833 8.49579,-3.93692 2.76914,-1.35042 5.50943,-2.76014 8.32879,-4.00587 2.76516,-1.25474 5.47188,-2.67247 8.36843,-3.60722 1.26633,-0.36215 2.56448,-0.78342 3.89756,-0.80012 0.85192,-0.0107 0.82765,0.0752 1.53593,0.42768 -4.10255,-2.46371 -8.2051,-4.92742 -12.30765,-7.39113 0.87022,0.63506 1.41465,1.66666 1.57425,2.72657 0.29538,1.91362 0.0477,3.87804 0.15627,5.8075 0.009,2.09402 0.0596,4.20901 -0.31768,6.27765 -0.62923,2.46598 -2.34965,4.1958 -4.19368,5.88001 -0.32766,0.3196 -3.65762,3.49777 -3.50955,3.62853 10.25275,9.05415 7.97742,10.62804 13.96992,8.18094 2.5228,-1.13814 5.07757,-2.32734 6.8435,-4.54726 1.43052,-1.90044 2.50357,-3.95606 3.45643,-6.13263 3.80252,-7.93496 6.00519,-2.82532 -9.84222,-12.99277 -1.31575,0.30131 -2.7269,0.0687 -4.0426,0.43813 -1.44785,0.29793 -2.89095,0.62174 -4.3327,0.94508 -1.92223,0.5696 -3.83482,1.18028 -5.7206,1.8611 -2.74705,0.97129 -5.44361,2.07764 -8.158,3.13566 -2.96416,1.26301 -5.91996,2.56526 -8.73087,4.14719 -6.05698,3.565 -12.00387,7.32161 -17.79115,11.31094 -2.0647,1.36328 -4.22082,2.82155 -6.34676,4.10232 -0.74734,0.45023 -1.50867,0.8775 -2.2773,1.29035 -0.36773,0.19752 -1.45346,0.27595 -1.12997,0.53975 11.25958,9.18223 7.85353,10.77408 12.66198,8.30613 2.05871,-0.98645 4.25118,-1.63592 6.4617,-2.18772 2.86526,-0.69453 5.75463,-1.28469 8.57389,-2.15883 2.96319,-0.74043 5.89479,-1.61773 8.71335,-2.8061 4.36341,-1.83564 8.67479,-3.78933 12.91544,-5.89422 4.38121,-2.11024 8.66513,-4.43844 13.14946,-6.32484 0.79723,-0.27088 1.6527,-0.63379 2.51458,-0.67988 0.2792,-0.0149 1.07008,0.25495 0.83172,0.10879 -18.76731,-11.50759 -15.88476,-8.71313 -10.93879,-6.55684 1.1779,1.68282 1.33452,3.92655 0.61867,5.83 -1.51283,2.47276 -3.62336,4.50648 -5.0562,7.03708 4.23053,2.78584 8.38879,5.68466 12.6916,8.35752 0.11663,0.0725 0.19532,-0.19325 0.28736,-0.29514 0.26485,-0.29315 0.5285,-0.58756 0.78305,-0.88969 1.27543,-1.51383 2.4844,-3.08757 3.40139,-4.851 0.26271,-0.51611 0.54183,-1.02418 0.78813,-1.54831 0.2028,-0.43159 0.93409,-1.0235 0.55835,-1.31712 -12.01227,-9.38683 -10.01885,-11.64406 -15.33077,-5.90598 -1.79042,1.77947 -4.35818,4.82031 -7.00038,5.20786 -0.29162,0.0428 -0.58062,-0.10179 -0.87093,-0.15268 4.02282,2.65442 7.9953,5.38677 12.06847,7.96326 0.11339,0.0717 -0.15575,-0.27006 -0.0969,-0.39064 0.21338,-0.43739 0.56456,-0.79318 0.86224,-1.17819 1.14105,-1.47578 2.35791,-2.89629 3.60821,-4.2798 1.58596,-1.68233 3.44224,-3.73251 5.76525,-4.30599 -4.2131,-2.54932 -8.56527,-4.88182 -12.6393,-7.64798 -0.21311,-0.1447 0.33388,-0.3983 0.45002,-0.62823 0.0471,-0.0933 -0.15595,0.13926 -0.23661,0.20573 -0.55561,0.45787 -1.15351,0.8601 -1.69495,1.33579 -1.35883,1.24037 -2.64174,2.57235 -4.13464,3.65708 -1.919,1.45056 -3.90613,2.80573 -5.82514,4.25672 -2.96436,2.10935 -5.88464,4.27737 -8.74246,6.52975 -4.39397,3.19928 -8.92679,6.20958 -13.49223,9.15664 -4.30697,2.6285 -8.69454,5.17349 -13.33203,7.17839 -1.61461,0.66836 -3.19119,1.33514 -4.94353,1.54626 -5.13769,0.61898 -13.74732,-7.46153 9.72713,7.05026 -0.23995,-0.0713 -0.52011,-0.0631 -0.71985,-0.21402 -1.18523,-0.89541 1.85209,-3.97341 2.42794,-4.54589 2.44199,-2.14394 5.31075,-3.71203 7.98142,-5.55112 3.74114,-2.32298 7.54254,-4.54338 11.43148,-6.61142 4.66388,-2.36268 9.40496,-4.55199 14.20446,-6.62622 4.15951,-1.87582 8.27828,-3.84601 12.55452,-5.45065 1.64953,-0.59544 3.32117,-1.23265 5.04263,-1.59795 0.36175,-0.0768 0.73109,-0.18172 1.09941,-0.14867 0.18958,0.017 0.32738,0.19427 0.49107,0.29141 -4.04564,-2.7953 -8.09128,-5.59059 -12.13692,-8.38589 -1.49052,0.86633 -3.08899,1.54686 -4.54602,2.47941 -2.60577,1.60644 -5.19029,3.24617 -7.81092,4.82826 -4.32192,2.71934 -8.65468,5.41931 -13.38153,7.40097 -5.45576,2.72376 -10.99132,5.30639 -16.30537,8.29999 -0.95554,0.60573 -2.68974,1.62849 -3.56714,2.44843 -0.384,0.35884 -1.40022,0.87251 -1.00295,1.21659 7.44833,6.45106 8.6187,8.38054 15.11935,9.26037 4.61007,0.64895 9.14931,1.68107 13.50224,3.3662 3.83153,1.54046 7.61457,3.58519 11.77229,4.0085 2.81893,-0.10125 5.47716,-0.90361 7.97168,-2.23334 3.55204,-2.01968 6.71321,-4.56746 9.56112,-7.50291 0.0581,-0.0554 2.15763,-2.09521 2.47518,-2.27343 0.0932,-0.0523 0.3483,0.24732 0.25797,0.19025 -4.10953,-2.59651 -8.21085,-5.206 -12.31627,-7.80901 0.0837,0.25546 0.21511,0.49997 0.251,0.76638 0.0637,0.47319 0.0301,0.95446 0.0451,1.4317 0.13767,4.2041 0.32794,8.40601 0.55578,12.6062 0,0 13.47024,6.86343 13.47024,6.86343 z" /> | |
| 53 | - <path | |
| 54 | - style="fill:#ffffff" | |
| 55 | - id="path1035" | |
| 56 | - d="m 185.8578,280.25304 c 2.1647,-1.03493 4.30367,-2.1219 6.44766,-3.19929 1.27197,-0.62913 2.53847,-1.12777 3.94755,-1.32128 1.43773,-0.12953 2.88474,-0.10256 4.32689,-0.10046 1.74211,0.005 3.48422,0.0109 5.22634,0.0137 1.74919,0.003 3.49839,0.003 5.24759,0.003 1.44315,9e-5 2.8863,0 4.32945,-3e-5 1.33089,-4e-5 2.66179,-5e-5 3.99269,-5e-5 1.32776,0 2.65551,0 3.98326,0 1.32931,0 2.65862,0 3.98793,0 0.17489,0.0539 0.34978,0.10779 0.52467,0.16169 0,0 -12.10961,-9.05049 -12.10961,-9.05049 v 0 c -0.21526,8.9e-4 -0.43052,0.002 -0.64578,0.003 -1.32931,0 -2.65861,0 -3.98792,0 -1.32776,0 -2.65552,0 -3.98327,10e-6 -1.33091,1e-5 -2.66181,3e-5 -3.99271,7e-5 -1.44308,3e-5 -2.88616,9e-5 -4.32924,-2.5e-4 -1.74869,-5.5e-4 -3.49737,-10e-4 -5.24605,-0.005 -1.74413,-0.004 -3.48827,-0.0127 -5.23239,-0.005 -1.50245,0.0167 -3.01946,0.0159 -4.49505,0.33689 -1.46312,0.37423 -2.82923,0.98965 -4.17862,1.66268 -2.15021,1.10101 -4.33465,2.13332 -6.47881,3.24688 0,0 12.66542,8.25477 12.66542,8.25477 z" /> | |
| 57 | - <path | |
| 58 | - style="fill:#ffffff" | |
| 59 | - id="path1037" | |
| 60 | - d="m 517.46462,337.23029 c -2.56265,4.0164 -4.88261,8.17784 -7.31927,12.26999 -1.7766,3.09546 -4.32727,5.482 -7.18992,7.54762 -2.68734,1.95262 -5.15996,4.16998 -7.5556,6.46553 -1.38721,1.40056 -2.95482,2.59485 -4.53481,3.76485 -1.22963,0.91055 -2.3367,1.76135 -3.77132,2.29409 -5.09097,-1.74318 -1.08156,-0.40133 12.37559,6.98305 0.0258,0.0142 -0.004,-0.0636 0.0139,-0.0874 0.1837,-0.24854 0.37344,-0.49383 0.58333,-0.72068 0.75786,-0.81912 1.45438,-1.37948 2.30908,-2.12616 4.70831,-3.64505 10.04801,-6.34425 15.17505,-9.33458 2.03876,-1.27739 4.2988,-2.28601 6.19949,-3.78479 0.32219,-0.25406 0.62927,-0.53694 0.87598,-0.8648 0.15744,-0.20924 0.5329,-0.57258 0.31596,-0.71924 -4.29597,-2.90429 -8.86989,-5.37458 -13.30483,-8.06188 -3.57739,1.68493 -6.85132,3.90699 -10.27248,5.87217 -2.74604,1.62213 -5.6826,2.87125 -8.61663,4.10562 -2.65401,1.21151 -5.20027,2.67454 -7.58553,4.35282 -2.14447,1.40823 -4.28086,2.81275 -6.3615,4.31081 -1.40627,0.71348 -2.30929,1.77998 -2.62065,3.32068 0.81016,7.36832 12.11254,9.3282 18.52914,9.04592 2.50936,-0.31243 5.04454,-0.61344 7.40166,-1.56914 1.97441,-0.61026 2.76446,-1.84839 3.76012,-3.5379 1.07739,-2.05927 1.9625,-4.20328 2.99711,-6.28434 1.3492,-2.74011 3.16209,-5.22591 4.86933,-7.75101 1.18101,-1.65339 2.46708,-3.2623 3.46898,-5.03427 1.7824,-6.11494 -7.84664,-6.6416 -13.30612,-9.6205 -0.2292,-0.12506 -3.29355,0.22879 -3.63397,0.27387 -2.89195,0.30971 -5.62076,1.31686 -8.29068,2.41542 -2.82065,1.13197 -5.46628,2.58099 -8.06971,4.13143 -2.53638,1.22945 -4.83578,2.84226 -7.17486,4.3939 -1.3709,0.9908 -2.65439,2.08728 -3.75772,3.36967 -1.68219,1.5319 -3.2942,3.1344 -4.9593,4.68411 -1.45269,1.52497 -2.83742,3.11478 -4.17917,4.73738 -3.63798,5.37456 3.79556,4.99992 12.33474,9.71531 0.36277,0.20032 0.74858,-0.35639 1.13043,-0.51739 2.83931,-1.19711 0.0772,0.11931 3.2847,-1.4869 2.05827,-1.08132 4.03317,-2.30736 5.99544,-3.55117 0.47837,-0.30323 2.68062,-1.74462 3.32498,-2.03614 0.35437,-0.16032 0.74954,-0.2081 1.1243,-0.31215 5.21693,1.88088 0.36376,0.16898 -12.03101,-6.67223 -0.15964,-0.0881 0.50681,0.0961 0.47276,0.27524 -0.0452,0.23791 -0.50356,0.73864 -0.59892,0.8559 -1.01692,1.01725 -2.09668,1.99757 -3.23289,2.88304 -1.13366,0.77062 -1.78521,1.99288 -2.66487,3.00144 -0.4999,0.5181 -1.82578,2.76169 -0.85978,3.33392 4.5024,2.66709 9.1647,5.0545 13.74706,7.58175 2.23462,0.21341 4.48944,-0.29226 6.57794,-1.06304 0.183,-0.12081 0.39287,-0.20847 0.549,-0.36244 0.24654,-0.24315 0.89597,-1.50801 0.63823,-1.67783 -10.79379,-7.11175 -8.78038,-7.42141 -15.10327,-7.57337 -2.15751,-0.16966 -4.25743,0.16954 -6.33775,0.69945 -1.47207,0.22532 -2.86569,0.7409 -4.32815,0.98616 -1.4266,0.0627 -2.75764,0.56693 -4.1574,0.79381 -1.34956,0.0761 -2.77182,0.34036 -4.09815,0.0146 -0.19362,-0.0476 -0.39408,-0.0731 -0.58087,-0.14283 -0.44722,-0.167 -1.67781,-0.9967 -1.05789,-0.6456 20.98616,11.88561 15.60495,9.05661 11.16471,6.32842 -1.5196,-0.99201 -1.64758,-2.44567 -1.34593,-4.11665 0.81182,-3.10827 3.25991,-5.22887 5.61134,-7.23229 3.34059,-2.85841 7.1928,-4.9601 11.03557,-7.04621 4.20603,-2.0403 8.36157,-4.17644 12.43734,-6.46651 2.83986,-1.81873 5.72411,-3.57291 8.44219,-5.57327 1.02334,-0.93747 3.59337,-2.12394 4.07012,-3.46338 0.1339,-0.37621 0.1713,-0.78006 0.25695,-1.17009 -4.58492,-2.39842 -9.04567,-5.05089 -13.75477,-7.19526 -0.3497,-0.15925 -0.7063,0.30296 -1.06207,0.44814 -2.01467,0.82213 -3.9665,1.70254 -5.91589,2.67225 -3.65467,2.01385 -7.46814,3.79404 -10.77826,6.35973 -0.21249,0.34647 -0.5386,0.64517 -0.63747,1.03939 -1.30502,5.20313 13.83595,8.79595 14.32339,9.01748 0.61437,0.27922 1.34531,-0.12016 2.0121,-0.22411 1.77354,-0.2765 3.17125,-0.64859 4.92228,-1.06644 4.5451,-1.30218 8.81106,-3.4264 12.94588,-5.69043 1.97421,-0.92375 3.81889,-2.13756 5.873,-2.87661 1.77393,-1.01584 3.70407,-1.74284 5.64601,-2.36653 0.2903,-0.0996 0.5646,-0.27948 0.8709,-0.29874 0.14174,-0.009 0.48783,0.29097 0.36421,0.22107 -4.36595,-2.4686 -8.555,-5.27784 -13.09134,-7.41726 -0.35331,-0.16663 -0.51614,0.58655 -0.77843,0.87603 -1.08393,1.19626 -1.25521,1.36145 -2.40611,2.57297 -3.24235,3.30458 -6.48143,6.60383 -9.86178,9.77025 -3.28404,3.17757 -6.24306,6.66774 -9.44409,9.92313 -2.01291,2.02617 -4.18264,3.87968 -6.23082,5.8679 -1.9223,1.70425 -3.86185,3.39422 -5.88858,4.9743 -0.96722,0.65082 -1.83726,1.617 -2.96371,2.01994 -0.16514,0.0591 -0.37401,0.12202 -0.52514,0.033 -0.1206,-0.071 -0.22959,-0.47479 -0.10781,-0.4058 4.37015,2.47548 8.64001,5.12397 12.96002,7.68596 0.53794,-0.73321 1.39731,-1.28776 1.82599,-2.1042 2.47982,-4.72287 -10.9966,-8.27065 -13.75481,-9.82443 -3.01935,-0.16368 -6.00541,0.25868 -8.9999,0.5565 -3.33136,0.25062 -6.66491,0.53234 -9.97631,0.97952 -1.49207,0.0622 -2.91511,0.57342 -4.40233,0.66511 -0.31252,0.0193 -0.63635,0.0538 -0.93899,-0.0266 -0.22732,-0.0604 -0.79953,-0.49519 -0.59419,-0.3805 4.19573,2.34367 8.37764,4.712 12.56647,7.06801 -0.12644,-0.0812 -0.29432,-0.11966 -0.37932,-0.24357 -0.73362,-1.06945 0.52294,-2.95045 0.98506,-3.83506 1.54566,-2.92927 3.75683,-5.3607 6.04032,-7.72501 3.25479,-3.46003 6.89274,-6.53596 10.49318,-9.62639 3.01741,-2.51586 6.22504,-4.77774 9.2122,-7.33076 2.43349,-2.03466 5.14772,-3.71373 7.78144,-5.47881 7.74668,-5.34821 5.26549,-2.15496 -8.93837,-11.02194 -3.67679,2.35575 -7.28617,4.81658 -10.95286,7.1879 -3.60708,2.14166 -6.78641,4.82897 -9.89312,7.62168 -1.86144,1.77073 -3.57985,3.68242 -5.41099,5.4821 -1.46318,1.5278 -2.96981,3.07905 -4.72685,4.26969 -1.60606,1.0531 -3.23293,2.05513 -4.91002,2.98816 -1.48842,0.87657 -2.93256,1.85966 -4.62833,2.26668 -2.29749,0.73424 -4.56561,1.54956 -6.89065,2.19388 -0.21725,0.033 -1.44035,0.30317 -1.78598,0.0997 -0.1635,-0.0962 -0.41263,-0.60608 -0.24764,-0.51244 4.26109,2.41814 8.34115,5.14836 12.62053,7.53399 0.13307,0.0742 0.031,-0.30774 0.0903,-0.44808 0.14456,-0.342 0.34422,-0.658 0.51633,-0.987 2.37142,-3.19148 5.06218,-6.13906 7.75268,-9.063 2.78598,-3.28539 5.95741,-6.19831 9.12416,-9.10549 2.81779,-2.65813 5.52878,-5.42524 8.32563,-8.10749 2.35912,-2.2275 4.9302,-4.20401 7.51479,-6.15787 2.21682,-1.57455 4.70767,-2.64853 7.23257,-3.61266 2.59322,-1.05976 5.31693,-1.74942 7.95483,-2.67784 6.95804,-2.23836 5.26274,-0.70784 -10.5794,-8.877 -0.38723,-0.19968 -0.77405,0.40021 -1.15813,0.6059 -0.57211,0.30639 -1.14022,0.62019 -1.71034,0.93029 -5.10523,2.95137 -10.1952,5.93597 -15.4318,8.64977 -3.75271,2.05164 -7.48656,4.12742 -11.25821,6.14133 -2.71694,1.28113 -5.35381,2.71843 -8.01141,4.11619 -1.86867,0.9152 -3.66552,1.95908 -5.47922,2.98047 -1.72455,1.10921 -3.54098,2.13062 -5.05664,3.51931 -0.90463,0.82401 -2.04789,2.27228 -3.2307,2.66682 1.2635,-5.34205 -0.16936,0.0679 12.96906,7.26428 0.17418,0.0954 0.22262,-0.32904 0.32813,-0.49729 0.11736,-0.18717 0.22653,-0.37935 0.34031,-0.56871 0.0591,-0.0985 0.27684,-0.23544 0.17847,-0.2947 -4.39251,-2.6466 -8.90136,-5.09514 -13.35205,-7.6427 -1.49531,0.85332 -2.7992,1.97088 -4.22888,2.92082 -4.9037,3.89088 -8.1625,-1.2946 11.33893,8.0153 0.21258,0.10149 0.0789,-0.46569 0.14649,-0.69135 0.0927,-0.30958 0.22227,-0.60693 0.33341,-0.91039 0.9684,-2.27791 2.17116,-4.41805 3.44801,-6.53272 1.83613,-2.66105 4.2652,-4.84603 6.6056,-7.05325 2.98662,-2.55678 6.31032,-4.67369 9.50952,-6.94733 2.50895,-1.86606 5.26592,-3.33396 7.90531,-4.98838 2.47853,-1.36936 5.0954,-2.41932 7.72677,-3.44606 3.54649,-1.27142 7.2593,-1.95785 10.88689,-2.95161 3.44504,-1.09078 6.90807,-2.11655 10.36057,-3.18415 1.57896,-0.50775 3.15215,-1.01407 4.66702,-1.69465 0.22148,-0.0995 0.40862,-0.34535 0.65075,-0.32727 0.13052,0.01 -0.0964,0.24337 -0.14462,0.36505 -4.28721,-2.811 -8.39676,-5.91369 -12.86162,-8.433 -0.33959,-0.19162 -0.65762,0.4197 -0.9957,0.61396 -2.34749,1.34885 -4.80653,2.53706 -7.25277,3.6926 -4.36193,1.75711 -8.68282,3.60481 -12.99058,5.49322 -4.10098,1.72429 -7.9938,3.8697 -11.98214,5.82283 -2.74984,1.24947 -5.53582,2.41306 -8.32154,3.58056 -2.33776,0.90325 -4.64698,1.81777 -6.82899,3.06087 -1.6298,0.95465 -2.93181,2.35203 -4.53805,3.32872 -1.22767,0.5776 -2.9154,2.1317 -4.32374,1.80128 -2.79977,-4.49808 -0.18699,-0.17007 12.40537,6.86221 0.13592,0.0759 -0.24331,-0.20851 -0.31037,-0.34901 -0.3676,-0.7701 -0.29072,-1.79017 -0.29405,-2.60894 0.27412,-1.91104 1.29442,-3.60878 2.49962,-5.08165 1.12269,-1.14938 2.65266,-1.66837 3.92767,-2.62115 1.45993,-0.82313 2.77198,-1.84586 4.11828,-2.83995 1.71524,-1.19027 3.37924,-2.44494 5.19567,-3.48489 2.614,-1.40322 5.09913,-3.01589 7.68692,-4.46278 2.75467,-1.40031 5.54523,-2.81309 8.46488,-3.83629 3.572,-1.39268 7.35073,-2.15702 11.06617,-3.06688 2.1504,-0.43081 4.24068,-1.17674 6.24799,-2.0536 1.84007,-0.61386 3.57214,-1.48886 5.38903,-2.15968 2.39709,-0.73828 4.7544,-1.60292 7.1525,-2.33873 2.13542,-0.69731 4.39264,-0.70615 6.57979,-1.09077 1.97808,-0.16946 3.86686,-0.73435 5.77371,-1.23803 2.24307,-0.87141 4.58412,-1.44007 6.8694,-2.18694 2.22625,-0.79063 3.47755,-2.45154 4.24552,-4.59789 0.49708,-1.74364 0.66891,-3.12814 -0.47933,-4.63119 -3.93217,0.9071 -9.47789,-8.04187 -15.22808,-8.02582 -0.90173,0.003 -1.78593,0.33746 -2.64717,0.56315 -2.16621,0.70967 -3.8994,2.23159 -5.77133,3.47988 -1.28133,0.95004 -2.63044,1.80516 -3.90936,2.7585 -0.35558,0.26506 -0.71563,0.52642 -1.04581,0.82251 -0.19353,0.17354 -0.73737,0.43927 -0.51972,0.58138 4.38614,2.86373 9.04269,5.29022 13.56403,7.93533 2.86923,-1.07989 5.45916,-2.74922 7.90565,-4.5716 1.30728,-1.18837 2.73972,-2.28872 3.7411,-3.76068 0.69028,-1.09515 0.57253,-2.17863 0.22624,-3.35306 0,0 -13.75609,-6.27088 -13.75609,-6.27088 v 0 c 0.43127,0.58847 0.90259,1.25446 0.5011,1.95547 -0.93702,1.30814 -2.28597,2.27716 -3.47973,3.33299 -2.35429,1.69155 -4.84173,3.25548 -7.6231,4.14233 3.94379,2.81864 7.78552,5.78584 11.83137,8.45591 0.15408,0.10169 0.25521,-0.2681 0.39804,-0.38505 1.55286,-1.27142 3.28779,-2.30435 4.90031,-3.49462 1.73096,-1.16737 3.36424,-2.58064 5.46172,-3.01518 0.62992,-0.11694 0.95322,-0.2188 1.59062,-0.18113 4.62466,0.27329 16.94781,10.99054 -10.17189,-6.59562 1.74126,0.70663 1.64617,1.19544 1.27065,2.96111 -0.59489,1.92717 -1.5473,2.7829 -3.50056,3.38484 -2.28542,0.69029 -4.56126,1.40392 -6.83314,2.13394 -1.86368,0.50835 -3.75489,0.86129 -5.67334,1.09378 -2.26937,0.26543 -4.53808,0.57399 -6.71882,1.29155 -2.40682,0.79019 -4.81647,1.57331 -7.20528,2.41681 -1.80044,0.7305 -3.58032,1.50925 -5.40406,2.1821 -2.00043,0.75722 -4.01242,1.47936 -6.12371,1.87071 -3.75896,0.93132 -7.56641,1.75112 -11.16582,3.21306 -2.93909,1.16982 -5.82135,2.51337 -8.59563,4.03386 -2.57918,1.47872 -5.10173,3.05203 -7.70477,4.49029 -1.81837,1.12195 -3.53915,2.37807 -5.2793,3.6155 -1.32898,0.99565 -2.69698,1.91207 -4.09599,2.80484 -1.43419,0.93394 -3.04899,1.61757 -4.15253,2.98549 -1.3792,1.75296 -2.51107,3.72858 -2.9716,5.9322 -0.42607,6.72358 2.58546,13.89204 13.95641,11.7794 1.70484,-0.52484 3.23516,-1.48185 4.74126,-2.41844 1.50416,-1.05667 2.85238,-2.33425 4.47183,-3.22635 2.17907,-1.14349 4.47115,-1.99876 6.74447,-2.93278 2.80281,-1.1927 5.62687,-2.33761 8.37308,-3.65834 3.94777,-1.99116 7.87216,-4.0231 11.94217,-5.76243 4.31945,-1.89631 8.68307,-3.6801 13.01855,-5.53542 1.95914,-0.9522 3.90805,-1.926 5.82546,-2.96058 0.50397,-0.27193 0.99884,-0.56038 1.49829,-0.84052 0.31277,-0.17543 1.20979,-0.29179 0.93838,-0.52618 -3.74847,-3.2371 -7.92031,-5.94879 -11.88046,-8.92318 -1.22719,1.18526 -4.01355,1.49621 -5.55291,2.18899 -3.43466,1.07096 -6.88468,2.09121 -10.32725,3.13547 -3.67765,0.9652 -7.42888,1.70351 -10.98125,3.09056 -2.69832,1.05992 -5.36095,2.21186 -7.87351,3.66898 -2.70171,1.61585 -5.40577,3.22217 -7.95915,5.07237 -3.26329,2.2977 -6.63032,4.47577 -9.62334,7.12899 -2.43197,2.30605 -4.93949,4.60371 -6.81524,7.40375 -1.324,2.1933 -2.56344,4.41585 -3.58314,6.77064 -0.28262,0.72486 -0.78535,1.96378 -0.70523,2.0225 11.11095,8.14252 9.2911,11.16909 14.84179,7.04588 1.36792,-0.96609 2.68778,-2.00505 4.16026,-2.81433 -4.13289,-2.7284 -8.2024,-5.55535 -12.39869,-8.18521 -0.0147,-0.009 -0.14428,0.25714 -0.49677,0.8256 -0.14461,0.23321 -0.65593,0.52359 -0.4415,0.6948 11.86492,9.47378 8.42352,11.94948 12.84847,8.4416 1.25222,-0.84217 2.34972,-1.89283 3.41529,-2.95596 1.52098,-1.23338 3.20569,-2.25517 4.88718,-3.25333 1.81755,-0.98999 3.586,-2.06616 5.45951,-2.95033 2.66572,-1.38772 5.30731,-2.8214 8.0166,-4.1241 3.77769,-1.99676 7.47864,-4.12937 11.25287,-6.13319 5.2221,-2.75618 10.32081,-5.73451 15.44415,-8.66863 0.5681,-0.30701 1.13636,-0.61371 1.70429,-0.92104 0.39362,-0.213 1.51892,-0.34725 1.18035,-0.63995 -10.78271,-9.32163 -7.87059,-10.07916 -14.60754,-7.84877 -2.67927,0.86748 -5.3831,1.67329 -7.99425,2.73628 -2.62213,1.04815 -5.18008,2.27437 -7.45988,3.96169 -2.62589,1.99186 -5.21853,4.03187 -7.60812,6.30749 -2.7874,2.6763 -5.48264,5.44509 -8.30843,8.07963 -3.19517,2.93828 -6.36993,5.90573 -9.19109,9.21472 -2.77132,3.02267 -5.56044,6.06177 -7.9508,9.39909 -0.21109,0.3984 -0.44372,0.7861 -0.63328,1.19518 -0.17102,0.3691 -0.76758,0.88555 -0.44676,1.13567 8.73674,6.81144 8.79946,9.68501 15.48409,8.02983 2.31854,-0.69686 4.60328,-1.49915 6.91256,-2.22598 1.69473,-0.6526 3.23281,-1.61021 4.7984,-2.5221 1.71744,-0.96571 3.37745,-2.03192 5.0069,-3.13882 1.74325,-1.34381 3.35258,-2.85474 4.85028,-4.46755 1.79202,-1.80288 3.519,-3.67001 5.3695,-5.41483 3.06276,-2.73341 6.23067,-5.31534 9.76881,-7.42471 3.56377,-2.32805 7.04548,-4.81206 10.75925,-6.89388 -13.9328,-9.40918 -8.69904,-10.77058 -16.32658,-5.58395 -2.66559,1.80994 -5.43462,3.49608 -7.86903,5.61743 -3.01558,2.52213 -6.21582,4.80304 -9.21838,7.34065 -3.63424,3.12382 -7.29004,6.24902 -10.5699,9.75083 -2.36345,2.48192 -4.64404,5.04026 -6.25701,8.09181 -0.98025,1.86575 -1.87764,3.74138 -1.49605,5.91216 4.47978,2.60314 8.89349,5.32345 13.43933,7.80943 0.21105,0.11542 1.77486,-0.0717 1.89732,-0.0878 1.46508,-0.19215 2.91168,-0.51867 4.38649,-0.64271 3.31234,-0.38091 6.63266,-0.6784 9.95549,-0.95017 2.84468,-0.29162 5.80106,-0.75883 8.61293,-0.0364 -4.01164,-2.39741 -8.13559,-4.6162 -12.03493,-7.19225 -0.57156,-0.37759 1.09357,-0.4585 0.87991,-0.0213 -0.43108,0.88221 -1.47212,1.41354 -1.93076,2.28225 11.59732,8.66545 9.22916,11.9968 16.51576,6.29329 2.04341,-1.63486 4.03926,-3.33374 5.96812,-5.10217 2.06707,-1.98767 4.24691,-3.85399 6.25376,-5.90532 3.16428,-3.26173 6.1361,-6.70905 9.40961,-9.867 3.39505,-3.19207 6.64747,-6.51931 9.90836,-9.84623 0.25916,-0.27451 3.50217,-3.69601 3.39004,-3.79676 -9.96672,-8.9553 -7.81314,-10.57163 -13.77855,-8.49854 -1.94142,0.70676 -3.84608,1.49186 -5.68861,2.42977 -2.04138,0.89469 -3.9767,2.02298 -5.96838,3.02078 -4.06108,2.16276 -8.23356,4.22156 -12.71629,5.34929 -1.62184,0.34891 -3.11936,0.73218 -4.77701,0.8605 -0.61269,0.0474 -2.33845,-0.37035 -1.84358,-0.006 3.71143,2.73254 7.973,4.64748 11.74493,7.29587 0.25756,0.18084 -0.59442,0.45068 -0.8848,0.32936 -0.20091,-0.0839 0.22928,-0.37022 0.34392,-0.55533 3.31339,-2.409 7.02441,-4.18858 10.62438,-6.12136 1.49899,-0.72828 2.99183,-1.4498 4.55192,-2.04062 0.40737,-0.15427 0.8126,-0.31832 1.23226,-0.43509 0.27664,-0.077 1.09197,0.009 0.84932,-0.1441 -4.08456,-2.58476 -8.36799,-4.841 -12.55199,-7.26149 0.23631,-0.26817 0.46018,-0.54785 0.70893,-0.80451 0.0633,-0.0653 -0.085,0.16444 -0.15015,0.22795 -0.20966,0.20434 -0.43979,0.38689 -0.66838,0.5698 -0.49416,0.39542 -1.39427,1.05108 -1.89772,1.41656 -0.35135,0.25507 -0.70466,0.50742 -1.05699,0.76112 -2.69606,1.9333 -5.52364,3.67906 -8.3446,5.4215 -4.08556,2.2643 -8.25282,4.36573 -12.4297,6.4548 -3.91946,2.15218 -7.81879,4.36688 -11.20893,7.31806 -2.56409,2.24927 -5.13013,4.66762 -6.08973,8.05732 -0.12254,0.45699 -0.29555,0.90336 -0.36763,1.37098 -0.21991,1.42675 0.1583,3.14531 0.98221,4.31256 0.6152,0.57294 0.40284,0.43897 1.13778,0.86491 4.4268,2.56565 9.66767,7.64816 15.51758,8.2066 1.45832,-0.0239 2.92651,-0.0724 4.35816,-0.37589 1.38534,-0.3112 2.76572,-0.60664 4.17151,-0.81405 1.44323,-0.36329 2.89038,-0.69179 4.34497,-1.01042 2.00379,-0.416 4.03855,-0.52536 6.07966,-0.33812 5.72181,0.63545 5.81025,4.57384 -11.08516,-7.05781 -0.29961,-0.20627 0.46661,-0.55834 0.69003,-0.84538 0.0199,-0.0256 0.0697,-0.0944 0.0376,-0.0895 -0.14156,0.0215 -0.2695,0.0969 -0.40425,0.14532 -1.96016,0.55559 -4.04613,0.95541 -6.03614,0.31552 4.10167,2.38174 8.28145,4.63379 12.30502,7.14522 0.18644,0.11637 -0.43218,0.0811 -0.64597,0.1321 -0.13909,0.0332 -0.29574,0.20363 -0.41181,0.12012 -0.14738,-0.10602 0.68335,-1.16831 0.78671,-1.28219 0.79287,-0.96803 1.45595,-2.04923 2.51794,-2.76208 1.18285,-1.00593 2.39933,-1.99452 3.41218,-3.17648 0.54333,-0.72825 1.11914,-1.48461 1.24315,-2.42177 0.72239,-5.45934 -9.27368,-9.76863 -14.60467,-8.23205 -3.72115,1.5986 -6.82861,4.35869 -10.52914,6.02585 -0.79037,0.35684 -2.17456,1.06117 -3.10237,1.21728 -0.22721,0.0382 -0.8739,-0.22497 -0.68489,-0.0932 19.74632,13.76521 9.39646,11.90022 12.61258,7.28013 1.31409,-1.56503 2.64049,-3.12515 4.08106,-4.57625 1.69193,-1.58653 3.36292,-3.19567 5.0329,-4.80455 1.06326,-1.15314 2.25451,-2.16265 3.55446,-3.04299 2.30645,-1.56012 4.65827,-3.0362 7.13059,-4.32514 2.54667,-1.50998 5.18658,-2.83633 7.93981,-3.93423 2.59206,-1.0032 5.24439,-1.8682 8.03951,-2.05215 0.75935,-0.0307 1.59431,-0.11006 2.35351,0.0289 0.2913,0.0533 1.09313,0.43888 0.84307,0.28023 -4.01992,-2.55051 -8.1716,-4.89054 -12.1736,-7.46908 -0.12926,-0.0833 0.30923,-0.13404 0.45394,-0.082 0.0848,0.0305 0.0364,0.17647 0.0545,0.26471 -0.97645,1.73866 -2.26935,3.29571 -3.40716,4.92792 -1.74567,2.58014 -3.58934,5.12616 -4.97015,7.92547 -1.01904,2.04947 -1.90463,4.1522 -2.94347,6.19086 -0.97594,1.65484 -1.31084,2.04841 -3.20104,2.64489 -2.30959,0.69141 -4.70391,1.0518 -7.11217,1.15573 -1.14872,-0.0364 -1.5685,-0.0119 -2.68256,-0.18722 -0.36652,-0.0577 -0.73474,-0.11952 -1.08936,-0.22867 -0.27354,-0.0842 -1.02827,-0.49867 -0.78483,-0.34817 12.1521,7.51284 16.98049,7.11315 11.74075,7.44753 -0.64524,-0.69029 1.54481,-1.79198 1.90769,-2.06977 2.0632,-1.5008 4.23889,-2.83145 6.34049,-4.2778 2.35593,-1.56803 4.81573,-2.99305 7.42314,-4.10393 2.96865,-1.28918 5.94312,-2.576 8.71726,-4.2586 2.65138,-1.54923 0.9872,-0.56128 3.718,-2.22274 1.63477,-0.99462 3.31862,-2.11829 5.12482,-2.79026 0.3178,-0.11823 0.66178,-0.14818 0.99267,-0.22226 -4.06444,-2.69254 -8.12888,-5.38507 -12.19331,-8.07762 -0.0247,-0.0164 0.0948,0.0277 0.0743,0.0492 -0.22712,0.23843 -0.47605,0.45804 -0.74216,0.65199 -1.93532,1.41053 -4.11286,2.46482 -6.15034,3.70855 -5.2118,3.0227 -10.63123,5.78809 -15.33673,9.58923 -1.13626,1.02541 -2.40527,2.04636 -3.20845,3.3799 -0.23462,0.38955 -0.96384,0.95945 -0.59618,1.22707 11.84803,8.6244 8.4217,9.58039 13.95895,7.68437 3.19911,-1.63845 5.94733,-4.08316 8.58378,-6.49278 2.36984,-2.23814 4.80586,-4.40541 7.4576,-6.30866 2.96125,-2.25307 5.63225,-4.7928 7.49983,-8.05914 2.42312,-4.07031 4.73001,-8.20967 7.28048,-12.20339 0,0 -13.04362,-7.64316 -13.04362,-7.64316 z" /> | |
| 61 | - <path | |
| 62 | - style="fill:#ffffff" | |
| 63 | - id="path1039" | |
| 64 | - d="m 432.49953,344.24829 c -0.74992,1.88333 -1.06676,4.9053 -2.27069,6.52441 17.7842,9.09169 10.87195,10.15007 14.63577,2.53805 1.10268,-1.69406 2.3453,-3.29642 3.58552,-4.89165 1.1227,-1.38745 2.25262,-2.77369 3.29055,-4.22698 1.32978,-2.11295 2.84636,-4.09666 4.29583,-6.1268 1.30305,-1.75714 2.50915,-3.58428 3.74208,-5.39123 1.56571,-1.86607 2.88779,-3.92042 4.5192,-5.73014 1.88665,-1.74545 3.98152,-3.25993 6.12942,-4.66512 2.9896,-2.09562 6.07519,-4.03418 8.97079,-6.26338 2.78,-1.954 5.25025,-4.28817 8.0846,-6.16612 2.11189,-1.2452 4.15986,-2.59054 6.24117,-3.88429 3.02352,-1.90978 5.76591,-4.21725 8.62645,-6.35471 1.48877,-1.16753 3.03884,-2.25756 4.59377,-3.33481 0.2987,-0.20694 0.56223,-0.49503 0.91007,-0.60017 0.10854,-0.0328 0.32024,0.31389 0.22153,0.25812 -4.34332,-2.4539 -8.64841,-4.97486 -12.97261,-7.46229 -2.62513,4.04578 -5.83026,7.70019 -8.9005,11.41297 -3.66383,4.80372 -8.12475,8.87004 -12.73052,12.74179 -5.91272,4.63036 -8.29371,2.67995 10.5926,8.76614 0.72744,0.23442 0.95585,-1.19331 1.45579,-1.7714 1.41805,-1.63974 2.66603,-2.92715 4.16048,-4.51228 2.07947,-2.31538 4.40674,-4.3784 6.66784,-6.50762 1.84986,-1.74198 3.62603,-3.51822 5.55816,-5.17079 2.44558,-1.96438 4.85201,-4.07114 7.7316,-5.37452 1.77224,-0.39347 3.46115,-1.03221 5.19071,-1.59061 2.31711,-0.81022 4.51877,-2.02167 6.60839,-3.31065 1.39458,-0.88305 2.74941,-1.91401 4.27263,-2.56811 0.19353,-0.1003 0.38287,-0.20912 0.58059,-0.3009 0.59457,-0.27597 0.58438,-0.16429 1.12628,-0.58611 0.45898,-0.35728 0.79797,-0.75022 1.30061,-1.05309 5.14148,-4.96325 -7.95067,-9.73777 -13.28583,-9.85314 -2.48876,0.35892 -4.69207,1.55828 -6.90425,2.67053 -2.3624,0.89905 -3.73647,2.37469 -4.94941,4.5448 -1.13905,2.59111 -2.308,5.17539 -3.35541,7.80576 -1.96526,6.34677 5.52532,5.98779 13.1748,9.82243 0.28349,0.14211 3.63407,-1.76581 4.01264,-1.97846 2.35819,-1.33273 4.68655,-2.71263 6.98133,-4.15154 1.37572,-0.7433 2.66223,-1.6029 3.75923,-2.71805 1.17204,-1.09559 1.77792,-2.49721 2.12557,-4.03109 0.0998,-1.18885 -0.16945,-2.73124 -1.30979,-3.41737 -13.34308,-8.02843 -8.61169,-6.96412 -14.89582,-8.20796 -2.60759,-0.54437 -5.18738,-0.18878 -7.72525,0.49502 -0.65085,0.12605 -1.04631,0.33591 -1.66437,0.0414 -0.177,-0.0843 -0.61222,-0.48509 -0.44157,-0.38856 4.20664,2.42581 8.41328,4.85163 12.61992,7.27744 -0.0556,-0.0879 -0.16461,-0.15979 -0.1668,-0.2638 -0.005,-0.23223 0.098,-0.45412 0.14694,-0.68117 0.55462,-1.72933 1.86737,-2.87785 3.2017,-4.01207 0.89484,-1.03596 2.11945,-1.6709 3.12761,-2.57143 0.86027,-0.47119 1.53234,-0.96433 1.98432,-1.83939 -10.36867,-6.55283 -10.56003,-10.30222 -16.32628,-5.43659 -1.3875,0.80231 -2.64757,1.80472 -3.95489,2.73368 -1.67535,0.85692 -3.39189,1.62876 -5.04528,2.53289 -1.98355,0.85518 -3.9245,1.76609 -5.9385,2.55617 -2.6027,1.25889 -5.19179,2.53692 -7.73292,3.91622 -3.16884,1.42333 -6.13113,3.23939 -9.20069,4.85676 -3.01773,1.63467 -5.94816,3.43762 -8.7288,5.45006 -1.8161,1.09651 -3.20033,2.68211 -4.6919,4.14933 -2.56301,2.52117 0.62871,-0.80843 -1.82713,1.78993 -1.29257,1.17689 -2.33137,2.57077 -3.45106,3.89804 -1.30688,1.49913 -2.76578,2.85213 -4.2452,4.17729 -1.67526,1.40297 -3.27521,2.85848 -4.81531,4.40468 -1.11976,1.32692 -1.82601,2.96903 -2.59642,4.51708 -0.89521,1.49446 -1.60042,3.07987 -2.33327,4.65731 -0.55628,0.99643 -1.16583,1.96813 -1.65121,3.00157 -0.53699,0.94305 -0.94412,1.93177 -1.55569,2.83258 -0.8921,1.16304 -1.39503,2.55479 -2.18277,3.77523 -0.75855,1.14369 -1.22847,2.45654 -1.66572,3.75099 -0.47382,1.2505 -0.77063,2.5524 -1.27452,3.79267 0.0176,-0.0459 0.01,-0.16167 0.0529,-0.13767 15.20424,8.50293 10.5337,11.06607 15.80147,3.6489 2.08695,-2.75768 3.99725,-5.64466 5.97647,-8.48077 1.92629,-2.76638 3.80319,-5.56446 5.65316,-8.38206 1.26769,-2.14829 2.54516,-4.29976 3.72202,-6.4994 0.99127,-1.696 1.86944,-3.43898 2.60941,-5.25851 0.6295,-1.85568 1.18095,-3.73524 1.94295,-5.54209 0.89682,-2.17472 2.06346,-4.22311 3.20854,-6.27422 1.00575,-1.47324 1.84774,-3.08799 2.93752,-4.50437 0.10494,-0.13639 1.21585,-1.33809 1.34628,-1.47981 0.9082,-1.31448 2.09267,-2.36626 3.17242,-3.52376 0.95213,-0.94011 1.9487,-1.7963 2.92104,-2.71484 1.11996,-1.04095 2.26946,-2.03134 3.34625,-3.11929 1.36036,-1.04378 2.73501,-2.1089 4.26504,-2.89286 1.62924,-1.00996 3.42299,-1.69537 5.08542,-2.63746 1.4904,-0.68881 3.08098,-1.11573 4.60632,-1.70803 1.61959,-0.626 3.03985,-1.70627 4.57116,-2.53507 1.26489,-0.46675 2.54065,-0.95411 3.84876,-1.29477 1.16488,-0.23001 2.35876,-0.2251 3.54126,-0.23621 0.64204,0.066 1.31618,-0.0895 1.95561,4.1e-4 0.38998,0.0548 0.54234,0.17649 0.85356,0.35797 -4.23079,-2.43099 -8.46158,-4.86198 -12.69237,-7.29297 -0.23596,0.40922 0.32876,-0.0608 0.27332,0.0117 -0.23121,0.30203 -1.16696,0.73465 -1.48098,0.91793 -1.89828,0.86389 -3.80655,1.66682 -5.80597,2.2709 -2.38683,0.81515 -4.90251,1.25962 -7.18253,2.35472 -1.59845,0.71242 -3.19309,1.38261 -4.74426,2.19516 -2.17518,1.09322 -4.14076,2.57229 -6.05109,4.07209 -1.04757,0.75941 -1.86421,1.78884 -2.84381,2.62126 -0.47353,0.40237 -1.00615,0.72951 -1.50758,1.09652 -2.60957,1.82293 -5.07268,3.85707 -7.44319,5.9788 -2.38247,2.05357 -4.89087,3.94947 -7.26141,6.01746 -1.45747,1.27654 -2.72386,2.74505 -3.98576,4.21007 -1.17894,1.2966 -2.3584,2.58174 -3.34479,4.03392 -0.80492,1.04949 -1.52848,2.15792 -2.2549,3.2618 -0.56841,1.04014 -0.90533,2.19537 -1.26485,3.32241 -0.20666,1.14317 -0.70444,2.17858 -1.1385,3.24297 -0.37682,1.24532 -0.9082,2.43344 -1.34007,3.65859 -0.59614,1.23685 -1.07713,2.51619 -1.55984,3.79939 -0.60561,1.64317 -1.22029,3.28304 -1.72453,4.96068 -0.49888,1.22534 -0.83747,2.50404 -1.31075,3.73744 -0.40444,1.06336 -0.97991,2.04175 -1.55375,3.01889 0,0 13.08691,7.56878 13.08691,7.56878 v 0 c 0.61569,-1.04805 1.22928,-2.09904 1.67579,-3.23392 0.49152,-1.27241 0.84263,-2.58551 1.37098,-3.84669 0.46931,-1.6472 1.08207,-3.24556 1.66603,-4.85454 0.50216,-1.29407 0.94652,-2.60845 1.58598,-3.84657 0.4,-1.21115 0.96582,-2.36112 1.30393,-3.59348 0.42896,-1.10903 1.01951,-2.16153 1.20003,-3.35429 0.28527,-0.99865 0.6179,-2.00144 1.04898,-2.94116 0.72768,-1.09373 1.44377,-2.19601 2.24241,-3.24048 0.96693,-1.3969 2.13628,-2.61995 3.25929,-3.89028 1.2158,-1.40982 2.45249,-2.8098 3.87076,-4.02235 2.38823,-2.05378 4.89907,-3.95769 7.27043,-6.03197 2.36178,-2.07152 4.79443,-4.06893 7.37306,-5.86698 1.54036,-1.15559 2.87935,-2.51821 4.3653,-3.73979 1.87881,-1.41018 3.76354,-2.83918 5.90154,-3.83977 1.53809,-0.76523 3.13304,-1.40357 4.69438,-2.11969 2.31022,-0.90327 4.75639,-1.41584 7.09887,-2.23031 2.062,-0.71132 4.09072,-1.52659 6.00735,-2.575 1.15677,-0.75487 2.31614,-1.57824 2.36727,-3.09574 -11.84239,-7.15983 -8.79952,-8.19681 -16.23653,-7.94552 -1.27385,0.0591 -2.55519,0.15442 -3.77266,0.56575 -1.36356,0.46811 -2.76869,0.85036 -4.03011,1.56676 -1.44448,0.83593 -2.83281,1.8206 -4.43538,2.33025 -1.60897,0.54133 -3.21588,1.11756 -4.72908,1.89291 -1.7058,0.87233 -3.44756,1.67232 -5.09656,2.65452 -1.58254,0.9473 -3.11251,2.01939 -4.48952,3.24809 -1.07479,1.06783 -2.23755,2.0338 -3.32634,3.0869 -0.97659,0.93843 -2.02953,1.78709 -2.95848,2.77592 -1.11653,1.14012 -2.228,2.27157 -3.18959,3.55198 -1.73615,1.91381 -3.12213,4.07803 -4.46152,6.28494 -1.17125,2.09783 -2.36412,4.19304 -3.28797,6.41396 -0.77999,1.81247 -1.36826,3.69143 -1.96005,5.57265 -0.69501,1.77895 -1.57036,3.46126 -2.52724,5.11323 -1.14995,2.16186 -2.41205,4.27023 -3.65327,6.38065 -1.84142,2.80594 -3.71367,5.58986 -5.63123,8.34468 -1.97876,2.83242 -3.8863,5.71663 -5.9713,8.47202 -5.09974,7.21931 -3.61585,3.58041 10.14902,11.86734 0.10559,0.0636 0.13753,-0.20454 0.2063,-0.30682 0.54562,-1.27233 0.85593,-2.60811 1.33519,-3.90196 0.366,-1.18393 0.79847,-2.37723 1.47685,-3.41749 0.76962,-1.22092 1.29148,-2.58159 2.16466,-3.74426 0.64823,-0.97619 1.10877,-2.02653 1.6781,-3.04528 0.45874,-1.04165 1.10992,-1.99296 1.6475,-2.99498 0.7231,-1.56766 1.42248,-3.13956 2.31322,-4.62198 0.69376,-1.40359 1.32111,-2.91229 2.35041,-4.10035 1.51226,-1.5356 3.15699,-2.90965 4.77261,-4.33535 1.51521,-1.38933 3.03002,-2.786 4.35719,-4.36096 1.07589,-1.31874 2.15268,-2.63092 3.39427,-3.80416 1.98924,-2.04024 3.98674,-4.10902 6.36889,-5.69823 2.76588,-1.9319 5.63882,-3.7109 8.62332,-5.28656 3.05605,-1.64619 6.06258,-3.38186 9.20471,-4.86291 2.50272,-1.33987 5.04539,-2.60674 7.63563,-3.76923 2.04517,-0.82102 4.03382,-1.76494 6.03055,-2.69514 1.72296,-0.88646 3.50857,-1.66371 5.15022,-2.6955 1.33179,-0.93952 2.63362,-1.91845 4.00061,-2.80598 5.75132,-4.27305 6.41889,-0.18499 -9.91136,-9.53506 0.0988,-0.15669 0.22735,-0.29822 0.29653,-0.47009 0.0309,-0.0768 -0.11478,0.11966 -0.17731,0.17392 -0.45656,0.39618 -1.06226,0.60574 -1.48578,1.04994 -1.07663,0.82836 -2.19514,1.6074 -3.14048,2.59206 -1.53745,1.34942 -2.95155,2.80683 -3.65012,4.78163 -0.0188,0.0593 -0.65643,1.94397 -0.51994,2.04078 7.75805,5.50234 9.22853,9.3143 15.73862,7.79935 2.42682,-0.47812 4.85872,-0.64526 7.24894,0.15555 5.24405,2.34611 1.12226,0.54529 -11.09364,-6.66691 -0.24934,-0.14721 0.51196,0.27175 0.75737,0.42541 0.62106,0.38887 1.13479,0.73189 1.24929,1.49492 -0.19447,1.28451 -0.67305,2.36229 -1.67066,3.23761 -1.0601,0.99375 -2.34252,1.69237 -3.58241,2.43713 -2.28455,1.40297 -4.58146,2.78316 -6.93152,4.07482 -0.6401,0.33075 -1.7557,0.97424 -2.50893,1.12733 -0.29072,0.0591 -1.13609,-0.18506 -0.88979,-0.0197 3.99496,2.68242 8.18171,5.06786 12.24564,7.64459 0.11606,0.0736 -0.36688,0.007 -0.3917,-0.1286 -0.0517,-0.28183 0.14352,-0.55483 0.21528,-0.83225 1.0086,-2.57511 2.17547,-5.08853 3.25754,-7.633 1.14069,-2.06393 2.33352,-2.87066 4.47439,-3.77341 1.99682,-0.99435 4.09811,-2.12487 6.39726,-1.94691 5.54739,1.31252 -1.16486,-0.19796 -11.70509,-7.15668 -0.22944,-0.15148 1.03266,-0.87795 0.47926,-0.42008 -0.1142,0.0945 -0.23753,0.17734 -0.3563,0.26601 -0.91035,0.69602 -1.797,1.34558 -2.88727,1.75467 -1.48835,0.81922 -2.91672,1.74573 -4.3402,2.6716 -2.04124,1.18747 -4.14532,2.33613 -6.41512,3.0194 -1.78134,0.56187 -3.56442,1.13378 -5.31489,1.77606 -2.89473,1.52569 -5.43227,3.61724 -7.94047,5.7005 -4.2817,3.73703 -8.32405,7.76259 -12.30794,11.8115 -1.41228,1.50514 -2.88298,3.03195 -4.21881,4.60891 -0.54545,0.64392 -2.16213,1.37043 -1.57814,1.97962 10.37811,10.8258 9.09373,11.67867 15.32582,6.79575 4.65474,-3.95024 9.15169,-8.10228 12.87166,-12.97238 3.18286,-3.85479 6.53093,-7.63942 9.17583,-11.89157 -4.19727,-2.86373 -8.27973,-5.90343 -12.59181,-8.59118 -0.077,-0.048 -1.09652,0.65829 -1.56463,0.98599 -1.5592,1.09152 -3.11333,2.19347 -4.61332,3.36563 -2.81974,2.1259 -5.54732,4.39017 -8.5582,6.24779 -2.08506,1.3181 -4.17789,2.62318 -6.27025,3.9296 -2.81241,1.94701 -5.33289,4.26353 -8.12848,6.23518 -2.89927,2.17261 -5.95114,4.11376 -8.92117,6.18537 -2.22251,1.51205 -4.43203,3.08505 -6.33475,4.9951 -1.64219,1.88227 -3.03883,3.95761 -4.61661,5.89356 -1.22912,1.79711 -2.42749,3.61668 -3.72627,5.3644 -1.44811,2.02933 -2.9611,4.01363 -4.29362,6.12305 -1.03598,1.43422 -2.15766,2.80499 -3.26514,4.18412 -1.28435,1.68579 -2.61731,3.34228 -3.73394,5.1467 -0.61541,1.23637 -1.10338,2.39149 -1.42745,3.7376 -1.42213,5.90724 -0.30903,0.87064 12.96295,8.86478 1.37512,-2.07493 1.70669,-4.65994 2.52212,-6.96893 0,0 -13.28274,-7.2196 -13.28274,-7.2196 z" /> | |
| 65 | - <path | |
| 66 | - style="fill:#ffffff" | |
| 67 | - id="path1041" | |
| 68 | - d="m 444.95379,346.65456 c 1.06642,-2.60177 2.26717,-5.1388 3.53885,-7.64477 0.80062,-1.72005 1.70753,-3.33379 2.78034,-4.89383 0.99874,-1.25158 1.79112,-2.64198 2.78939,-3.89223 0.97549,-1.20036 1.66709,-2.60021 2.6938,-3.75713 1.13014,-1.5117 1.88641,-3.2593 2.68308,-4.96014 0.7471,-1.60342 1.60226,-3.14777 2.40761,-4.72157 1.18872,-1.66559 2.27151,-3.4063 3.48455,-5.05396 0.96809,-1.31722 1.78836,-2.73014 2.77318,-4.03489 0.90823,-1.43352 1.91835,-2.76684 3.06487,-4.0174 1.43521,-1.23262 2.86359,-2.45013 4.14673,-3.84264 1.10736,-0.88478 2.1079,-1.91275 3.22743,-2.78438 1.26625,-0.92328 2.40104,-2.02565 3.74382,-2.83749 1.36021,-0.84114 2.47965,-1.99587 3.75402,-2.93834 1.17313,-0.82096 2.1014,-1.91914 3.19251,-2.83265 0.96511,-1.06249 1.9825,-2.0389 3.15071,-2.87458 1.46588,-1.3037 3.03026,-2.5058 4.41761,-3.8948 1.26476,-0.99496 2.67815,-1.75513 3.97292,-2.70979 1.23962,-0.91909 2.67716,-1.37512 4.09945,-1.89879 1.53746,-0.34526 2.98,-0.81959 4.436,-1.41426 0.95752,-0.25952 2.32209,-0.8001 3.33156,-0.62574 0.20945,0.0362 0.38685,0.17626 0.58027,0.26439 -4.20114,-2.33375 -8.40228,-4.6675 -12.60343,-7.00126 0,0 13.96868,5.78186 13.96868,5.78186 v 0 c -4.4863,-2.61747 -8.9726,-5.23493 -13.4589,-7.8524 -1.51335,0.0943 -2.98338,0.40961 -4.37883,1.01983 -1.4393,0.56639 -2.96156,0.82534 -4.40858,1.37347 -1.5382,0.55268 -3.00033,1.2792 -4.32591,2.24462 -1.37284,0.95626 -2.85935,1.75693 -4.09307,2.90202 -1.38146,1.30479 -2.85379,2.51476 -4.31435,3.72921 -1.15751,0.90413 -2.18874,1.92435 -3.21309,2.97405 -1.03083,0.94401 -2.01396,1.93579 -3.14828,2.76194 -1.19514,0.98762 -2.37284,1.99266 -3.68464,2.82915 -1.32344,0.92776 -2.55283,1.98338 -3.82992,2.9733 -1.12752,0.96567 -2.25289,1.94069 -3.33352,2.95626 -1.30383,1.37931 -2.79466,2.54328 -4.15785,3.86097 -1.16319,1.29631 -2.19199,2.67816 -3.13817,4.13963 -0.9771,1.31712 -1.81169,2.72753 -2.78204,4.04984 -1.24707,1.72053 -2.39712,3.51107 -3.60412,5.25971 -0.815,1.58975 -1.6765,3.15157 -2.43517,4.77007 -0.73319,1.57868 -1.42437,3.21086 -2.47717,4.60741 -0.99785,1.20674 -1.75357,2.57945 -2.72447,3.80831 -1.00142,1.28731 -1.83229,2.69059 -2.83871,3.9755 -1.1137,1.6144 -2.04714,3.29226 -2.88163,5.06874 -1.2825,2.52926 -2.50723,5.08361 -3.57702,7.71214 0,0 13.17149,7.42062 13.17149,7.42062 z" /> | |
| 69 | - <path | |
| 70 | - style="fill:#ffffff" | |
| 71 | - id="path1043" | |
| 72 | - d="m 47.723738,302.48814 c -0.235582,2.57035 -0.413918,5.14543 -0.527844,7.72396 -0.04401,1.66877 -0.637615,3.23133 -0.971978,4.84849 -0.49334,2.0254 -0.803411,4.07474 -0.962727,6.15115 -0.22051,2.38235 -0.149823,4.7319 0.269472,7.08396 0.481444,2.35269 0.997451,4.69608 1.45286,7.05476 0.457995,2.16877 0.573854,4.38236 0.790824,6.58142 0.233663,1.60072 0.456482,3.20102 0.57548,4.81477 0.08973,1.43491 0.08843,2.87328 0.09188,4.31034 -0.0031,5.94595 -0.872531,1.79721 13.666195,7.35427 0.230286,0.088 0.424137,-2.99784 0.437609,-3.20433 0.09063,-2.46503 0.06323,-4.93249 0.04974,-7.39861 -0.08329,-2.38251 0.008,-4.7492 0.31058,-7.11289 0.318125,-3.12377 1.109164,-6.1645 1.882127,-9.19858 0.603361,-2.38574 1.199296,-4.775 1.831907,-7.15334 0.5698,-2.1975 1.009163,-4.42888 1.423266,-6.66069 0.356849,-2.02356 0.443261,-4.078 0.494757,-6.12769 0.10103,-1.42529 0.383836,-2.82762 0.53435,-4.24816 -0.0029,-1.13487 0.180083,-2.1682 0.603434,-3.21105 0.321111,-0.5869 0.15321,-0.2786 0.503328,-0.92512 0,0 -13.113313,-7.52296 -13.113313,-7.52296 v 0 c -0.373806,0.69103 -0.177993,0.33257 -0.588169,1.07497 -0.466058,1.17181 -0.891486,2.30042 -0.810581,3.59475 -0.118838,1.46005 -0.383281,2.8963 -0.58493,4.34604 -0.03272,1.97659 -0.0796,3.95891 -0.384361,5.91626 -0.38509,2.19498 -0.834288,4.38156 -1.361672,6.54616 -0.65643,2.37946 -1.223845,4.78566 -1.843449,7.17462 -0.780965,3.08634 -1.636353,6.16979 -1.961447,9.34711 -0.358177,2.4239 -0.514696,4.85383 -0.409938,7.30476 0.01422,2.44019 0.03976,4.88083 -0.0099,7.32079 -0.02942,0.76513 -0.03695,1.29897 -0.112712,2.05085 -0.02471,0.24528 -0.307475,0.60077 -0.09933,0.73287 4.278007,2.71486 8.786753,5.0478 13.181546,7.56923 0.0572,0.0328 -0.153475,-0.0392 -0.168712,-0.10336 -0.04652,-0.19592 4.77e-4,-0.40274 7.16e-4,-0.60411 -7.7e-5,-1.46409 0.0069,-2.92885 -0.05054,-4.3921 -0.08299,-1.64226 -0.248525,-3.27751 -0.534015,-4.89805 -0.334753,-2.2089 -0.271582,-4.47121 -0.7496,-6.66552 -0.430658,-2.36726 -0.924448,-4.71652 -1.449337,-7.062 -0.472671,-2.26866 -0.697763,-4.4124 -0.444127,-6.73588 0.115274,-2.02246 0.364088,-4.02381 0.877321,-5.98869 0.294207,-1.70188 0.964594,-3.3091 1.075721,-5.04646 0.09428,-2.55627 0.265712,-5.10876 0.493001,-7.65667 0,0 -13.40746,-6.98527 -13.40746,-6.98527 z" /> | |
| 73 | - <path | |
| 74 | - style="fill:#ffffff" | |
| 75 | - id="path1045" | |
| 76 | - d="m 638.45033,529.45411 c 0.32043,-1.43279 0.87109,-2.79362 1.51319,-4.10875 0.49898,-1.07666 1.0692,-2.11787 1.58949,-3.18392 0.3604,-0.79469 0.72883,-1.65852 0.63073,-2.55458 -0.0213,-0.19465 -0.0803,-0.38329 -0.12051,-0.57493 -12.05402,-7.29915 -8.97396,-7.82949 -16.48178,-7.87355 -1.51954,-0.003 -3.03908,0.007 -4.55861,0.0125 -0.43502,-0.13711 -1.85991,0.26465 -2.19604,0.10033 -0.0753,-0.0368 -0.16683,-0.27616 -0.0951,-0.23283 4.27548,2.58135 8.49877,5.24815 12.74816,7.87222 -0.0323,0.11502 -0.19798,0.28158 -0.0968,0.34508 0.11127,0.0698 0.23042,-0.12621 0.34594,-0.18875 0.82367,-0.44601 1.64573,-0.88641 2.48418,-1.30455 1.04859,-0.43593 2.07827,-0.92742 3.18607,-1.19426 0.1154,-0.0333 1.46149,-0.43299 1.45532,-0.21803 -0.003,0.12184 -0.0928,0.22543 -0.13915,0.33815 -15.99901,-11.90341 -7.73872,-9.62675 -14.63806,-7.60889 -1.12229,0.32922 -2.27318,0.57267 -3.42629,0.77165 -0.8597,0.13995 -1.69864,0.34359 -2.57144,0.3832 -0.54215,-0.0101 -1.09515,0.074 -1.62901,-0.0558 -0.24961,-0.0607 -0.48864,-0.15896 -0.73526,-0.23084 -0.0894,-0.0566 -0.19053,-0.098 -0.26821,-0.16984 -0.0706,-0.0653 -0.25175,-0.28213 -0.16846,-0.23411 4.25969,2.45608 8.54644,4.87008 12.7194,7.47078 0.0386,0.0241 -0.62896,0.70858 -0.14215,0.34641 0.41198,-0.21773 0.79311,-0.50185 1.21319,-0.7044 0.241,-0.1162 0.49677,-0.19887 0.74453,-0.29986 0.64051,-0.18906 1.25438,-0.4587 1.9201,-0.54617 0.53439,-0.0211 1.07056,0.004 1.60554,0.0114 0.66172,0.20645 1.36743,0.21542 2.0551,0.24269 0.2059,0.0384 0.41918,-0.0195 0.62534,0.0116 0.0937,0.0141 0.17952,0.0618 0.27192,0.0828 0.32123,0.0728 0.65864,0.0923 0.98578,0.12092 0,0 -12.12893,-9.02458 -12.12893,-9.02458 v 0 c -0.64258,-0.0911 -1.27712,-0.13195 -1.92456,-0.15915 -0.70608,-0.0683 -1.41657,-0.216 -2.12625,-0.14532 -0.60794,0.0224 -1.21803,0.0643 -1.79888,0.26164 -0.70334,0.23112 -1.4064,0.47243 -2.0796,0.78154 -0.8134,0.39578 -1.61239,0.85829 -2.26694,1.48977 -4.40189,5.22506 7.05828,9.21158 12.81615,9.58956 0.87601,0.1034 1.76219,0.0157 2.63569,-0.0834 0.88485,-0.14403 1.76111,-0.3377 2.64498,-0.48765 1.20051,-0.2558 2.40354,-0.52668 3.55497,-0.95904 7.22662,-2.97311 -0.75811,-4.03385 -9.72884,-10.2551 -0.10392,0.0865 -0.19775,0.18687 -0.31174,0.25961 -0.14271,0.0911 -0.37549,0.11873 -0.53047,0.15979 -0.37663,0.0998 -0.74429,0.23119 -1.11935,0.33636 -1.13859,0.40564 -2.2512,0.88549 -3.336,1.41811 -0.68468,0.35565 -1.37148,0.71028 -2.0284,1.11615 -0.26605,0.16437 -0.84138,0.52704 -1.05722,0.77979 -0.20267,0.23735 -0.35044,0.51655 -0.52566,0.77483 4.25429,2.7014 8.45898,5.48255 12.76287,8.1042 0.1058,0.0645 0.23191,-0.0911 0.35356,-0.11452 0.71214,-0.1371 1.45642,-0.0262 2.17386,-0.0697 1.50973,-0.005 3.02005,-0.019 4.52929,0.0278 0.63448,0.0341 1.28759,0.049 1.90849,0.20211 0.17207,0.0424 0.34359,0.0927 0.50543,0.16496 0.13833,0.0617 0.51975,0.31137 0.38936,0.2343 -4.17013,-2.46505 -8.33904,-4.93215 -12.50856,-7.39822 0.13533,-0.0252 0.26836,-0.0779 0.406,-0.0756 0.69198,0.0116 0.0239,1.25024 -0.0802,1.48859 -0.51442,1.06625 -1.09068,2.1018 -1.58591,3.17763 -0.69439,1.40539 -1.30774,2.85802 -1.66838,4.38859 0,0 13.29807,7.19133 13.29807,7.19133 z" /> | |
| 77 | 5 | </g> |
| 78 | 6 | </svg> |
| @@ -1,78 +1,6 @@ | ||
| 1 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
| 2 | -<!-- Created with Inkscape (http://www.inkscape.org/) --> | |
| 2 | +<svg id="svg2" width="660.14" height="621.01" version="1.1" viewBox="0 0 660.14 621.01" xmlns="http://www.w3.org/2000/svg"> | |
| 3 | - | |
| 3 | + <g id="g8" transform="translate(-12.7,-12.6)"> | |
| 4 | -<svg | |
| 4 | + <path id="path972" d="m171.3 47.3c5.5 33.5 12.9 56.6-16 78-15.6 13.6-27.3 32.5-43.1 45.3-12.7 6.5-49.7 1.9-63.5-0.6-15.9 28-27.4 57-36 88 13.7 10.8 35 39.1 53.3 38.7 0 0-3.8 77.9-8.1 90.5-10.8 15-30.6 29.8-45.2 41.5l25.3 69.3c7.3 3.1 54.7 9.3 54.7 9.3 42.8 3.6 33.4 58.2 89.3 46.7-0.6 3.6-2 9.1 1.2 11.9 4.9 4.2 11.7-3.7 17.4-1.8 14.2 4.8 19.7 29 37.4 16.5 3.7 14.9 13.7 53.6 34.5 53 20.5-0.5 4.5-62.6 42.8-63.7 20.7-0.6 53.1-5.3 69 10.9 11.9 12.1 12.1 52 28.2 50.8 7.5-0.6 21.5-24 22.8-55.1 21.4 13.6 26.8-8.6 45.3-15 5.7-2 13.9 4.9 18.7 1.1 3.4-2.8 0-8.5-1.3-11.4 42.4 4.4 54.3-1.8 78.7-45.3 21.7 0 43.4 2.3 65.2 2 25.1-43.5 31.5-72.2 30.9-76.7-2.7-18-34.1-27.2-42.5-42.8 1-27.9-1.7-56.9-1.7-85.3 0.5-18.7 33.7-29.7 42.8-44.6 4-6.5-1.4-16.9-3.5-23.4-6.5-19.7-14.1-42.9-26.5-59.7-9.7-13.1-44.3 5.1-63-2.8-19.3-20.2-39.2-39.3-60-58.5-15.9-14.8 2.9-45.4-3.6-63.8-3.8-10.8-75.4-40.2-86.8-35-16.2 9.4-22.6 33.9-39.6 41.8-28.2-5.2-60.9 7-88.8 0.6-14.8-10.2-30.4-30.8-41.5-45.1-31.1 6.9-58 21.6-86.7 34.7m400 218.7c-58.8-77.3-132.9-36.3-148 72-5.7-38.5-2.8-80.8-2.7-120 0-12.8 0.2-30.4 10.8-39.4 27.2-23 139.8-3.8 139.8 87.4m-301.3 53.3c-17.7-99.9-99.2-127.2-144-54.7-19.7-65 99.7-95.5 123.9-85 24 10.4 30.5 86.9 21.4 139.8m-46.6-96.1c5 13.9 18 23.4 25.5 36 7.4 12.5 9.7 32.1 22.5 40-3.9-24.8-18.5-72.1-48-76m200 77.3c10.9-7.5 13.7-23.5 19.9-34.7 7.7-13.8 22.1-25 26.8-40-27.9 4.5-44.2 50.2-46.7 74.7m-265.3-45.3c5.2-20.6 30.4-14.3 45-8.2m340.4 21.6c-18.3-11-31.3-18.6-53.3-18.7 19-16.3 44.5-1 53.3 18.7m-358.9-1.6c65.8-8.8 89.9 90 78.5 104.8-12.5 16.3-80.6 23.8-103.4 2.9-13.3-12.2-18.5-33.7-24.2-50.2l-4 1.3-5.3-17.3c24.3-2.8 31-32.7 54.4-40.6m333.6 2.3c32.6 19 36.5 42.5 55 39.9l-4.3 25-5.3-9.3c-8.7 20.9-12.7 50.6-37.3 58.5-40.9 13.1-102.3 2-98-29.1 13.4-39 40.6-87.6 86-86.8m-86.6 302.8c0 17.8-2.3 32.1-10.7 48-9.4-5.8-23.3-33.8-13.4-43.9 5.5-5.7 16.9-4.1 24.1-4.1m-145.3 4-8 46.7c-16.3-9.2-22.8-30.3-24-48l32 1.3"/> | |
| 5 | - version="1.1" | |
| 6 | - id="svg2" | |
| 7 | - width="682.66669" | |
| 8 | - height="682.66669" | |
| 9 | - viewBox="0 0 682.66669 682.66669" | |
| 10 | - sodipodi:docname="kobold.svg" | |
| 11 | - inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | |
| 12 | - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 13 | - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 14 | - xmlns="http://www.w3.org/2000/svg" | |
| 15 | - xmlns:svg="http://www.w3.org/2000/svg"> | |
| 16 | - <defs | |
| 17 | - id="defs6" /> | |
| 18 | - <sodipodi:namedview | |
| 19 | - id="namedview4" | |
| 20 | - pagecolor="#ffffff" | |
| 21 | - bordercolor="#000000" | |
| 22 | - borderopacity="0.25" | |
| 23 | - inkscape:showpageshadow="2" | |
| 24 | - inkscape:pageopacity="0.0" | |
| 25 | - inkscape:pagecheckerboard="0" | |
| 26 | - inkscape:deskcolor="#d1d1d1" | |
| 27 | - showgrid="false" | |
| 28 | - inkscape:zoom="1.5043945" | |
| 29 | - inkscape:cx="460.98281" | |
| 30 | - inkscape:cy="311.08862" | |
| 31 | - inkscape:window-width="2560" | |
| 32 | - inkscape:window-height="1377" | |
| 33 | - inkscape:window-x="2552" | |
| 34 | - inkscape:window-y="-8" | |
| 35 | - inkscape:window-maximized="1" | |
| 36 | - inkscape:current-layer="g8" /> | |
| 37 | - <g | |
| 38 | - inkscape:groupmode="layer" | |
| 39 | - inkscape:label="Image" | |
| 40 | - id="g8"> | |
| 41 | - <path | |
| 42 | - style="stroke:none;stroke-width:1.33333" | |
| 43 | - d="m 171.32749,47.331392 v 4 c 12.46,9.0412 4.032,24.8744 4.91067,37.33333 0.60933,8.61773 4.48133,15.097198 3.08933,23.999998 -9.768,-1.0508 -17.05467,6.59294 -24,12.62774 -15.61067,13.5656 -27.25574,32.5056 -43.14654,45.2576 -6.2576,5.02133 -14.829587,3.284 -22.186787,2.55733 -13.6492,-1.34666 -27.88494,-0.35466 -41.33334,-3.10933 -15.8956,27.97867 -27.41493,57.04 -36,88 13.74,10.83867 35.03,39.1 53.33334,38.66667 l -1.33334,6.66667 -5.33333,-2.66667 2.66667,14.66667 -4,-2.66667 -1.33334,6.66667 h 6.66667 l -1.33333,25.33333 h -1.33334 l -5.33333,-8 c 1.2756,13.55867 6.9132,37.864 2.5692,50.54 -2.02707,5.91467 -8.7172,10.132 -13.23613,14.05067 -10.5992,9.19067 -20.984,18.72 -31.99974,27.40933 l 25.33334,69.33334 h 1.33333 l 1.33333,-10.66667 17.33334,28 -2.66667,-5.33333 30.66667,4 1.33333,-6.66667 8,5.33334 -2.66667,-5.33334 c 33.997197,-5.85866 39.588927,35.29201 66.666667,47.46401 6.416,2.88533 16.03067,0.56933 22.66667,-0.79734 -0.58267,3.59734 -2.02267,9.11867 1.24133,11.92667 4.892,4.20667 11.732,-3.67467 17.35467,-1.764 14.21733,4.83467 19.67333,28.956 37.404,16.504 3.70133,14.85067 13.68667,53.584 34.496,53.04533 8.328,-0.216 10.988,-14.944 12.73867,-21.04533 5.44933,-18.99333 5.108,-41.92533 30.09866,-42.62667 20.65067,-0.58 53.08801,-5.31333 69.00801,10.92534 10.052,10.25333 8.66666,27.47066 15.73333,39.69333 2.42933,4.20133 6.73733,11.54 12.48667,11.09867 5.58133,-0.42934 9.27466,-8.18267 11.60533,-12.424 7.32533,-13.32667 10.54,-27.56534 11.16667,-42.66667 21.444,13.56267 26.752,-8.56 45.33333,-14.99333 5.74,-1.98667 13.94134,4.92 18.68,1.088 3.448,-2.788 0.0213,-8.492 -1.34666,-11.428 11.49466,1.196 36.74933,2.12 46.66667,-3.89067 13.856,-8.39867 22.12,-24.84267 33.33333,-36.10934 l -1.3333,-5.33333 41.3333,1.33333 c 1.3253,7.79467 6.2707,7.924 13.3333,8 v -6.66666 l 5.3334,5.33333 h 2.6666 c 10.9174,-18.93867 20.5987,-39.24933 27.5454,-60 2.1786,-6.504 6.8373,-15.692 5.808,-22.64934 -2.6627,-18.00933 -34.1347,-27.24133 -42.4814,-42.756 -4.5173,-8.39333 0.028,-24.07866 0.8147,-33.26133 1.5,-17.472 -3,-34.648 -2.5173,-51.99734 0.5226,-18.74133 33.68,-29.71733 42.796,-44.60933 3.9746,-6.49467 -1.3827,-16.932 -3.5227,-23.39334 -6.516,-19.676 -14.056,-42.93333 -26.5373,-59.73733 -4.7547,-6.40133 -10.9067,-4.772 -17.9054,-4.38667 -12.3666,0.68134 -33.6346,6.424 -45.12,1.564 -7.93997,-3.36133 -13.2693,-13.912 -18.93463,-20.06666 -12.87067,-13.98401 -27.26667,-25.57214 -41.10534,-38.47241 -15.90933,-14.829598 2.85333,-45.371998 -3.62133,-63.770668 -3.06134,-8.700397 -20.756,-14.264397 -28.552,-17.694131 -13.49867,-5.9384 -27.244,-11.378134 -41.33334,-15.735334 -5.22,-1.6144 -11.668,-4.60664 -16.88933,-1.585333 -16.16,9.350267 -22.56667,33.947998 -39.556,41.756398 -9.47467,4.3548 -27.90934,-1.2064 -38.22134,-1.7456 -15.03333,-0.786 -36.49067,6.88534 -50.54,2.33574 -5.92267,-1.9176 -10.304,-8.63254 -14.20267,-13.12894 -9.148,-10.549598 -18.88133,-20.827998 -27.25733,-31.999732 -31.076,6.852134 -58.048,21.650001 -86.66667,34.666672 m 297.33334,37.33333 v 1.33333 h -4 l 4,-1.33333 m 4,0 -1.33333,1.33333 1.33333,-1.33333 m -253.33334,1.33333 -1.33333,1.33334 1.33333,-1.33334 m 192.00001,44.000008 -1.33334,1.33333 1.33334,-1.33333 m 159.99997,136 h -1.3333 c -6.54533,-9.72 -13.10133,-19.16666 -22.67333,-26.17733 -6.84667,-5.01334 -14.52267,-8.54934 -22.66001,-10.896 -64.19333,-18.52 -94.28267,58.57733 -101.33333,109.07333 -4.65867,-12.744 -2.66667,-27.976 -2.66667,-41.33333 0,-26.22133 -0.096,-52.44534 0.003,-78.66667 0.048,-12.81733 0.236,-30.37867 10.848,-39.352 11.536,-9.75467 35,-9.336 49.14933,-7.74 50.02934,5.64267 90.66531,43.30267 90.66671,95.092 M 271.32782,319.3314 h -1.33333 c -8.73067,-49.15734 -41.592,-109.81467 -100,-92.31201 -8.24267,2.46934 -15.92267,6.46267 -22.66401,11.82 -8.93866,7.10267 -15.3944,16.20534 -21.336,25.82534 -14.76986,-48.75467 53.38134,-80.68134 90.66667,-85.05067 10.04267,-1.176 23.616,-4.14533 33.25467,0.0387 11.71067,5.08533 16.88267,22.98533 19.47067,34.34533 7.42133,32.56134 7.61067,72.44534 1.94133,105.33334 m -46.66667,-96.00001 c 4.99867,13.94667 17.988,23.35334 25.46001,36.00001 7.37733,12.484 9.68933,32.056 22.54,40 -3.912,-24.796 -18.472,-72.13734 -48.00001,-76.00001 m 200.00001,77.33334 c 10.88133,-7.50267 13.66933,-23.524 19.86667,-34.66667 7.652,-13.75733 22.06133,-25.04533 26.8,-40 -27.912,4.53067 -44.212,50.15067 -46.66667,74.66667 m -220.00034,-54.6667 v 1.33334 l -45.33333,8 c 5.248,-20.64801 30.736,-15.41734 45.33333,-9.33334 m 340.00002,22.66667 c -18.28667,-11.04667 -31.34001,-18.64267 -53.33334,-18.66667 19.048,-16.336 44.54933,-0.956 53.33334,18.66667 m -62.66667,-21.33333 -5.33334,8 5.33334,-8 m -330.66668,9.33333 v 9.33333 h -2.66667 l 2.66667,-9.33333 m 33.33333,10.66667 c -10.14666,28.372 -20.33733,64.79466 -3.732,93.332 23.412,40.23467 69.13467,19.88133 79.73201,-17.332 l -37.33334,12 c 7.55333,-29.95734 6.71333,-51.172 -5.33333,-80 l -4,1.33333 2.66666,-9.33333 c 25.18534,13.21066 33.43067,41.44266 45.02534,65.33333 5.99867,12.36134 12.33733,26.51467 2.584,39.25867 -4.64,6.06267 -15.18,9.508 -22.276,11.536 -21.07067,6.024 -63.98134,7.04267 -81.088,-8.61333 -13.28934,-12.164 -18.45467,-33.68534 -24.24534,-50.18134 l -4,1.33333 -5.33333,-17.33333 c 24.27867,-2.78133 33.91467,-33.52133 57.33333,-41.33333 m 328.00001,1.33333 c 13.32267,7.76533 22.768,16.98133 33.40401,27.99867 6.94666,7.19466 13.224,14.832 23.92933,13.33466 l 1.3333,1.33334 -5.3333,24 h -1.33333 l -2.66667,-9.33334 h -1.33333 c -8.71867,20.86401 -12.74534,50.58534 -37.33334,58.46534 -21.72,6.96133 -56.508,7.996 -77.33333,-1.67467 -10.91334,-5.068 -22.63334,-13.23333 -20.69734,-27.45733 0.92267,-6.78667 6.69067,-14.096 10.03067,-20 h 1.33333 c 0,8.11867 -0.77733,16.57467 3.116,24 22.06534,42.08667 74.49067,26.58933 82.21734,-16 l -33.33334,8 c 2.796,-20.556 10.612,-60.17867 -10.66666,-73.33334 l -2.66667,6.66667 h -1.33333 c 3.22666,-14.35067 15.708,-19.72133 27.70266,-8.79467 6.95467,6.336 9.74534,15.98934 12.044,24.79334 0.59067,2.264 2.49067,13.96 7.14667,10.308 8.94933,-7.02 -8.35467,-33.472 -8.22667,-42.30667 M 215.32749,311.3314 c -13.772,4.168 -8.14267,19.588 -8,30.66666 -7.48933,-6.38666 -6.66267,-16.27333 -6.66667,-25.33333 -18.06266,-3.052 -5.47733,-24.708 8.75467,-13.59333 2.832,2.212 4.33867,5.13333 5.912,8.26 m 261.33334,1.33333 v 2.66667 c -12.91733,5.19066 -8.896,15.32533 -6.66666,26.66666 -5.38134,-3.632 -15.92667,-20.34133 -17.05334,-26.78266 -1.472,-8.40667 8.44,-10.26267 14.36134,-7.90534 3.31866,1.32267 6.33733,3.48134 9.35866,5.35467 m -413.333337,2.66667 -1.33333,1.33333 1.33333,-1.33333 m -6.66667,13.33333 4,2.66667 v -1.33334 l -4,-1.33333 m 18.66667,180.00001 v 4 h -1.33333 l 1.33333,-4 m 349.333337,62.66667 c 0,17.77733 -2.26,32.136 -10.66667,48 -9.4,-5.81067 -23.30933,-33.76267 -13.44266,-43.90267 5.54,-5.69333 16.94133,-4.096 24.10933,-4.09733 m -145.33334,4 -8,46.66666 c -16.29333,-9.21866 -22.78666,-30.32 -24,-48 l 32,1.33334 m 124.00001,17.33333 v 10.66667 h 1.33333 z" | |
| 44 | - id="path972" /> | |
| 45 | - <path | |
| 46 | - style="fill:#ffffff" | |
| 47 | - id="path1031" | |
| 48 | - d="m 171.07018,314.24465 c 1.43533,2.55362 3.43647,4.86204 6.02366,6.31063 15.51628,8.6877 9.94595,7.11918 18.45758,9.09921 2.9676,0.68502 6.00726,0.33711 9.00575,0.4851 0.51777,0.0256 1.97943,0.5002 1.53847,0.22762 -12.3291,-7.62122 -8.46459,-9.40075 -13.47067,-6.74985 -0.81161,0.51959 -1.60672,1.06589 -2.43482,1.55876 -4.52165,2.69122 -9.22362,5.08101 -13.79908,7.67767 -5.00626,2.84116 -5.79872,3.37745 -10.6807,6.35696 -3.21994,2.00581 -5.34709,3.29487 -8.43672,5.34334 -0.93485,0.61982 -1.86152,1.25236 -2.77524,1.90291 -0.53177,0.3786 -1.12785,0.69454 -1.56007,1.18372 -0.23297,0.26368 -0.67179,0.8249 -0.36192,0.99157 4.80104,2.58231 9.91238,4.5407 14.86857,6.81105 12.64758,-5.66419 24.4458,-13.03247 36.15953,-20.38753 8.83007,-5.82628 17.49966,-11.90043 26.13449,-18.01368 0.12258,-0.10977 0.51254,-0.25112 0.36775,-0.32931 -4.67223,-2.52289 -9.13452,-5.66802 -14.23142,-7.15676 -1.20748,-0.35269 -2.10539,1.37742 -3.14865,2.08026 -4.89213,3.29582 -9.54755,6.82945 -14.1897,10.46637 -9.49744,7.36804 -17.9992,15.83825 -26.01509,24.76432 -6.30592,7.59525 -8.14218,8.23949 10.2995,13.47521 1.78331,0.50629 3.3167,-1.66017 4.9328,-2.56834 5.73793,-3.22441 9.53881,-5.73893 15.19815,-9.3143 13.24893,-8.54994 25.96694,-17.90399 37.94901,-28.15332 1.04824,-0.91395 2.1079,-1.81498 3.14472,-2.74187 0.79084,-0.70699 1.56569,-1.43176 2.33769,-2.15926 0.53118,-0.50056 1.12786,-0.94513 1.57156,-1.52464 0.198,-0.25861 0.59124,-0.77034 0.30618,-0.92788 -14.71815,-8.13451 -10.1663,-9.48815 -17.93001,-5.21701 -11.41265,6.6473 -22.20287,14.27124 -32.75212,22.19736 -10.66088,8.29117 -20.89797,17.10296 -30.98403,26.07851 -0.98028,0.86051 -2.03371,1.64422 -2.94083,2.58153 -5.39678,5.57637 8.13193,7.60867 14.30926,9.09646 2.00094,0.48192 3.79185,-1.6031 5.66556,-2.45469 5.07424,-2.30622 9.60823,-4.57573 14.62099,-7.03268 11.14435,-5.70338 22.44245,-11.13252 33.38489,-17.22113 5.46451,-0.58006 14.20537,-1.41277 -14.20952,-6.74516 -1.73723,-0.32601 -2.8735,2.0593 -4.29587,3.10863 -5.86769,4.32876 -11.61624,8.8152 -17.40775,13.24456 -0.88721,0.84917 -1.97162,1.53157 -2.66162,2.54751 -0.28753,0.42334 -0.56545,1.30065 -0.10868,1.53142 5.12688,2.59017 10.33852,5.33462 15.9671,6.48047 1.71551,0.34924 3.17757,-1.47245 4.73846,-2.26529 4.90381,-2.49085 6.99171,-3.77194 11.81268,-6.53639 1.94264,-1.1845 3.88822,-2.3642 5.82792,-3.55351 4.47851,-2.74595 8.99301,-5.47352 13.30943,-8.47306 0.65181,-0.45295 2.58101,-1.00805 1.90658,-1.4266 -4.79779,-2.9775 -10.29254,-4.64766 -15.43882,-6.97148 -11.72135,5.99277 -22.91179,12.94688 -34.23961,19.6353 -6.60264,3.95287 -13.30201,7.72362 -20.18434,11.16672 -9.33663,4.8581 -9.99423,3.7481 8.12157,11.42393 0.83219,0.3526 1.70531,-0.59973 2.5527,-0.91405 1.02537,-0.38033 2.04352,-0.77986 3.06527,-1.1698 6.88738,-3.16305 13.93717,-5.90731 21.04709,-8.52021 4.06204,-1.52457 8.09972,-3.2178 12.32549,-4.25606 1.68407,-0.41377 2.88582,-0.56931 4.57605,-0.83571 -1.4108,-2.79602 3.18988,0.90932 2.44908,0.52434 -4.45476,-2.31509 -8.49787,-5.5392 -13.24768,-7.16519 -0.90867,-0.31106 -1.4379,1.27434 -2.1802,1.8838 -2.94314,2.41647 -3.50484,2.75165 -6.63769,5.0276 -6.26617,4.40862 -12.714,8.5456 -19.11454,12.75349 -4.45769,4.93043 -2.50115,2.00475 11.93444,10.09798 0.63232,0.3545 1.41442,-0.31883 2.11844,-0.49163 2.98621,-0.73295 5.93525,-1.60178 8.89459,-2.4344 5.27098,-1.7298 10.76117,-2.53812 16.20623,-3.48887 4.60406,-0.82453 9.1585,-1.85176 13.62925,-3.22747 3.11796,-1.15071 6.24676,-2.27099 9.18927,-3.82602 1.66601,-0.96562 3.51589,-1.59353 5.16478,-2.58761 0.59316,-0.3576 1.14403,-0.78107 1.71659,-1.1708 1.74687,-1.35545 2.65746,-2.35985 3.62271,-4.37357 0.84126,-2.07386 1.06124,-4.30847 1.19133,-6.52137 0.0789,-1.7605 0.0646,-3.52332 0.0576,-5.28505 0.42359,-6.7192 3.03353,-1.77248 -13.2615,-9.57841 -0.44856,-0.21487 -0.88179,0.46044 -1.3207,0.69441 -0.54734,0.29178 -1.09201,0.58855 -1.63801,0.88282 -3.72432,2.34742 -7.18224,5.08426 -10.82303,7.54849 -3.13256,2.27465 -6.59511,3.9843 -10.07352,5.64923 -2.68913,1.31556 -5.50527,2.2938 -8.12552,3.76052 4.28068,2.88035 8.26686,6.25599 12.84202,8.64104 0.64153,0.33443 1.30296,-0.62985 1.9647,-0.92223 4.26541,-1.8846 1.57695,-0.53142 6.36978,-2.80944 1.13947,-0.54159 6.36523,-3.15514 7.52569,-3.73405 2.81628,-1.45312 5.68135,-2.8072 8.50641,-4.24271 0.33246,-0.16893 1.29943,-0.30491 0.99237,-0.51652 -12.71423,-8.76181 -8.74434,-10.10314 -15.04117,-6.92273 -6.79382,3.53204 -13.74745,6.74262 -20.52354,10.31127 -7.5434,3.95521 -15.43438,7.12357 -23.44161,9.9938 -4.93666,1.93963 -10.00951,3.44204 -15.13808,4.76283 -0.61961,0.37578 -4.11298,0.37957 -4.53103,0.75257 -0.23898,0.21323 -0.7467,0.65064 -0.48212,0.83114 4.26858,2.91201 8.88087,5.28514 13.32131,7.92771 3.06017,-0.26036 6.1004,-0.66469 9.16565,-0.88282 3.5349,-0.27157 7.08772,-0.15372 10.62077,-0.41487 2.34311,-0.20178 4.68416,-0.42837 7.03502,-0.51429 1.98063,0.0101 3.95881,-0.2504 5.90142,-0.62775 1.98537,-0.26974 3.8929,-0.84394 5.80038,-1.43236 1.70419,-0.58082 3.50678,-0.50524 5.26603,-0.74464 2.53961,-0.30944 5.00901,-1.04243 7.50092,-1.60707 1.79673,-0.39005 3.62652,-0.69197 5.40419,-1.16894 0.77169,-0.20705 0.73152,-0.48877 0.6584,0.0322 -15.02092,-10.25008 -8.42834,-9.62098 -17.23578,-6.65309 -1.91185,0.77525 -3.84198,1.48065 -5.76047,2.23372 -2.1782,0.94213 -4.52971,1.33577 -6.77528,2.06463 -1.92286,0.62993 -3.95096,0.6523 -5.85054,1.34594 -1.43592,0.6249 -2.95828,0.80537 -4.50244,0.90949 -1.69973,0.41292 -3.45038,0.54237 -5.19138,0.64517 -1.88828,0.13475 -3.82163,0.27962 -5.61684,0.9172 -0.0211,-0.0295 -0.0943,-0.10741 -0.0634,-0.0885 4.4075,2.68861 8.5688,5.82092 13.20029,8.10215 0.50696,0.2497 0.95543,-0.60426 1.44328,-0.88951 2.47696,-1.44829 4.88574,-2.95444 7.29325,-4.51881 4.43353,-2.70197 8.70382,-5.65959 12.75546,-8.90627 1.32906,-0.98398 2.49335,-2.14207 3.62575,-3.33846 0,0 -12.90598,-7.87335 -12.90598,-7.87335 v 0 c -1.10475,1.13152 -2.24531,2.21955 -3.51853,3.16535 -4.0487,3.17935 -8.28306,6.10197 -12.67783,8.78408 -1.64902,1.06545 -3.39232,2.21147 -5.07695,3.22299 -0.72249,0.43381 -1.46465,0.83405 -2.19248,1.25885 -0.47497,0.27721 -1.81706,0.47105 -1.41578,0.8471 10.42477,9.76936 7.5619,11.33669 12.0127,8.76713 1.78097,-0.40206 3.61469,-0.5307 5.43243,-0.65354 1.74871,-0.15261 3.47842,-0.41518 5.21287,-0.6753 1.60693,-0.20434 3.15791,-0.59502 4.6777,-1.16108 1.93936,-0.48218 3.95267,-0.655 5.84512,-1.338 2.30159,-0.64532 4.63,-1.21698 6.8368,-2.15547 1.91913,-0.72053 3.81715,-1.49554 5.74076,-2.20447 8.62643,-3.25519 5.68409,-1.80659 -7.41768,-10.83793 -0.57951,0.42283 -1.36317,0.47509 -2.05091,0.62728 -1.43747,0.31811 -2.87464,0.63797 -4.31681,0.93429 -2.44658,0.58032 -4.88995,1.23651 -7.40174,1.46555 -1.83742,0.16771 -3.67129,0.3449 -5.42332,0.97071 -1.86505,0.56113 -3.76192,0.9855 -5.68941,1.27195 -1.92476,0.2718 -3.85014,0.48183 -5.79717,0.48098 -2.35121,0.13789 -4.69492,0.37057 -7.04479,0.52789 -3.54525,0.15356 -7.09771,0.15487 -10.63737,0.43806 -3.05823,0.25769 -6.10045,0.66447 -9.16265,0.87861 4.10095,2.6678 8.35865,5.10888 12.30286,8.00339 0.22243,0.16323 -0.50266,0.42091 -0.45351,0.69239 0.0141,0.0781 4.37054,-0.75142 4.58956,-0.83694 5.16506,-1.33006 10.23089,-2.98806 15.21856,-4.88087 8.04727,-2.92727 15.97342,-6.16211 23.55564,-10.16528 6.79245,-3.54284 13.74759,-6.75919 20.5298,-10.32082 6.1694,-3.46088 3.5939,-1.2335 -10.25015,-9.64727 -0.31437,-0.19106 -0.65421,0.33667 -0.98284,0.50202 -3.77307,1.89844 -0.71516,0.34579 -5.12115,2.54028 -1.12963,0.56264 -2.25547,1.13287 -3.38321,1.69931 -3.64669,1.81381 -7.27715,3.65196 -11.01842,5.26635 -0.92249,0.39807 -1.84114,0.80968 -2.78853,1.14419 -0.5191,0.18329 -2.01711,0.0453 -1.60091,0.40566 3.71695,3.21784 7.99353,5.72576 11.99029,8.58864 2.63436,-1.37598 5.4429,-2.34498 8.08645,-3.70104 3.53151,-1.71393 7.01231,-3.53031 10.19911,-5.84269 3.562,-2.48006 7.00535,-5.14327 10.72295,-7.3938 0.5256,-0.26341 1.03573,-0.56029 1.57681,-0.79022 0.34553,-0.14683 1.37754,-0.11453 1.07482,-0.33657 -13.82664,-10.14164 -11.55582,-12.30727 -12.54762,-6.01864 0.009,1.73726 0.0275,3.47508 -0.0202,5.21196 -0.0821,2.02459 -0.20817,4.07984 -0.90984,6.00091 -0.81376,1.75352 -1.64737,2.5454 -3.20333,3.67747 -2.14295,1.42279 -4.50639,2.42113 -6.78341,3.60728 -2.91093,1.43792 -5.96998,2.53986 -9.03631,3.59764 -4.44558,1.29448 -8.96449,2.28607 -13.52651,3.07895 -5.48707,0.94315 -10.98138,1.90216 -16.29401,3.61552 -1.12137,0.3136 -4.98063,1.40492 -6.11919,1.67531 -0.86475,0.20537 -1.72806,0.45221 -2.61364,0.5278 -0.54137,0.0462 -2.04418,-0.50768 -1.62151,-0.16625 11.42704,9.23077 8.40436,11.68934 12.60187,7.50083 6.42684,-4.20992 12.88092,-8.38087 19.1554,-12.81757 2.49735,-1.83513 4.3837,-3.14306 6.72175,-5.16166 0.83724,-0.72285 3.09224,-1.40127 2.41497,-2.27578 -6.33848,-8.18436 -7.96984,-10.4425 -14.66541,-9.60946 -5.89378,1.07124 -11.51294,3.08172 -17.0598,5.32199 -7.10879,2.62566 -14.12733,5.44994 -21.05306,8.52907 -1.01221,0.37581 -2.01669,0.77314 -3.03664,1.12742 -0.81369,0.28264 -3.03652,0.14035 -2.46272,0.78279 11.80339,13.21539 6.99615,10.9296 11.92284,8.12062 1.31947,-0.7523 3.74865,-1.97655 5.01135,-2.62453 6.89121,-3.49042 13.61336,-7.28283 20.24024,-11.25236 11.27734,-6.67828 22.43811,-13.59569 34.1506,-19.49953 -3.31035,-3.15832 -6.16118,-6.88237 -9.93104,-9.47496 -0.63445,-0.43632 -1.23051,0.92684 -1.86559,1.36225 -1.15761,0.79367 -2.33243,1.56218 -3.51117,2.32412 -5.12389,3.31211 -10.35273,6.45766 -15.57115,9.61718 -4.38295,2.49441 -7.24864,4.21481 -11.74375,6.43131 -1.51803,0.74853 -5.44101,0.61334 -4.62479,2.09606 2.12153,3.85393 7.02265,5.3155 10.31252,8.23619 0.20792,0.18458 -0.98326,0.41742 -0.80971,0.20021 0.73463,-0.91941 1.71836,-1.60849 2.57754,-2.41273 5.78153,-4.42323 11.52997,-8.88866 17.38712,-13.21198 1.42972,-1.05531 5.0733,-1.52866 4.31223,-3.13445 -5.81826,-12.27593 -6.3744,-11.75565 -11.23511,-9.58703 -10.94552,6.03635 -22.20646,11.4862 -33.34269,17.15676 -4.22937,2.06502 -10.22531,5.0476 -14.57292,6.96022 -1.81778,0.79967 -6.27975,0.40599 -5.52019,2.24089 1.70084,4.10876 6.3992,6.24034 10.07003,8.75027 1.49441,1.02179 0.88047,-9.44405 3.83393,-3.84665 10.06538,-8.93869 20.2702,-17.72751 30.91727,-25.97198 10.50559,-7.86626 21.24325,-15.45041 32.62871,-22.00546 7.36761,-3.8532 3.96829,-0.86418 -8.13522,-10.03306 -0.061,-0.0462 0.28008,-0.0735 0.22901,-0.0165 -0.47916,0.5348 -1.02792,1.00305 -1.55236,1.49352 -0.76503,0.71545 -1.53338,1.4276 -2.31581,2.12398 -1.03519,0.92135 -2.09105,1.81921 -3.13658,2.72881 -11.95628,10.17483 -24.62464,19.47989 -37.83252,27.9695 -4.36546,2.74513 -10.44576,6.69692 -15.10959,9.17839 -1.49209,0.79389 -5.49167,0.65909 -4.61365,2.10329 2.38531,3.9234 6.89231,6.21587 10.96295,8.34018 1.93356,1.00905 0.87576,-8.92908 3.97258,-5.19905 7.97197,-8.85114 16.43325,-17.24046 25.87613,-24.53933 4.62049,-3.60275 9.25775,-7.09819 14.12437,-10.36353 1.0398,-0.69767 3.88038,-1.05567 3.13843,-2.06435 -8.3794,-11.39171 -7.52997,-11.87727 -11.60778,-8.80552 -8.62141,6.08752 -17.25943,12.16238 -26.08925,17.94303 -11.65263,7.29252 -23.38273,14.63386 -36.00522,20.15284 3.76123,2.73954 7.65708,5.30322 11.28369,8.21863 0.17317,0.13921 -0.64943,0.598 -0.45933,0.48299 0.54064,-0.3271 1.0066,-0.76466 1.52415,-1.12721 3.62633,-2.54031 7.40811,-4.8487 11.16951,-7.18018 4.99607,-3.03314 5.5246,-3.4057 10.64708,-6.30516 4.6037,-2.6058 9.38129,-4.97264 13.86718,-7.78308 0.88771,-0.55616 1.72092,-1.19491 2.58138,-1.79236 0.50182,-0.59773 2.09084,-1.27701 1.50547,-1.79319 -3.87871,-3.42027 -8.57482,-5.79813 -13.05103,-8.38775 -0.47089,-0.27242 -1.08498,-0.086 -1.62866,-0.10489 -2.94034,-0.10204 -5.89671,-0.0548 -8.77687,-0.79164 -6.84974,-2.01396 -3.16404,-1.19536 7.6358,5.56497 0.6805,0.42597 -1.44013,-0.72013 -2.10585,-1.16884 -1.12844,-0.76059 -2.34725,-1.94327 -3.22517,-2.98418 -0.37019,-0.43891 -0.67583,-0.92844 -1.01374,-1.39266 0,0 -13.77387,-6.23173 -13.77387,-6.23173 z" /> | |
| 49 | - <path | |
| 50 | - style="fill:#ffffff" | |
| 51 | - id="path1033" | |
| 52 | - d="m 232.73967,343.98425 c -0.16239,-4.21792 -0.38636,-8.43294 -0.53272,-12.65164 -0.17653,-6.95973 -1.85631,-4.87751 -13.29234,-10.63359 -0.04,-0.0201 -3.53227,3.33793 -3.78645,3.58181 -1.99028,1.91133 -4.00381,3.86285 -6.2969,5.4174 -1.06135,0.71952 -1.93388,1.15314 -3.0558,1.75665 -1.95113,0.78708 -4.0813,1.77702 -6.2486,1.72206 -0.41992,-0.0106 -0.83194,-0.11686 -1.24792,-0.1753 -0.48134,-0.10515 -0.97296,-0.17113 -1.44404,-0.31546 -1.88325,-0.57698 -3.69696,-1.49191 -5.50071,-2.26758 -2.1798,-0.9374 -2.2769,-0.95062 -4.52683,-1.82148 -4.44489,-1.59935 -9.05198,-2.51641 -13.72931,-3.06968 -0.51119,-0.087 -1.04311,-0.0927 -1.53355,-0.26109 -0.28832,-0.099 -1.01299,-0.67747 -0.75025,-0.5229 4.24182,2.49551 8.37529,5.17918 12.68969,7.54702 0.17035,0.0935 0.32998,-0.20543 0.49213,-0.31253 0.47218,-0.31192 0.93292,-0.64129 1.41076,-0.94447 0.64704,-0.41054 1.30876,-0.79748 1.96314,-1.19621 5.32786,-2.88263 10.80956,-5.47437 16.22858,-8.18209 4.78718,-1.95989 9.10461,-4.78507 13.46258,-7.52548 2.62671,-1.56346 5.19283,-3.22119 7.80177,-4.81406 1.1227,-0.93327 3.56593,-1.49092 4.53824,-2.47123 0.16716,-0.16853 0.24952,-0.40389 0.37428,-0.60583 -4.23667,-2.89754 -8.32988,-6.01688 -12.71002,-8.69261 -0.29187,-0.1783 -0.64392,0.2312 -0.96946,0.33612 -2.15826,0.69562 -4.30644,1.33446 -6.42473,2.15203 -4.29152,1.63988 -8.41307,3.66717 -12.6077,5.53018 -4.82936,2.0624 -9.56932,4.30241 -14.24575,6.68873 -3.90778,2.11644 -7.73683,4.3642 -11.5033,6.72178 -1.61315,0.98308 -3.112,2.13675 -4.70891,3.14424 -0.37871,0.23893 -0.78785,0.42969 -1.15472,0.68643 -0.77674,0.54356 -1.56314,1.30623 -2.2582,1.94248 -1.3674,1.53824 -3.49987,3.52541 -3.14491,5.83895 0.0542,0.35357 0.23681,0.67509 0.35521,1.01264 4.611,2.65737 9.10124,5.53626 13.833,7.9721 0.39531,0.2035 0.88276,-0.1075 1.32296,-0.17003 2.35331,-0.33431 4.41661,-1.16882 6.57287,-2.14245 4.66944,-2.08131 9.10602,-4.67138 13.44997,-7.35827 4.57957,-2.99451 9.14835,-6.0171 13.558,-9.25944 2.82993,-2.279 5.77344,-4.40642 8.73187,-6.51307 1.93406,-1.45357 3.91458,-2.83753 5.84743,-4.29188 1.50721,-1.13725 2.80699,-2.51181 4.20715,-3.77294 0.53578,-0.55497 1.54467,-0.85008 1.93694,-1.55022 2.9445,-5.25545 -8.98814,-6.22301 -12.95355,-8.48667 -2.46378,1.36153 -4.50197,3.20247 -6.39761,5.26251 -0.39857,0.48953 -5.58042,6.12839 -4.84005,6.67322 4.2012,3.09163 8.7864,5.62424 13.1796,8.43636 3.68207,-1.12239 6.2577,-3.8927 8.89656,-6.52432 0.0606,-0.0662 1.62195,-1.787 1.8513,-1.9885 0.12384,-0.10881 0.28737,-0.16161 0.43105,-0.24242 -4.39526,-2.2892 -8.63135,-4.91417 -13.18578,-6.86759 -0.34145,-0.14645 -0.2515,0.70012 -0.40601,1.03799 -0.22397,0.48975 -0.48546,0.96145 -0.72819,1.44218 -0.86092,1.6686 -2.0156,3.14343 -3.23225,4.56183 -0.24595,0.28673 -0.47983,0.58649 -0.7558,0.84446 -0.0265,0.0247 -0.071,-0.11872 -0.0392,-0.10134 4.40101,2.40648 8.77951,4.85392 13.16926,7.28089 1.46759,-2.4309 3.48191,-4.44214 4.9583,-6.86589 1.06137,-2.32718 0.93692,-4.83266 0.0823,-7.19189 -2.80578,-4.83762 -12.6773,-7.80282 -14.85026,-8.84928 -0.31655,-0.15245 -3.53109,1.16694 -3.87595,1.30947 -4.47781,1.99153 -8.79044,4.33462 -13.22011,6.43083 -4.21998,2.0963 -8.51473,4.03792 -12.87256,5.82964 -2.77042,1.16583 -5.6843,1.88722 -8.58772,2.62102 -2.80453,0.92069 -5.70723,1.44571 -8.56832,2.15075 -2.29694,0.62103 -4.58347,1.319 -6.66594,2.49128 -0.12684,0.0518 -0.49398,0.0785 -0.38052,0.15532 4.23064,2.86275 8.3752,5.89525 12.88037,8.30297 0.43136,0.23053 0.84219,-0.49762 1.26527,-0.74304 2.97907,-1.72813 5.83126,-3.60518 8.70066,-5.51101 5.75823,-3.98884 11.70446,-7.70199 17.74697,-11.24239 2.80788,-1.4419 5.6684,-2.82242 8.61115,-3.96542 2.70305,-1.02336 5.36367,-2.15739 8.10939,-3.06346 1.86988,-0.63702 3.74282,-1.3128 5.66466,-1.77909 1.44074,-0.18382 2.82028,-0.62291 4.25689,-0.83578 0.96481,-0.11847 1.939,-0.1301 2.9096,-0.15324 0.20012,-0.005 0.40765,-0.0685 0.60035,-0.0143 0.0912,0.0256 0.11119,0.15329 0.16679,0.22994 -12.44464,-7.68644 -11.33159,-11.89752 -15.32577,-3.63822 -0.92044,2.08126 -1.91653,4.06144 -3.30388,5.86997 -1.67673,1.99068 -4.14201,2.87169 -6.44358,3.91089 -5.85047,2.07862 -6.41927,-0.30481 11.67311,7.81678 0.39119,0.1756 0.56205,-0.64848 0.86246,-0.95446 0.8056,-0.82054 1.65794,-1.59387 2.48089,-2.39702 2.13255,-1.90308 3.73829,-3.81663 4.58765,-6.56018 0.51997,-2.17666 0.50849,-4.42627 0.51403,-6.65016 -0.0365,-1.98098 0.0282,-3.96596 -0.0874,-5.94446 -0.0469,-1.31854 -0.54398,-2.4502 -1.18941,-3.56455 -11.86794,-7.43097 -10.44561,-9.61528 -19.54019,-6.64195 -2.90504,1.06197 -5.64842,2.5092 -8.47323,3.76296 -2.82972,1.2514 -5.55757,2.71314 -8.35105,4.03951 -2.73802,1.46585 -5.62337,2.6416 -8.46532,3.89092 -2.93432,1.44932 -5.95304,2.71572 -8.93136,4.07021 -2.61697,1.18126 -5.17726,2.46451 -7.68626,3.85548 -0.43106,0.29155 -1.18643,0.74935 -1.55054,1.17364 -3.52989,4.11327 7.88695,6.33505 13.16143,8.67144 0.33506,0.14842 0.66895,-0.29947 1.00343,-0.44921 1.5692,-0.85195 3.27529,-1.39841 4.82461,-2.29156 0.80789,-0.46573 1.57825,-0.99384 2.37385,-1.48028 2.34583,-1.58221 4.30424,-3.63646 6.41685,-5.49806 1.46344,-1.30423 2.65707,-2.81893 3.34364,-4.6575 0.25683,-1.25844 0.81728,-2.81572 0.29801,-4.09133 -0.10581,-0.25993 -0.31332,-0.46569 -0.46998,-0.69854 -4.55348,-2.53758 -9.0634,-5.15497 -13.66045,-7.61274 -0.17399,-0.093 -0.39472,-0.015 -0.59186,-0.007 -0.12411,0.005 -0.47422,-0.0219 -0.36984,0.0455 4.24259,2.73583 8.50906,5.4383 12.85552,8.0059 0.073,0.0431 0.4994,-0.37586 0.75985,-0.61595 1.05025,-1.01942 1.86431,-2.28026 2.42797,-3.62806 0.31654,-0.52272 0.40322,-1.30266 0.96533,-1.64806 0.0979,-0.0602 0.21352,-0.0853 0.32028,-0.12794 0.13562,-0.0966 0.27123,-0.19324 0.40684,-0.28986 5.19693,-2.57523 10.31369,3.11931 -12.05308,-8.24825 -0.31398,-0.15958 -0.57926,0.4008 -0.86889,0.60121 -2.16543,1.33756 -4.16339,2.92389 -6.13481,4.52914 -1.66623,1.5478 -3.16695,3.26179 -4.80545,4.83609 -4.54417,4.78544 2.02375,3.79484 12.61783,9.20028 0.0449,0.0229 3.73072,-1.71529 3.99793,-1.84236 2.68136,-1.48749 5.35558,-2.96967 8.08898,-4.35972 1.77149,-0.97442 3.61795,-1.83865 5.50693,-2.55921 1.83273,-0.61603 3.6719,-1.20408 5.51169,-1.79859 1.74554,-0.54089 3.55893,-0.73243 5.34083,-1.11531 1.46277,-0.3253 2.95799,-0.3803 4.44952,-0.41387 1.62889,-0.0231 3.25805,-0.0161 4.88705,-0.0128 0.7882,0.19692 0.44532,0.0265 1.04462,0.47783 0,0 -11.91856,-9.30063 -11.91856,-9.30063 v 0 c -0.87451,-0.0778 -0.42265,-0.0541 -1.35592,-0.0645 -1.64103,8e-5 -3.28289,-0.005 -4.92286,0.0623 -1.54681,0.0866 -3.08424,0.26499 -4.59518,0.61963 -1.81956,0.37014 -3.67292,0.59739 -5.42732,1.241 -1.87635,0.58373 -3.73576,1.22084 -5.57977,1.8995 -1.8899,0.82 -3.77812,1.64933 -5.57518,2.66212 -2.72347,1.37718 -5.34241,2.93712 -8.06232,4.31931 -0.6843,0.3143 -2.00098,0.94025 -2.73657,1.19988 -0.32907,0.11614 -1.28367,0.058 -1.01018,0.27479 13.7453,10.89314 8.53254,11.61904 12.61923,7.42493 1.58097,-1.56706 3.0709,-3.22812 4.73039,-4.71521 1.96252,-1.57136 3.95718,-3.10421 6.08414,-4.44895 0.30082,-0.21807 1.18637,-0.41454 0.90245,-0.65421 -7.7916,-6.57739 -8.31092,-11.32876 -13.4836,-7.94085 -1.05349,0.71525 -1.69754,1.6303 -2.14331,2.82068 -0.43797,1.15738 -1.16584,2.19887 -2.07857,3.03463 -0.0545,0.0481 -0.92528,0.80376 -0.89054,0.83075 3.96643,3.08211 8.18217,5.82945 12.29744,8.70984 0.21262,0.14882 0.55613,0.0335 0.73193,0.26553 -4.11791,-2.3947 -8.23583,-4.78939 -12.35375,-7.18409 0.11825,0.0189 1.04835,0.11235 1.20019,0.31114 0.361,0.47262 -0.13437,2.13833 -0.19015,2.6962 -0.51158,1.63225 -1.70596,2.86555 -2.95481,3.99408 -2.02917,1.82599 -3.95843,3.79249 -6.27205,5.26938 -2.2578,1.37928 -4.6055,2.54372 -7.05381,3.5521 -5.58612,1.85709 -0.0223,-0.24329 11.68112,7.64689 0.40851,0.27541 -1.63103,1.30748 0.076,0.15975 0.2753,-0.18511 0.5646,-0.3485 0.84691,-0.52274 2.4821,-1.36587 5.04373,-2.57855 7.62031,-3.75523 2.97736,-1.34282 5.97456,-2.64164 8.92032,-4.05358 2.86232,-1.24381 5.72956,-2.48833 8.49579,-3.93692 2.76914,-1.35042 5.50943,-2.76014 8.32879,-4.00587 2.76516,-1.25474 5.47188,-2.67247 8.36843,-3.60722 1.26633,-0.36215 2.56448,-0.78342 3.89756,-0.80012 0.85192,-0.0107 0.82765,0.0752 1.53593,0.42768 -4.10255,-2.46371 -8.2051,-4.92742 -12.30765,-7.39113 0.87022,0.63506 1.41465,1.66666 1.57425,2.72657 0.29538,1.91362 0.0477,3.87804 0.15627,5.8075 0.009,2.09402 0.0596,4.20901 -0.31768,6.27765 -0.62923,2.46598 -2.34965,4.1958 -4.19368,5.88001 -0.32766,0.3196 -3.65762,3.49777 -3.50955,3.62853 10.25275,9.05415 7.97742,10.62804 13.96992,8.18094 2.5228,-1.13814 5.07757,-2.32734 6.8435,-4.54726 1.43052,-1.90044 2.50357,-3.95606 3.45643,-6.13263 3.80252,-7.93496 6.00519,-2.82532 -9.84222,-12.99277 -1.31575,0.30131 -2.7269,0.0687 -4.0426,0.43813 -1.44785,0.29793 -2.89095,0.62174 -4.3327,0.94508 -1.92223,0.5696 -3.83482,1.18028 -5.7206,1.8611 -2.74705,0.97129 -5.44361,2.07764 -8.158,3.13566 -2.96416,1.26301 -5.91996,2.56526 -8.73087,4.14719 -6.05698,3.565 -12.00387,7.32161 -17.79115,11.31094 -2.0647,1.36328 -4.22082,2.82155 -6.34676,4.10232 -0.74734,0.45023 -1.50867,0.8775 -2.2773,1.29035 -0.36773,0.19752 -1.45346,0.27595 -1.12997,0.53975 11.25958,9.18223 7.85353,10.77408 12.66198,8.30613 2.05871,-0.98645 4.25118,-1.63592 6.4617,-2.18772 2.86526,-0.69453 5.75463,-1.28469 8.57389,-2.15883 2.96319,-0.74043 5.89479,-1.61773 8.71335,-2.8061 4.36341,-1.83564 8.67479,-3.78933 12.91544,-5.89422 4.38121,-2.11024 8.66513,-4.43844 13.14946,-6.32484 0.79723,-0.27088 1.6527,-0.63379 2.51458,-0.67988 0.2792,-0.0149 1.07008,0.25495 0.83172,0.10879 -18.76731,-11.50759 -15.88476,-8.71313 -10.93879,-6.55684 1.1779,1.68282 1.33452,3.92655 0.61867,5.83 -1.51283,2.47276 -3.62336,4.50648 -5.0562,7.03708 4.23053,2.78584 8.38879,5.68466 12.6916,8.35752 0.11663,0.0725 0.19532,-0.19325 0.28736,-0.29514 0.26485,-0.29315 0.5285,-0.58756 0.78305,-0.88969 1.27543,-1.51383 2.4844,-3.08757 3.40139,-4.851 0.26271,-0.51611 0.54183,-1.02418 0.78813,-1.54831 0.2028,-0.43159 0.93409,-1.0235 0.55835,-1.31712 -12.01227,-9.38683 -10.01885,-11.64406 -15.33077,-5.90598 -1.79042,1.77947 -4.35818,4.82031 -7.00038,5.20786 -0.29162,0.0428 -0.58062,-0.10179 -0.87093,-0.15268 4.02282,2.65442 7.9953,5.38677 12.06847,7.96326 0.11339,0.0717 -0.15575,-0.27006 -0.0969,-0.39064 0.21338,-0.43739 0.56456,-0.79318 0.86224,-1.17819 1.14105,-1.47578 2.35791,-2.89629 3.60821,-4.2798 1.58596,-1.68233 3.44224,-3.73251 5.76525,-4.30599 -4.2131,-2.54932 -8.56527,-4.88182 -12.6393,-7.64798 -0.21311,-0.1447 0.33388,-0.3983 0.45002,-0.62823 0.0471,-0.0933 -0.15595,0.13926 -0.23661,0.20573 -0.55561,0.45787 -1.15351,0.8601 -1.69495,1.33579 -1.35883,1.24037 -2.64174,2.57235 -4.13464,3.65708 -1.919,1.45056 -3.90613,2.80573 -5.82514,4.25672 -2.96436,2.10935 -5.88464,4.27737 -8.74246,6.52975 -4.39397,3.19928 -8.92679,6.20958 -13.49223,9.15664 -4.30697,2.6285 -8.69454,5.17349 -13.33203,7.17839 -1.61461,0.66836 -3.19119,1.33514 -4.94353,1.54626 -5.13769,0.61898 -13.74732,-7.46153 9.72713,7.05026 -0.23995,-0.0713 -0.52011,-0.0631 -0.71985,-0.21402 -1.18523,-0.89541 1.85209,-3.97341 2.42794,-4.54589 2.44199,-2.14394 5.31075,-3.71203 7.98142,-5.55112 3.74114,-2.32298 7.54254,-4.54338 11.43148,-6.61142 4.66388,-2.36268 9.40496,-4.55199 14.20446,-6.62622 4.15951,-1.87582 8.27828,-3.84601 12.55452,-5.45065 1.64953,-0.59544 3.32117,-1.23265 5.04263,-1.59795 0.36175,-0.0768 0.73109,-0.18172 1.09941,-0.14867 0.18958,0.017 0.32738,0.19427 0.49107,0.29141 -4.04564,-2.7953 -8.09128,-5.59059 -12.13692,-8.38589 -1.49052,0.86633 -3.08899,1.54686 -4.54602,2.47941 -2.60577,1.60644 -5.19029,3.24617 -7.81092,4.82826 -4.32192,2.71934 -8.65468,5.41931 -13.38153,7.40097 -5.45576,2.72376 -10.99132,5.30639 -16.30537,8.29999 -0.95554,0.60573 -2.68974,1.62849 -3.56714,2.44843 -0.384,0.35884 -1.40022,0.87251 -1.00295,1.21659 7.44833,6.45106 8.6187,8.38054 15.11935,9.26037 4.61007,0.64895 9.14931,1.68107 13.50224,3.3662 3.83153,1.54046 7.61457,3.58519 11.77229,4.0085 2.81893,-0.10125 5.47716,-0.90361 7.97168,-2.23334 3.55204,-2.01968 6.71321,-4.56746 9.56112,-7.50291 0.0581,-0.0554 2.15763,-2.09521 2.47518,-2.27343 0.0932,-0.0523 0.3483,0.24732 0.25797,0.19025 -4.10953,-2.59651 -8.21085,-5.206 -12.31627,-7.80901 0.0837,0.25546 0.21511,0.49997 0.251,0.76638 0.0637,0.47319 0.0301,0.95446 0.0451,1.4317 0.13767,4.2041 0.32794,8.40601 0.55578,12.6062 0,0 13.47024,6.86343 13.47024,6.86343 z" /> | |
| 53 | - <path | |
| 54 | - style="fill:#ffffff" | |
| 55 | - id="path1035" | |
| 56 | - d="m 185.8578,280.25304 c 2.1647,-1.03493 4.30367,-2.1219 6.44766,-3.19929 1.27197,-0.62913 2.53847,-1.12777 3.94755,-1.32128 1.43773,-0.12953 2.88474,-0.10256 4.32689,-0.10046 1.74211,0.005 3.48422,0.0109 5.22634,0.0137 1.74919,0.003 3.49839,0.003 5.24759,0.003 1.44315,9e-5 2.8863,0 4.32945,-3e-5 1.33089,-4e-5 2.66179,-5e-5 3.99269,-5e-5 1.32776,0 2.65551,0 3.98326,0 1.32931,0 2.65862,0 3.98793,0 0.17489,0.0539 0.34978,0.10779 0.52467,0.16169 0,0 -12.10961,-9.05049 -12.10961,-9.05049 v 0 c -0.21526,8.9e-4 -0.43052,0.002 -0.64578,0.003 -1.32931,0 -2.65861,0 -3.98792,0 -1.32776,0 -2.65552,0 -3.98327,10e-6 -1.33091,1e-5 -2.66181,3e-5 -3.99271,7e-5 -1.44308,3e-5 -2.88616,9e-5 -4.32924,-2.5e-4 -1.74869,-5.5e-4 -3.49737,-10e-4 -5.24605,-0.005 -1.74413,-0.004 -3.48827,-0.0127 -5.23239,-0.005 -1.50245,0.0167 -3.01946,0.0159 -4.49505,0.33689 -1.46312,0.37423 -2.82923,0.98965 -4.17862,1.66268 -2.15021,1.10101 -4.33465,2.13332 -6.47881,3.24688 0,0 12.66542,8.25477 12.66542,8.25477 z" /> | |
| 57 | - <path | |
| 58 | - style="fill:#ffffff" | |
| 59 | - id="path1037" | |
| 60 | - d="m 517.46462,337.23029 c -2.56265,4.0164 -4.88261,8.17784 -7.31927,12.26999 -1.7766,3.09546 -4.32727,5.482 -7.18992,7.54762 -2.68734,1.95262 -5.15996,4.16998 -7.5556,6.46553 -1.38721,1.40056 -2.95482,2.59485 -4.53481,3.76485 -1.22963,0.91055 -2.3367,1.76135 -3.77132,2.29409 -5.09097,-1.74318 -1.08156,-0.40133 12.37559,6.98305 0.0258,0.0142 -0.004,-0.0636 0.0139,-0.0874 0.1837,-0.24854 0.37344,-0.49383 0.58333,-0.72068 0.75786,-0.81912 1.45438,-1.37948 2.30908,-2.12616 4.70831,-3.64505 10.04801,-6.34425 15.17505,-9.33458 2.03876,-1.27739 4.2988,-2.28601 6.19949,-3.78479 0.32219,-0.25406 0.62927,-0.53694 0.87598,-0.8648 0.15744,-0.20924 0.5329,-0.57258 0.31596,-0.71924 -4.29597,-2.90429 -8.86989,-5.37458 -13.30483,-8.06188 -3.57739,1.68493 -6.85132,3.90699 -10.27248,5.87217 -2.74604,1.62213 -5.6826,2.87125 -8.61663,4.10562 -2.65401,1.21151 -5.20027,2.67454 -7.58553,4.35282 -2.14447,1.40823 -4.28086,2.81275 -6.3615,4.31081 -1.40627,0.71348 -2.30929,1.77998 -2.62065,3.32068 0.81016,7.36832 12.11254,9.3282 18.52914,9.04592 2.50936,-0.31243 5.04454,-0.61344 7.40166,-1.56914 1.97441,-0.61026 2.76446,-1.84839 3.76012,-3.5379 1.07739,-2.05927 1.9625,-4.20328 2.99711,-6.28434 1.3492,-2.74011 3.16209,-5.22591 4.86933,-7.75101 1.18101,-1.65339 2.46708,-3.2623 3.46898,-5.03427 1.7824,-6.11494 -7.84664,-6.6416 -13.30612,-9.6205 -0.2292,-0.12506 -3.29355,0.22879 -3.63397,0.27387 -2.89195,0.30971 -5.62076,1.31686 -8.29068,2.41542 -2.82065,1.13197 -5.46628,2.58099 -8.06971,4.13143 -2.53638,1.22945 -4.83578,2.84226 -7.17486,4.3939 -1.3709,0.9908 -2.65439,2.08728 -3.75772,3.36967 -1.68219,1.5319 -3.2942,3.1344 -4.9593,4.68411 -1.45269,1.52497 -2.83742,3.11478 -4.17917,4.73738 -3.63798,5.37456 3.79556,4.99992 12.33474,9.71531 0.36277,0.20032 0.74858,-0.35639 1.13043,-0.51739 2.83931,-1.19711 0.0772,0.11931 3.2847,-1.4869 2.05827,-1.08132 4.03317,-2.30736 5.99544,-3.55117 0.47837,-0.30323 2.68062,-1.74462 3.32498,-2.03614 0.35437,-0.16032 0.74954,-0.2081 1.1243,-0.31215 5.21693,1.88088 0.36376,0.16898 -12.03101,-6.67223 -0.15964,-0.0881 0.50681,0.0961 0.47276,0.27524 -0.0452,0.23791 -0.50356,0.73864 -0.59892,0.8559 -1.01692,1.01725 -2.09668,1.99757 -3.23289,2.88304 -1.13366,0.77062 -1.78521,1.99288 -2.66487,3.00144 -0.4999,0.5181 -1.82578,2.76169 -0.85978,3.33392 4.5024,2.66709 9.1647,5.0545 13.74706,7.58175 2.23462,0.21341 4.48944,-0.29226 6.57794,-1.06304 0.183,-0.12081 0.39287,-0.20847 0.549,-0.36244 0.24654,-0.24315 0.89597,-1.50801 0.63823,-1.67783 -10.79379,-7.11175 -8.78038,-7.42141 -15.10327,-7.57337 -2.15751,-0.16966 -4.25743,0.16954 -6.33775,0.69945 -1.47207,0.22532 -2.86569,0.7409 -4.32815,0.98616 -1.4266,0.0627 -2.75764,0.56693 -4.1574,0.79381 -1.34956,0.0761 -2.77182,0.34036 -4.09815,0.0146 -0.19362,-0.0476 -0.39408,-0.0731 -0.58087,-0.14283 -0.44722,-0.167 -1.67781,-0.9967 -1.05789,-0.6456 20.98616,11.88561 15.60495,9.05661 11.16471,6.32842 -1.5196,-0.99201 -1.64758,-2.44567 -1.34593,-4.11665 0.81182,-3.10827 3.25991,-5.22887 5.61134,-7.23229 3.34059,-2.85841 7.1928,-4.9601 11.03557,-7.04621 4.20603,-2.0403 8.36157,-4.17644 12.43734,-6.46651 2.83986,-1.81873 5.72411,-3.57291 8.44219,-5.57327 1.02334,-0.93747 3.59337,-2.12394 4.07012,-3.46338 0.1339,-0.37621 0.1713,-0.78006 0.25695,-1.17009 -4.58492,-2.39842 -9.04567,-5.05089 -13.75477,-7.19526 -0.3497,-0.15925 -0.7063,0.30296 -1.06207,0.44814 -2.01467,0.82213 -3.9665,1.70254 -5.91589,2.67225 -3.65467,2.01385 -7.46814,3.79404 -10.77826,6.35973 -0.21249,0.34647 -0.5386,0.64517 -0.63747,1.03939 -1.30502,5.20313 13.83595,8.79595 14.32339,9.01748 0.61437,0.27922 1.34531,-0.12016 2.0121,-0.22411 1.77354,-0.2765 3.17125,-0.64859 4.92228,-1.06644 4.5451,-1.30218 8.81106,-3.4264 12.94588,-5.69043 1.97421,-0.92375 3.81889,-2.13756 5.873,-2.87661 1.77393,-1.01584 3.70407,-1.74284 5.64601,-2.36653 0.2903,-0.0996 0.5646,-0.27948 0.8709,-0.29874 0.14174,-0.009 0.48783,0.29097 0.36421,0.22107 -4.36595,-2.4686 -8.555,-5.27784 -13.09134,-7.41726 -0.35331,-0.16663 -0.51614,0.58655 -0.77843,0.87603 -1.08393,1.19626 -1.25521,1.36145 -2.40611,2.57297 -3.24235,3.30458 -6.48143,6.60383 -9.86178,9.77025 -3.28404,3.17757 -6.24306,6.66774 -9.44409,9.92313 -2.01291,2.02617 -4.18264,3.87968 -6.23082,5.8679 -1.9223,1.70425 -3.86185,3.39422 -5.88858,4.9743 -0.96722,0.65082 -1.83726,1.617 -2.96371,2.01994 -0.16514,0.0591 -0.37401,0.12202 -0.52514,0.033 -0.1206,-0.071 -0.22959,-0.47479 -0.10781,-0.4058 4.37015,2.47548 8.64001,5.12397 12.96002,7.68596 0.53794,-0.73321 1.39731,-1.28776 1.82599,-2.1042 2.47982,-4.72287 -10.9966,-8.27065 -13.75481,-9.82443 -3.01935,-0.16368 -6.00541,0.25868 -8.9999,0.5565 -3.33136,0.25062 -6.66491,0.53234 -9.97631,0.97952 -1.49207,0.0622 -2.91511,0.57342 -4.40233,0.66511 -0.31252,0.0193 -0.63635,0.0538 -0.93899,-0.0266 -0.22732,-0.0604 -0.79953,-0.49519 -0.59419,-0.3805 4.19573,2.34367 8.37764,4.712 12.56647,7.06801 -0.12644,-0.0812 -0.29432,-0.11966 -0.37932,-0.24357 -0.73362,-1.06945 0.52294,-2.95045 0.98506,-3.83506 1.54566,-2.92927 3.75683,-5.3607 6.04032,-7.72501 3.25479,-3.46003 6.89274,-6.53596 10.49318,-9.62639 3.01741,-2.51586 6.22504,-4.77774 9.2122,-7.33076 2.43349,-2.03466 5.14772,-3.71373 7.78144,-5.47881 7.74668,-5.34821 5.26549,-2.15496 -8.93837,-11.02194 -3.67679,2.35575 -7.28617,4.81658 -10.95286,7.1879 -3.60708,2.14166 -6.78641,4.82897 -9.89312,7.62168 -1.86144,1.77073 -3.57985,3.68242 -5.41099,5.4821 -1.46318,1.5278 -2.96981,3.07905 -4.72685,4.26969 -1.60606,1.0531 -3.23293,2.05513 -4.91002,2.98816 -1.48842,0.87657 -2.93256,1.85966 -4.62833,2.26668 -2.29749,0.73424 -4.56561,1.54956 -6.89065,2.19388 -0.21725,0.033 -1.44035,0.30317 -1.78598,0.0997 -0.1635,-0.0962 -0.41263,-0.60608 -0.24764,-0.51244 4.26109,2.41814 8.34115,5.14836 12.62053,7.53399 0.13307,0.0742 0.031,-0.30774 0.0903,-0.44808 0.14456,-0.342 0.34422,-0.658 0.51633,-0.987 2.37142,-3.19148 5.06218,-6.13906 7.75268,-9.063 2.78598,-3.28539 5.95741,-6.19831 9.12416,-9.10549 2.81779,-2.65813 5.52878,-5.42524 8.32563,-8.10749 2.35912,-2.2275 4.9302,-4.20401 7.51479,-6.15787 2.21682,-1.57455 4.70767,-2.64853 7.23257,-3.61266 2.59322,-1.05976 5.31693,-1.74942 7.95483,-2.67784 6.95804,-2.23836 5.26274,-0.70784 -10.5794,-8.877 -0.38723,-0.19968 -0.77405,0.40021 -1.15813,0.6059 -0.57211,0.30639 -1.14022,0.62019 -1.71034,0.93029 -5.10523,2.95137 -10.1952,5.93597 -15.4318,8.64977 -3.75271,2.05164 -7.48656,4.12742 -11.25821,6.14133 -2.71694,1.28113 -5.35381,2.71843 -8.01141,4.11619 -1.86867,0.9152 -3.66552,1.95908 -5.47922,2.98047 -1.72455,1.10921 -3.54098,2.13062 -5.05664,3.51931 -0.90463,0.82401 -2.04789,2.27228 -3.2307,2.66682 1.2635,-5.34205 -0.16936,0.0679 12.96906,7.26428 0.17418,0.0954 0.22262,-0.32904 0.32813,-0.49729 0.11736,-0.18717 0.22653,-0.37935 0.34031,-0.56871 0.0591,-0.0985 0.27684,-0.23544 0.17847,-0.2947 -4.39251,-2.6466 -8.90136,-5.09514 -13.35205,-7.6427 -1.49531,0.85332 -2.7992,1.97088 -4.22888,2.92082 -4.9037,3.89088 -8.1625,-1.2946 11.33893,8.0153 0.21258,0.10149 0.0789,-0.46569 0.14649,-0.69135 0.0927,-0.30958 0.22227,-0.60693 0.33341,-0.91039 0.9684,-2.27791 2.17116,-4.41805 3.44801,-6.53272 1.83613,-2.66105 4.2652,-4.84603 6.6056,-7.05325 2.98662,-2.55678 6.31032,-4.67369 9.50952,-6.94733 2.50895,-1.86606 5.26592,-3.33396 7.90531,-4.98838 2.47853,-1.36936 5.0954,-2.41932 7.72677,-3.44606 3.54649,-1.27142 7.2593,-1.95785 10.88689,-2.95161 3.44504,-1.09078 6.90807,-2.11655 10.36057,-3.18415 1.57896,-0.50775 3.15215,-1.01407 4.66702,-1.69465 0.22148,-0.0995 0.40862,-0.34535 0.65075,-0.32727 0.13052,0.01 -0.0964,0.24337 -0.14462,0.36505 -4.28721,-2.811 -8.39676,-5.91369 -12.86162,-8.433 -0.33959,-0.19162 -0.65762,0.4197 -0.9957,0.61396 -2.34749,1.34885 -4.80653,2.53706 -7.25277,3.6926 -4.36193,1.75711 -8.68282,3.60481 -12.99058,5.49322 -4.10098,1.72429 -7.9938,3.8697 -11.98214,5.82283 -2.74984,1.24947 -5.53582,2.41306 -8.32154,3.58056 -2.33776,0.90325 -4.64698,1.81777 -6.82899,3.06087 -1.6298,0.95465 -2.93181,2.35203 -4.53805,3.32872 -1.22767,0.5776 -2.9154,2.1317 -4.32374,1.80128 -2.79977,-4.49808 -0.18699,-0.17007 12.40537,6.86221 0.13592,0.0759 -0.24331,-0.20851 -0.31037,-0.34901 -0.3676,-0.7701 -0.29072,-1.79017 -0.29405,-2.60894 0.27412,-1.91104 1.29442,-3.60878 2.49962,-5.08165 1.12269,-1.14938 2.65266,-1.66837 3.92767,-2.62115 1.45993,-0.82313 2.77198,-1.84586 4.11828,-2.83995 1.71524,-1.19027 3.37924,-2.44494 5.19567,-3.48489 2.614,-1.40322 5.09913,-3.01589 7.68692,-4.46278 2.75467,-1.40031 5.54523,-2.81309 8.46488,-3.83629 3.572,-1.39268 7.35073,-2.15702 11.06617,-3.06688 2.1504,-0.43081 4.24068,-1.17674 6.24799,-2.0536 1.84007,-0.61386 3.57214,-1.48886 5.38903,-2.15968 2.39709,-0.73828 4.7544,-1.60292 7.1525,-2.33873 2.13542,-0.69731 4.39264,-0.70615 6.57979,-1.09077 1.97808,-0.16946 3.86686,-0.73435 5.77371,-1.23803 2.24307,-0.87141 4.58412,-1.44007 6.8694,-2.18694 2.22625,-0.79063 3.47755,-2.45154 4.24552,-4.59789 0.49708,-1.74364 0.66891,-3.12814 -0.47933,-4.63119 -3.93217,0.9071 -9.47789,-8.04187 -15.22808,-8.02582 -0.90173,0.003 -1.78593,0.33746 -2.64717,0.56315 -2.16621,0.70967 -3.8994,2.23159 -5.77133,3.47988 -1.28133,0.95004 -2.63044,1.80516 -3.90936,2.7585 -0.35558,0.26506 -0.71563,0.52642 -1.04581,0.82251 -0.19353,0.17354 -0.73737,0.43927 -0.51972,0.58138 4.38614,2.86373 9.04269,5.29022 13.56403,7.93533 2.86923,-1.07989 5.45916,-2.74922 7.90565,-4.5716 1.30728,-1.18837 2.73972,-2.28872 3.7411,-3.76068 0.69028,-1.09515 0.57253,-2.17863 0.22624,-3.35306 0,0 -13.75609,-6.27088 -13.75609,-6.27088 v 0 c 0.43127,0.58847 0.90259,1.25446 0.5011,1.95547 -0.93702,1.30814 -2.28597,2.27716 -3.47973,3.33299 -2.35429,1.69155 -4.84173,3.25548 -7.6231,4.14233 3.94379,2.81864 7.78552,5.78584 11.83137,8.45591 0.15408,0.10169 0.25521,-0.2681 0.39804,-0.38505 1.55286,-1.27142 3.28779,-2.30435 4.90031,-3.49462 1.73096,-1.16737 3.36424,-2.58064 5.46172,-3.01518 0.62992,-0.11694 0.95322,-0.2188 1.59062,-0.18113 4.62466,0.27329 16.94781,10.99054 -10.17189,-6.59562 1.74126,0.70663 1.64617,1.19544 1.27065,2.96111 -0.59489,1.92717 -1.5473,2.7829 -3.50056,3.38484 -2.28542,0.69029 -4.56126,1.40392 -6.83314,2.13394 -1.86368,0.50835 -3.75489,0.86129 -5.67334,1.09378 -2.26937,0.26543 -4.53808,0.57399 -6.71882,1.29155 -2.40682,0.79019 -4.81647,1.57331 -7.20528,2.41681 -1.80044,0.7305 -3.58032,1.50925 -5.40406,2.1821 -2.00043,0.75722 -4.01242,1.47936 -6.12371,1.87071 -3.75896,0.93132 -7.56641,1.75112 -11.16582,3.21306 -2.93909,1.16982 -5.82135,2.51337 -8.59563,4.03386 -2.57918,1.47872 -5.10173,3.05203 -7.70477,4.49029 -1.81837,1.12195 -3.53915,2.37807 -5.2793,3.6155 -1.32898,0.99565 -2.69698,1.91207 -4.09599,2.80484 -1.43419,0.93394 -3.04899,1.61757 -4.15253,2.98549 -1.3792,1.75296 -2.51107,3.72858 -2.9716,5.9322 -0.42607,6.72358 2.58546,13.89204 13.95641,11.7794 1.70484,-0.52484 3.23516,-1.48185 4.74126,-2.41844 1.50416,-1.05667 2.85238,-2.33425 4.47183,-3.22635 2.17907,-1.14349 4.47115,-1.99876 6.74447,-2.93278 2.80281,-1.1927 5.62687,-2.33761 8.37308,-3.65834 3.94777,-1.99116 7.87216,-4.0231 11.94217,-5.76243 4.31945,-1.89631 8.68307,-3.6801 13.01855,-5.53542 1.95914,-0.9522 3.90805,-1.926 5.82546,-2.96058 0.50397,-0.27193 0.99884,-0.56038 1.49829,-0.84052 0.31277,-0.17543 1.20979,-0.29179 0.93838,-0.52618 -3.74847,-3.2371 -7.92031,-5.94879 -11.88046,-8.92318 -1.22719,1.18526 -4.01355,1.49621 -5.55291,2.18899 -3.43466,1.07096 -6.88468,2.09121 -10.32725,3.13547 -3.67765,0.9652 -7.42888,1.70351 -10.98125,3.09056 -2.69832,1.05992 -5.36095,2.21186 -7.87351,3.66898 -2.70171,1.61585 -5.40577,3.22217 -7.95915,5.07237 -3.26329,2.2977 -6.63032,4.47577 -9.62334,7.12899 -2.43197,2.30605 -4.93949,4.60371 -6.81524,7.40375 -1.324,2.1933 -2.56344,4.41585 -3.58314,6.77064 -0.28262,0.72486 -0.78535,1.96378 -0.70523,2.0225 11.11095,8.14252 9.2911,11.16909 14.84179,7.04588 1.36792,-0.96609 2.68778,-2.00505 4.16026,-2.81433 -4.13289,-2.7284 -8.2024,-5.55535 -12.39869,-8.18521 -0.0147,-0.009 -0.14428,0.25714 -0.49677,0.8256 -0.14461,0.23321 -0.65593,0.52359 -0.4415,0.6948 11.86492,9.47378 8.42352,11.94948 12.84847,8.4416 1.25222,-0.84217 2.34972,-1.89283 3.41529,-2.95596 1.52098,-1.23338 3.20569,-2.25517 4.88718,-3.25333 1.81755,-0.98999 3.586,-2.06616 5.45951,-2.95033 2.66572,-1.38772 5.30731,-2.8214 8.0166,-4.1241 3.77769,-1.99676 7.47864,-4.12937 11.25287,-6.13319 5.2221,-2.75618 10.32081,-5.73451 15.44415,-8.66863 0.5681,-0.30701 1.13636,-0.61371 1.70429,-0.92104 0.39362,-0.213 1.51892,-0.34725 1.18035,-0.63995 -10.78271,-9.32163 -7.87059,-10.07916 -14.60754,-7.84877 -2.67927,0.86748 -5.3831,1.67329 -7.99425,2.73628 -2.62213,1.04815 -5.18008,2.27437 -7.45988,3.96169 -2.62589,1.99186 -5.21853,4.03187 -7.60812,6.30749 -2.7874,2.6763 -5.48264,5.44509 -8.30843,8.07963 -3.19517,2.93828 -6.36993,5.90573 -9.19109,9.21472 -2.77132,3.02267 -5.56044,6.06177 -7.9508,9.39909 -0.21109,0.3984 -0.44372,0.7861 -0.63328,1.19518 -0.17102,0.3691 -0.76758,0.88555 -0.44676,1.13567 8.73674,6.81144 8.79946,9.68501 15.48409,8.02983 2.31854,-0.69686 4.60328,-1.49915 6.91256,-2.22598 1.69473,-0.6526 3.23281,-1.61021 4.7984,-2.5221 1.71744,-0.96571 3.37745,-2.03192 5.0069,-3.13882 1.74325,-1.34381 3.35258,-2.85474 4.85028,-4.46755 1.79202,-1.80288 3.519,-3.67001 5.3695,-5.41483 3.06276,-2.73341 6.23067,-5.31534 9.76881,-7.42471 3.56377,-2.32805 7.04548,-4.81206 10.75925,-6.89388 -13.9328,-9.40918 -8.69904,-10.77058 -16.32658,-5.58395 -2.66559,1.80994 -5.43462,3.49608 -7.86903,5.61743 -3.01558,2.52213 -6.21582,4.80304 -9.21838,7.34065 -3.63424,3.12382 -7.29004,6.24902 -10.5699,9.75083 -2.36345,2.48192 -4.64404,5.04026 -6.25701,8.09181 -0.98025,1.86575 -1.87764,3.74138 -1.49605,5.91216 4.47978,2.60314 8.89349,5.32345 13.43933,7.80943 0.21105,0.11542 1.77486,-0.0717 1.89732,-0.0878 1.46508,-0.19215 2.91168,-0.51867 4.38649,-0.64271 3.31234,-0.38091 6.63266,-0.6784 9.95549,-0.95017 2.84468,-0.29162 5.80106,-0.75883 8.61293,-0.0364 -4.01164,-2.39741 -8.13559,-4.6162 -12.03493,-7.19225 -0.57156,-0.37759 1.09357,-0.4585 0.87991,-0.0213 -0.43108,0.88221 -1.47212,1.41354 -1.93076,2.28225 11.59732,8.66545 9.22916,11.9968 16.51576,6.29329 2.04341,-1.63486 4.03926,-3.33374 5.96812,-5.10217 2.06707,-1.98767 4.24691,-3.85399 6.25376,-5.90532 3.16428,-3.26173 6.1361,-6.70905 9.40961,-9.867 3.39505,-3.19207 6.64747,-6.51931 9.90836,-9.84623 0.25916,-0.27451 3.50217,-3.69601 3.39004,-3.79676 -9.96672,-8.9553 -7.81314,-10.57163 -13.77855,-8.49854 -1.94142,0.70676 -3.84608,1.49186 -5.68861,2.42977 -2.04138,0.89469 -3.9767,2.02298 -5.96838,3.02078 -4.06108,2.16276 -8.23356,4.22156 -12.71629,5.34929 -1.62184,0.34891 -3.11936,0.73218 -4.77701,0.8605 -0.61269,0.0474 -2.33845,-0.37035 -1.84358,-0.006 3.71143,2.73254 7.973,4.64748 11.74493,7.29587 0.25756,0.18084 -0.59442,0.45068 -0.8848,0.32936 -0.20091,-0.0839 0.22928,-0.37022 0.34392,-0.55533 3.31339,-2.409 7.02441,-4.18858 10.62438,-6.12136 1.49899,-0.72828 2.99183,-1.4498 4.55192,-2.04062 0.40737,-0.15427 0.8126,-0.31832 1.23226,-0.43509 0.27664,-0.077 1.09197,0.009 0.84932,-0.1441 -4.08456,-2.58476 -8.36799,-4.841 -12.55199,-7.26149 0.23631,-0.26817 0.46018,-0.54785 0.70893,-0.80451 0.0633,-0.0653 -0.085,0.16444 -0.15015,0.22795 -0.20966,0.20434 -0.43979,0.38689 -0.66838,0.5698 -0.49416,0.39542 -1.39427,1.05108 -1.89772,1.41656 -0.35135,0.25507 -0.70466,0.50742 -1.05699,0.76112 -2.69606,1.9333 -5.52364,3.67906 -8.3446,5.4215 -4.08556,2.2643 -8.25282,4.36573 -12.4297,6.4548 -3.91946,2.15218 -7.81879,4.36688 -11.20893,7.31806 -2.56409,2.24927 -5.13013,4.66762 -6.08973,8.05732 -0.12254,0.45699 -0.29555,0.90336 -0.36763,1.37098 -0.21991,1.42675 0.1583,3.14531 0.98221,4.31256 0.6152,0.57294 0.40284,0.43897 1.13778,0.86491 4.4268,2.56565 9.66767,7.64816 15.51758,8.2066 1.45832,-0.0239 2.92651,-0.0724 4.35816,-0.37589 1.38534,-0.3112 2.76572,-0.60664 4.17151,-0.81405 1.44323,-0.36329 2.89038,-0.69179 4.34497,-1.01042 2.00379,-0.416 4.03855,-0.52536 6.07966,-0.33812 5.72181,0.63545 5.81025,4.57384 -11.08516,-7.05781 -0.29961,-0.20627 0.46661,-0.55834 0.69003,-0.84538 0.0199,-0.0256 0.0697,-0.0944 0.0376,-0.0895 -0.14156,0.0215 -0.2695,0.0969 -0.40425,0.14532 -1.96016,0.55559 -4.04613,0.95541 -6.03614,0.31552 4.10167,2.38174 8.28145,4.63379 12.30502,7.14522 0.18644,0.11637 -0.43218,0.0811 -0.64597,0.1321 -0.13909,0.0332 -0.29574,0.20363 -0.41181,0.12012 -0.14738,-0.10602 0.68335,-1.16831 0.78671,-1.28219 0.79287,-0.96803 1.45595,-2.04923 2.51794,-2.76208 1.18285,-1.00593 2.39933,-1.99452 3.41218,-3.17648 0.54333,-0.72825 1.11914,-1.48461 1.24315,-2.42177 0.72239,-5.45934 -9.27368,-9.76863 -14.60467,-8.23205 -3.72115,1.5986 -6.82861,4.35869 -10.52914,6.02585 -0.79037,0.35684 -2.17456,1.06117 -3.10237,1.21728 -0.22721,0.0382 -0.8739,-0.22497 -0.68489,-0.0932 19.74632,13.76521 9.39646,11.90022 12.61258,7.28013 1.31409,-1.56503 2.64049,-3.12515 4.08106,-4.57625 1.69193,-1.58653 3.36292,-3.19567 5.0329,-4.80455 1.06326,-1.15314 2.25451,-2.16265 3.55446,-3.04299 2.30645,-1.56012 4.65827,-3.0362 7.13059,-4.32514 2.54667,-1.50998 5.18658,-2.83633 7.93981,-3.93423 2.59206,-1.0032 5.24439,-1.8682 8.03951,-2.05215 0.75935,-0.0307 1.59431,-0.11006 2.35351,0.0289 0.2913,0.0533 1.09313,0.43888 0.84307,0.28023 -4.01992,-2.55051 -8.1716,-4.89054 -12.1736,-7.46908 -0.12926,-0.0833 0.30923,-0.13404 0.45394,-0.082 0.0848,0.0305 0.0364,0.17647 0.0545,0.26471 -0.97645,1.73866 -2.26935,3.29571 -3.40716,4.92792 -1.74567,2.58014 -3.58934,5.12616 -4.97015,7.92547 -1.01904,2.04947 -1.90463,4.1522 -2.94347,6.19086 -0.97594,1.65484 -1.31084,2.04841 -3.20104,2.64489 -2.30959,0.69141 -4.70391,1.0518 -7.11217,1.15573 -1.14872,-0.0364 -1.5685,-0.0119 -2.68256,-0.18722 -0.36652,-0.0577 -0.73474,-0.11952 -1.08936,-0.22867 -0.27354,-0.0842 -1.02827,-0.49867 -0.78483,-0.34817 12.1521,7.51284 16.98049,7.11315 11.74075,7.44753 -0.64524,-0.69029 1.54481,-1.79198 1.90769,-2.06977 2.0632,-1.5008 4.23889,-2.83145 6.34049,-4.2778 2.35593,-1.56803 4.81573,-2.99305 7.42314,-4.10393 2.96865,-1.28918 5.94312,-2.576 8.71726,-4.2586 2.65138,-1.54923 0.9872,-0.56128 3.718,-2.22274 1.63477,-0.99462 3.31862,-2.11829 5.12482,-2.79026 0.3178,-0.11823 0.66178,-0.14818 0.99267,-0.22226 -4.06444,-2.69254 -8.12888,-5.38507 -12.19331,-8.07762 -0.0247,-0.0164 0.0948,0.0277 0.0743,0.0492 -0.22712,0.23843 -0.47605,0.45804 -0.74216,0.65199 -1.93532,1.41053 -4.11286,2.46482 -6.15034,3.70855 -5.2118,3.0227 -10.63123,5.78809 -15.33673,9.58923 -1.13626,1.02541 -2.40527,2.04636 -3.20845,3.3799 -0.23462,0.38955 -0.96384,0.95945 -0.59618,1.22707 11.84803,8.6244 8.4217,9.58039 13.95895,7.68437 3.19911,-1.63845 5.94733,-4.08316 8.58378,-6.49278 2.36984,-2.23814 4.80586,-4.40541 7.4576,-6.30866 2.96125,-2.25307 5.63225,-4.7928 7.49983,-8.05914 2.42312,-4.07031 4.73001,-8.20967 7.28048,-12.20339 0,0 -13.04362,-7.64316 -13.04362,-7.64316 z" /> | |
| 61 | - <path | |
| 62 | - style="fill:#ffffff" | |
| 63 | - id="path1039" | |
| 64 | - d="m 432.49953,344.24829 c -0.74992,1.88333 -1.06676,4.9053 -2.27069,6.52441 17.7842,9.09169 10.87195,10.15007 14.63577,2.53805 1.10268,-1.69406 2.3453,-3.29642 3.58552,-4.89165 1.1227,-1.38745 2.25262,-2.77369 3.29055,-4.22698 1.32978,-2.11295 2.84636,-4.09666 4.29583,-6.1268 1.30305,-1.75714 2.50915,-3.58428 3.74208,-5.39123 1.56571,-1.86607 2.88779,-3.92042 4.5192,-5.73014 1.88665,-1.74545 3.98152,-3.25993 6.12942,-4.66512 2.9896,-2.09562 6.07519,-4.03418 8.97079,-6.26338 2.78,-1.954 5.25025,-4.28817 8.0846,-6.16612 2.11189,-1.2452 4.15986,-2.59054 6.24117,-3.88429 3.02352,-1.90978 5.76591,-4.21725 8.62645,-6.35471 1.48877,-1.16753 3.03884,-2.25756 4.59377,-3.33481 0.2987,-0.20694 0.56223,-0.49503 0.91007,-0.60017 0.10854,-0.0328 0.32024,0.31389 0.22153,0.25812 -4.34332,-2.4539 -8.64841,-4.97486 -12.97261,-7.46229 -2.62513,4.04578 -5.83026,7.70019 -8.9005,11.41297 -3.66383,4.80372 -8.12475,8.87004 -12.73052,12.74179 -5.91272,4.63036 -8.29371,2.67995 10.5926,8.76614 0.72744,0.23442 0.95585,-1.19331 1.45579,-1.7714 1.41805,-1.63974 2.66603,-2.92715 4.16048,-4.51228 2.07947,-2.31538 4.40674,-4.3784 6.66784,-6.50762 1.84986,-1.74198 3.62603,-3.51822 5.55816,-5.17079 2.44558,-1.96438 4.85201,-4.07114 7.7316,-5.37452 1.77224,-0.39347 3.46115,-1.03221 5.19071,-1.59061 2.31711,-0.81022 4.51877,-2.02167 6.60839,-3.31065 1.39458,-0.88305 2.74941,-1.91401 4.27263,-2.56811 0.19353,-0.1003 0.38287,-0.20912 0.58059,-0.3009 0.59457,-0.27597 0.58438,-0.16429 1.12628,-0.58611 0.45898,-0.35728 0.79797,-0.75022 1.30061,-1.05309 5.14148,-4.96325 -7.95067,-9.73777 -13.28583,-9.85314 -2.48876,0.35892 -4.69207,1.55828 -6.90425,2.67053 -2.3624,0.89905 -3.73647,2.37469 -4.94941,4.5448 -1.13905,2.59111 -2.308,5.17539 -3.35541,7.80576 -1.96526,6.34677 5.52532,5.98779 13.1748,9.82243 0.28349,0.14211 3.63407,-1.76581 4.01264,-1.97846 2.35819,-1.33273 4.68655,-2.71263 6.98133,-4.15154 1.37572,-0.7433 2.66223,-1.6029 3.75923,-2.71805 1.17204,-1.09559 1.77792,-2.49721 2.12557,-4.03109 0.0998,-1.18885 -0.16945,-2.73124 -1.30979,-3.41737 -13.34308,-8.02843 -8.61169,-6.96412 -14.89582,-8.20796 -2.60759,-0.54437 -5.18738,-0.18878 -7.72525,0.49502 -0.65085,0.12605 -1.04631,0.33591 -1.66437,0.0414 -0.177,-0.0843 -0.61222,-0.48509 -0.44157,-0.38856 4.20664,2.42581 8.41328,4.85163 12.61992,7.27744 -0.0556,-0.0879 -0.16461,-0.15979 -0.1668,-0.2638 -0.005,-0.23223 0.098,-0.45412 0.14694,-0.68117 0.55462,-1.72933 1.86737,-2.87785 3.2017,-4.01207 0.89484,-1.03596 2.11945,-1.6709 3.12761,-2.57143 0.86027,-0.47119 1.53234,-0.96433 1.98432,-1.83939 -10.36867,-6.55283 -10.56003,-10.30222 -16.32628,-5.43659 -1.3875,0.80231 -2.64757,1.80472 -3.95489,2.73368 -1.67535,0.85692 -3.39189,1.62876 -5.04528,2.53289 -1.98355,0.85518 -3.9245,1.76609 -5.9385,2.55617 -2.6027,1.25889 -5.19179,2.53692 -7.73292,3.91622 -3.16884,1.42333 -6.13113,3.23939 -9.20069,4.85676 -3.01773,1.63467 -5.94816,3.43762 -8.7288,5.45006 -1.8161,1.09651 -3.20033,2.68211 -4.6919,4.14933 -2.56301,2.52117 0.62871,-0.80843 -1.82713,1.78993 -1.29257,1.17689 -2.33137,2.57077 -3.45106,3.89804 -1.30688,1.49913 -2.76578,2.85213 -4.2452,4.17729 -1.67526,1.40297 -3.27521,2.85848 -4.81531,4.40468 -1.11976,1.32692 -1.82601,2.96903 -2.59642,4.51708 -0.89521,1.49446 -1.60042,3.07987 -2.33327,4.65731 -0.55628,0.99643 -1.16583,1.96813 -1.65121,3.00157 -0.53699,0.94305 -0.94412,1.93177 -1.55569,2.83258 -0.8921,1.16304 -1.39503,2.55479 -2.18277,3.77523 -0.75855,1.14369 -1.22847,2.45654 -1.66572,3.75099 -0.47382,1.2505 -0.77063,2.5524 -1.27452,3.79267 0.0176,-0.0459 0.01,-0.16167 0.0529,-0.13767 15.20424,8.50293 10.5337,11.06607 15.80147,3.6489 2.08695,-2.75768 3.99725,-5.64466 5.97647,-8.48077 1.92629,-2.76638 3.80319,-5.56446 5.65316,-8.38206 1.26769,-2.14829 2.54516,-4.29976 3.72202,-6.4994 0.99127,-1.696 1.86944,-3.43898 2.60941,-5.25851 0.6295,-1.85568 1.18095,-3.73524 1.94295,-5.54209 0.89682,-2.17472 2.06346,-4.22311 3.20854,-6.27422 1.00575,-1.47324 1.84774,-3.08799 2.93752,-4.50437 0.10494,-0.13639 1.21585,-1.33809 1.34628,-1.47981 0.9082,-1.31448 2.09267,-2.36626 3.17242,-3.52376 0.95213,-0.94011 1.9487,-1.7963 2.92104,-2.71484 1.11996,-1.04095 2.26946,-2.03134 3.34625,-3.11929 1.36036,-1.04378 2.73501,-2.1089 4.26504,-2.89286 1.62924,-1.00996 3.42299,-1.69537 5.08542,-2.63746 1.4904,-0.68881 3.08098,-1.11573 4.60632,-1.70803 1.61959,-0.626 3.03985,-1.70627 4.57116,-2.53507 1.26489,-0.46675 2.54065,-0.95411 3.84876,-1.29477 1.16488,-0.23001 2.35876,-0.2251 3.54126,-0.23621 0.64204,0.066 1.31618,-0.0895 1.95561,4.1e-4 0.38998,0.0548 0.54234,0.17649 0.85356,0.35797 -4.23079,-2.43099 -8.46158,-4.86198 -12.69237,-7.29297 -0.23596,0.40922 0.32876,-0.0608 0.27332,0.0117 -0.23121,0.30203 -1.16696,0.73465 -1.48098,0.91793 -1.89828,0.86389 -3.80655,1.66682 -5.80597,2.2709 -2.38683,0.81515 -4.90251,1.25962 -7.18253,2.35472 -1.59845,0.71242 -3.19309,1.38261 -4.74426,2.19516 -2.17518,1.09322 -4.14076,2.57229 -6.05109,4.07209 -1.04757,0.75941 -1.86421,1.78884 -2.84381,2.62126 -0.47353,0.40237 -1.00615,0.72951 -1.50758,1.09652 -2.60957,1.82293 -5.07268,3.85707 -7.44319,5.9788 -2.38247,2.05357 -4.89087,3.94947 -7.26141,6.01746 -1.45747,1.27654 -2.72386,2.74505 -3.98576,4.21007 -1.17894,1.2966 -2.3584,2.58174 -3.34479,4.03392 -0.80492,1.04949 -1.52848,2.15792 -2.2549,3.2618 -0.56841,1.04014 -0.90533,2.19537 -1.26485,3.32241 -0.20666,1.14317 -0.70444,2.17858 -1.1385,3.24297 -0.37682,1.24532 -0.9082,2.43344 -1.34007,3.65859 -0.59614,1.23685 -1.07713,2.51619 -1.55984,3.79939 -0.60561,1.64317 -1.22029,3.28304 -1.72453,4.96068 -0.49888,1.22534 -0.83747,2.50404 -1.31075,3.73744 -0.40444,1.06336 -0.97991,2.04175 -1.55375,3.01889 0,0 13.08691,7.56878 13.08691,7.56878 v 0 c 0.61569,-1.04805 1.22928,-2.09904 1.67579,-3.23392 0.49152,-1.27241 0.84263,-2.58551 1.37098,-3.84669 0.46931,-1.6472 1.08207,-3.24556 1.66603,-4.85454 0.50216,-1.29407 0.94652,-2.60845 1.58598,-3.84657 0.4,-1.21115 0.96582,-2.36112 1.30393,-3.59348 0.42896,-1.10903 1.01951,-2.16153 1.20003,-3.35429 0.28527,-0.99865 0.6179,-2.00144 1.04898,-2.94116 0.72768,-1.09373 1.44377,-2.19601 2.24241,-3.24048 0.96693,-1.3969 2.13628,-2.61995 3.25929,-3.89028 1.2158,-1.40982 2.45249,-2.8098 3.87076,-4.02235 2.38823,-2.05378 4.89907,-3.95769 7.27043,-6.03197 2.36178,-2.07152 4.79443,-4.06893 7.37306,-5.86698 1.54036,-1.15559 2.87935,-2.51821 4.3653,-3.73979 1.87881,-1.41018 3.76354,-2.83918 5.90154,-3.83977 1.53809,-0.76523 3.13304,-1.40357 4.69438,-2.11969 2.31022,-0.90327 4.75639,-1.41584 7.09887,-2.23031 2.062,-0.71132 4.09072,-1.52659 6.00735,-2.575 1.15677,-0.75487 2.31614,-1.57824 2.36727,-3.09574 -11.84239,-7.15983 -8.79952,-8.19681 -16.23653,-7.94552 -1.27385,0.0591 -2.55519,0.15442 -3.77266,0.56575 -1.36356,0.46811 -2.76869,0.85036 -4.03011,1.56676 -1.44448,0.83593 -2.83281,1.8206 -4.43538,2.33025 -1.60897,0.54133 -3.21588,1.11756 -4.72908,1.89291 -1.7058,0.87233 -3.44756,1.67232 -5.09656,2.65452 -1.58254,0.9473 -3.11251,2.01939 -4.48952,3.24809 -1.07479,1.06783 -2.23755,2.0338 -3.32634,3.0869 -0.97659,0.93843 -2.02953,1.78709 -2.95848,2.77592 -1.11653,1.14012 -2.228,2.27157 -3.18959,3.55198 -1.73615,1.91381 -3.12213,4.07803 -4.46152,6.28494 -1.17125,2.09783 -2.36412,4.19304 -3.28797,6.41396 -0.77999,1.81247 -1.36826,3.69143 -1.96005,5.57265 -0.69501,1.77895 -1.57036,3.46126 -2.52724,5.11323 -1.14995,2.16186 -2.41205,4.27023 -3.65327,6.38065 -1.84142,2.80594 -3.71367,5.58986 -5.63123,8.34468 -1.97876,2.83242 -3.8863,5.71663 -5.9713,8.47202 -5.09974,7.21931 -3.61585,3.58041 10.14902,11.86734 0.10559,0.0636 0.13753,-0.20454 0.2063,-0.30682 0.54562,-1.27233 0.85593,-2.60811 1.33519,-3.90196 0.366,-1.18393 0.79847,-2.37723 1.47685,-3.41749 0.76962,-1.22092 1.29148,-2.58159 2.16466,-3.74426 0.64823,-0.97619 1.10877,-2.02653 1.6781,-3.04528 0.45874,-1.04165 1.10992,-1.99296 1.6475,-2.99498 0.7231,-1.56766 1.42248,-3.13956 2.31322,-4.62198 0.69376,-1.40359 1.32111,-2.91229 2.35041,-4.10035 1.51226,-1.5356 3.15699,-2.90965 4.77261,-4.33535 1.51521,-1.38933 3.03002,-2.786 4.35719,-4.36096 1.07589,-1.31874 2.15268,-2.63092 3.39427,-3.80416 1.98924,-2.04024 3.98674,-4.10902 6.36889,-5.69823 2.76588,-1.9319 5.63882,-3.7109 8.62332,-5.28656 3.05605,-1.64619 6.06258,-3.38186 9.20471,-4.86291 2.50272,-1.33987 5.04539,-2.60674 7.63563,-3.76923 2.04517,-0.82102 4.03382,-1.76494 6.03055,-2.69514 1.72296,-0.88646 3.50857,-1.66371 5.15022,-2.6955 1.33179,-0.93952 2.63362,-1.91845 4.00061,-2.80598 5.75132,-4.27305 6.41889,-0.18499 -9.91136,-9.53506 0.0988,-0.15669 0.22735,-0.29822 0.29653,-0.47009 0.0309,-0.0768 -0.11478,0.11966 -0.17731,0.17392 -0.45656,0.39618 -1.06226,0.60574 -1.48578,1.04994 -1.07663,0.82836 -2.19514,1.6074 -3.14048,2.59206 -1.53745,1.34942 -2.95155,2.80683 -3.65012,4.78163 -0.0188,0.0593 -0.65643,1.94397 -0.51994,2.04078 7.75805,5.50234 9.22853,9.3143 15.73862,7.79935 2.42682,-0.47812 4.85872,-0.64526 7.24894,0.15555 5.24405,2.34611 1.12226,0.54529 -11.09364,-6.66691 -0.24934,-0.14721 0.51196,0.27175 0.75737,0.42541 0.62106,0.38887 1.13479,0.73189 1.24929,1.49492 -0.19447,1.28451 -0.67305,2.36229 -1.67066,3.23761 -1.0601,0.99375 -2.34252,1.69237 -3.58241,2.43713 -2.28455,1.40297 -4.58146,2.78316 -6.93152,4.07482 -0.6401,0.33075 -1.7557,0.97424 -2.50893,1.12733 -0.29072,0.0591 -1.13609,-0.18506 -0.88979,-0.0197 3.99496,2.68242 8.18171,5.06786 12.24564,7.64459 0.11606,0.0736 -0.36688,0.007 -0.3917,-0.1286 -0.0517,-0.28183 0.14352,-0.55483 0.21528,-0.83225 1.0086,-2.57511 2.17547,-5.08853 3.25754,-7.633 1.14069,-2.06393 2.33352,-2.87066 4.47439,-3.77341 1.99682,-0.99435 4.09811,-2.12487 6.39726,-1.94691 5.54739,1.31252 -1.16486,-0.19796 -11.70509,-7.15668 -0.22944,-0.15148 1.03266,-0.87795 0.47926,-0.42008 -0.1142,0.0945 -0.23753,0.17734 -0.3563,0.26601 -0.91035,0.69602 -1.797,1.34558 -2.88727,1.75467 -1.48835,0.81922 -2.91672,1.74573 -4.3402,2.6716 -2.04124,1.18747 -4.14532,2.33613 -6.41512,3.0194 -1.78134,0.56187 -3.56442,1.13378 -5.31489,1.77606 -2.89473,1.52569 -5.43227,3.61724 -7.94047,5.7005 -4.2817,3.73703 -8.32405,7.76259 -12.30794,11.8115 -1.41228,1.50514 -2.88298,3.03195 -4.21881,4.60891 -0.54545,0.64392 -2.16213,1.37043 -1.57814,1.97962 10.37811,10.8258 9.09373,11.67867 15.32582,6.79575 4.65474,-3.95024 9.15169,-8.10228 12.87166,-12.97238 3.18286,-3.85479 6.53093,-7.63942 9.17583,-11.89157 -4.19727,-2.86373 -8.27973,-5.90343 -12.59181,-8.59118 -0.077,-0.048 -1.09652,0.65829 -1.56463,0.98599 -1.5592,1.09152 -3.11333,2.19347 -4.61332,3.36563 -2.81974,2.1259 -5.54732,4.39017 -8.5582,6.24779 -2.08506,1.3181 -4.17789,2.62318 -6.27025,3.9296 -2.81241,1.94701 -5.33289,4.26353 -8.12848,6.23518 -2.89927,2.17261 -5.95114,4.11376 -8.92117,6.18537 -2.22251,1.51205 -4.43203,3.08505 -6.33475,4.9951 -1.64219,1.88227 -3.03883,3.95761 -4.61661,5.89356 -1.22912,1.79711 -2.42749,3.61668 -3.72627,5.3644 -1.44811,2.02933 -2.9611,4.01363 -4.29362,6.12305 -1.03598,1.43422 -2.15766,2.80499 -3.26514,4.18412 -1.28435,1.68579 -2.61731,3.34228 -3.73394,5.1467 -0.61541,1.23637 -1.10338,2.39149 -1.42745,3.7376 -1.42213,5.90724 -0.30903,0.87064 12.96295,8.86478 1.37512,-2.07493 1.70669,-4.65994 2.52212,-6.96893 0,0 -13.28274,-7.2196 -13.28274,-7.2196 z" /> | |
| 65 | - <path | |
| 66 | - style="fill:#ffffff" | |
| 67 | - id="path1041" | |
| 68 | - d="m 444.95379,346.65456 c 1.06642,-2.60177 2.26717,-5.1388 3.53885,-7.64477 0.80062,-1.72005 1.70753,-3.33379 2.78034,-4.89383 0.99874,-1.25158 1.79112,-2.64198 2.78939,-3.89223 0.97549,-1.20036 1.66709,-2.60021 2.6938,-3.75713 1.13014,-1.5117 1.88641,-3.2593 2.68308,-4.96014 0.7471,-1.60342 1.60226,-3.14777 2.40761,-4.72157 1.18872,-1.66559 2.27151,-3.4063 3.48455,-5.05396 0.96809,-1.31722 1.78836,-2.73014 2.77318,-4.03489 0.90823,-1.43352 1.91835,-2.76684 3.06487,-4.0174 1.43521,-1.23262 2.86359,-2.45013 4.14673,-3.84264 1.10736,-0.88478 2.1079,-1.91275 3.22743,-2.78438 1.26625,-0.92328 2.40104,-2.02565 3.74382,-2.83749 1.36021,-0.84114 2.47965,-1.99587 3.75402,-2.93834 1.17313,-0.82096 2.1014,-1.91914 3.19251,-2.83265 0.96511,-1.06249 1.9825,-2.0389 3.15071,-2.87458 1.46588,-1.3037 3.03026,-2.5058 4.41761,-3.8948 1.26476,-0.99496 2.67815,-1.75513 3.97292,-2.70979 1.23962,-0.91909 2.67716,-1.37512 4.09945,-1.89879 1.53746,-0.34526 2.98,-0.81959 4.436,-1.41426 0.95752,-0.25952 2.32209,-0.8001 3.33156,-0.62574 0.20945,0.0362 0.38685,0.17626 0.58027,0.26439 -4.20114,-2.33375 -8.40228,-4.6675 -12.60343,-7.00126 0,0 13.96868,5.78186 13.96868,5.78186 v 0 c -4.4863,-2.61747 -8.9726,-5.23493 -13.4589,-7.8524 -1.51335,0.0943 -2.98338,0.40961 -4.37883,1.01983 -1.4393,0.56639 -2.96156,0.82534 -4.40858,1.37347 -1.5382,0.55268 -3.00033,1.2792 -4.32591,2.24462 -1.37284,0.95626 -2.85935,1.75693 -4.09307,2.90202 -1.38146,1.30479 -2.85379,2.51476 -4.31435,3.72921 -1.15751,0.90413 -2.18874,1.92435 -3.21309,2.97405 -1.03083,0.94401 -2.01396,1.93579 -3.14828,2.76194 -1.19514,0.98762 -2.37284,1.99266 -3.68464,2.82915 -1.32344,0.92776 -2.55283,1.98338 -3.82992,2.9733 -1.12752,0.96567 -2.25289,1.94069 -3.33352,2.95626 -1.30383,1.37931 -2.79466,2.54328 -4.15785,3.86097 -1.16319,1.29631 -2.19199,2.67816 -3.13817,4.13963 -0.9771,1.31712 -1.81169,2.72753 -2.78204,4.04984 -1.24707,1.72053 -2.39712,3.51107 -3.60412,5.25971 -0.815,1.58975 -1.6765,3.15157 -2.43517,4.77007 -0.73319,1.57868 -1.42437,3.21086 -2.47717,4.60741 -0.99785,1.20674 -1.75357,2.57945 -2.72447,3.80831 -1.00142,1.28731 -1.83229,2.69059 -2.83871,3.9755 -1.1137,1.6144 -2.04714,3.29226 -2.88163,5.06874 -1.2825,2.52926 -2.50723,5.08361 -3.57702,7.71214 0,0 13.17149,7.42062 13.17149,7.42062 z" /> | |
| 69 | - <path | |
| 70 | - style="fill:#ffffff" | |
| 71 | - id="path1043" | |
| 72 | - d="m 47.723738,302.48814 c -0.235582,2.57035 -0.413918,5.14543 -0.527844,7.72396 -0.04401,1.66877 -0.637615,3.23133 -0.971978,4.84849 -0.49334,2.0254 -0.803411,4.07474 -0.962727,6.15115 -0.22051,2.38235 -0.149823,4.7319 0.269472,7.08396 0.481444,2.35269 0.997451,4.69608 1.45286,7.05476 0.457995,2.16877 0.573854,4.38236 0.790824,6.58142 0.233663,1.60072 0.456482,3.20102 0.57548,4.81477 0.08973,1.43491 0.08843,2.87328 0.09188,4.31034 -0.0031,5.94595 -0.872531,1.79721 13.666195,7.35427 0.230286,0.088 0.424137,-2.99784 0.437609,-3.20433 0.09063,-2.46503 0.06323,-4.93249 0.04974,-7.39861 -0.08329,-2.38251 0.008,-4.7492 0.31058,-7.11289 0.318125,-3.12377 1.109164,-6.1645 1.882127,-9.19858 0.603361,-2.38574 1.199296,-4.775 1.831907,-7.15334 0.5698,-2.1975 1.009163,-4.42888 1.423266,-6.66069 0.356849,-2.02356 0.443261,-4.078 0.494757,-6.12769 0.10103,-1.42529 0.383836,-2.82762 0.53435,-4.24816 -0.0029,-1.13487 0.180083,-2.1682 0.603434,-3.21105 0.321111,-0.5869 0.15321,-0.2786 0.503328,-0.92512 0,0 -13.113313,-7.52296 -13.113313,-7.52296 v 0 c -0.373806,0.69103 -0.177993,0.33257 -0.588169,1.07497 -0.466058,1.17181 -0.891486,2.30042 -0.810581,3.59475 -0.118838,1.46005 -0.383281,2.8963 -0.58493,4.34604 -0.03272,1.97659 -0.0796,3.95891 -0.384361,5.91626 -0.38509,2.19498 -0.834288,4.38156 -1.361672,6.54616 -0.65643,2.37946 -1.223845,4.78566 -1.843449,7.17462 -0.780965,3.08634 -1.636353,6.16979 -1.961447,9.34711 -0.358177,2.4239 -0.514696,4.85383 -0.409938,7.30476 0.01422,2.44019 0.03976,4.88083 -0.0099,7.32079 -0.02942,0.76513 -0.03695,1.29897 -0.112712,2.05085 -0.02471,0.24528 -0.307475,0.60077 -0.09933,0.73287 4.278007,2.71486 8.786753,5.0478 13.181546,7.56923 0.0572,0.0328 -0.153475,-0.0392 -0.168712,-0.10336 -0.04652,-0.19592 4.77e-4,-0.40274 7.16e-4,-0.60411 -7.7e-5,-1.46409 0.0069,-2.92885 -0.05054,-4.3921 -0.08299,-1.64226 -0.248525,-3.27751 -0.534015,-4.89805 -0.334753,-2.2089 -0.271582,-4.47121 -0.7496,-6.66552 -0.430658,-2.36726 -0.924448,-4.71652 -1.449337,-7.062 -0.472671,-2.26866 -0.697763,-4.4124 -0.444127,-6.73588 0.115274,-2.02246 0.364088,-4.02381 0.877321,-5.98869 0.294207,-1.70188 0.964594,-3.3091 1.075721,-5.04646 0.09428,-2.55627 0.265712,-5.10876 0.493001,-7.65667 0,0 -13.40746,-6.98527 -13.40746,-6.98527 z" /> | |
| 73 | - <path | |
| 74 | - style="fill:#ffffff" | |
| 75 | - id="path1045" | |
| 76 | - d="m 638.45033,529.45411 c 0.32043,-1.43279 0.87109,-2.79362 1.51319,-4.10875 0.49898,-1.07666 1.0692,-2.11787 1.58949,-3.18392 0.3604,-0.79469 0.72883,-1.65852 0.63073,-2.55458 -0.0213,-0.19465 -0.0803,-0.38329 -0.12051,-0.57493 -12.05402,-7.29915 -8.97396,-7.82949 -16.48178,-7.87355 -1.51954,-0.003 -3.03908,0.007 -4.55861,0.0125 -0.43502,-0.13711 -1.85991,0.26465 -2.19604,0.10033 -0.0753,-0.0368 -0.16683,-0.27616 -0.0951,-0.23283 4.27548,2.58135 8.49877,5.24815 12.74816,7.87222 -0.0323,0.11502 -0.19798,0.28158 -0.0968,0.34508 0.11127,0.0698 0.23042,-0.12621 0.34594,-0.18875 0.82367,-0.44601 1.64573,-0.88641 2.48418,-1.30455 1.04859,-0.43593 2.07827,-0.92742 3.18607,-1.19426 0.1154,-0.0333 1.46149,-0.43299 1.45532,-0.21803 -0.003,0.12184 -0.0928,0.22543 -0.13915,0.33815 -15.99901,-11.90341 -7.73872,-9.62675 -14.63806,-7.60889 -1.12229,0.32922 -2.27318,0.57267 -3.42629,0.77165 -0.8597,0.13995 -1.69864,0.34359 -2.57144,0.3832 -0.54215,-0.0101 -1.09515,0.074 -1.62901,-0.0558 -0.24961,-0.0607 -0.48864,-0.15896 -0.73526,-0.23084 -0.0894,-0.0566 -0.19053,-0.098 -0.26821,-0.16984 -0.0706,-0.0653 -0.25175,-0.28213 -0.16846,-0.23411 4.25969,2.45608 8.54644,4.87008 12.7194,7.47078 0.0386,0.0241 -0.62896,0.70858 -0.14215,0.34641 0.41198,-0.21773 0.79311,-0.50185 1.21319,-0.7044 0.241,-0.1162 0.49677,-0.19887 0.74453,-0.29986 0.64051,-0.18906 1.25438,-0.4587 1.9201,-0.54617 0.53439,-0.0211 1.07056,0.004 1.60554,0.0114 0.66172,0.20645 1.36743,0.21542 2.0551,0.24269 0.2059,0.0384 0.41918,-0.0195 0.62534,0.0116 0.0937,0.0141 0.17952,0.0618 0.27192,0.0828 0.32123,0.0728 0.65864,0.0923 0.98578,0.12092 0,0 -12.12893,-9.02458 -12.12893,-9.02458 v 0 c -0.64258,-0.0911 -1.27712,-0.13195 -1.92456,-0.15915 -0.70608,-0.0683 -1.41657,-0.216 -2.12625,-0.14532 -0.60794,0.0224 -1.21803,0.0643 -1.79888,0.26164 -0.70334,0.23112 -1.4064,0.47243 -2.0796,0.78154 -0.8134,0.39578 -1.61239,0.85829 -2.26694,1.48977 -4.40189,5.22506 7.05828,9.21158 12.81615,9.58956 0.87601,0.1034 1.76219,0.0157 2.63569,-0.0834 0.88485,-0.14403 1.76111,-0.3377 2.64498,-0.48765 1.20051,-0.2558 2.40354,-0.52668 3.55497,-0.95904 7.22662,-2.97311 -0.75811,-4.03385 -9.72884,-10.2551 -0.10392,0.0865 -0.19775,0.18687 -0.31174,0.25961 -0.14271,0.0911 -0.37549,0.11873 -0.53047,0.15979 -0.37663,0.0998 -0.74429,0.23119 -1.11935,0.33636 -1.13859,0.40564 -2.2512,0.88549 -3.336,1.41811 -0.68468,0.35565 -1.37148,0.71028 -2.0284,1.11615 -0.26605,0.16437 -0.84138,0.52704 -1.05722,0.77979 -0.20267,0.23735 -0.35044,0.51655 -0.52566,0.77483 4.25429,2.7014 8.45898,5.48255 12.76287,8.1042 0.1058,0.0645 0.23191,-0.0911 0.35356,-0.11452 0.71214,-0.1371 1.45642,-0.0262 2.17386,-0.0697 1.50973,-0.005 3.02005,-0.019 4.52929,0.0278 0.63448,0.0341 1.28759,0.049 1.90849,0.20211 0.17207,0.0424 0.34359,0.0927 0.50543,0.16496 0.13833,0.0617 0.51975,0.31137 0.38936,0.2343 -4.17013,-2.46505 -8.33904,-4.93215 -12.50856,-7.39822 0.13533,-0.0252 0.26836,-0.0779 0.406,-0.0756 0.69198,0.0116 0.0239,1.25024 -0.0802,1.48859 -0.51442,1.06625 -1.09068,2.1018 -1.58591,3.17763 -0.69439,1.40539 -1.30774,2.85802 -1.66838,4.38859 0,0 13.29807,7.19133 13.29807,7.19133 z" /> | |
| 77 | 5 | </g> |
| 78 | 6 | </svg> |
| @@ -1,48 +1,7 @@ | ||
| 1 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
| 2 | -<svg | |
| 2 | +<svg id="svg8" width="431.92pt" height="430.69pt" preserveAspectRatio="xMidYMid" version="1.0" viewBox="0 0 431.92 430.69" xmlns="http://www.w3.org/2000/svg"> | |
| 3 | - version="1.0" | |
| 3 | + <g id="g3" transform="matrix(.1 0 0 -.1 -40.075 470.79)"> | |
| 4 | - width="431.92484pt" | |
| 4 | + <path id="path1" d="m2495 4698c-9-97-8-195-10-293-65-14-170 12-229-19 45-55 96-105 146-156-3-83-5-166-7-249-102-101-201-211-322-290-27-14-54-3-89 38-65 77-439-60-369 189 45 119-53 28-91-8-51 115-151 211-246 133-124-105-64-173-5-231 48-64 103-62 174-62 48-54 96-107 145-160 66 0 107 14 143-53 37-63 87-117 135-172-289 13-616-97-827 146-162 210-213 129-213 111 0-155 182-121 344-360 77-118 166-205 166-240-69-128-143-254-212-382h-159c-6 53 16 115-2 166-102-50-248-79-340-135-92-64-208-14-221-64-13-48-15-47 157-47 156 0 311-7 467-13v-77h561c52 44 105 87 166 116 87 39 78 44-89 45-145 2-166 2-503 14 42 19 81 47 127 55 43 0 115 188 171 225 56 21 118 23 177 28 56-38 107-85 177-91 31-3 53 0 53 5-53 72-127 130-198 184l-4 90c-5 123-28 104-28 121 210 14 425 14 637 21-58 55-127 86-127 175 0 72-30 121-30 128 0 2 28 4 61 4 68-7 111 42 159 82v139l55 51c106 98 95 132 95-292v-371c-30-28-59-56-89-84l-2-194c-2-219-6-209 76-210 22-1 50-8 62-16 18-13 23-13 30-2 5 9 33 16 71 20 35 3 66 8 69 11 6 6 10 155 16 393-31 21-62 43-93 64 3 251-4 502-13 753 60-51 142-89 150-174 7-89 10-96 65-134 44-37 95-36 149-36-12-68-15-137-20-205-37-35-78-66-111-105 0-22 2 19 629-12-23-39-28-82-29-126 0-85-1-86-161-228-66-58-118-111-116-118 9-27 130 16 213 76 45 32 47 33 164 34l118 2c46-83 124-154 157-245 0-47-496-7-555-43 81-49 166-97 246-150h632l18 75c20-2 119 19 109-12-20-51-3-96-55-148-40-41-33-57 13-30 32 20 119 49 242 90 9 34 6 78 29 107 12 8 86 8 194 8 4 12 22 62 22 65 0 2-46 4-103 7-194 6-120 46-241 79-97 26-138 76-178 81-44 11-38-118-41-152h-178c-47 93-87 190-141 279-88 140 63 263 142 365 149 210 291 154 331 366-217 0-138-117-381-269-71-42-690-15-690-12 70 58 140 211 217 211 110 0 171 181 256 166 107-20 302 172 135 294-157 116-226-120-245-120-9 0-60 37-87 42-17 3-19-3-17-57 6-274-307-57-389-190-12-19-29-35-38-35-17 0-405 306-429 337-18 45-13 96-13 143h89c53 0 93 4 96 11 17 26-154 157-166 161-21 7-9 351-19 361-2 2-126 29-135 5z"/> | |
| 5 | - height="430.70734pt" | |
| 5 | + <path id="path3" d="m2160 2555c-33-28-66-57-99-85h-291v-220c-128 0-96-128-264-96-55 11-143 173-159 203-19 41-119 33-154 30-6-12 122-252 133-313 15-96-132-130-195-242-71-133-240-162-302-284-36-70-25-84 51-64 48 12 64 25 154 118 149 155 126 148 509 148 174 0 317-4 317-8-60-67-112-142-162-217-114 4-177-83-246-158-101 14-134 18-207-65-166-201 173-364 249-110 15 49 125-85 110 18-4 24-16 149 202 141 134-3 172 4 180 35 13 52 26 48 147-43 117-90 232-183 347-276v-114c-58-3-122-7-177-17-3-3 26-33 63-68 136-125 121-89 123-288 1-96 4-176 6-179 114 63 135 89 135 210v109c78 11 169-18 244 9-43 65-109 118-166 172-24 285 101 312 274 482 81 84 115 36 169-32l291-6c34-35 60-64 56-116-12-117 80 11 122-61 123-230 398-46 238 129-66 74-63 73-127 73-88 2-151 93-211 150h-79c-93 0-94 3-111 83-25 121-96 113-50 137 14 8 116 10 328 8 355-3 350-2 442-101 150-161 142-154 194-161 138-22 24 143-103 180-112 52-233 185-363 398 58 114 136 224 172 347 0 10-40 6-112-12-75-20-186-386-387-204-48 42-86 37-94 107-8 55-7 111-7 166-106 3-213 5-319 7l-78 76c-6 74-9 151-18 227-18 20-336-347-376-369-60-30-362 398-379 371-15-25-20-173-20-225zm394-315c12 0 229 220 244 220 16 0 72-75 72-96 0-18-290-304-308-304-11 0-326 287-330 301-4 10 41 75 65 95 23 20 239-216 257-216zm-751-149c84-11 197-15 284-16 140-120 271-251 403-380-10-256-8-257-19-505 0-5-57 42-136 106-8 53-16 107-24 160-89 89-191 52-182 84 3 11 9 61 20 175 54 50 55 51 96 104-12 21-352-1-676 26 51 48 91 72 91 148-4 72 60 129 120 187 7 7 17-43 23-89zm1647-23c1-67-10-140 20-186 6-15 39-17 36-31-5-9-121-15-294-23-146-138-294-274-447-404-8-49 3-111-29-153-36-39-125-103-136-96-8 6-10 64-10 502 141 137 282 273 430 403 299-9 306-19 310 51 16 48 96-42 120-63z"/> | |
| 6 | - viewBox="0 0 431.92484 430.70734" | |
| 7 | - preserveAspectRatio="xMidYMid" | |
| 8 | - id="svg8" | |
| 9 | - sodipodi:docname="koboldhorde.svg" | |
| 10 | - inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | |
| 11 | - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 12 | - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 13 | - xmlns="http://www.w3.org/2000/svg" | |
| 14 | - xmlns:svg="http://www.w3.org/2000/svg"> | |
| 15 | - <defs | |
| 16 | - id="defs12" /> | |
| 17 | - <sodipodi:namedview | |
| 18 | - id="namedview10" | |
| 19 | - pagecolor="#ffffff" | |
| 20 | - bordercolor="#000000" | |
| 21 | - borderopacity="0.25" | |
| 22 | - inkscape:showpageshadow="2" | |
| 23 | - inkscape:pageopacity="0.0" | |
| 24 | - inkscape:pagecheckerboard="0" | |
| 25 | - inkscape:deskcolor="#d1d1d1" | |
| 26 | - inkscape:document-units="pt" | |
| 27 | - showgrid="false" | |
| 28 | - inkscape:zoom="1.5043945" | |
| 29 | - inkscape:cx="237.63713" | |
| 30 | - inkscape:cy="285.82928" | |
| 31 | - inkscape:window-width="2560" | |
| 32 | - inkscape:window-height="1377" | |
| 33 | - inkscape:window-x="5112" | |
| 34 | - inkscape:window-y="-8" | |
| 35 | - inkscape:window-maximized="1" | |
| 36 | - inkscape:current-layer="svg8" /> | |
| 37 | - <g | |
| 38 | - transform="matrix(0.1,0,0,-0.1,-40.075165,470.78832)" | |
| 39 | - stroke="none" | |
| 40 | - id="g6"> | |
| 41 | - <path | |
| 42 | - d="m 2495,4698 c -3,-7 -6,-76 -7,-153 l -3,-140 -115,-5 c -63,-3 -114,-9 -114,-14 1,-4 34,-41 74,-82 l 72,-74 -4,-125 -3,-124 -65,-67 c -74,-75 -224,-205 -257,-223 -27,-14 -54,-3 -89,38 -22,26 -24,26 -156,26 -132,0 -135,0 -172,28 -52,40 -60,67 -41,135 9,30 14,56 11,59 -7,7 -47,-16 -77,-43 l -25,-24 -18,41 c -10,23 -39,56 -68,80 -63,49 -110,53 -160,12 -58,-49 -67,-65 -68,-118 0,-49 2,-53 63,-113 62,-61 63,-62 118,-62 h 56 l 72,-80 73,-80 h 51 c 61,0 66,-3 92,-53 11,-21 46,-69 77,-105 l 58,-67 -320,-3 -321,-2 -55,26 c -47,22 -67,42 -131,125 -79,102 -128,139 -186,139 -23,0 -27,-4 -27,-28 0,-61 91,-163 177,-198 64,-27 97,-59 167,-162 36,-55 75,-107 86,-117 24,-22 80,-108 80,-123 0,-6 -15,-35 -34,-64 -18,-29 -66,-113 -106,-185 l -72,-133 h -80 -79 l 2,80 c 1,44 0,83 -4,86 -3,3 -31,-8 -63,-24 -67,-33 -197,-82 -220,-82 -9,0 -34,-13 -57,-29 -36,-25 -55,-30 -129,-36 -77,-6 -87,-9 -92,-28 -13,-48 -15,-47 157,-47 90,0 232,-3 315,-7 l 152,-6 v -39 -38 h 281 280 l 57,47 c 31,25 80,56 109,69 87,39 78,44 -89,45 -84,1 -232,4 -328,8 l -175,6 55,28 c 30,15 63,27 72,27 16,0 58,44 58,61 0,17 92,150 113,164 12,7 57,17 100,21 l 77,7 62,-43 c 49,-35 72,-45 115,-48 31,-3 53,0 53,5 0,11 -93,102 -156,152 l -42,32 -4,90 c -2,50 -9,94 -16,101 -7,7 -12,16 -12,20 0,4 56,8 123,8 68,0 212,3 319,7 l 195,6 -56,50 c -72,65 -71,63 -71,125 0,29 -7,69 -15,89 -8,19 -15,37 -15,39 0,2 28,4 61,4 57,0 65,3 110,41 l 49,41 v 69 70 l 55,51 c 106,98 95,132 95,-292 v -371 l -45,-42 -44,-42 -2,-194 c -2,-219 -6,-209 76,-210 22,-1 50,-8 62,-16 18,-13 23,-13 30,-2 5,9 33,16 71,20 35,3 66,8 69,11 3,3 8,93 11,199 l 5,194 -47,32 -46,32 v 202 c 0,111 -3,281 -7,377 l -6,174 39,-31 c 107,-86 106,-85 111,-143 7,-89 10,-96 65,-134 43,-29 62,-36 101,-36 h 48 l -5,-32 c -3,-18 -8,-64 -10,-103 l -5,-70 -56,-50 c -30,-28 -55,-53 -55,-55 0,-3 78,-3 172,-2 95,2 237,1 315,-3 l 142,-7 -14,-29 c -9,-17 -15,-56 -15,-97 0,-85 -1,-86 -161,-228 -66,-58 -118,-111 -116,-118 9,-27 130,16 213,76 45,32 47,33 164,34 l 118,2 78,-114 c 44,-62 79,-122 79,-131 0,-16 -18,-19 -205,-24 -113,-3 -238,-7 -277,-8 -40,0 -73,-5 -73,-11 0,-5 21,-18 48,-30 26,-12 81,-43 123,-70 l 75,-50 h 316 316 l 9,38 9,37 58,3 c 53,3 58,1 51,-15 -4,-10 -11,-39 -15,-66 -5,-35 -15,-57 -40,-82 -40,-41 -33,-57 13,-30 18,11 79,35 137,55 l 105,35 9,50 c 5,28 14,53 20,57 6,4 52,8 103,8 h 91 l 11,31 c 6,18 11,33 11,34 0,2 -46,4 -103,7 -96,3 -106,5 -152,36 -27,18 -67,37 -89,43 -44,12 -112,44 -136,64 -8,7 -27,15 -42,17 -27,5 -27,5 -34,-73 l -7,-79 h -89 -89 l -57,118 c -31,64 -69,137 -84,161 -22,36 -28,57 -28,102 0,56 2,60 54,116 29,32 81,98 116,147 49,69 76,96 122,125 138,84 191,138 202,204 l 7,37 h -41 c -59,0 -88,-19 -159,-104 -65,-78 -120,-127 -181,-165 -34,-20 -46,-21 -362,-21 -192,0 -328,4 -328,9 0,5 19,24 42,42 22,18 61,64 85,101 44,68 44,68 90,68 44,0 49,3 134,86 84,82 89,86 122,80 49,-9 84,7 145,67 50,49 52,53 52,105 0,65 -10,84 -62,122 -61,45 -119,42 -165,-6 -18,-20 -43,-54 -54,-75 -10,-21 -22,-39 -26,-39 -3,0 -20,9 -37,19 -17,11 -40,21 -50,23 -17,3 -19,-3 -17,-57 1,-46 -3,-68 -19,-93 -31,-51 -68,-61 -191,-55 -131,7 -152,2 -179,-42 -12,-19 -29,-35 -38,-35 -17,0 -405,306 -429,337 -7,10 -13,45 -13,80 v 63 h 89 c 53,0 93,4 96,11 4,6 -4,22 -16,36 -32,34 -134,120 -150,125 -9,3 -13,49 -13,180 -1,97 -3,178 -6,181 -3,3 -33,8 -67,12 -49,5 -64,4 -68,-7 z" | |
| 43 | - id="path2" /> | |
| 44 | - <path | |
| 45 | - d="m 2166,2764 c -3,-9 -6,-59 -6,-112 v -97 l -49,-42 -50,-43 h -145 -146 v -110 -110 h -25 c -17,0 -43,-16 -75,-46 -53,-49 -96,-63 -164,-50 -30,6 -42,18 -89,89 -30,45 -61,97 -70,114 -16,30 -20,31 -83,35 -36,1 -68,0 -71,-5 -2,-4 25,-67 61,-140 36,-72 68,-150 72,-173 6,-38 3,-44 -33,-80 -21,-21 -62,-58 -91,-82 -29,-24 -61,-60 -71,-80 -26,-49 -106,-125 -161,-152 -56,-28 -116,-83 -141,-132 -36,-70 -25,-84 51,-64 48,12 64,25 154,118 149,155 126,148 509,148 174,0 317,-4 317,-8 0,-5 -14,-21 -31,-38 -17,-16 -53,-63 -81,-104 l -50,-75 -57,-6 c -54,-7 -58,-9 -122,-79 l -67,-73 -58,6 c -71,8 -88,0 -149,-71 -34,-41 -45,-62 -45,-88 0,-51 23,-98 64,-129 31,-24 43,-27 85,-23 73,7 121,50 145,130 5,16 8,16 48,-2 63,-29 69,-27 62,20 -6,34 -2,45 20,75 48,63 67,70 182,66 134,-3 172,4 180,35 13,52 26,48 147,-43 63,-49 167,-130 231,-182 l 116,-94 v -57 -57 l -86,-6 c -48,-3 -89,-8 -91,-11 -3,-3 26,-33 63,-68 136,-125 121,-89 123,-288 1,-96 4,-176 6,-179 3,-2 22,12 44,32 22,19 51,43 65,52 25,17 26,20 26,126 v 109 h 119 c 66,0 122,4 125,9 3,5 -33,46 -80,90 l -86,82 7,109 c 8,130 17,146 140,256 43,38 100,90 127,117 64,61 94,63 137,8 l 32,-40 145,-3 146,-3 30,-34 c 27,-30 30,-39 26,-82 -5,-48 -4,-49 21,-48 14,1 40,4 56,8 26,5 32,2 45,-21 8,-15 32,-46 53,-68 35,-36 44,-40 86,-40 38,0 57,7 90,30 45,33 61,63 61,116 0,27 -11,46 -52,91 -66,74 -63,73 -127,73 -53,0 -55,1 -132,75 l -79,75 h -79 c -51,0 -82,4 -89,13 -5,6 -15,38 -22,70 -8,38 -22,70 -41,92 -30,33 -30,34 -9,45 14,8 116,10 328,8 355,-3 350,-2 442,-101 150,-161 142,-154 194,-161 64,-10 75,1 51,55 -26,56 -86,105 -154,125 -45,13 -65,26 -101,67 -25,28 -75,83 -111,122 -37,38 -86,101 -109,140 l -42,69 32,56 c 40,70 140,277 140,291 0,10 -40,6 -112,-12 -30,-8 -42,-18 -59,-52 -28,-54 -75,-125 -107,-159 -49,-53 -158,-50 -221,7 -21,19 -49,38 -63,44 -21,8 -26,18 -31,63 -4,29 -7,78 -7,110 v 56 l -160,4 -159,3 -39,38 -39,38 -7,112 c -4,61 -8,113 -11,115 -8,9 -40,-21 -195,-186 -89,-95 -170,-177 -181,-183 -30,-15 -11,-34 -263,249 -60,67 -112,122 -116,122 -4,0 -11,-7 -14,-16 z m 388,-524 c 4,0 59,50 121,110 62,61 117,110 123,110 16,0 72,-75 72,-96 0,-18 -290,-304 -308,-304 -11,0 -326,287 -330,301 -4,10 41,75 65,95 10,9 38,-13 131,-102 64,-62 121,-114 126,-114 z m -759,-97 c 4,-21 7,-44 8,-52 2,-11 31,-14 143,-15 l 141,-1 69,-60 c 37,-33 128,-118 201,-190 l 133,-130 v -230 c 0,-218 -4,-275 -19,-275 -3,0 -35,24 -71,53 l -65,53 -12,79 c -6,44 -12,81 -12,81 -1,1 -17,14 -37,29 -25,20 -54,30 -93,34 -42,5 -55,10 -52,21 2,8 7,51 12,95 l 8,80 51,47 c 29,25 49,51 45,57 -4,7 -93,11 -268,12 -144,1 -295,4 -335,8 l -73,6 45,43 c 45,43 46,45 46,105 0,61 1,63 56,125 31,34 60,62 64,62 5,0 12,-17 15,-37 z m 1608,-34 47,-41 v -82 c 0,-69 3,-86 20,-104 12,-12 25,-22 31,-22 6,0 8,-4 5,-9 -3,-5 -71,-12 -150,-16 l -144,-7 -159,-148 c -87,-81 -187,-171 -223,-201 l -65,-55 -5,-66 c -3,-45 -11,-72 -24,-87 -36,-39 -125,-103 -136,-96 -6,4 -10,92 -10,254 v 248 l 103,99 c 108,106 215,206 285,267 l 42,37 h 149 c 158,0 161,1 161,51 0,29 20,23 73,-22 z" | |
| 46 | - id="path4" /> | |
| 47 | 6 | </g> |
| 48 | 7 | </svg> |
| @@ -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> | |
| @@ -0,0 +1 @@ | ||
| 1 | +<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"></path></svg> | |
| 1 | \ No newline at end of file | |
| @@ -1 +1,4 @@ | ||
| 1 | -<svg xmlns="http://www.w3.org/2000/svg" viewBox="83.349 83.3488 333.396 333.395" width="333.396px" height="333.395px"><path d="M 246.799 88.148 C 237.605 99.343 230.641 107.833 225.288 114.438 C 219.934 121.04 216.192 125.76 213.443 129.413 C 210.694 133.068 208.937 135.657 207.555 138.004 C 206.173 140.351 205.166 142.451 203.918 145.134 C 203.529 145.968 203.154 146.767 202.809 147.499 C 202.464 148.229 202.148 148.895 201.878 149.459 C 201.608 150.023 201.384 150.483 201.222 150.812 C 201.06 151.139 200.96 151.332 200.939 151.353 C 200.919 151.375 200.764 151.356 200.506 151.3 C 200.246 151.244 199.879 151.153 199.436 151.037 C 198.991 150.917 198.469 150.773 197.892 150.603 C 197.317 150.436 196.689 150.247 196.036 150.046 C 193.038 149.119 190.766 148.419 188.573 147.896 C 186.381 147.37 184.269 147.017 181.59 146.781 C 178.913 146.546 175.671 146.422 171.216 146.362 C 166.763 146.297 161.1 146.294 153.58 146.292 L 146.676 146.288 L 139.771 146.285 L 132.866 146.285 L 125.963 146.282 L 125.963 161.225 L 125.963 176.168 L 125.963 191.11 L 125.963 206.054 L 124.505 206.222 L 123.043 206.394 L 121.58 206.565 L 120.123 206.739 C 118.48 206.931 116.775 207.193 115.008 207.526 C 113.241 207.858 111.416 208.259 109.537 208.733 C 107.656 209.205 105.722 209.74 103.738 210.348 C 101.755 210.954 99.725 211.627 97.647 212.364 C 96.339 212.83 94.795 213.413 93.208 214.032 C 91.621 214.654 90 215.308 88.538 215.915 C 87.078 216.521 85.78 217.079 84.849 217.507 C 83.916 217.933 83.349 218.228 83.349 218.304 C 83.349 218.42 83.547 218.985 83.877 219.854 C 84.21 220.722 84.681 221.891 85.224 223.212 C 85.766 224.536 86.385 226.012 87.016 227.493 C 87.65 228.973 88.298 230.46 88.895 231.803 C 92.249 239.318 96.203 246.526 100.658 253.31 C 105.109 260.093 110.057 266.454 115.394 272.272 C 120.73 278.09 126.455 283.367 132.461 287.984 C 138.465 292.602 144.751 296.561 151.21 299.743 C 154.416 301.323 157.57 302.733 160.7 303.984 C 163.828 305.234 166.929 306.324 170.027 307.259 C 173.121 308.195 176.211 308.975 179.318 309.607 C 182.422 310.239 185.543 310.723 188.7 311.066 C 191.033 311.319 193.65 311.47 196.411 311.526 C 199.175 311.58 202.086 311.54 205.006 311.41 C 207.923 311.28 210.852 311.059 213.651 310.756 C 216.452 310.453 219.124 310.065 221.53 309.602 C 222.605 309.396 223.636 309.202 224.579 309.032 C 225.521 308.86 226.377 308.712 227.1 308.591 C 227.825 308.472 228.417 308.38 228.834 308.325 C 229.253 308.271 229.495 308.252 229.52 308.277 C 229.639 308.401 229.468 308.844 229.097 309.479 C 228.727 310.113 228.16 310.942 227.484 311.844 C 226.807 312.746 226.024 313.719 225.224 314.643 C 224.423 315.568 223.606 316.443 222.867 317.146 C 221.275 318.657 219.615 319.961 217.836 321.079 C 216.055 322.197 214.155 323.128 212.083 323.891 C 210.01 324.654 207.767 325.248 205.3 325.694 C 202.832 326.139 200.141 326.437 197.175 326.604 C 193.395 326.816 189.994 327.211 186.887 327.829 C 183.779 328.444 180.965 329.279 178.358 330.369 C 175.752 331.46 173.355 332.803 171.08 334.439 C 168.806 336.074 166.656 338 164.545 340.251 C 163.003 341.894 161.632 343.591 160.417 345.379 C 159.202 347.155 158.141 349.036 157.218 351.03 C 156.299 353.03 155.514 355.155 154.851 357.438 C 154.187 359.726 153.647 362.172 153.207 364.827 C 153.04 365.842 152.913 366.599 152.77 367.206 C 152.629 367.806 152.477 368.253 152.26 368.612 C 152.039 368.982 151.76 369.267 151.359 369.577 C 150.959 369.876 150.447 370.191 149.76 370.613 C 146.959 372.322 144.585 374.482 142.688 376.955 C 140.791 379.425 139.371 382.199 138.474 385.12 C 137.578 388.043 137.204 391.112 137.403 394.171 C 137.604 397.235 138.377 400.285 139.767 403.172 C 140.861 405.439 142.364 407.507 144.142 409.321 C 145.922 411.128 147.979 412.675 150.17 413.873 C 152.363 415.081 154.697 415.943 157.03 416.386 C 159.365 416.839 161.698 416.875 163.895 416.418 C 164.949 416.196 166.066 415.865 167.196 415.439 C 168.328 415.022 169.477 414.508 170.59 413.938 C 171.704 413.364 172.786 412.725 173.788 412.048 C 174.788 411.367 175.707 410.655 176.498 409.921 C 178.133 408.414 179.505 406.573 180.589 404.517 C 181.676 402.458 182.476 400.186 182.966 397.809 C 183.455 395.427 183.639 392.941 183.488 390.468 C 183.336 387.983 182.855 385.516 182.015 383.162 C 181.582 381.95 180.935 380.688 180.125 379.438 C 179.315 378.191 178.341 376.955 177.262 375.785 C 176.185 374.625 174.998 373.524 173.755 372.556 C 172.515 371.586 171.22 370.754 169.926 370.097 C 168.776 369.515 167.913 369.078 167.286 368.673 C 166.659 368.268 166.268 367.888 166.061 367.416 C 165.854 366.955 165.831 366.396 165.939 365.633 C 166.049 364.866 166.289 363.903 166.61 362.606 C 167.239 360.073 168.181 357.677 169.396 355.447 C 170.611 353.217 172.097 351.164 173.814 349.33 C 175.528 347.5 177.474 345.885 179.607 344.542 C 181.739 343.195 184.058 342.114 186.521 341.341 C 186.953 341.205 187.6 341.061 188.405 340.918 C 189.209 340.774 190.17 340.632 191.23 340.497 C 192.288 340.361 193.447 340.231 194.643 340.119 C 195.841 340.003 197.079 339.906 198.295 339.827 C 201.179 339.647 203.718 339.414 206.01 339.104 C 208.301 338.796 210.345 338.408 212.239 337.919 C 214.133 337.43 215.877 336.839 217.568 336.12 C 219.258 335.404 220.895 334.56 222.575 333.563 C 224.387 332.489 226.003 331.431 227.5 330.319 C 228.997 329.208 230.375 328.042 231.706 326.75 C 233.036 325.458 234.32 324.041 235.631 322.425 C 236.941 320.811 238.279 318.999 239.716 316.919 L 240.575 315.675 L 241.434 314.431 L 242.295 313.186 L 243.154 311.942 L 243.154 326.201 L 243.154 340.461 L 243.154 354.718 L 243.154 368.982 L 242.045 369.749 L 240.935 370.52 L 239.826 371.287 L 238.717 372.06 C 237.028 373.232 235.59 374.381 234.363 375.574 C 233.136 376.776 232.119 378.012 231.271 379.369 C 230.425 380.723 229.744 382.188 229.194 383.828 C 228.644 385.475 228.221 387.29 227.883 389.357 C 227.479 391.83 227.522 394.366 227.954 396.838 C 228.384 399.312 229.204 401.725 230.354 403.963 C 231.502 406.2 232.982 408.267 234.729 410.037 C 236.476 411.812 238.496 413.299 240.725 414.378 C 242.961 415.451 245.261 416.147 247.564 416.456 C 249.866 416.765 252.172 416.695 254.42 416.259 C 256.67 415.821 258.86 415.016 260.936 413.85 C 263.011 412.685 264.971 411.168 266.752 409.303 C 267.88 408.125 268.805 407.004 269.558 405.869 C 270.312 404.73 270.893 403.56 271.335 402.289 C 271.779 401.005 272.081 399.612 272.276 398.009 C 272.469 396.41 272.555 394.602 272.563 392.497 C 272.57 390.978 272.569 389.795 272.536 388.827 C 272.504 387.847 272.44 387.086 272.323 386.397 C 272.208 385.705 272.038 385.089 271.792 384.412 C 271.547 383.734 271.226 382.997 270.806 382.07 C 270.242 380.813 269.59 379.645 268.853 378.552 C 268.115 377.458 267.292 376.441 266.385 375.504 C 265.476 374.575 264.482 373.72 263.404 372.943 C 262.327 372.17 261.164 371.474 259.918 370.858 L 258.956 370.387 L 257.997 369.912 L 257.034 369.443 L 256.073 368.964 L 256.069 361.769 L 256.067 354.564 L 256.064 347.36 L 256.061 340.163 C 256.059 336.205 256.072 332.439 256.094 329.025 C 256.116 325.612 256.15 322.547 256.192 319.988 C 256.233 317.43 256.282 315.375 256.339 313.979 C 256.395 312.583 256.457 311.846 256.52 311.925 C 256.586 312.001 256.737 312.211 256.957 312.525 C 257.177 312.839 257.463 313.255 257.799 313.747 C 258.134 314.24 258.514 314.803 258.923 315.413 C 259.33 316.023 259.767 316.677 260.21 317.345 C 261.259 318.93 262.251 320.334 263.231 321.608 C 264.21 322.884 265.181 324.03 266.189 325.097 C 267.199 326.165 268.243 327.152 269.376 328.113 C 270.508 329.075 271.726 330.008 273.076 330.964 C 275.237 332.493 277.247 333.763 279.249 334.822 C 281.25 335.88 283.244 336.729 285.372 337.409 C 287.499 338.088 289.763 338.603 292.306 338.994 C 294.847 339.391 297.667 339.661 300.908 339.859 C 302.102 339.933 303.315 340.021 304.482 340.117 C 305.649 340.212 306.771 340.317 307.787 340.422 C 308.804 340.531 309.713 340.637 310.456 340.741 C 311.2 340.842 311.775 340.944 312.123 341.031 C 314.804 341.716 317.399 342.888 319.797 344.43 C 322.195 345.971 324.398 347.874 326.3 350.033 C 328.2 352.182 329.795 354.583 330.987 357.111 C 332.176 359.636 332.954 362.288 333.219 364.942 L 333.311 365.851 L 333.4 366.755 L 333.487 367.664 L 333.577 368.57 L 332.828 368.973 L 332.08 369.369 L 331.331 369.766 L 330.58 370.171 C 328.268 371.397 326.239 372.85 324.51 374.503 C 322.782 376.157 321.349 378.012 320.228 380.046 C 319.108 382.076 318.298 384.294 317.809 386.655 C 317.321 389.026 317.161 391.537 317.332 394.194 C 317.514 396.986 318.125 399.626 319.11 402.047 C 320.093 404.473 321.455 406.682 323.135 408.609 C 324.812 410.535 326.813 412.191 329.079 413.492 C 331.346 414.804 333.877 415.771 336.617 416.34 C 340.253 417.085 343.896 416.704 347.28 415.477 C 350.658 414.247 353.773 412.16 356.353 409.511 C 358.941 406.853 360.988 403.623 362.234 400.105 C 363.484 396.582 363.929 392.762 363.303 388.924 C 362.986 386.999 362.469 385.132 361.769 383.362 C 361.063 381.582 360.179 379.902 359.126 378.334 C 358.078 376.779 356.865 375.337 355.511 374.047 C 354.153 372.762 352.659 371.629 351.044 370.681 C 350.548 370.39 350.073 370.101 349.631 369.823 C 349.186 369.544 348.781 369.287 348.427 369.052 C 348.074 368.816 347.784 368.608 347.563 368.445 C 347.35 368.279 347.213 368.156 347.174 368.089 C 347.136 368.028 347.076 367.839 347.002 367.559 C 346.926 367.279 346.841 366.906 346.738 366.472 C 346.641 366.033 346.533 365.524 346.426 364.982 C 346.319 364.434 346.202 363.845 346.093 363.251 C 345.827 361.805 345.469 360.28 345.026 358.734 C 344.585 357.193 344.07 355.622 343.499 354.103 C 342.926 352.579 342.301 351.092 341.649 349.713 C 340.989 348.331 340.301 347.057 339.606 345.941 C 338.365 343.951 336.881 342.049 335.214 340.264 C 333.55 338.488 331.694 336.833 329.706 335.343 C 327.721 333.854 325.599 332.529 323.396 331.407 C 321.193 330.286 318.908 329.368 316.592 328.692 C 315.968 328.51 315.181 328.325 314.274 328.149 C 313.368 327.97 312.343 327.796 311.24 327.635 C 310.135 327.472 308.952 327.322 307.73 327.188 C 306.509 327.055 305.25 326.938 303.992 326.845 C 302.835 326.758 301.629 326.652 300.44 326.533 C 299.251 326.415 298.077 326.285 296.986 326.151 C 295.893 326.015 294.886 325.877 294.026 325.743 C 293.163 325.608 292.45 325.478 291.951 325.359 C 290.551 325.028 289.144 324.58 287.755 324.032 C 286.368 323.482 284.996 322.832 283.67 322.094 C 282.343 321.357 281.061 320.531 279.845 319.631 C 278.63 318.731 277.484 317.757 276.431 316.723 C 275.528 315.836 274.654 314.903 273.863 313.989 C 273.069 313.075 272.358 312.184 271.778 311.386 C 271.197 310.585 270.749 309.877 270.482 309.33 C 270.215 308.783 270.132 308.398 270.279 308.242 C 270.319 308.199 270.599 308.205 271.069 308.254 C 271.539 308.302 272.201 308.388 273.006 308.512 C 273.81 308.633 274.756 308.789 275.797 308.972 C 276.837 309.155 277.971 309.361 279.151 309.589 C 281.082 309.962 282.663 310.252 284.125 310.478 C 285.586 310.702 286.929 310.864 288.385 310.98 C 289.843 311.095 291.412 311.165 293.33 311.207 C 295.246 311.249 297.507 311.264 300.348 311.27 C 303.731 311.276 306.472 311.257 308.843 311.18 C 311.214 311.103 313.217 310.969 315.118 310.744 C 317.024 310.519 318.828 310.203 320.809 309.766 C 322.789 309.328 324.942 308.768 327.542 308.055 C 332.681 306.644 337.629 304.941 342.407 302.934 C 347.183 300.926 351.786 298.616 356.237 295.996 C 360.679 293.374 364.967 290.442 369.116 287.188 C 373.258 283.937 377.262 280.363 381.138 276.462 C 384.43 273.143 387.487 269.801 390.337 266.384 C 393.193 262.965 395.853 259.47 398.333 255.848 C 400.816 252.227 403.137 248.477 405.327 244.549 C 407.513 240.621 409.562 236.514 411.525 232.177 C 412.561 229.876 413.445 227.881 414.159 226.174 C 414.883 224.465 415.448 223.047 415.864 221.895 C 416.273 220.745 416.543 219.863 416.662 219.23 C 416.79 218.598 416.766 218.215 416.617 218.065 C 416.403 217.855 415.469 217.39 414.106 216.784 C 412.749 216.177 410.956 215.431 409.029 214.662 C 407.111 213.893 405.047 213.1 403.143 212.404 C 401.237 211.7 399.492 211.096 398.191 210.7 C 396.677 210.239 395.049 209.785 393.395 209.353 C 391.728 208.922 390.04 208.513 388.387 208.145 C 386.734 207.78 385.125 207.449 383.627 207.179 C 382.127 206.908 380.746 206.695 379.552 206.553 L 378.67 206.449 L 377.799 206.345 L 376.92 206.242 L 376.049 206.139 L 376.049 191.175 L 376.049 176.209 L 376.049 161.245 L 376.049 146.282 L 369.208 146.288 L 362.377 146.294 L 355.542 146.3 L 348.71 146.305 C 341.359 146.316 335.755 146.325 331.3 146.393 C 326.842 146.461 323.533 146.582 320.775 146.808 C 318.016 147.032 315.807 147.359 313.553 147.835 C 311.298 148.315 308.996 148.944 306.05 149.775 C 305.408 149.956 304.791 150.123 304.223 150.27 C 303.656 150.416 303.14 150.545 302.701 150.646 C 302.263 150.75 301.902 150.821 301.644 150.865 C 301.384 150.909 301.232 150.919 301.208 150.892 C 301.185 150.865 301.079 150.664 300.909 150.329 C 300.739 149.991 300.505 149.516 300.222 148.939 C 299.941 148.362 299.611 147.679 299.251 146.929 C 298.889 146.183 298.499 145.366 298.094 144.516 C 296.849 141.904 295.91 139.934 294.85 138.024 C 293.792 136.115 292.615 134.263 290.896 131.882 C 289.177 129.5 286.916 126.595 283.689 122.568 C 280.463 118.545 276.269 113.409 270.689 106.572 C 266.798 101.8 263.658 97.966 261.13 94.892 C 258.6 91.819 256.681 89.508 255.231 87.798 C 253.782 86.083 252.8 84.967 252.148 84.281 C 251.494 83.597 251.168 83.34 251.03 83.349 C 250.988 83.351 250.839 83.488 250.602 83.734 C 250.365 83.978 250.045 84.33 249.661 84.765 C 249.277 85.2 248.832 85.719 248.346 86.291 C 247.861 86.861 247.338 87.491 246.799 88.148 M 266.415 121.571 C 270.276 126.312 273.576 130.479 276.367 134.164 C 279.161 137.852 281.446 141.053 283.281 143.861 C 285.116 146.665 286.5 149.073 287.489 151.167 C 288.478 153.261 289.071 155.042 289.325 156.6 C 289.391 157.001 289.427 157.324 289.406 157.608 C 289.387 157.891 289.312 158.132 289.156 158.371 C 288.999 158.614 288.764 158.85 288.422 159.121 C 288.081 159.394 287.635 159.704 287.057 160.086 C 286.13 160.701 284.911 161.698 283.538 162.934 C 282.166 164.169 280.642 165.642 279.106 167.213 C 277.57 168.782 276.02 170.446 274.598 172.064 C 273.176 173.682 271.881 175.253 270.852 176.635 C 269.742 178.128 268.593 179.804 267.454 181.582 C 266.313 183.359 265.182 185.239 264.108 187.135 C 263.031 189.033 262.014 190.945 261.098 192.793 C 260.183 194.64 259.372 196.421 258.712 198.054 C 257.763 200.401 256.843 203.065 255.996 205.859 C 255.15 208.65 254.379 211.569 253.725 214.423 C 253.07 217.278 252.534 220.067 252.159 222.599 C 251.786 225.133 251.572 227.407 251.565 229.235 C 251.559 231.007 251.505 232.294 251.409 233.097 C 251.313 233.901 251.172 234.221 250.988 234.059 C 250.805 233.896 250.578 233.251 250.308 232.123 C 250.039 230.997 249.728 229.388 249.375 227.297 C 248.928 224.65 248.419 221.945 247.871 219.288 C 247.324 216.629 246.741 214.02 246.144 211.567 C 245.548 209.108 244.941 206.809 244.349 204.765 C 243.755 202.725 243.176 200.941 242.636 199.525 C 242.096 198.111 241.387 196.463 240.57 194.704 C 239.751 192.947 238.827 191.079 237.856 189.225 C 236.887 187.368 235.876 185.527 234.884 183.818 C 233.891 182.112 232.922 180.542 232.034 179.229 C 231.241 178.051 230.127 176.6 228.825 175.023 C 227.525 173.446 226.04 171.74 224.505 170.059 C 222.969 168.38 221.388 166.72 219.895 165.239 C 218.402 163.755 216.997 162.448 215.818 161.464 C 215.336 161.059 214.877 160.657 214.458 160.269 C 214.038 159.881 213.662 159.514 213.344 159.185 C 213.026 158.855 212.769 158.564 212.591 158.328 C 212.412 158.094 212.315 157.918 212.315 157.821 C 212.315 157.515 212.586 156.721 213.037 155.637 C 213.488 154.55 214.117 153.171 214.832 151.693 C 215.545 150.216 216.346 148.636 217.135 147.152 C 217.926 145.667 218.707 144.278 219.388 143.171 C 220.106 142.009 220.79 140.942 221.523 139.864 C 222.255 138.785 223.035 137.697 223.945 136.49 C 224.854 135.283 225.892 133.959 227.139 132.41 C 228.388 130.863 229.845 129.087 231.591 126.978 C 232.242 126.195 233.071 125.195 234.012 124.055 C 234.953 122.912 236.008 121.635 237.109 120.301 C 238.209 118.966 239.355 117.572 240.481 116.204 C 241.608 114.835 242.714 113.49 243.733 112.249 L 245.587 109.989 L 247.441 107.734 L 249.294 105.474 L 251.148 103.218 L 251.468 103.562 L 251.787 103.908 L 252.107 104.253 L 252.425 104.6 C 252.602 104.789 253.127 105.411 253.925 106.367 C 254.724 107.321 255.795 108.617 257.063 110.156 C 258.33 111.693 259.793 113.476 261.378 115.41 C 262.962 117.344 264.666 119.429 266.415 121.571 M 183.998 160.363 C 185.176 160.625 186.407 160.937 187.616 161.281 C 188.827 161.622 190.016 161.992 191.109 162.365 C 192.203 162.735 193.201 163.112 194.031 163.464 C 194.862 163.817 195.522 164.145 195.942 164.429 C 196.089 164.529 196.193 164.698 196.252 164.982 C 196.308 165.272 196.318 165.679 196.28 166.259 C 196.241 166.839 196.151 167.592 196.008 168.57 C 195.864 169.545 195.668 170.747 195.412 172.22 C 195.151 173.746 194.94 175.096 194.774 176.424 C 194.605 177.758 194.481 179.068 194.391 180.521 C 194.301 181.972 194.241 183.564 194.208 185.453 C 194.172 187.342 194.161 189.528 194.161 192.172 C 194.164 195.029 194.175 197.291 194.21 199.182 C 194.244 201.077 194.306 202.596 194.408 203.967 C 194.511 205.335 194.654 206.558 194.858 207.853 C 195.061 209.146 195.321 210.509 195.658 212.173 C 195.862 213.183 196.089 214.264 196.321 215.352 C 196.555 216.44 196.797 217.535 197.032 218.574 C 197.265 219.613 197.493 220.596 197.7 221.462 C 197.905 222.326 198.091 223.072 198.243 223.638 C 198.394 224.202 198.535 224.745 198.659 225.244 C 198.782 225.742 198.892 226.196 198.979 226.582 C 199.065 226.966 199.132 227.283 199.171 227.509 C 199.212 227.735 199.225 227.868 199.206 227.889 C 199.187 227.907 199.005 227.826 198.692 227.662 C 198.379 227.498 197.934 227.251 197.392 226.94 C 196.848 226.63 196.208 226.256 195.501 225.836 C 194.794 225.418 194.021 224.953 193.218 224.462 C 191.127 223.191 189.283 222.093 187.58 221.118 C 185.876 220.143 184.316 219.291 182.796 218.508 C 181.276 217.728 179.798 217.016 178.258 216.323 C 176.721 215.629 175.121 214.956 173.362 214.247 C 170.717 213.183 168.056 212.21 165.422 211.339 C 162.788 210.463 160.182 209.691 157.644 209.029 C 155.104 208.371 152.636 207.82 150.279 207.395 C 147.925 206.969 145.681 206.665 143.592 206.499 L 142.411 206.402 L 141.228 206.307 L 140.044 206.211 L 138.86 206.118 L 138.86 194.393 L 138.86 182.673 L 138.86 170.946 L 138.86 159.226 L 143.872 159.226 L 148.881 159.228 L 153.894 159.231 L 158.904 159.235 C 163.288 159.238 166.776 159.245 169.604 159.275 C 172.433 159.304 174.608 159.354 176.374 159.432 C 178.138 159.514 179.492 159.621 180.68 159.775 C 181.87 159.924 182.895 160.118 183.998 160.363 M 364.273 182.135 C 364.273 185.703 364.256 188.927 364.232 191.757 C 364.21 194.592 364.168 197.034 364.123 199.037 C 364.073 201.036 364.017 202.602 363.951 203.678 C 363.884 204.753 363.806 205.345 363.72 205.401 C 363.647 205.446 363.37 205.516 362.943 205.606 C 362.51 205.689 361.917 205.788 361.219 205.895 C 360.52 206.004 359.703 206.12 358.815 206.24 C 357.924 206.356 356.965 206.475 355.976 206.593 C 353.857 206.84 351.706 207.169 349.519 207.584 C 347.333 207.998 345.11 208.5 342.841 209.089 C 340.568 209.674 338.248 210.35 335.881 211.113 C 333.508 211.88 331.079 212.734 328.586 213.682 C 326.553 214.453 324.965 215.067 323.583 215.633 C 322.202 216.201 321.032 216.721 319.838 217.313 C 318.643 217.904 317.43 218.565 315.96 219.411 C 314.488 220.256 312.763 221.287 310.551 222.616 C 309.679 223.141 308.838 223.631 308.064 224.069 C 307.29 224.507 306.584 224.894 305.98 225.209 C 305.376 225.524 304.876 225.767 304.515 225.919 C 304.155 226.073 303.935 226.136 303.892 226.09 C 303.846 226.043 303.838 225.853 303.86 225.549 C 303.882 225.248 303.933 224.832 304.011 224.332 C 304.09 223.832 304.193 223.251 304.319 222.615 C 304.443 221.979 304.59 221.291 304.752 220.58 C 305.47 217.443 306.014 215.037 306.432 212.994 C 306.85 210.95 307.141 209.27 307.353 207.569 C 307.562 205.873 307.692 204.161 307.789 202.063 C 307.885 199.964 307.946 197.477 308.022 194.23 C 308.085 191.376 308.129 189.088 308.139 187.161 C 308.15 185.237 308.129 183.672 308.067 182.266 C 308.006 180.861 307.902 179.617 307.75 178.328 C 307.598 177.043 307.396 175.711 307.135 174.134 C 306.71 171.557 306.387 169.65 306.2 168.219 C 306.013 166.785 305.963 165.825 306.087 165.14 C 306.209 164.453 306.505 164.042 307.009 163.705 C 307.512 163.372 308.223 163.115 309.179 162.732 C 311.103 161.968 312.767 161.372 314.532 160.905 C 316.301 160.441 318.17 160.105 320.508 159.868 C 322.849 159.628 325.656 159.482 329.301 159.394 C 332.944 159.308 337.422 159.278 343.102 159.267 L 348.391 159.254 L 353.686 159.245 L 358.977 159.235 L 364.273 159.226 L 364.273 164.952 L 364.273 170.68 L 364.273 176.408 L 364.273 182.135 M 294.672 175.255 C 295.22 178.215 295.627 180.998 295.891 183.706 C 296.156 186.417 296.277 189.05 296.257 191.703 C 296.236 194.356 296.071 197.028 295.763 199.821 C 295.454 202.614 295.003 205.519 294.406 208.644 C 293.797 211.84 293.112 214.843 292.333 217.708 C 291.556 220.571 290.684 223.295 289.702 225.927 C 288.719 228.56 287.624 231.1 286.4 233.599 C 285.175 236.098 283.82 238.555 282.318 241.02 C 281.712 242.01 280.648 243.522 279.328 245.293 C 278.009 247.063 276.436 249.097 274.815 251.131 C 273.192 253.166 271.522 255.203 270.01 256.985 C 268.497 258.766 267.144 260.289 266.154 261.3 L 265.432 262.034 L 264.711 262.769 L 263.989 263.505 L 263.268 264.24 L 263.324 259.533 L 263.381 254.828 L 263.438 250.12 L 263.494 245.413 C 263.544 241.302 263.592 238.027 263.653 235.351 C 263.714 232.674 263.788 230.597 263.886 228.883 C 263.984 227.17 264.11 225.82 264.274 224.597 C 264.438 223.375 264.644 222.28 264.901 221.077 C 265.759 217.074 266.758 213.304 267.919 209.718 C 269.078 206.136 270.399 202.738 271.899 199.486 C 273.402 196.236 275.083 193.128 276.966 190.122 C 278.847 187.116 280.93 184.215 283.231 181.372 C 284.057 180.355 285.031 179.27 286.048 178.218 C 287.064 177.162 288.122 176.139 289.116 175.244 C 290.109 174.346 291.038 173.58 291.794 173.034 C 292.549 172.49 293.134 172.169 293.439 172.169 C 293.53 172.169 293.633 172.257 293.74 172.41 C 293.849 172.568 293.962 172.795 294.074 173.075 C 294.185 173.355 294.296 173.687 294.397 174.058 C 294.5 174.427 294.593 174.834 294.672 175.255 M 214.395 177.903 C 216.932 180.555 219.226 183.303 221.306 186.208 C 223.386 189.113 225.254 192.18 226.941 195.469 C 228.628 198.754 230.134 202.263 231.491 206.056 C 232.848 209.852 234.059 213.927 235.152 218.351 C 235.497 219.75 235.762 220.875 235.967 222.052 C 236.173 223.23 236.32 224.462 236.431 226.076 C 236.542 227.689 236.617 229.684 236.678 232.391 C 236.739 235.098 236.787 238.515 236.843 242.97 C 236.897 247.165 236.932 250.429 236.945 252.96 C 236.956 255.491 236.943 257.287 236.9 258.545 C 236.857 259.805 236.783 260.525 236.67 260.902 C 236.559 261.279 236.409 261.312 236.215 261.199 C 235.931 261.033 235.183 260.258 234.159 259.102 C 233.133 257.946 231.83 256.409 230.435 254.715 C 229.04 253.02 227.55 251.167 226.157 249.385 C 224.763 247.601 223.46 245.887 222.435 244.464 C 221.492 243.154 220.43 241.461 219.336 239.557 C 218.241 237.652 217.115 235.538 216.04 233.384 C 214.966 231.23 213.945 229.037 213.062 226.975 C 212.18 224.913 211.436 222.985 210.915 221.36 C 210.715 220.733 210.491 219.974 210.254 219.14 C 210.019 218.305 209.774 217.396 209.534 216.468 C 209.293 215.536 209.057 214.589 208.838 213.677 C 208.62 212.764 208.422 211.887 208.257 211.105 C 207.734 208.612 207.373 204.972 207.161 200.908 C 206.951 196.847 206.892 192.364 206.97 188.194 C 207.047 184.025 207.261 180.165 207.598 177.346 C 207.937 174.531 208.397 172.757 208.967 172.757 C 209.037 172.757 209.24 172.9 209.55 173.163 C 209.861 173.425 210.276 173.8 210.768 174.267 C 211.26 174.734 211.827 175.29 212.443 175.906 C 213.057 176.521 213.719 177.198 214.395 177.903 M 362.287 222.616 C 362.039 224.822 361.696 226.999 361.25 229.168 C 360.802 231.336 360.258 233.494 359.605 235.661 C 358.949 237.827 358.193 240.002 357.321 242.202 C 356.451 244.402 355.468 246.626 354.373 248.894 C 353.42 250.857 352.54 252.568 351.651 254.143 C 350.761 255.716 349.869 257.152 348.886 258.564 C 347.908 259.976 346.841 261.366 345.604 262.846 C 344.366 264.324 342.963 265.895 341.318 267.67 C 339.751 269.361 338.317 270.826 336.939 272.137 C 335.552 273.447 334.221 274.599 332.854 275.666 C 331.483 276.729 330.076 277.706 328.545 278.66 C 327.016 279.613 325.361 280.546 323.495 281.524 C 321.16 282.75 318.988 283.737 316.707 284.525 C 314.424 285.314 312.031 285.904 309.247 286.342 C 306.462 286.78 303.287 287.064 299.44 287.237 C 295.592 287.414 291.074 287.479 285.605 287.479 L 281.702 287.479 L 277.799 287.479 L 273.896 287.479 L 269.992 287.479 L 270.084 287.147 L 270.176 286.818 L 270.267 286.486 L 270.359 286.155 C 270.515 285.585 271.039 284.472 271.797 283.034 C 272.555 281.597 273.55 279.834 274.654 277.964 C 275.759 276.092 276.971 274.114 278.164 272.243 C 279.357 270.373 280.53 268.61 281.557 267.176 C 286.49 260.272 291.976 254.005 297.894 248.447 C 303.812 242.89 310.161 238.045 316.821 233.98 C 323.478 229.92 330.443 226.642 337.598 224.224 C 344.751 221.805 352.094 220.246 359.505 219.621 C 359.812 219.595 360.119 219.566 360.408 219.538 C 360.702 219.512 360.977 219.484 361.223 219.458 C 361.467 219.431 361.682 219.408 361.848 219.385 C 362.019 219.365 362.146 219.345 362.209 219.333 C 362.27 219.318 362.323 219.401 362.356 219.562 C 362.393 219.721 362.413 219.958 362.42 220.256 C 362.429 220.554 362.42 220.912 362.398 221.311 C 362.377 221.709 362.343 222.151 362.287 222.616 M 127.615 226.441 C 128.292 230.02 129.18 233.67 130.242 237.294 C 131.303 240.92 132.536 244.52 133.907 247.993 C 135.276 251.468 136.779 254.816 138.38 257.942 C 139.98 261.066 141.674 263.97 143.424 266.55 C 144.672 268.387 146.181 270.359 147.856 272.368 C 149.529 274.375 151.367 276.418 153.273 278.395 C 155.18 280.373 157.152 282.285 159.098 284.03 C 161.042 285.778 162.959 287.36 164.749 288.675 C 165.572 289.278 166.345 289.86 167.036 290.395 C 167.727 290.928 168.34 291.415 168.84 291.828 C 169.34 292.244 169.73 292.585 169.977 292.83 C 170.226 293.076 170.333 293.222 170.27 293.245 C 170.079 293.318 169.361 293.128 168.296 292.752 C 167.229 292.375 165.819 291.812 164.242 291.142 C 162.667 290.473 160.928 289.694 159.204 288.884 C 157.485 288.076 155.783 287.236 154.28 286.444 C 148.912 283.61 143.725 280.241 138.787 276.403 C 133.85 272.565 129.161 268.258 124.779 263.552 C 120.398 258.843 116.327 253.735 112.627 248.29 C 108.926 242.846 105.598 237.068 102.705 231.024 C 102.347 230.274 102.01 229.555 101.708 228.897 C 101.405 228.236 101.138 227.636 100.919 227.125 C 100.7 226.615 100.527 226.195 100.416 225.896 C 100.305 225.595 100.254 225.419 100.28 225.393 C 100.305 225.366 100.453 225.299 100.693 225.199 C 100.936 225.098 101.274 224.965 101.685 224.809 C 102.093 224.653 102.577 224.475 103.105 224.282 C 103.634 224.09 104.209 223.883 104.806 223.672 C 106.009 223.25 107.545 222.805 109.237 222.376 C 110.928 221.944 112.775 221.528 114.594 221.164 C 116.416 220.798 118.211 220.485 119.804 220.256 C 121.393 220.028 122.779 219.888 123.781 219.869 L 124.429 219.858 L 125.074 219.846 L 125.721 219.835 L 126.365 219.822 L 126.679 221.477 L 126.992 223.132 L 127.305 224.785 L 127.615 226.441 M 150.401 221.008 C 154.24 221.696 158.178 222.712 162.136 224.018 C 166.097 225.324 170.077 226.921 174.005 228.775 C 177.933 230.628 181.809 232.737 185.555 235.067 C 189.3 237.396 192.918 239.945 196.335 242.681 C 197.475 243.592 198.972 244.936 200.637 246.513 C 202.302 248.09 204.136 249.904 205.952 251.762 C 207.767 253.618 209.561 255.52 211.151 257.272 C 212.738 259.023 214.118 260.626 215.102 261.887 C 216.675 263.902 218.309 266.157 219.896 268.474 C 221.483 270.789 223.022 273.167 224.404 275.429 C 225.787 277.689 227.013 279.833 227.971 281.68 C 228.931 283.527 229.625 285.078 229.943 286.155 L 230.04 286.486 L 230.138 286.818 L 230.234 287.147 L 230.332 287.479 L 226.503 287.477 L 222.673 287.477 L 218.844 287.476 L 215.015 287.475 C 209.688 287.474 205.347 287.414 201.67 287.244 C 197.995 287.073 194.981 286.791 192.311 286.34 C 189.64 285.89 187.313 285.277 185.006 284.44 C 182.7 283.607 180.415 282.551 177.83 281.222 C 174.857 279.694 171.933 277.808 169.117 275.629 C 166.299 273.452 163.592 270.984 161.055 268.292 C 158.521 265.6 156.16 262.686 154.036 259.619 C 151.913 256.549 150.026 253.325 148.439 250.014 C 147.262 247.55 146.142 244.939 145.129 242.32 C 144.116 239.702 143.208 237.077 142.447 234.583 C 141.685 232.09 141.071 229.729 140.647 227.639 C 140.221 225.549 139.987 223.73 139.984 222.322 C 139.984 221.708 139.984 221.246 140.018 220.897 C 140.049 220.548 140.113 220.311 140.234 220.152 C 140.354 219.994 140.534 219.91 140.801 219.868 C 141.068 219.825 141.421 219.822 141.887 219.822 C 142.149 219.822 142.585 219.855 143.15 219.915 C 143.714 219.974 144.405 220.063 145.175 220.169 C 145.948 220.277 146.798 220.405 147.684 220.547 C 148.573 220.689 149.493 220.845 150.401 221.008 M 386.42 221.248 C 387.353 221.423 388.673 221.736 390.135 222.113 C 391.591 222.492 393.193 222.935 394.695 223.376 C 396.202 223.819 397.608 224.256 398.663 224.619 C 399.736 224.982 400.461 225.272 400.609 225.415 C 400.68 225.485 400.53 225.956 400.194 226.725 C 399.861 227.493 399.357 228.559 398.729 229.813 C 398.108 231.071 397.359 232.516 396.542 234.047 C 395.729 235.578 394.837 237.194 393.935 238.788 C 391.085 243.831 387.831 248.73 384.286 253.39 C 380.74 258.048 376.892 262.47 372.836 266.561 C 368.783 270.648 364.517 274.407 360.141 277.739 C 355.761 281.071 351.268 283.979 346.75 286.368 C 345.452 287.056 344.007 287.771 342.554 288.454 C 341.116 289.135 339.666 289.782 338.358 290.338 C 337.05 290.892 335.883 291.352 335 291.658 C 334.116 291.965 333.516 292.119 333.34 292.056 C 333.27 292.032 333.351 291.899 333.557 291.677 C 333.766 291.455 334.099 291.147 334.531 290.774 C 334.964 290.402 335.5 289.964 336.102 289.485 C 336.708 289.004 337.386 288.483 338.112 287.941 C 341.136 285.688 343.974 283.293 346.63 280.752 C 349.291 278.211 351.77 275.522 354.078 272.675 C 356.388 269.829 358.521 266.827 360.496 263.659 C 362.468 260.491 364.28 257.157 365.927 253.651 C 367.035 251.305 368.003 249.097 368.864 246.948 C 369.735 244.801 370.491 242.715 371.167 240.614 C 371.845 238.512 372.436 236.399 372.969 234.196 C 373.503 231.992 373.983 229.7 374.42 227.243 L 374.728 225.536 L 375.036 223.826 L 375.34 222.118 L 375.649 220.411 L 376.434 220.411 L 377.215 220.411 L 378.007 220.411 L 378.79 220.412 C 379.221 220.412 379.759 220.435 380.363 220.477 C 380.966 220.519 381.638 220.582 382.324 220.657 C 383.019 220.733 383.73 220.823 384.425 220.923 C 385.125 221.022 385.801 221.133 386.42 221.248 M 166.406 383.113 C 167.419 383.787 168.276 384.729 168.956 385.829 C 169.636 386.933 170.14 388.194 170.444 389.526 C 170.75 390.856 170.855 392.249 170.74 393.607 C 170.623 394.962 170.287 396.289 169.705 397.491 C 169.275 398.374 168.776 399.155 168.21 399.84 C 167.643 400.526 167.009 401.111 166.308 401.589 C 165.608 402.076 164.842 402.456 164.012 402.74 C 163.182 403.017 162.286 403.196 161.331 403.266 C 160.319 403.339 159.365 403.306 158.461 403.149 C 157.561 402.989 156.714 402.722 155.918 402.326 C 155.12 401.943 154.38 401.436 153.684 400.806 C 152.989 400.182 152.347 399.439 151.749 398.569 C 150.993 397.473 150.484 396.193 150.213 394.844 C 149.94 393.491 149.901 392.061 150.082 390.669 C 150.263 389.281 150.665 387.932 151.269 386.723 C 151.88 385.518 152.69 384.454 153.693 383.652 C 154.574 382.95 155.573 382.409 156.637 382.032 C 157.704 381.652 158.834 381.438 159.969 381.389 C 161.104 381.338 162.25 381.458 163.342 381.743 C 164.433 382.032 165.476 382.487 166.406 383.113 M 345.257 382.546 C 345.746 382.799 346.29 383.216 346.852 383.725 C 347.405 384.236 347.969 384.833 348.486 385.466 C 349.005 386.089 349.475 386.74 349.844 387.357 C 350.214 387.97 350.484 388.541 350.592 389.004 C 350.641 389.209 350.681 389.489 350.711 389.834 C 350.741 390.167 350.758 390.564 350.768 390.987 C 350.78 391.407 350.775 391.861 350.764 392.314 C 350.753 392.762 350.73 393.223 350.694 393.657 C 350.519 395.874 349.727 397.797 348.544 399.335 C 347.358 400.876 345.774 402.039 344.023 402.733 C 342.273 403.416 340.349 403.642 338.478 403.309 C 336.608 402.974 334.783 402.084 333.238 400.552 C 332.63 399.949 332.127 399.371 331.714 398.778 C 331.303 398.185 330.981 397.582 330.739 396.925 C 330.496 396.269 330.327 395.558 330.219 394.757 C 330.11 393.949 330.063 393.055 330.063 392.027 C 330.063 391.002 330.137 390.074 330.287 389.23 C 330.437 388.384 330.669 387.62 330.99 386.92 C 331.313 386.21 331.724 385.565 332.24 384.952 C 332.751 384.338 333.368 383.766 334.095 383.201 C 334.781 382.662 335.614 382.248 336.528 381.942 C 337.445 381.642 338.436 381.46 339.45 381.399 C 340.465 381.338 341.495 381.406 342.484 381.595 C 343.476 381.782 344.419 382.102 345.257 382.546 M 256.549 383.921 C 257.04 384.288 257.567 384.871 258.086 385.576 C 258.603 386.279 259.108 387.117 259.552 387.97 C 259.996 388.827 260.38 389.704 260.654 390.521 C 260.927 391.338 261.089 392.084 261.091 392.668 C 261.093 393.424 260.884 394.317 260.52 395.267 C 260.156 396.208 259.636 397.208 259.024 398.158 C 258.412 399.112 257.705 400.024 256.963 400.796 C 256.221 401.575 255.444 402.216 254.691 402.622 C 253.829 403.093 252.859 403.383 251.845 403.496 C 250.833 403.619 249.778 403.563 248.745 403.359 C 247.713 403.153 246.705 402.789 245.785 402.286 C 244.865 401.776 244.035 401.126 243.361 400.348 C 242.582 399.455 241.949 398.455 241.467 397.409 C 240.988 396.359 240.659 395.258 240.488 394.145 C 240.317 393.027 240.304 391.897 240.457 390.794 C 240.607 389.696 240.924 388.62 241.412 387.617 C 241.927 386.563 242.454 385.689 243.041 384.979 C 243.624 384.273 244.264 383.725 245.007 383.317 C 245.748 382.912 246.591 382.65 247.577 382.499 C 248.565 382.347 249.696 382.312 251.015 382.365 C 251.715 382.396 252.322 382.44 252.859 382.506 C 253.397 382.575 253.866 382.662 254.293 382.784 C 254.72 382.906 255.104 383.052 255.472 383.239 C 255.839 383.435 256.189 383.652 256.549 383.921" fill-rule="evenodd" id="object-1" transform="matrix(1, 0, 0, 1, 1.4210854715202004e-14, 0)" style="stroke-opacity: 0;"></path></svg> | |
| 1 | \ No newline at end of file | |
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<svg id="svg1" width="159.95" height="153.04" version="1.1" viewBox="15 17.641 119.96 114.78" xmlns="http://www.w3.org/2000/svg"> | |
| 3 | + <path id="path28" d="m66.09 28.369c-3.74 3.74-6.37 8.08-8.77 12.71-8.7-2.78-17.96-1.75-26.98-1.75v20.57c-5.23 0.57-10.62 1.81-15.34 4.21 8.44 22.69 28.34 34.63 52.61 30.97-1.51 3.38-5.23 5.31-8.72 5.99-5.49 0.19-15.41 1.83-18.16 10.93-0.54 1.67-0.27 3.51-1.83 4.53-7.48 4.33-4.75 14.41 2.62 15.76 0.84 0.15 1.68 0.17 2.47 0.01 6.73-1.35 11.11-11.06 1.22-16.43-1.35-0.82 0.74-4.51 0.76-4.55 4.95-8.64 13.73-2.88 22.43-10.45 1.61-1.5 2.84-2.76 4.12-4.53v19.63c-10.21 5.36-4.72 17.92 4.05 16.27 6.63-1.23 10.97-11.45 0.59-16.28 0.03-3.47-0.08-19.92 0.16-19.63 1.11 1.55 2.17 3.14 3.48 4.54 8.69 8.79 18.47 1.27 23.32 11.02 0.67 1.22 0.3 4.27-0.15 4.49-7.53 3.87-5.86 14.28 2.18 15.89 8.5 1.66 13.73-10.9 5.19-15.72-3.42-1.84-0.63-5.33-5.7-10.47-5.96-6.06-12.32-3.78-17.08-5.59-4.09-1.53-6.54-5.18-6.29-5.43 0.22-0.22 24.55 6.91 43.22-14.41 3.82-4.84 7.74-10.2 9.47-16.23 0.01-0.96-7.34-3.65-14.62-4.51v-20.6c-8.38 0.38-16.99-0.99-25.2 1.2-0.73 0.2-1.71 0.43-1.74 0.38-4.26-8.9-11.5-16-18.06-23.25m9.12 17.49c2.24 2.78 4.35 5.82 4.66 7.72 0.17 1.06-1.08 0.74-5.3 5.32-8.84 9.51-8.16 21.63-8.5 21.34-3.43-17.8-5.92-16.88-13.92-26.24 2.91-7.3 8.9-12.96 13.98-18.79m-24.17 19.67c1.46 0.45 3.24 0.53 4.41 1.59-1.57 7.36-0.34 14.39 1.06 21.65-1.71-0.53-8.68-6.49-21.72-7.5v-16.14c5.38 0.35 9.84-0.85 15.17 0.16m65.77 15.73c-1.37 0.5-9.12 0.18-19.14 5.92-1.23 0.71-2.33 1.26-2.4 1.2 3.34-16.15 0.34-18.66 0.79-20.98 0.58-1.79 7.99-1.97 8.36-1.98 4.19-0.07 8.39-0.05 12.59-0.06m-25.03 5.63c2.92 13.59-5.05 24.39-11.33 30.51-0.35-9.95 0.4-26.27 10.86-31.69m-28.45 1.97c7.76 7.75 9.06 19.24 8.61 29.72-14.31-13.23-10.59-31.49-10.56-31.49m55.18 17.16c-3.52 19.54-15.76 22.61-33.22 22.32 5.74-13.4 19.09-21.86 33.19-23.45 0.11-0.02 0.09 0.55 0.04 0.96m-83.54 5.23c3.07 12.37 15.29 18.99 14.41 19.25-12.76-3.72-21.69-15-25.19-23.35 2.99-1.21 6.18-1.77 9.39-1.92m8.65 0.41c17.46 3 27.77 19.13 28.77 22.88-16.7 0.78-29.72-4.91-32.42-23.17 0 0 1.34 0 2.43 0.13m86.54 0.42c1.35 0.24 4.55 1.07 4.74 1.27 0.12 0.11-1.41 2.94-2.4 4.6-8.44 14.22-21.37 18.47-21.81 18.34-0.38-0.17 10.75-6.06 14.27-19.92 0.38-1.57 0.67-3.16 0.96-4.74m-75.31 56c3 1.9 1.51 7.59-2.86 6.89-4.87-0.77-3.54-8.67 1.76-7.37m65.48 0.28c3.68 1.81 1.88 7.86-2.44 7.14-5.49-0.93-2.81-9.81 2.44-7.14m-31.37 1.04c3.31 4.38-2.57 8.05-5.3 5.08-3.31-3.6 1.54-8.07 4.75-5.65" fill-rule="evenodd"/> | |
| 4 | +</svg> | |
| @@ -1,60 +1,2 @@ | ||
| 1 | 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
| 2 | -<!-- Created with Inkscape (http://www.inkscape.org/) --> | |
| 2 | +<svg id="svg1" width="8.28mm" height="9.4349mm" version="1.1" viewBox="0 0 8.28 9.4349" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path id="path9" d="m4.14 0c-0.05 0.08-0.1 0.16-0.14 0.24-0.07 0.11-0.17 0.19-0.29 0.22-0.2 0.04-0.39 0.09-0.58 0.13 0.19 0.05 0.38 0.1 0.58 0.14 0.12 0.03 0.22 0.11 0.29 0.22 0.04 0.08 0.09 0.16 0.14 0.24 0.04-0.08 0.09-0.16 0.13-0.24 0.07-0.11 0.17-0.19 0.29-0.22 0.2-0.04 0.39-0.09 0.59-0.14-0.2-0.04-0.39-0.09-0.59-0.14-0.12-0.02-0.22-0.1-0.29-0.21-0.04-0.08-0.09-0.16-0.13-0.24zm-1.42 0.81c-0.04 0.08-0.09 0.16-0.14 0.24-0.06 0.11-0.16 0.19-0.29 0.22-0.19 0.05-0.38 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.05-0.09 0.09-0.17 0.14-0.25 0.06-0.11 0.17-0.18 0.29-0.21 0.19-0.05 0.39-0.1 0.58-0.14-0.19-0.05-0.39-0.09-0.58-0.14-0.12-0.03-0.23-0.11-0.29-0.22-0.05-0.08-0.09-0.16-0.14-0.24zm2.82 0c-0.04 0.08-0.09 0.16-0.14 0.24-0.06 0.11-0.16 0.19-0.29 0.22-0.19 0.05-0.38 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.05-0.09 0.09-0.17 0.14-0.25 0.06-0.11 0.17-0.18 0.29-0.21 0.19-0.05 0.39-0.1 0.58-0.14-0.19-0.05-0.39-0.09-0.58-0.14-0.12-0.03-0.23-0.11-0.29-0.22-0.05-0.08-0.09-0.16-0.14-0.24zm-1.4 0.83c-0.05 0.07-0.1 0.15-0.14 0.23-0.07 0.11-0.17 0.19-0.29 0.22-0.2 0.05-0.39 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.04-0.09 0.09-0.17 0.13-0.25 0.07-0.11 0.17-0.18 0.29-0.21 0.2-0.05 0.39-0.1 0.59-0.14-0.2-0.05-0.39-0.09-0.59-0.14-0.12-0.03-0.22-0.11-0.29-0.22-0.04-0.08-0.09-0.16-0.13-0.23zm-3.16 0.14-0.98 0.55v4.8c1.29 0.74 2.58 1.49 3.87 2.23 0.25 0.14 0.46 0.05 0.55 0 1.29-0.75 2.57-1.49 3.86-2.23v-4.8l-0.98-0.55c-1.11 0.86-2.96 0.79-3.16 2.4-0.2-1.61-2.04-1.55-3.16-2.4z"/></svg> | |
| 3 | - | |
| 4 | -<svg | |
| 5 | - width="8.2832384mm" | |
| 6 | - height="9.4351206mm" | |
| 7 | - viewBox="0 0 8.2832384 9.4351206" | |
| 8 | - version="1.1" | |
| 9 | - id="svg1" | |
| 10 | - xml:space="preserve" | |
| 11 | - inkscape:version="1.3 (0e150ed, 2023-07-21)" | |
| 12 | - sodipodi:docname="scale.svg" | |
| 13 | - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 14 | - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 15 | - xmlns="http://www.w3.org/2000/svg" | |
| 16 | - xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | |
| 17 | - id="namedview1" | |
| 18 | - pagecolor="#ffffff" | |
| 19 | - bordercolor="#000000" | |
| 20 | - borderopacity="0.25" | |
| 21 | - inkscape:showpageshadow="2" | |
| 22 | - inkscape:pageopacity="0.0" | |
| 23 | - inkscape:pagecheckerboard="0" | |
| 24 | - inkscape:deskcolor="#d1d1d1" | |
| 25 | - inkscape:document-units="mm" | |
| 26 | - showgrid="false" | |
| 27 | - inkscape:zoom="1.0289025" | |
| 28 | - inkscape:cx="8.7471843" | |
| 29 | - inkscape:cy="8.2612296" | |
| 30 | - inkscape:window-width="1280" | |
| 31 | - inkscape:window-height="688" | |
| 32 | - inkscape:window-x="0" | |
| 33 | - inkscape:window-y="25" | |
| 34 | - inkscape:window-maximized="1" | |
| 35 | - inkscape:current-layer="layer1" /><defs | |
| 36 | - id="defs1" /><g | |
| 37 | - inkscape:label="Layer 1" | |
| 38 | - inkscape:groupmode="layer" | |
| 39 | - id="layer1" | |
| 40 | - transform="translate(-88.746886,-143.1532)"><g | |
| 41 | - id="g1" | |
| 42 | - transform="matrix(0.26458333,0,0,0.26458333,88.077577,143.1532)"><path | |
| 43 | - d="m 14.3423,8.42148 0.0218,-0.00723 2.1725,-0.51502 c 0.4642,-0.10879 0.8631,-0.40619 1.1025,-0.81965 l 0.5041,-0.87044 0.0146,-0.02902 0.0145,0.02902 0.5041,0.87044 c 0.2394,0.41346 0.6383,0.71086 1.1025,0.81965 l 2.1725,0.51502 0.0254,0.01087 H 21.9514 L 19.7789,8.94737 C 19.3147,9.0562 18.9158,9.35359 18.6764,9.76705 l -0.5041,0.87045 -0.0145,0.029 -0.0146,-0.029 -0.5041,-0.87045 C 17.3997,9.35359 17.0008,9.0562 16.5366,8.94737 L 14.3641,8.43239 14.3423,8.42512 Z" | |
| 44 | - id="path10" /><path | |
| 45 | - d="M 14.3423,2.245 14.3641,2.23775 16.5366,1.72274 C 17.0008,1.61393 17.3997,1.31653 17.6391,0.903078 L 18.1432,0.0290146 18.1578,0 18.1723,0.0290146 18.6764,0.899453 C 18.9158,1.31291 19.3147,1.61031 19.7789,1.71911 L 21.9514,2.23412 21.9768,2.245 H 21.9514 L 19.7789,2.76726 C 19.3147,2.87607 18.9158,3.17347 18.6764,3.58692 L 18.1723,4.45738 18.1578,4.48639 18.1432,4.45738 17.6391,3.58692 C 17.3997,3.17347 17.0008,2.87607 16.5366,2.76726 L 14.3641,2.25225 Z" | |
| 46 | - id="path11" /><path | |
| 47 | - d="M 18.183,15.7998 C 17.9328,13.7832 16.754,11.9807 14.9697,10.9507 11.749,9.03938 8.96364,8.82176 6.23265,6.73999 L 2.52967,8.79638 18.183,17.8344 33.8364,8.79638 30.1334,6.74359 c -2.731,2.08181 -5.5164,2.29943 -8.737,4.21071 -1.7844,1.0301 -2.9631,2.8326 -3.2134,4.8491 z" | |
| 48 | - id="path12" /><path | |
| 49 | - d="M 18.183,35.6603 C 17.824,35.653 17.4649,35.5551 17.1458,35.3701 L 2.52967,26.9269 V 8.79646 L 17.1458,17.236 c 0.3227,0.185 0.6782,0.2829 1.0372,0.2902 0.3591,-0.0073 0.7181,-0.1052 1.0373,-0.2902 L 33.8364,8.79646 V 26.9306 l -14.6161,8.4395 c -0.3228,0.185 -0.6782,0.2829 -1.0373,0.2902 z" | |
| 50 | - id="path13" /><path | |
| 51 | - d="m 18.183,35.6603 c 0.3591,-0.0073 0.7181,-0.1052 1.0373,-0.2902 l 14.616,-8.4395 V 8.79646 L 19.2203,17.236 c -0.3228,0.185 -0.6782,0.2829 -1.0373,0.2902" | |
| 52 | - id="path14" /><path | |
| 53 | - d="M 18.183,17.5262 C 17.824,17.5189 17.4649,17.421 17.1458,17.236 L 2.52967,8.79646 V 26.9306 l 14.61613,8.4395 c 0.3227,0.185 0.6782,0.2829 1.0372,0.2902" | |
| 54 | - id="path15" /><path | |
| 55 | - d="M 18.1831,17.5262 C 17.824,17.5189 17.465,17.421 17.1458,17.236 l -1.4507,-0.8378 v 18.1341 l 1.4507,0.8378 c 0.3228,0.185 0.6782,0.2829 1.0373,0.2902 0.359,-0.0073 0.7181,-0.1052 1.0372,-0.2902 l 1.4508,-0.8378 V 16.3946 l -1.4508,0.8378 c -0.3227,0.1849 -0.6782,0.2829 -1.0372,0.2901 z" | |
| 56 | - id="path16" /><path | |
| 57 | - d="M 9,5.32055 9.02174,5.31328 11.1942,4.79826 c 0.4643,-0.10879 0.8632,-0.40618 1.1026,-0.81964 l 0.5041,-0.87044 0.0145,-0.02902 0.0145,0.02902 0.5041,0.87044 c 0.2394,0.41346 0.6384,0.71085 1.1026,0.81964 l 2.1725,0.51502 0.0253,0.01087 H 16.6091 L 14.4366,5.84644 C 13.9724,5.95523 13.5734,6.25263 13.334,6.66609 L 12.8299,7.53653 12.8154,7.56555 12.8009,7.53653 12.2968,6.66609 C 12.0574,6.25263 11.6585,5.95523 11.1942,5.84644 L 9.02174,5.33142 9,5.32415 Z" | |
| 58 | - id="path17" /><path | |
| 59 | - d="m 19.6593,5.32055 0.0218,-0.00727 2.1724,-0.51502 c 0.4642,-0.10879 0.8632,-0.40618 1.1026,-0.81964 l 0.5041,-0.87044 0.0145,-0.02902 0.0145,0.02902 0.5042,0.87044 c 0.2393,0.41346 0.6383,0.71085 1.1025,0.81964 l 2.1724,0.51502 0.0255,0.01087 H 27.2683 L 25.0959,5.84644 C 24.6317,5.95523 24.2327,6.25263 23.9934,6.66609 L 23.4892,7.53653 23.4747,7.56555 23.4602,7.53653 22.9561,6.66609 C 22.7167,6.25263 22.3177,5.95523 21.8535,5.84644 L 19.6811,5.33142 19.6593,5.32415 Z" | |
| 60 | - id="path18" /></g></g></svg> | |
| @@ -1,88 +1,7 @@ | ||
| 1 | 1 | <?xml version="1.0" standaloneencoding="noUTF-8"?> |
| 2 | -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | |
| 2 | +<svg id="svg1" width="1281.8pt" height="1215.8pt" preserveAspectRatio="xMidYMid" version="1.0" viewBox="0 0 1281.8 1215.8" xmlns="http://www.w3.org/2000/svg"> | |
| 3 | - "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | |
| 3 | + <metadata id="metadata1">Created by potrace 1.15, written by Peter Selinger 2001-2017</metadata> | |
| 4 | -<svg version="1.0" xmlns="http://www.w3.org/2000/svg" | |
| 4 | + <g id="g1" transform="matrix(.1 0 0 -.1 1.9156 1216.5)"> | |
| 5 | - width="1280.000000pt" height="1217.000000pt" viewBox="0 0 1280.000000 1217.000000" | |
| 5 | + <path id="path1" d="m6998 11572c-96-258-333-414-533-587-370-329-494-412-1390-435-874 36-1595-534-2206-1104-62 39-142 56-199 104 0 9 262 294 464 632 74 121 192 285-66 247-417-111-1220-1151-572-305 100 137 564 539 318 683-471 46-1109-1370-1236-1102 23 505 514 977 207 1300-445 44-287-954-795-1541-1448-1651-1018-2700-654-2758 335-53 734 316 734 261 0-180-204-689-108-846 752-1155 2134-1636 3377-2057 99-35-1443-411-1876-970-121-157-587-192-679-346-256-719 2022-1610 3117-1657 812 100 1428-660 2088-1018 130-59 213-134 286 50-220 493-1110 853-956 1021 132 137 1731-15 1731 90 0 8-16 33-35 56-357 414-1665-11-1935 283 0 119 972 340 1341 597 121 97 56 186-113 194-574-4-1043-309-1478-410-184-41-447-69-725 3 83 35 240 88 530 225 448 212 563 317 748 487-10 44-16 92-41 131-101 125-621-82-1438-572-133-80-241-138-256-138-49 0-615 99-802 140-338 75-525 153-473 231 22 29 245 92 467 133 435 77 883 115 1287 305 216 102 386 272 568 422 257 211 446 400 314 650-170 319-510 265-909 653-388 377-556 379-810 585 78 10 157 17 235 26 2709 261 3344 2357 3261 1940-125-518-151-1060-65-1343 200-639 1746-555 2003-537 674 40 1167-320 1761-550 726-269 856-137 765 9-161 263-1223 582-1169 597 508 133 2142 769 1614 1010-297 136-1465-492-1435-455 91 57 965 1014 655 1059-597-178-1028-736-1477-1159-75 29-155 45-228 78 0 20 239 507 275 701 27 105 141 556-120 415-211-131-498-997-534-986-290 78-585 141-886 150-236-1-496 47-461 358 71 337 157 167 490 943 217 528-107 470 77 605 459 340 1060 1370 1115 1494 351 733 1106 1835 145 2401-1027 452-1067-31-2106 150-558 97-937 83-1207-543z"/> | |
| 6 | - preserveAspectRatio="xMidYMid meet"> | |
| 7 | -<metadata> | |
| 8 | -Created by potrace 1.15, written by Peter Selinger 2001-2017 | |
| 9 | -</metadata> | |
| 10 | -<g transform="translate(0.000000,1217.000000) scale(0.100000,-0.100000)" stroke="none"> | |
| 11 | -<path d="M7704 12160 c-84 -17 -256 -71 -307 -96 -186 -94 -274 -202 -399 | |
| 12 | --492 -32 -74 -72 -152 -90 -175 -80 -100 -141 -163 -211 -215 -41 -32 -145 | |
| 13 | --120 -232 -197 -228 -203 -324 -268 -491 -331 -160 -60 -436 -92 -899 -104 | |
| 14 | --428 -11 -478 -15 -585 -50 -499 -163 -1061 -520 -1543 -979 l-78 -75 -100 49 | |
| 15 | -c-54 27 -99 52 -99 55 0 3 35 46 77 95 131 154 287 370 387 537 77 130 96 168 | |
| 16 | -96 200 0 36 -2 38 -25 32 -15 -4 -39 0 -60 10 -29 14 -42 15 -77 5 -147 -39 | |
| 17 | --405 -230 -705 -523 -79 -76 -143 -136 -143 -132 0 3 37 49 81 102 45 54 132 | |
| 18 | -165 195 248 64 86 132 166 159 186 95 72 107 84 129 129 13 25 33 91 45 146 | |
| 19 | -12 55 29 120 38 144 22 59 11 76 -53 78 -27 0 -63 4 -80 8 -23 5 -44 0 -85 | |
| 20 | --20 -78 -39 -264 -232 -441 -455 -80 -102 -192 -243 -249 -315 -135 -171 -213 | |
| 21 | --258 -273 -306 -57 -46 -91 -50 -108 -14 -16 35 15 222 42 250 10 11 35 65 54 | |
| 22 | -120 19 55 69 182 110 283 42 100 75 186 74 190 -1 4 4 25 11 46 14 39 12 113 | |
| 23 | --5 328 l-7 77 -72 6 c-80 8 -99 2 -153 -49 -101 -93 -192 -314 -302 -728 -110 | |
| 24 | --413 -138 -498 -195 -589 -13 -20 -79 -99 -145 -175 -206 -235 -338 -412 -628 | |
| 25 | --844 -188 -278 -318 -656 -353 -1020 -16 -171 -1 -459 33 -638 9 -50 15 -62 | |
| 26 | -29 -60 11 2 31 -19 60 -62 60 -88 105 -118 205 -134 140 -22 189 -10 460 118 | |
| 27 | -49 24 131 68 182 99 51 31 92 51 92 44 0 -7 -34 -185 -75 -396 l-74 -383 41 | |
| 28 | --67 c23 -36 53 -82 67 -101 l25 -35 -33 30 c-51 46 -34 16 56 -98 105 -132 | |
| 29 | -471 -492 628 -618 130 -105 509 -381 670 -489 384 -257 905 -493 1135 -515 49 | |
| 30 | --4 137 -27 255 -65 192 -63 338 -106 475 -140 47 -11 92 -23 99 -26 23 -8 -73 | |
| 31 | --49 -282 -120 -492 -168 -840 -315 -1062 -447 -330 -197 -432 -274 -532 -403 | |
| 32 | --27 -35 -57 -53 -210 -124 -124 -57 -199 -86 -248 -95 -118 -21 -186 -60 -221 | |
| 33 | --127 -14 -28 -17 -124 -5 -168 4 -15 1 -43 -10 -70 -19 -51 -16 -70 11 -70 14 | |
| 34 | -0 21 -10 25 -37 4 -21 12 -49 18 -63 21 -43 179 -193 257 -243 41 -27 134 -87 | |
| 35 | -205 -133 209 -137 484 -268 770 -367 55 -19 163 -62 240 -94 77 -33 201 -81 | |
| 36 | -275 -107 74 -27 191 -68 260 -93 179 -65 382 -113 721 -172 271 -47 301 -50 | |
| 37 | -350 -40 36 8 116 10 239 6 276 -8 386 -41 584 -172 53 -35 153 -111 222 -169 | |
| 38 | -145 -121 277 -222 384 -293 143 -96 584 -356 659 -390 42 -19 87 -40 101 -48 | |
| 39 | -14 -7 41 -16 62 -20 45 -9 93 23 107 69 5 17 12 39 16 49 5 12 2 17 -8 17 -8 | |
| 40 | -0 -29 21 -45 47 -30 46 -189 207 -322 326 l-65 59 63 -47 c34 -26 110 -92 168 | |
| 41 | --147 93 -89 129 -111 129 -82 0 5 4 16 9 23 7 11 -13 36 -81 100 -108 103 | |
| 42 | --189 162 -433 316 -185 117 -268 185 -336 277 -19 25 -39 60 -45 78 -10 27 -9 | |
| 43 | -34 10 54 27 28 108 40 386 57 219 13 1193 2 1213 -13 27 -22 132 15 132 46 0 | |
| 44 | -8 -16 33 -35 56 -19 23 -35 50 -35 60 0 18 -46 35 -55 19 -3 -4 -31 1 -63 12 | |
| 45 | --149 52 -318 76 -737 105 -235 16 -261 16 -435 1 -102 -10 -214 -17 -250 -17 | |
| 46 | --114 0 -360 70 -360 103 0 16 32 46 80 75 45 26 325 127 407 146 34 8 75 23 | |
| 47 | -90 34 60 42 161 85 358 152 235 80 316 118 406 190 81 65 86 74 65 117 -9 18 | |
| 48 | --16 44 -16 57 l0 23 -162 -3 c-129 -1 -190 -7 -295 -28 -222 -44 -459 -119 | |
| 49 | --758 -238 -238 -95 -287 -112 -425 -144 -169 -38 -325 -50 -487 -36 -76 7 | |
| 50 | --160 18 -188 26 l-50 13 28 12 c16 6 30 11 32 11 12 0 345 143 470 202 332 | |
| 51 | -157 506 266 675 420 l73 67 -12 55 c-7 31 -20 65 -29 76 -101 125 -621 -82 | |
| 52 | --1438 -572 -133 -80 -241 -138 -256 -138 -49 0 -615 99 -802 140 -252 56 -364 | |
| 53 | -92 -411 135 -20 19 -45 37 -56 40 -21 7 -24 32 -6 56 22 29 245 92 467 133 47 | |
| 54 | -8 213 36 370 61 476 76 694 134 917 244 131 65 236 136 357 244 45 40 140 120 | |
| 55 | -211 178 161 132 280 255 324 339 33 62 34 65 28 156 -5 80 -10 102 -38 155 | |
| 56 | --31 58 -117 153 -186 206 -18 14 -116 65 -219 114 -243 116 -360 193 -504 333 | |
| 57 | --188 183 -293 261 -565 422 -82 48 -171 105 -198 125 l-47 38 42 5 c24 3 111 | |
| 58 | -12 193 21 493 52 979 180 1545 407 263 106 500 246 735 435 126 101 364 329 | |
| 59 | -502 482 101 111 328 407 426 556 40 61 57 80 53 60 -125 -518 -151 -1060 -65 | |
| 60 | --1343 25 -80 96 -214 139 -262 18 -19 27 -27 20 -17 -7 9 -10 17 -8 17 3 0 43 | |
| 61 | --25 89 -55 171 -113 412 -161 1029 -206 451 -32 470 -33 734 -14 271 19 415 | |
| 62 | -17 541 -10 110 -23 118 -29 72 -51 -41 -20 -43 -23 -24 -47 10 -14 18 -14 53 | |
| 63 | --5 34 9 53 8 105 -6 90 -25 320 -118 479 -193 230 -110 431 -199 535 -238 124 | |
| 64 | --46 312 -95 385 -99 30 -2 83 -9 117 -17 61 -13 62 -13 65 -48 2 -30 7 -37 28 | |
| 65 | --39 51 -8 174 54 178 89 1 8 5 31 8 51 5 28 1 46 -16 72 -82 134 -460 330 | |
| 66 | --1014 527 -140 50 -173 65 -155 70 96 28 461 151 584 196 326 121 639 263 813 | |
| 67 | -371 42 26 99 58 125 71 114 55 208 178 193 251 -9 45 -55 100 -101 121 -162 | |
| 68 | -74 -676 -86 -1330 -413 -73 -37 -114 -53 -105 -42 8 11 56 66 105 124 50 58 | |
| 69 | -110 130 135 160 65 82 184 203 275 280 76 64 84 74 145 194 54 107 65 136 68 | |
| 70 | -190 6 87 -5 103 -73 111 -67 9 -106 -5 -219 -76 -219 -139 -550 -418 -1035 | |
| 71 | --873 l-223 -210 -114 38 c-63 20 -114 39 -114 40 0 2 14 34 31 72 18 38 61 | |
| 72 | -139 96 224 36 85 80 191 98 235 19 44 41 121 50 170 9 50 25 132 36 183 10 51 | |
| 73 | -19 108 19 128 0 45 -23 100 -48 113 -32 17 -92 13 -127 -9 -111 -69 -302 -428 | |
| 74 | --489 -918 -22 -56 -32 -72 -45 -68 -53 17 -275 68 -386 90 -242 47 -310 55 | |
| 75 | --500 60 -119 3 -209 10 -240 19 -107 30 -209 104 -164 119 16 5 -5 45 -43 80 | |
| 76 | --32 30 -33 46 -14 140 25 118 65 191 296 548 48 74 101 169 118 210 16 41 50 | |
| 77 | -125 76 185 84 204 97 320 50 450 -40 112 -41 105 27 155 85 63 378 365 475 | |
| 78 | -490 101 130 244 343 354 526 46 76 124 201 172 278 49 76 100 166 114 200 28 | |
| 79 | -66 55 121 281 566 218 431 289 627 323 890 6 47 18 125 27 173 14 75 14 101 4 | |
| 80 | -165 -19 128 -101 273 -210 375 -28 26 -51 50 -51 52 0 3 14 5 30 5 17 0 30 3 | |
| 81 | -30 8 -1 22 -136 101 -289 167 -216 95 -278 115 -466 153 -304 63 -465 66 -712 | |
| 82 | -13 -355 -76 -560 -80 -928 -16 -304 53 -410 63 -501 45z m2941 -1516 c-15 -49 | |
| 83 | --44 -132 -67 -184 -59 -140 -200 -441 -204 -437 -2 2 28 75 66 162 69 155 185 | |
| 84 | -443 210 518 24 75 20 29 -5 -59z m-3225 -9228 c92 -8 187 -23 215 -32 l50 -18 | |
| 85 | --35 -7 c-87 -19 -329 -31 -406 -21 -443 60 -615 88 -588 94 48 12 586 0 764 | |
| 86 | --16z"/> | |
| 87 | 6 | </g> |
| 88 | 7 | </svg> |
| @@ -0,0 +1,9 @@ | ||
| 1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="64" height="64"> | |
| 2 | + <path d="M8.16 23h21.177v-5.86l-4.023-2.307-.694-.3-16.46.113z" fill="none" /> | |
| 3 | + <path | |
| 4 | + d="M22.012 22.222c.197-.675.122-1.294-.206-1.754-.3-.422-.807-.666-1.416-.694l-11.545-.15c-.075 0-.14-.038-.178-.094s-.047-.13-.028-.206c.038-.113.15-.197.272-.206l11.648-.15c1.38-.066 2.88-1.182 3.404-2.55l.666-1.735a.38.38 0 0 0 .02-.225c-.75-3.395-3.78-5.927-7.4-5.927-3.34 0-6.17 2.157-7.184 5.15-.657-.488-1.5-.75-2.392-.666-1.604.16-2.9 1.444-3.048 3.048a3.58 3.58 0 0 0 .084 1.191A4.84 4.84 0 0 0 0 22.1c0 .234.02.47.047.703.02.113.113.197.225.197H21.58a.29.29 0 0 0 .272-.206l.16-.572z" | |
| 5 | + /> | |
| 6 | + <path | |
| 7 | + d="M25.688 14.803l-.32.01c-.075 0-.14.056-.17.13l-.45 1.566c-.197.675-.122 1.294.206 1.754.3.422.807.666 1.416.694l2.457.15c.075 0 .14.038.178.094s.047.14.028.206c-.038.113-.15.197-.272.206l-2.56.15c-1.388.066-2.88 1.182-3.404 2.55l-.188.478c-.038.094.028.188.13.188h8.797a.23.23 0 0 0 .225-.169A6.41 6.41 0 0 0 32 21.106a6.32 6.32 0 0 0-6.312-6.302" | |
| 8 | + /> | |
| 9 | +</svg> | |
| 9 | \ No newline at end of file | |
| @@ -697,7 +697,7 @@ | ||
| 697 | 697 | </span> |
| 698 | 698 | </div> |
| 699 | 699 | </div> |
| 700 | 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 | 701 | <div class="range-block-title" data-i18n="Temperature"> |
| 702 | 702 | Temperature |
| 703 | 703 | </div> |
| @@ -710,7 +710,7 @@ | ||
| 710 | 710 | </div> |
| 711 | 711 | </div> |
| 712 | 712 | </div> |
| 713 | 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 | 714 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 715 | 715 | Frequency Penalty |
| 716 | 716 | </div> |
| @@ -723,7 +723,7 @@ | ||
| 723 | 723 | </div> |
| 724 | 724 | </div> |
| 725 | 725 | </div> |
| 726 | 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 | 727 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 728 | 728 | Presence Penalty |
| 729 | 729 | </div> |
| @@ -736,7 +736,7 @@ | ||
| 736 | 736 | </div> |
| 737 | 737 | </div> |
| 738 | 738 | </div> |
| 739 | 739 | <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt,workers_ai"> |
| 740 | 740 | <div class="range-block-title" data-i18n="Top K"> |
| 741 | 741 | Top K |
| 742 | 742 | </div> |
| @@ -749,7 +749,7 @@ | ||
| 749 | 749 | </div> |
| 750 | 750 | </div> |
| 751 | 751 | </div> |
| 752 | 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 | 753 | <div class="range-block-title" data-i18n="Top P"> |
| 754 | 754 | Top P |
| 755 | 755 | </div> |
| @@ -762,7 +762,7 @@ | ||
| 762 | 762 | </div> |
| 763 | 763 | </div> |
| 764 | 764 | </div> |
| 765 | 765 | <div class="range-block" data-source="openrouter,nanogpt,chutes,workers_ai"> |
| 766 | 766 | <div class="range-block-title" data-i18n="Repetition Penalty"> |
| 767 | 767 | Repetition Penalty |
| 768 | 768 | </div> |
| @@ -986,7 +986,7 @@ | ||
| 986 | 986 | </div> |
| 987 | 987 | </div> |
| 988 | 988 | </div> |
| 989 | 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 | 990 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 991 | 991 | Seed |
| 992 | 992 | </div> |
| @@ -1269,8 +1269,8 @@ | ||
| 1269 | 1269 | <span data-i18n="Top K">Top K</span> |
| 1270 | 1270 | <div class="fa-solid fa-circle-info opacity50p" title="Top K sets a maximum amount of top tokens that can be chosen from. 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). Set to 0 (or -1, depending on your backend) to disable." data-i18n="[title]Top_K_desc"></div> |
| 1271 | 1271 | </small> |
| 1272 | 1272 | <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="200500" step="1"> |
| 1273 | 1273 | <input class="neo-range-input" type="number" min="-1" max="200500" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui"> |
| 1274 | 1274 | </div> |
| 1275 | 1275 | <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> |
| 1276 | 1276 | <small> |
| @@ -1400,7 +1400,7 @@ | ||
| 1400 | 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 | 1401 | </div> |
| 1402 | 1402 | |
| 1403 | 1403 | <div data-tg-type="koboldcpp, llamacpp, ooba, tabby" id="adaptive_p_block" class="wide100p"> |
| 1404 | 1404 | <h4 class="wide100p textAlignCenter"> |
| 1405 | 1405 | <label data-i18n="Adaptive-P">Adaptive-P</label> |
| 1406 | 1406 | <a href="https://github.com/MrJackSpade/adaptive-p-docs" target="_blank"> |
| @@ -1831,6 +1831,7 @@ | ||
| 1831 | 1831 | <div data-name="tfs" draggable="true"><span>Tail Free Sampling</span><small></small></div> |
| 1832 | 1832 | <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div> |
| 1833 | 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 | 1835 | <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div> |
| 1835 | 1836 | <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div> |
| 1836 | 1837 | <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div> |
| @@ -1996,7 +1997,7 @@ | ||
| 1996 | 1997 | </b> |
| 1997 | 1998 | </div> |
| 1998 | 1999 | </div> |
| 1999 | 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 | 2001 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 2001 | 2002 | <input id="openai_function_calling" type="checkbox" /> |
| 2002 | 2003 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -2010,8 +2011,22 @@ | ||
| 2010 | 2011 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 2011 | 2012 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong> |
| 2012 | 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 | 2026 | </div> |
| 2014 | - <div class="range-block" data-source="openrouter"> | |
| 2027 | + </div> | |
| 2028 | + </div> | |
| 2029 | + <div class="range-block" data-source="openrouter,custom"> | |
| 2015 | 2030 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10"> |
| 2016 | 2031 | <div class="flex-container oneline-dropdown"> |
| 2017 | 2032 | <label for="tool_reasoning_mode" data-i18n="Interleaved Thinking"> |
| @@ -2035,7 +2050,7 @@ | ||
| 2035 | 2050 | </em> |
| 2036 | 2051 | </div> |
| 2037 | 2052 | </div> |
| 2038 | 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 | 2054 | <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 2040 | 2055 | <input id="openai_media_inlining" type="checkbox" /> |
| 2041 | 2056 | <span data-i18n="Send inline media">Send inline media</span> |
| @@ -2059,7 +2074,7 @@ | ||
| 2059 | 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 | 2075 | <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong> |
| 2061 | 2076 | </div> |
| 2062 | 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 | 2078 | <div class="flex-container oneline-dropdown"> |
| 2064 | 2079 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> |
| 2065 | 2080 | Inline Image Quality |
| @@ -2120,7 +2135,7 @@ | ||
| 2120 | 2135 | </div> |
| 2121 | 2136 | </div> |
| 2122 | 2137 | </div> |
| 2123 | 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 | 2139 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2125 | 2140 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2126 | 2141 | <span data-i18n="Request model reasoning">Request model reasoning</span> |
| @@ -2129,12 +2144,12 @@ | ||
| 2129 | 2144 | <span data-i18n="Allows the model to return its thinking process."> |
| 2130 | 2145 | Allows the model to return its thinking process. |
| 2131 | 2146 | </span> |
| 2132 | 2147 | <strong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai,moonshot,openrouter,deepseek"> |
| 2133 | 2148 | This setting affects visibility only. |
| 2134 | 2149 | </strong> |
| 2135 | 2150 | </div> |
| 2136 | 2151 | </div> |
| 2137 | 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 | 2153 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. 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 | 2154 | <label for="openai_reasoning_effort"> |
| 2140 | 2155 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| @@ -2445,7 +2460,8 @@ | ||
| 2445 | 2460 | <small data-i18n="Click Authorize below or get the key from"> |
| 2446 | 2461 | Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. |
| 2447 | 2462 | <br> |
| 2448 | 2463 | <a href="https://openrouter.ai/accountsettings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a> |
| 2464 | + <span class="openrouter_credits_display marginLeft5"></span> | |
| 2449 | 2465 | </div> |
| 2450 | 2466 | <div class="flex-container"> |
| 2451 | 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 | 2910 | <option value="azure_openai">Azure OpenAI</option> |
| 2895 | 2911 | <option value="chutes">Chutes</option> |
| 2896 | 2912 | <option value="claude">Claude</option> |
| 2913 | + <option value="workers_ai">Cloudflare Workers AI</option> | |
| 2897 | 2914 | <option value="cohere">Cohere</option> |
| 2898 | 2915 | <!-- Temporarily disabled. --> |
| 2899 | 2916 | <!-- <option value="cometapi">CometAPI</option> --> |
| @@ -2904,6 +2921,7 @@ | ||
| 2904 | 2921 | <option value="makersuite">Google AI Studio</option> |
| 2905 | 2922 | <option value="vertexai">Google Vertex AI</option> |
| 2906 | 2923 | <option value="mistralai">MistralAI</option> |
| 2924 | + <option value="minimax">MiniMax</option> | |
| 2907 | 2925 | <option value="moonshot">Moonshot AI</option> |
| 2908 | 2926 | <option value="nanogpt">NanoGPT</option> |
| 2909 | 2927 | <option value="openrouter">OpenRouter</option> |
| @@ -3028,8 +3046,17 @@ | ||
| 3028 | 3046 | <div> |
| 3029 | 3047 | <h4 data-i18n="OpenAI Model">OpenAI Model</h4> |
| 3030 | 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 | 3053 | <optgroup label="GPT-5.4"> |
| 3032 | 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 | 3060 | </optgroup> |
| 3034 | 3061 | <optgroup label="GPT-5.3"> |
| 3035 | 3062 | <option value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option> |
| @@ -3143,6 +3170,7 @@ | ||
| 3143 | 3170 | <h4 data-i18n="Claude Model">Claude Model</h4> |
| 3144 | 3171 | <select id="model_claude_select"> |
| 3145 | 3172 | <optgroup label="Versions"> |
| 3173 | + <option value="claude-opus-4-7">claude-opus-4-7</option> | |
| 3146 | 3174 | <option value="claude-opus-4-6">claude-opus-4-6</option> |
| 3147 | 3175 | <option value="claude-opus-4-5">claude-opus-4-5</option> |
| 3148 | 3176 | <option value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option> |
| @@ -3176,7 +3204,8 @@ | ||
| 3176 | 3204 | <small data-i18n="Click Authorize below or get the key from"> |
| 3177 | 3205 | Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. |
| 3178 | 3206 | <br> |
| 3179 | 3207 | <a href="https://openrouter.ai/accountsettings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a> |
| 3208 | + <span class="openrouter_credits_display marginLeft5"></span> | |
| 3180 | 3209 | </div> |
| 3181 | 3210 | <div class="flex-container"> |
| 3182 | 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 | 3224 | <input id="openrouter_use_fallback" type="checkbox" /> |
| 3196 | 3225 | <span data-i18n="Allow fallback models">Allow fallback models</span> |
| 3197 | 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 | 3227 | <div> |
| 3229 | 3228 | <h4> |
| 3230 | 3229 | <span data-i18n="Model Providers">Model Providers</span> |
| @@ -3344,6 +3343,8 @@ | ||
| 3344 | 3343 | <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option> |
| 3345 | 3344 | </optgroup> |
| 3346 | 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 | 3348 | <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option> |
| 3348 | 3349 | <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option> |
| 3349 | 3350 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> |
| @@ -3566,6 +3567,32 @@ | ||
| 3566 | 3567 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3567 | 3568 | </select> |
| 3568 | 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 | 3596 | <div id="electronhub_form" data-source="electronhub"> |
| 3570 | 3597 | <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4> |
| 3571 | 3598 | <div> |
| @@ -3584,37 +3611,6 @@ | ||
| 3584 | 3611 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3585 | 3612 | </select> |
| 3586 | 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 | 3614 | </div> |
| 3619 | 3615 | <div id="chutes_form" data-source="chutes"> |
| 3620 | 3616 | <h4 data-i18n="Chutes API Key">Chutes API Key</h4> |
| @@ -3634,29 +3630,11 @@ | ||
| 3634 | 3630 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3635 | 3631 | </select> |
| 3636 | 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 | 3633 | </div> |
| 3658 | 3634 | <div id="nanogpt_form" data-source="nanogpt"> |
| 3659 | 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 | 3638 | <div class="flex-container"> |
| 3661 | 3639 | <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3662 | 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 | 3648 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3671 | 3649 | </select> |
| 3672 | 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 | 3684 | </div> |
| 3674 | 3685 | <div id="deepseek_form" data-source="deepseek"> |
| 3675 | 3686 | <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4> |
| @@ -3683,9 +3694,7 @@ | ||
| 3683 | 3694 | <div> |
| 3684 | 3695 | <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4> |
| 3685 | 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 | 3698 | </select> |
| 3690 | 3699 | </div> |
| 3691 | 3700 | </div> |
| @@ -3924,6 +3933,7 @@ | ||
| 3924 | 3933 | <h4 data-i18n="Z.AI Model">Z.AI Model</h4> |
| 3925 | 3934 | <select id="model_zai_select"> |
| 3926 | 3935 | <option value="glm-5-turbo">glm-5-turbo</option> |
| 3936 | + <option value="glm-5v-turbo">glm-5v-turbo</option> | |
| 3927 | 3937 | <option value="glm-5.1">glm-5.1</option> |
| 3928 | 3938 | <option value="glm-5">glm-5</option> |
| 3929 | 3939 | <option value="glm-4.7">glm-4.7</option> |
| @@ -3986,6 +3996,37 @@ | ||
| 3986 | 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 | 3997 | </div> |
| 3988 | 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 | 4030 | <div id="prompt_post_processing_form"> |
| 3990 | 4031 | <h4> |
| 3991 | 4032 | <span data-i18n="Prompt Post-Processing"> |
| @@ -4365,7 +4406,7 @@ | ||
| 4365 | 4406 | <small data-i18n="First User Prefix">First User Prefix</small> |
| 4366 | 4407 | <textarea id="instruct_first_input_sequence" data-macros class="text_pole textarea_compact autoSetHeight"></textarea> |
| 4367 | 4408 | </div> |
| 4368 | 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 | 4410 | <small data-i18n="Last User Prefix">Last User Prefix</small> |
| 4370 | 4411 | <textarea id="instruct_last_input_sequence" data-macros class="text_pole wide100p textarea_compact autoSetHeight"></textarea> |
| 4371 | 4412 | </div> |
| @@ -4775,7 +4816,7 @@ | ||
| 4775 | 4816 | <input type="file" id="world_import_file" accept=".json,.lorebook,.png" name="avatar" hidden> |
| 4776 | 4817 | <div id="world_create_button" class="menu_button menu_button_icon"> |
| 4777 | 4818 | <i class="fa-solid fa-globe"></i> |
| 4778 | 4819 | <span data-i18n="NewCreate">NewCreate</span> |
| 4779 | 4820 | </div> |
| 4780 | 4821 | <small data-i18n="or">or</small> |
| 4781 | 4822 | <select id="world_editor_select"> |
| @@ -6733,7 +6774,7 @@ | ||
| 6733 | 6774 | <small class="chat_file_size select_chat_block_filename_item"></small> |
| 6734 | 6775 | <small class="chat_messages_num select_chat_block_filename_item"></small> |
| 6735 | 6776 | </div> |
| 6736 | 6777 | <div class="select_chat_actions flex-container gap10px"> |
| 6737 | 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 | 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 | 6780 | <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div> |
| @@ -24,6 +24,7 @@ import chalk from 'chalk'; | ||
| 24 | 24 | import yaml from 'yaml'; |
| 25 | 25 | import * as chevrotain from 'chevrotain'; |
| 26 | 26 | import { gzipSync, gzip } from 'fflate'; |
| 27 | +import { sha256 } from 'js-sha256'; | |
| 27 | 28 | |
| 28 | 29 | /** |
| 29 | 30 | * Expose the libraries to the 'window' object. |
| @@ -105,6 +106,7 @@ export default { | ||
| 105 | 106 | chevrotain, |
| 106 | 107 | gzipSync, |
| 107 | 108 | gzip, |
| 109 | + sha256, | |
| 108 | 110 | }; |
| 109 | 111 | |
| 110 | 112 | export { |
| @@ -132,4 +134,5 @@ export { | ||
| 132 | 134 | chevrotain, |
| 133 | 135 | gzipSync, |
| 134 | 136 | gzip, |
| 137 | + sha256, | |
| 135 | 138 | }; |
| @@ -213,7 +213,7 @@ import { | ||
| 213 | 213 | tag_import_setting, |
| 214 | 214 | applyCharacterTagsToMessageDivs, |
| 215 | 215 | } from './scripts/tags.js'; |
| 216 | 216 | import { checkOpenRouterAuth, initSecrets, readSecretState } from './scripts/secrets.js'; |
| 217 | 217 | import { markdownExclusionExt } from './scripts/showdown-exclusion.js'; |
| 218 | 218 | import { markdownUnderscoreExt } from './scripts/showdown-underscore.js'; |
| 219 | 219 | import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js'; |
| @@ -717,6 +717,7 @@ async function firstLoadInit() { | ||
| 717 | 717 | initLoaderOverlay.appendChild(splashMessage); |
| 718 | 718 | |
| 719 | 719 | const initLoaderHandle = loader.show({ |
| 720 | + slug: 'app-init', | |
| 720 | 721 | toastMode: loader.ToastMode.NONE, |
| 721 | 722 | overlayContent: initLoaderOverlay, |
| 722 | 723 | }); |
| @@ -741,12 +742,13 @@ async function firstLoadInit() { | ||
| 741 | 742 | initKoboldSettings(); |
| 742 | 743 | initNovelAISettings(); |
| 743 | 744 | initSystemPrompts(); |
| 744 | 745 | await initExtensions(); |
| 745 | 746 | initExtensionSlashCommands(); |
| 746 | 747 | ToolManager.initToolSlashCommands(); |
| 747 | 748 | await initPresetManager(); |
| 748 | 749 | await initSystemMessages(); |
| 749 | 750 | await getSettings(initLoaderHandle); |
| 751 | + await checkOpenRouterAuth(); | |
| 750 | 752 | initKeyboard(); |
| 751 | 753 | initDynamicStyles(); |
| 752 | 754 | initTags(); |
| @@ -1910,17 +1912,32 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san | ||
| 1910 | 1912 | } |
| 1911 | 1913 | |
| 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 | + */ | |
| 1923 | +export function createModelIcon(apiName, modelName = '') { | |
| 1924 | + const image = new Image(); | |
| 1925 | + image.classList.add('icon-svg'); | |
| 1926 | + image.src = `/img/${apiName}.svg`; | |
| 1927 | + image.title = modelName ? `${apiName} - ${modelName}` : apiName; | |
| 1928 | + return image; | |
| 1929 | +} | |
| 1930 | + | |
| 1931 | +/** | |
| 1913 | 1932 | * Inserts or replaces an SVG icon adjacent to the provided message's timestamp. |
| 1914 | 1933 | * |
| 1915 | 1934 | * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced. |
| 1916 | 1935 | * @param {ChatMessageExtra} extra - Contains the API and model details. |
| 1917 | 1936 | */ |
| 1918 | 1937 | function insertSVGIcon(mes, extra) { |
| 1919 | - // Determine the SVG filename | |
| 1938 | + const apiName = extra?.api || ''; | |
| 1920 | - let modelName = extra?.api || ''; | |
| 1921 | 1939 | |
| 1922 | - // If there's no API information, we can't determine which SVG to use | |
| 1940 | + if (!apiName) { | |
| 1923 | - if (!modelName) { | |
| 1924 | 1941 | return; |
| 1925 | 1942 | } |
| 1926 | 1943 | |
| @@ -1937,16 +1954,14 @@ function insertSVGIcon(mes, extra) { | ||
| 1937 | 1954 | }; |
| 1938 | 1955 | }; |
| 1939 | 1956 | |
| 1940 | 1957 | const createModelImageinsertIcon = (className, targetSelector, insertBefore) => { |
| 1941 | 1958 | const image = new ImagecreateModelIcon(apiName, extra?.model); |
| 1942 | 1959 | image.classList.add('icon-svg', className); |
| 1943 | - image.src = `/img/${modelName}.svg`; | |
| 1944 | - image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`; | |
| 1945 | 1960 | insertOrReplaceSVG(image, className, targetSelector, insertBefore); |
| 1946 | 1961 | }; |
| 1947 | 1962 | |
| 1948 | 1963 | createModelImageinsertIcon('timestamp-icon', '.timestamp'); |
| 1949 | 1964 | createModelImageinsertIcon('thinking-icon', '.mes_reasoning_header_title', true); |
| 1950 | 1965 | } |
| 1951 | 1966 | |
| 1952 | 1967 | /** |
| @@ -2907,6 +2922,11 @@ export function substituteParamsLegacy(content, _name1, _name2, _original, _grou | ||
| 2907 | 2922 | export function substituteParams(content, options = {}) { |
| 2908 | 2923 | if (!content) return ''; |
| 2909 | 2924 | |
| 2925 | + if (typeof content !== 'string') { | |
| 2926 | + console.warn('substituteParams: content will be coerced to string', content); | |
| 2927 | + content = String(content); | |
| 2928 | + } | |
| 2929 | + | |
| 2910 | 2930 | // Handle legacy signature calls to substituteParams |
| 2911 | 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 | 2932 | const isOptionsObject = options && typeof options === 'object' && !Array.isArray(options); |
| @@ -3847,9 +3867,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst | ||
| 3847 | 3867 | |
| 3848 | 3868 | // If the prompt was given as a string, convert to a message-style object assuming user role |
| 3849 | 3869 | if (typeof prompt === 'string') { |
| 3850 | 3870 | const message = api{ ===role: 'openaiuser', content: prompt.trim() }; |
| 3851 | - ? { role: 'user', content: prompt.trim() } | |
| 3852 | - : { role: 'system', content: prompt }; | |
| 3853 | 3871 | prompt = [message]; |
| 3854 | 3872 | } else { // checks for message-style object |
| 3855 | 3873 | if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided'); |
| @@ -3876,7 +3894,12 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst | ||
| 3876 | 3894 | // prepend system prompt, if provided |
| 3877 | 3895 | if (systemPrompt) { |
| 3878 | 3896 | systemPrompt = substituteParams(systemPrompt); |
| 3879 | 3897 | systemPrompt = isInstruct ? (formatInstructModeStoryString(systemPrompt) + '\n') : systemPrompt.trim(); |
| 3898 | + if (isInstruct && systemPrompt.length > 0 && !systemPrompt.endsWith('\n')) { | |
| 3899 | + if (power_user.instruct.wrap && !power_user.instruct.story_string_suffix) { | |
| 3900 | + systemPrompt += '\n'; | |
| 3901 | + } | |
| 3902 | + } | |
| 3880 | 3903 | prompt.unshift({ role: 'system', content: systemPrompt }); |
| 3881 | 3904 | } |
| 3882 | 3905 | |
| @@ -3906,7 +3929,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst | ||
| 3906 | 3929 | * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used. |
| 3907 | 3930 | * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response. |
| 3908 | 3931 | * @prop {string} [prefill] An optional prefill for the prompt. |
| 3909 | 3932 | * @prop {objectJsonSchema} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction. |
| 3910 | 3933 | */ |
| 3911 | 3934 | |
| 3912 | 3935 | /** |
| @@ -4018,7 +4041,7 @@ export async function generateRawData({ prompt = '', api = null, instructOverrid | ||
| 4018 | 4041 | } |
| 4019 | 4042 | |
| 4020 | 4043 | if (jsonSchema) { |
| 4021 | 4044 | return extractJsonFromData(data, { mainApi: api, returnInvalidJson: jsonSchema.returnInvalid }); |
| 4022 | 4045 | } |
| 4023 | 4046 | |
| 4024 | 4047 | return data; |
| @@ -4181,6 +4204,7 @@ function removeLastMessage() { | ||
| 4181 | 4204 | * @property {object} value JSON schema value. |
| 4182 | 4205 | * @property {string} [description] Description of the schema. |
| 4183 | 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 | 4209 | * @typedef {object} GenerateOptions |
| 4186 | 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 | 4291 | if (selected_group && !is_group_generating) { |
| 4268 | 4292 | if (!dryRun) { |
| 4269 | 4293 | // Returns the promise that generateGroupWrapper returns; resolves when generation is done |
| 4270 | 4294 | return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, jsonSchema }); |
| 4271 | 4295 | } |
| 4272 | 4296 | |
| 4273 | 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 | 4473 | for (let i = coreChat.length - 1; i >= 0; i--) { |
| 4450 | 4474 | const depth = coreChat.length - i - (isContinue ? 2 : 1); |
| 4451 | 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 | 4480 | coreChat[i] = { |
| 4453 | 4481 | ...coreChat[i], |
| 4454 | 4482 | mes: promptReasoning.addToMessage(isOtherGroupMember |
| 4483 | + ? coreChat[i].mes | |
| 4484 | + : promptReasoning.addToMessage( | |
| 4455 | 4485 | coreChat[i].mes, |
| 4456 | 4486 | getRegexedString( |
| 4457 | 4487 | String(coreChat[i].extra?.reasoning ?? ''), |
| @@ -4697,7 +4727,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4697 | 4727 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST); |
| 4698 | 4728 | } |
| 4699 | 4729 | |
| 4700 | 4730 | if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct && !isImpersonate) { |
| 4701 | 4731 | // Reformat with the last input sequence (if any) |
| 4702 | 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 | 5331 | streamingProcessor.firstMessageText = ''; |
| 5302 | 5332 | } |
| 5303 | 5333 | |
| 5304 | 5334 | streamingProcessor.generator = await sendStreamingRequest(type, generate_data, { jsonSchema }); |
| 5305 | 5335 | |
| 5306 | 5336 | hideSwipeButtons(); |
| 5307 | 5337 | let getMessage = await streamingProcessor.generate(); |
| @@ -5390,7 +5420,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 5390 | 5420 | |
| 5391 | 5421 | if (jsonSchema) { |
| 5392 | 5422 | unblockGeneration(type); |
| 5393 | - return extractJsonFromData(data); | |
| 5423 | + return extractJsonFromData(data, { returnInvalidJson: jsonSchema.returnInvalid ?? false }); | |
| 5394 | 5424 | } |
| 5395 | 5425 | |
| 5396 | 5426 | //const getData = await response.json(); |
| @@ -5823,11 +5853,11 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul | ||
| 5823 | 5853 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt); |
| 5824 | 5854 | } else { |
| 5825 | 5855 | chat.push(message); |
| 5856 | + await saveChatConditional(); | |
| 5826 | 5857 | const chat_id = (chat.length - 1); |
| 5827 | 5858 | await eventSource.emit(event_types.MESSAGE_SENT, chat_id); |
| 5828 | 5859 | addOneMessage(message); |
| 5829 | 5860 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); |
| 5830 | - await saveChatConditional(); | |
| 5831 | 5861 | } |
| 5832 | 5862 | |
| 5833 | 5863 | return message; |
| @@ -6003,7 +6033,7 @@ function setInContextMessages(msgInContextCount, type) { | ||
| 6003 | 6033 | |
| 6004 | 6034 | if (lastMessageBlock.length === 0) { |
| 6005 | 6035 | const firstMessageId = getFirstDisplayedMessageId(); |
| 6006 | 6036 | chatElement.find(`.mes[mesid="${firstMessageId}"]`).addClass('lastInContext'); |
| 6007 | 6037 | } |
| 6008 | 6038 | |
| 6009 | 6039 | // 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 | 6244 | * Extracts JSON from the response data. |
| 6215 | 6245 | * @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 | 6250 | * @returns {string} Extracted JSON string from the response data |
| 6217 | 6251 | */ |
| 6218 | 6252 | export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null, returnInvalidJson = false } = {}) { |
| 6219 | 6253 | mainApi = mainApi ?? main_api; |
| 6220 | 6254 | chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source; |
| 6221 | 6255 | |
| @@ -6238,6 +6272,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource | ||
| 6238 | 6272 | break; |
| 6239 | 6273 | case chat_completion_sources.PERPLEXITY: |
| 6240 | 6274 | result = tryParse(removeReasoningFromString(text)); |
| 6275 | + if (!result && returnInvalidJson) { | |
| 6276 | + return text; | |
| 6277 | + } | |
| 6241 | 6278 | break; |
| 6242 | 6279 | case chat_completion_sources.VERTEXAI: |
| 6243 | 6280 | case chat_completion_sources.MAKERSUITE: |
| @@ -6258,6 +6295,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource | ||
| 6258 | 6295 | case chat_completion_sources.ZAI: |
| 6259 | 6296 | default: |
| 6260 | 6297 | result = tryParse(text); |
| 6298 | + if (!result && returnInvalidJson) { | |
| 6299 | + return text; | |
| 6300 | + } | |
| 6261 | 6301 | break; |
| 6262 | 6302 | } |
| 6263 | 6303 | } break; |
| @@ -7924,6 +7964,15 @@ export async function getSettings(initLoaderHandle = null) { | ||
| 7924 | 7964 | const isVersionChanged = settings.currentVersion !== currentVersion; |
| 7925 | 7965 | await loadExtensionSettings(settings, isVersionChanged, enableAutoUpdate); |
| 7926 | 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 | } |
| 7928 | 7977 | |
| 7929 | 7978 | firstRun = !!settings.firstRun; |
| @@ -10565,6 +10614,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile | ||
| 10565 | 10614 | } |
| 10566 | 10615 | |
| 10567 | 10616 | const loaderHandle = showLoader ? loader.show({ |
| 10617 | + slug: 'chat-rename', | |
| 10568 | 10618 | title: t`Rename Chat`, |
| 10569 | 10619 | message: t`Renaming chat…`, |
| 10570 | 10620 | toastMode: loader.ToastMode.STATIC, |
| @@ -10602,6 +10652,9 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile | ||
| 10602 | 10652 | if (currentChatId) { |
| 10603 | 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 | 10658 | } catch { |
| 10606 | 10659 | await delay(500); |
| 10607 | 10660 | await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT); |
| @@ -11166,6 +11219,7 @@ jQuery(async function () { | ||
| 11166 | 11219 | $('#select_chat_cross').trigger('click'); |
| 11167 | 11220 | |
| 11168 | 11221 | const loaderHandle = loader.show({ |
| 11222 | + slug: 'chat-delete', | |
| 11169 | 11223 | title: t`Delete Chat`, |
| 11170 | 11224 | message: t`Deleting chat…`, |
| 11171 | 11225 | toastMode: loader.ToastMode.STATIC, |
| @@ -12460,7 +12514,9 @@ jQuery(async function () { | ||
| 12460 | 12514 | $('#avatar-and-name-block').slideToggle(); |
| 12461 | 12515 | }); |
| 12462 | 12516 | |
| 12463 | 12517 | $(document).on('mouseup touchendclick', '#show_more_messages', async function (event) { |
| 12518 | + event.stopPropagation(); | |
| 12519 | + event.preventDefault(); | |
| 12464 | 12520 | await showMoreMessages(); |
| 12465 | 12521 | }); |
| 12466 | 12522 | |
| @@ -848,6 +848,7 @@ class BulkEditOverlay { | ||
| 848 | 848 | const deleteChats = checkbox.prop('checked') ?? false; |
| 849 | 849 | |
| 850 | 850 | const loaderHandle = loader.show({ |
| 851 | + slug: 'bulk-delete', | |
| 851 | 852 | title: t`Bulk Delete`, |
| 852 | 853 | message: t`Deleting ${characterIds.length} character(s)…`, |
| 853 | 854 | toastMode: loader.ToastMode.STATIC, |
| @@ -407,6 +407,8 @@ function RA_autoconnect(PrevApi) { | ||
| 407 | 407 | || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) |
| 408 | 408 | || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI) |
| 409 | 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 | 412 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 411 | 413 | || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) |
| 412 | 414 | ) { |
| @@ -16,6 +16,7 @@ const buttonSelectors = [ | ||
| 16 | 16 | '.jg-menu .jg-button', |
| 17 | 17 | '.bg_example .mobile-only-menu-toggle', |
| 18 | 18 | '.paginationjs-pages li a', |
| 19 | + '#show_more_messages', | |
| 19 | 20 | ].join(', '); |
| 20 | 21 | |
| 21 | 22 | const listSelectors = [ |
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoaderloader } from './action-loader.js'; |
| 2 | 2 | import { t } from './i18n.js'; |
| 3 | 3 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 4 | 4 | import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; |
| @@ -7,6 +7,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | ||
| 7 | 7 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 8 | 8 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 9 | 9 | import { isFalseBoolean } from './utils.js'; |
| 10 | +import { DOMPurify } from '../lib.js'; | |
| 10 | 11 | |
| 11 | 12 | /** |
| 12 | 13 | * Registers slash commands for the action loader module. |
| @@ -116,6 +117,12 @@ export function registerActionLoaderSlashCommands() { | ||
| 116 | 117 | typeList: [ARGUMENT_TYPE.STRING], |
| 117 | 118 | }), |
| 118 | 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 | 126 | name: 'stopTooltip', |
| 120 | 127 | description: 'Tooltip text for the stop button (only used when toast=stoppable)', |
| 121 | 128 | typeList: [ARGUMENT_TYPE.STRING], |
| @@ -148,7 +155,8 @@ export function registerActionLoaderSlashCommands() { | ||
| 148 | 155 | const title = args.title ? String(args.title) : ''; |
| 149 | 156 | const stopTooltip = String(args.stopTooltip ?? t`Stop`); |
| 150 | 157 | |
| 151 | 158 | const loaderactionLoader = showActionLoaderloader.show({ |
| 159 | + slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap', | |
| 152 | 160 | blocking, |
| 153 | 161 | toastMode, |
| 154 | 162 | message, |
| @@ -162,7 +170,7 @@ export function registerActionLoaderSlashCommands() { | ||
| 162 | 170 | const result = await closureCopy.execute(); |
| 163 | 171 | return result.pipe; |
| 164 | 172 | } finally { |
| 165 | 173 | await loaderactionLoader.hide(); |
| 166 | 174 | } |
| 167 | 175 | }, |
| 168 | 176 | })); |
| @@ -232,6 +240,12 @@ export function registerActionLoaderSlashCommands() { | ||
| 232 | 240 | typeList: [ARGUMENT_TYPE.STRING], |
| 233 | 241 | }), |
| 234 | 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 | 249 | name: 'stopTooltip', |
| 236 | 250 | description: 'Tooltip text for the stop button (only used when toast=stoppable)', |
| 237 | 251 | typeList: [ARGUMENT_TYPE.STRING], |
| @@ -258,11 +272,12 @@ export function registerActionLoaderSlashCommands() { | ||
| 258 | 272 | const title = args.title ? String(args.title) : ''; |
| 259 | 273 | const stopTooltip = String(args.stopTooltip ?? t`Stop`); |
| 260 | 274 | |
| 261 | 275 | const handle = showActionLoaderloader.show({ |
| 276 | + slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show', | |
| 262 | 277 | blocking, |
| 263 | 278 | toastMode, |
| 264 | - message, | |
| 279 | + message: DOMPurify.sanitize(message), | |
| 265 | - title, | |
| 280 | + title: DOMPurify.sanitize(title), | |
| 266 | 281 | stopTooltip, |
| 267 | 282 | onStop: createClosureHandler(args.onStop), |
| 268 | 283 | onHide: createClosureHandler(args.onHide, { argName: 'onHide' }), |
| @@ -307,7 +322,7 @@ export function registerActionLoaderSlashCommands() { | ||
| 307 | 322 | } |
| 308 | 323 | |
| 309 | 324 | // No handle provided - hide all active loaders |
| 310 | 325 | const result = await hideActionLoaderloader.hide(); |
| 311 | 326 | return result ? 'true' : 'false'; |
| 312 | 327 | }, |
| 313 | 328 | })); |
| @@ -33,6 +33,7 @@ export const ActionLoaderToastMode = { | ||
| 33 | 33 | * @typedef {object} ActionLoaderOptions |
| 34 | 34 | * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders. |
| 35 | 35 | * @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 | 37 | * @property {string} [message='Generating...'] - The message to display in the toast |
| 37 | 38 | * @property {string} [title] - Optional title for the toast notification |
| 38 | 39 | * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button |
| @@ -73,8 +74,20 @@ function hasBlockingLoaders() { | ||
| 73 | 74 | * Manages its own toast, stop handler, and lifecycle. |
| 74 | 75 | */ |
| 75 | 76 | export 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 | 86 | /** @type {string} Unique identifier for this handle */ |
| 77 | 87 | #id; |
| 88 | + | |
| 89 | + /** @type {string|null} Unique slug for the loader */ | |
| 90 | + #slug = null; | |
| 78 | 91 | |
| 79 | 92 | /** @type {JQuery<HTMLElement>|null} The toast element for this loader */ |
| 80 | 93 | #toast = null; |
| @@ -96,9 +109,11 @@ export class ActionLoaderHandle { | ||
| 96 | 109 | * @param {object} options - Configuration options |
| 97 | 110 | * @param {boolean} [options.blocking=true] - Whether to show blocking overlay |
| 98 | 111 | * @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 | 113 | * @param {string} [options.message='Generating...'] - Message to display in the toast |
| 100 | 114 | * @param {string} [options.title] - Title for the toast notification |
| 101 | 115 | * @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 | 117 | * @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner) |
| 103 | 118 | * @param {(() => void)|null} [options.onStop] - Custom stop handler |
| 104 | 119 | * @param {(() => void)|null} [options.onHide] - Custom hide handler |
| @@ -106,14 +121,22 @@ export class ActionLoaderHandle { | ||
| 106 | 121 | constructor({ |
| 107 | 122 | blocking = true, |
| 108 | 123 | toastMode = ActionLoaderToastMode.STOPPABLE, |
| 124 | + slug = null, | |
| 109 | 125 | message = t`Generating...`, |
| 110 | 126 | title = '', |
| 111 | 127 | stopTooltip = t`Stop`, |
| 112 | 128 | overlayContent = null, |
| 113 | 129 | onStop = null, |
| 114 | 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 | 140 | this.#blocking = blocking; |
| 118 | 141 | this.#onStop = onStop; |
| 119 | 142 | this.#onHide = onHide; |
| @@ -148,6 +171,12 @@ export class ActionLoaderHandle { | ||
| 148 | 171 | const toastContent = document.createElement('div'); |
| 149 | 172 | toastContent.className = 'action-loader-toast'; |
| 150 | 173 | |
| 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 | 180 | const messageSpan = document.createElement('span'); |
| 152 | 181 | messageSpan.className = 'action-loader-message'; |
| 153 | 182 | messageSpan.textContent = message; |
| @@ -202,6 +231,22 @@ export class ActionLoaderHandle { | ||
| 202 | 231 | } |
| 203 | 232 | |
| 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 | 250 | * Whether this handle is still active (not disposed). |
| 206 | 251 | * @returns {boolean} |
| 207 | 252 | */ |
| @@ -700,6 +700,7 @@ export function initBookmarks() { | ||
| 700 | 700 | } |
| 701 | 701 | |
| 702 | 702 | const loaderHandle = loader.show({ |
| 703 | + slug: 'chat-load', | |
| 703 | 704 | title: t`Chat History`, |
| 704 | 705 | message: t`Loading chat…`, |
| 705 | 706 | toastMode: loader.ToastMode.STATIC, |
| @@ -72,6 +72,14 @@ function applyBrowserFixes() { | ||
| 72 | 72 | |
| 73 | 73 | if (isMobile()) { |
| 74 | 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 | 83 | console.debug('[Mobile] Device viewport change detected.'); |
| 76 | 84 | document.documentElement.style.position = 'fixed'; |
| 77 | 85 | requestAnimationFrame(() => document.documentElement.style.position = ''); |
| @@ -2288,11 +2288,14 @@ export function initChatUtilities() { | ||
| 2288 | 2288 | await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true }); |
| 2289 | 2289 | }); |
| 2290 | 2290 | |
| 2291 | 2291 | $(document).on('click', 'body .mes .mes_text, body .mes .mes_reasoning', function (event) { |
| 2292 | 2292 | if (!power_user.click_to_edit) return; |
| 2293 | 2293 | if (window.getSelection().toString()) return; |
| 2294 | 2294 | if ($('.edit_textarea').length) return; |
| 2295 | 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 | }); |
| 2297 | 2300 | |
| 2298 | 2301 | $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog); |
| @@ -51,6 +51,7 @@ import EventSourceStream from './sse-stream.js'; | ||
| 51 | 51 | * @property {string} [reverse_proxy] - Optional reverse proxy URL |
| 52 | 52 | * @property {string} [proxy_password] - Optional proxy password |
| 53 | 53 | * @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 | */ |
| 55 | 56 | |
| 56 | 57 | /** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */ |
| @@ -505,7 +506,7 @@ export class ChatCompletionService { | ||
| 505 | 506 | return async function* streamData() { |
| 506 | 507 | let text = ''; |
| 507 | 508 | const swipes = []; |
| 508 | 509 | const state = { reasoning: '', imageimages: [], signature: '', toolSignatures: {} }; |
| 509 | 510 | while (true) { |
| 510 | 511 | const { done, value } = await reader.read(); |
| 511 | 512 | if (done) return; |
| @@ -591,7 +592,7 @@ export class ChatCompletionService { | ||
| 591 | 592 | } |
| 592 | 593 | |
| 593 | 594 | // Ensure api-url is properly applied for all sources that accept it |
| 594 | 595 | ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint', 'minimax_endpoint'].forEach(field => { |
| 595 | 596 | // The order is: connection profile => CC preset => CC settings |
| 596 | 597 | overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field]; |
| 597 | 598 | }); |
| @@ -126,6 +126,12 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | ||
| 126 | 126 | // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside, |
| 127 | 127 | // it should be manually defined in CSS. |
| 128 | 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 | 135 | let focusRule = `${focusSelector} { ${rule.style.cssText} }`; |
| 130 | 136 | |
| 131 | 137 | // Wrap the generated rule into the same @media/@supports/@container chain (if any) |
| @@ -50,6 +50,7 @@ export const event_types = { | ||
| 50 | 50 | FORCE_SET_BACKGROUND: 'force_set_background', |
| 51 | 51 | CHAT_DELETED: 'chat_deleted', |
| 52 | 52 | CHAT_CREATED: 'chat_created', |
| 53 | + CHAT_RENAMED: 'chat_renamed', | |
| 53 | 54 | GROUP_CHAT_DELETED: 'group_chat_deleted', |
| 54 | 55 | GROUP_CHAT_CREATED: 'group_chat_created', |
| 55 | 56 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', |
| @@ -97,9 +98,16 @@ export const event_types = { | ||
| 97 | 98 | WORLDINFO_SCAN_DONE: 'worldinfo_scan_done', |
| 98 | 99 | MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted', |
| 99 | 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 | 105 | TTS_JOB_STARTED: 'tts_job_started', |
| 101 | 106 | TTS_AUDIO_READY: 'tts_audio_ready', |
| 102 | 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 | }; |
| 104 | 112 | |
| 105 | 113 | export const eventSource = new EventEmitter([event_types.APP_READY, event_types.APP_INITIALIZED]); |
| @@ -1,9 +1,9 @@ | ||
| 1 | 1 | import { DOMPurify, Popper } from '../lib.js'; |
| 2 | 2 | |
| 3 | 3 | import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js'; |
| 4 | 4 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| 5 | 5 | import { renderTemplate, renderTemplateAsync } from './templates.js'; |
| 6 | 6 | import { delay, deleteValueByPath, equalsIgnoreCaseAndAccents, escapeHtml, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js'; |
| 7 | 7 | import { getContext } from './st-context.js'; |
| 8 | 8 | import { isAdmin } from './user.js'; |
| 9 | 9 | import { addLocaleData, getCurrentLocale, t } from './i18n.js'; |
| @@ -61,6 +61,19 @@ let manifests = {}; | ||
| 61 | 61 | */ |
| 62 | 62 | const defaultUrl = 'http://localhost:5100'; |
| 63 | 63 | |
| 64 | +/** | |
| 65 | + * Checks if the extension is officially supported by its URL pattern. | |
| 66 | + * @param {string} url URL to check | |
| 67 | + * @returns {boolean} True if the URL matches the pattern, false otherwise (or not a valid URL) | |
| 68 | + */ | |
| 69 | +export const isOfficialExtension = (url) => { | |
| 70 | + try { | |
| 71 | + return /^https:\/\/github\.com\/SillyTavern\/(.+)$/i.test(new URL(url).href); | |
| 72 | + } catch (e) { | |
| 73 | + return false; | |
| 74 | + } | |
| 75 | +}; | |
| 76 | + | |
| 64 | 77 | let requiresReload = false; |
| 65 | 78 | let stateChanged = false; |
| 66 | 79 | let saveMetadataTimeout = null; |
| @@ -267,6 +280,18 @@ export async function doExtrasFetch(endpoint, args = {}) { | ||
| 267 | 280 | } |
| 268 | 281 | |
| 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 | + */ | |
| 289 | +function getNameSelector(name, { prefix = 'third-party' } = {}) { | |
| 290 | + const nameWithoutPrefix = prefix && name.startsWith(prefix) ? name.slice(prefix.length) : name; | |
| 291 | + return CSS.escape(nameWithoutPrefix); | |
| 292 | +} | |
| 293 | + | |
| 294 | +/** | |
| 270 | 295 | * Discovers extensions from the API. |
| 271 | 296 | * @returns {Promise<{name: string, type: string}[]>} |
| 272 | 297 | */ |
| @@ -343,7 +368,7 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) { | ||
| 343 | 368 | } |
| 344 | 369 | |
| 345 | 370 | toggleContainer |
| 346 | 371 | .find(`.extension_block[data-name="${name.replacegetNameSelector('third-party', ''name)}"] .extension_toggle input`) |
| 347 | 372 | .prop('checked', enable) |
| 348 | 373 | .toggleClass('toggle_enable', !enable) |
| 349 | 374 | .toggleClass('toggle_disable', enable) |
| @@ -355,11 +380,27 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) { | ||
| 355 | 380 | } |
| 356 | 381 | |
| 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 | + */ | |
| 388 | +function hasExtensionHook(name, hookName) { | |
| 389 | + const fullName = name.startsWith('third-party') ? name : `third-party${name}`; | |
| 390 | + const manifest = manifests[fullName]; | |
| 391 | + if (!manifest || !manifest.hooks || typeof manifest.hooks !== 'object') { | |
| 392 | + return false; | |
| 393 | + } | |
| 394 | + const hookFunctionName = manifest.hooks[hookName]; | |
| 395 | + return typeof hookFunctionName === 'string' && hookFunctionName.length > 0; | |
| 396 | +} | |
| 397 | + | |
| 398 | +/** | |
| 358 | 399 | * Calls a manifest hook for an extension. |
| 359 | 400 | * Hooks are optional function names exported from the extension's JS entry point module. |
| 360 | 401 | * The hook function can optionally return a Promise that will be awaited. |
| 361 | 402 | * @param {string} name Extension name |
| 362 | 403 | * @param {'install' | 'update' | 'delete' | 'clean' | 'enable' | 'disable' | 'activate'} hookName The hook to call |
| 363 | 404 | * @returns {Promise<void>} |
| 364 | 405 | */ |
| 365 | 406 | async function callExtensionHook(name, hookName) { |
| @@ -474,6 +515,21 @@ export function findExtension(name) { | ||
| 474 | 515 | } |
| 475 | 516 | |
| 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 | + */ | |
| 524 | +export function getExtensionManifest(name) { | |
| 525 | + const found = extensionNames.find(extName => | |
| 526 | + equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`), | |
| 527 | + ); | |
| 528 | + const manifest = found ? manifests[found] : null; | |
| 529 | + return manifest ? structuredClone(manifest) : null; | |
| 530 | +} | |
| 531 | + | |
| 532 | +/** | |
| 477 | 533 | * Loads manifest.json files for extensions. |
| 478 | 534 | * @param {string[]} names Array of extension names |
| 479 | 535 | * @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 | } |
| 822 | 878 | |
| 823 | 879 | /** |
| 824 | 880 | * Generates HTMLan stringelement for displaying an extension in the UI. |
| 825 | 881 | * |
| 826 | 882 | * @param {string} name - The name of the extension. |
| 827 | 883 | * @param {object} manifest - The manifest of the extension. |
| @@ -829,95 +885,180 @@ function addExtensionLocale(name, manifest) { | ||
| 829 | 885 | * @param {boolean} isDisabled - Whether the extension is disabled or not. |
| 830 | 886 | * @param {boolean} isExternal - Whether the extension is external or not. |
| 831 | 887 | * @param {string} checkboxClass - The class for the checkbox HTML element. |
| 832 | 888 | * @return {stringHTMLElement} - The HTML stringelement that represents the extension. |
| 833 | 889 | */ |
| 834 | 890 | function generateExtensionHtmlgenerateExtensionElement(name, manifest, isActive, isDisabled, isExternal, checkboxClass) { |
| 835 | 891 | function getExtensionIcon() { |
| 836 | 892 | const type = getExtensionType(name); |
| 893 | + const icon = document.createElement('i'); | |
| 894 | + icon.classList.add('fa-sm', 'fa-fw', 'fa-solid'); | |
| 837 | 895 | switch (type) { |
| 838 | 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 | 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 | 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 | 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 | } |
| 848 | 915 | |
| 849 | 916 | const isUserAdmin = isAdmin(); |
| 850 | - const extensionIcon = getExtensionIcon(); | |
| 851 | 917 | const displayName = manifest.display_name; |
| 852 | 918 | const displayVersion = manifest.version || ''; |
| 853 | 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); | |
| 858 | 951 | |
| 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'); | |
| 862 | 955 | |
| 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); | |
| 868 | 971 | |
| 869 | 972 | if (isActive && Array.isArray(manifest.optional)) { |
| 870 | 973 | const optional = new Set(manifest.optional); |
| 871 | 974 | modules.forEach(x => optional.delete(x)); |
| 872 | 975 | if (optional.size > 0) { |
| 873 | 976 | const optionalStringmodulesDiv = DOMPurify.sanitize([...optional]document.joincreateElement(', div')); |
| 874 | - modulesInfo = '<div class="extension_modules">' + t`Optional modules:` + ` <span class="optional">${optionalString}</span></div>`; | |
| 977 | + modulesDiv.classList.add('extension_modules'); | |
| 875 | - } | |
| 978 | + const optionalSpan = document.createElement('span'); | |
| 876 | - } else if (!isDisabled) { // Neither active nor disabled | |
| 979 | + optionalSpan.classList.add('optional'); | |
| 980 | + optionalSpan.textContent = [...optional].join(', '); | |
| 981 | + modulesDiv.append(t`Optional modules:`, ' ', optionalSpan); | |
| 982 | + statusSpan.appendChild(modulesDiv); | |
| 983 | + } | |
| 984 | + } else if (!isDisabled) { | |
| 985 | + // Neither active nor disabled | |
| 877 | 986 | const requirements = new Set(manifest.requires); |
| 878 | 987 | modules.forEach(x => requirements.delete(x)); |
| 879 | 988 | if (requirements.size > 0) { |
| 880 | 989 | const requirementsStringmodulesDiv = DOMPurify.sanitize([...requirements]document.joincreateElement(', div')); |
| 881 | - modulesInfo = `<div class="extension_modules">Missing modules: <span class="failure">${requirementsString}</span></div>`; | |
| 990 | + modulesDiv.classList.add('extension_modules'); | |
| 991 | + const failureSpan = document.createElement('span'); | |
| 992 | + failureSpan.classList.add('failure'); | |
| 993 | + failureSpan.textContent = [...requirements].join(', '); | |
| 994 | + modulesDiv.append(t`Missing modules:`, ' ', failureSpan); | |
| 995 | + statusSpan.appendChild(modulesDiv); | |
| 882 | 996 | } |
| 883 | 997 | } |
| 884 | 998 | |
| 885 | 999 | // 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); | |
| 886 | 1053 | |
| 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 | } |
| 915 | 1056 | |
| 916 | 1057 | /** |
| 917 | 1058 | * Gets extension data and generates the corresponding HTMLelement for displaying the extension. |
| 918 | 1059 | * |
| 919 | 1060 | * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest. |
| 920 | 1061 | * @return {object{isExternal: boolean, extensionElement: HTMLElement}} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtmlextensionElement' for the extension's HTML stringelement. |
| 921 | 1062 | */ |
| 922 | 1063 | function getExtensionData(extension) { |
| 923 | 1064 | const name = extension[0]; |
| @@ -927,33 +1068,43 @@ function getExtensionData(extension) { | ||
| 927 | 1068 | const isExternal = name.startsWith('third-party'); |
| 928 | 1069 | |
| 929 | 1070 | const checkboxClass = isDisabled ? 'checkbox_disabled' : ''; |
| 1071 | + const extensionElement = generateExtensionElement(name, manifest, isActive, isDisabled, isExternal, checkboxClass); | |
| 930 | 1072 | |
| 931 | - const extensionHtml = generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass); | |
| 1073 | + return { isExternal, extensionElement }; | |
| 932 | - | |
| 933 | - return { isExternal, extensionHtml }; | |
| 934 | 1074 | } |
| 935 | 1075 | |
| 936 | 1076 | |
| 937 | 1077 | /** |
| 938 | 1078 | * Gets the module information to be displayed. |
| 939 | 1079 | * |
| 940 | 1080 | * @return {stringHTMLElement} - The HTML stringelement forcontaining the module information. |
| 941 | 1081 | */ |
| 942 | 1082 | function 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 | } |
| 949 | 1100 | |
| 950 | 1101 | /** |
| 951 | 1102 | * Generates HTMLHTMLElement for the extension load errors. |
| 952 | 1103 | * @returns {stringHTMLElement} HTML- stringThe element containing the errors that occurred whileextension loadingload extensionserrors. |
| 953 | 1104 | */ |
| 954 | 1105 | function getExtensionLoadErrorsHtmlgetExtensionLoadErrors() { |
| 955 | 1106 | if (extensionLoadErrors.size === 0) { |
| 956 | 1107 | return document.createElement('div'); |
| 957 | 1108 | } |
| 958 | 1109 | |
| 959 | 1110 | const container = document.createElement('div'); |
| @@ -965,7 +1116,7 @@ function getExtensionLoadErrorsHtml() { | ||
| 965 | 1116 | container.appendChild(errorElement); |
| 966 | 1117 | } |
| 967 | 1118 | |
| 968 | 1119 | return container.outerHTML; |
| 969 | 1120 | } |
| 970 | 1121 | |
| 971 | 1122 | /** |
| @@ -982,22 +1133,35 @@ async function showExtensionsDetails() { | ||
| 982 | 1133 | initialScrollTop = oldPopup.content.scrollTop; |
| 983 | 1134 | await oldPopup.completeCancelled(); |
| 984 | 1135 | } |
| 985 | 1136 | const htmlErrorserrors = getExtensionLoadErrorsHtmlgetExtensionLoadErrors(); |
| 986 | - const htmlDefault = $('<div class="marginBot10"><h3>' + t`Built-in Extensions:` + '</h3></div>'); | |
| 1137 | + | |
| 987 | - | |
| 1138 | + const defaultContainer = document.createElement('div'); | |
| 988 | - const htmlExternal = $(`<div class="marginBot10"> | |
| 1139 | + defaultContainer.classList.add('marginBot10'); | |
| 989 | - <div class="flex-container alignitemscenter spaceBetween flexnowrap marginBot10"> | |
| 1140 | + const defaultHeading = document.createElement('h3'); | |
| 990 | - <h3 class="margin0">${t`Installed Extensions:`}</h3> | |
| 1141 | + defaultHeading.textContent = t`Built-in Extensions:`; | |
| 991 | - <div class="flex-container third_party_toolbar"></div> | |
| 1142 | + defaultContainer.appendChild(defaultHeading); | |
| 992 | - </div> | |
| 1143 | + | |
| 993 | - </div>`); | |
| 1144 | + const externalContainer = document.createElement('div'); | |
| 994 | - | |
| 1145 | + externalContainer.classList.add('marginBot10'); | |
| 995 | - const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5"> | |
| 1146 | + const externalHeader = document.createElement('div'); | |
| 996 | - <i class="fa-solid fa-spinner fa-spin"></i> | |
| 1147 | + externalHeader.classList.add('flex-container', 'alignitemscenter', 'spaceBetween', 'flexnowrap', 'marginBot10'); | |
| 997 | - <span>` + t`Loading third-party extensions... Please wait...` + `</span> | |
| 1148 | + const externalHeading = document.createElement('h3'); | |
| 998 | - </div>`); | |
| 1149 | + externalHeading.classList.add('margin0'); | |
| 999 | - | |
| 1150 | + externalHeading.textContent = t`Installed Extensions:`; | |
| 1000 | - htmlExternal.append(htmlLoading); | |
| 1151 | + const thirdPartyToolbar = document.createElement('div'); | |
| 1152 | + thirdPartyToolbar.classList.add('flex-container', 'third_party_toolbar'); | |
| 1153 | + externalHeader.append(externalHeading, thirdPartyToolbar); | |
| 1154 | + externalContainer.appendChild(externalHeader); | |
| 1155 | + | |
| 1156 | + const loadingEl = document.createElement('div'); | |
| 1157 | + loadingEl.classList.add('flex-container', 'alignItemsCenter', 'justifyCenter', 'marginTop10', 'marginBot5'); | |
| 1158 | + const loadingIcon = document.createElement('i'); | |
| 1159 | + loadingIcon.classList.add('fa-solid', 'fa-spinner', 'fa-spin'); | |
| 1160 | + const loadingSpan = document.createElement('span'); | |
| 1161 | + loadingSpan.textContent = t`Loading third-party extensions... Please wait...`; | |
| 1162 | + loadingEl.append(loadingIcon, loadingSpan); | |
| 1163 | + | |
| 1164 | + externalContainer.appendChild(loadingEl); | |
| 1001 | 1165 | |
| 1002 | 1166 | const sortOrderKey = 'extensions_sortByName'; |
| 1003 | 1167 | const sortByName = accountStorage.getItem(sortOrderKey) === 'true'; |
| @@ -1006,16 +1170,16 @@ async function showExtensionsDetails() { | ||
| 1006 | 1170 | let extensionsToToggle = []; |
| 1007 | 1171 | |
| 1008 | 1172 | extensions.forEach(value => { |
| 1009 | 1173 | const { isExternal, extensionHtmlextensionElement } = value; |
| 1010 | 1174 | const container = isExternal ? htmlExternalexternalContainer : htmlDefaultdefaultContainer; |
| 1011 | 1175 | container.appendappendChild(extensionHtmlextensionElement); |
| 1012 | 1176 | }); |
| 1013 | 1177 | |
| 1014 | 1178 | const htmlextensionsMenu = $('<div></div>') |
| 1015 | 1179 | .addClass('extensions_info') |
| 1016 | 1180 | .append(htmlErrorserrors) |
| 1017 | 1181 | .append(htmlDefaultdefaultContainer) |
| 1018 | 1182 | .append(htmlExternalexternalContainer) |
| 1019 | 1183 | .append(getModuleInformation()); |
| 1020 | 1184 | |
| 1021 | 1185 | { |
| @@ -1041,23 +1205,24 @@ async function showExtensionsDetails() { | ||
| 1041 | 1205 | const toggleAllExtensionsButton = document.createElement('div'); |
| 1042 | 1206 | toggleAllExtensionsButton.classList.add('menu_button', 'menu_button_icon'); |
| 1043 | 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); | |
| 1048 | 1213 | |
| 1049 | 1214 | const restoreBulkToggledExtensionsButton = document.createElement('div'); |
| 1050 | 1215 | restoreBulkToggledExtensionsButton.classList.add('menu_button', 'menu_button_icon', 'fa-solid', 'fa-arrow-right-rotate', 'displayNone'); |
| 1051 | 1216 | restoreBulkToggledExtensionsButton.title = t`Restore toggled extensions.\n\nIt does not restore extensions toggled individually.`; |
| 1052 | 1217 | |
| 1053 | 1218 | toggleAllExtensionsButton.addEventListener('click', () => { |
| 1054 | 1219 | extensionsToToggle = onToggleAllExtensions(extensionsToToggle, htmlExternal$(externalContainer)); |
| 1055 | 1220 | |
| 1056 | 1221 | for (const extension of extensionsToToggle) { |
| 1057 | 1222 | const { name } = extension; |
| 1058 | 1223 | |
| 1059 | - htmlExternal | |
| 1224 | + $(externalContainer) | |
| 1060 | 1225 | .find(`.extension_block[data-name="${name.replacegetNameSelector('third-party', ''name)}"] .extension_toggle input`) |
| 1061 | 1226 | .off('click') |
| 1062 | 1227 | .one('click', () => { |
| 1063 | 1228 | extensionsToToggle = extensionsToToggle.filter(ext => ext.name !== name); |
| @@ -1074,8 +1239,8 @@ async function showExtensionsDetails() { | ||
| 1074 | 1239 | const { name } = extension; |
| 1075 | 1240 | const isDisabled = extension_settings.disabledExtensions.includes(name); |
| 1076 | 1241 | |
| 1077 | - htmlExternal | |
| 1242 | + $(externalContainer) | |
| 1078 | 1243 | .find(`.extension_block[data-name="${name.replacegetNameSelector('third-party', ''name)}"] .extension_toggle input`) |
| 1079 | 1244 | .prop('checked', !isDisabled) |
| 1080 | 1245 | .toggleClass('toggle_enable', isDisabled) |
| 1081 | 1246 | .toggleClass('toggle_disable', !isDisabled) |
| @@ -1099,13 +1264,13 @@ async function showExtensionsDetails() { | ||
| 1099 | 1264 | }); |
| 1100 | 1265 | |
| 1101 | 1266 | toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton); |
| 1102 | 1267 | htmlExternal.find('.third_party_toolbar')thirdPartyToolbar.append(restoreBulkToggledExtensionsButton, toggleAllExtensionsButton); |
| 1103 | 1268 | htmlextensionsMenu.prepend(toolbar); |
| 1104 | 1269 | } |
| 1105 | 1270 | |
| 1106 | 1271 | let waitingForSave = false; |
| 1107 | 1272 | |
| 1108 | 1273 | const popup = new Popup(htmlextensionsMenu, POPUP_TYPE.TEXT, '', { |
| 1109 | 1274 | okButton: t`Close`, |
| 1110 | 1275 | wide: true, |
| 1111 | 1276 | large: true, |
| @@ -1147,7 +1312,7 @@ async function showExtensionsDetails() { | ||
| 1147 | 1312 | }); |
| 1148 | 1313 | popupPromise = popup.show(); |
| 1149 | 1314 | popup.content.scrollTop = initialScrollTop; |
| 1150 | 1315 | checkForUpdatesManual(sortFn, abortController.signal).finally(() => htmlLoadingloadingEl.remove()); |
| 1151 | 1316 | } catch (error) { |
| 1152 | 1317 | toastr.error(t`Error loading extensions. See browser console for details.`); |
| 1153 | 1318 | console.error(error); |
| @@ -1234,6 +1399,7 @@ async function updateExtension(extensionName, quiet, timeout = null) { | ||
| 1234 | 1399 | * This function makes a POST request to '/api/extensions/delete' with the extension's name. |
| 1235 | 1400 | * If the extension is deleted, it displays a success message. |
| 1236 | 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 | */ |
| 1238 | 1404 | async function onDeleteClick() { |
| 1239 | 1405 | const extensionName = $(this).data('name'); |
| @@ -1244,11 +1410,48 @@ async function onDeleteClick() { | ||
| 1244 | 1410 | return; |
| 1245 | 1411 | } |
| 1246 | 1412 | |
| 1247 | - // use callPopup to create a popup for the user to confirm before delete | |
| 1413 | + const hasCleanHook = hasExtensionHook(extensionName, 'clean'); | |
| 1248 | - const confirmation = await callGenericPopup(t`Are you sure you want to delete ${extensionName}?`, POPUP_TYPE.CONFIRM, '', {}); | |
| 1414 | + | |
| 1415 | + /** @type {import('./popup.js').CustomPopupInput[]} */ | |
| 1416 | + const customInputs = hasCleanHook ? [{ id: 'extension_delete_cleanup', label: t`Also clean up extension data`, defaultState: false }] : null; | |
| 1417 | + | |
| 1418 | + const popup = new Popup(t`Are you sure you want to delete ${escapeHtml(extensionName)}?`, POPUP_TYPE.CONFIRM, '', { customInputs }); | |
| 1419 | + const confirmation = await popup.show(); | |
| 1249 | 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 | + */ | |
| 1430 | +async function onCleanClick() { | |
| 1431 | + const extensionName = $(this).data('name'); | |
| 1432 | + | |
| 1433 | + const confirmation = await Popup.show.confirm(t`Clean extension data`, t`Are you sure you want to clean up data for ${escapeHtml(extensionName)}? This action cannot be undone.`); | |
| 1434 | + if (!confirmation) { | |
| 1435 | + return; | |
| 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 | + */ | |
| 1446 | +async function cleanExtension(extensionName) { | |
| 1447 | + const fullExtensionName = extensionName.startsWith('third-party') ? extensionName : `third-party${extensionName}`; | |
| 1448 | + await callExtensionHook(fullExtensionName, 'clean'); | |
| 1449 | + | |
| 1450 | + // Clean might have updated settings, which could race with the page reload, so we'll force save here | |
| 1451 | + await saveSettings(); | |
| 1452 | + | |
| 1453 | + toastr.success(t`Extension ${extensionName} data cleaned`); | |
| 1454 | + delay(1000).then(() => location.reload()); | |
| 1252 | 1455 | } |
| 1253 | 1456 | |
| 1254 | 1457 | async function onBranchClick() { |
| @@ -1303,8 +1506,8 @@ async function onMoveClick() { | ||
| 1303 | 1506 | |
| 1304 | 1507 | const confirmationHeader = t`Move extension`; |
| 1305 | 1508 | const confirmationText = source == 'global' |
| 1306 | 1509 | ? t`Are you sure you want to move ${escapeHtml(extensionName)} to your local extensions? This will make it available only for you.` |
| 1307 | 1510 | : t`Are you sure you want to move ${escapeHtml(extensionName)} to the global extensions? This will make it available for all users.`; |
| 1308 | 1511 | |
| 1309 | 1512 | const confirmation = await Popup.show.confirm(confirmationHeader, confirmationText); |
| 1310 | 1513 | |
| @@ -1353,9 +1556,16 @@ async function moveExtension(extensionName, source, destination) { | ||
| 1353 | 1556 | /** |
| 1354 | 1557 | * Deletes an extension via the API. |
| 1355 | 1558 | * @param {string} extensionName Extension name to delete |
| 1559 | + * @param {boolean} [shouldClean=false] Whether to also run the 'clean' hook before deleting | |
| 1356 | 1560 | */ |
| 1357 | 1561 | export 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'); | |
| 1359 | 1569 | |
| 1360 | 1570 | try { |
| 1361 | 1571 | await fetch('/api/extensions/delete', { |
| @@ -1370,6 +1580,9 @@ export async function deleteExtension(extensionName) { | ||
| 1370 | 1580 | console.error('Error:', error); |
| 1371 | 1581 | } |
| 1372 | 1582 | |
| 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 | 1586 | toastr.success(t`Extension ${extensionName} deleted`); |
| 1374 | 1587 | delay(1000).then(() => location.reload()); |
| 1375 | 1588 | } |
| @@ -1398,6 +1611,9 @@ async function getExtensionVersion(extensionName, abortSignal) { | ||
| 1398 | 1611 | const data = await response.json(); |
| 1399 | 1612 | return data; |
| 1400 | 1613 | } catch (error) { |
| 1614 | + if (error instanceof Error && error.name === 'AbortError') { | |
| 1615 | + return; | |
| 1616 | + } | |
| 1401 | 1617 | console.error('Error:', error); |
| 1402 | 1618 | } |
| 1403 | 1619 | } |
| @@ -1476,9 +1692,53 @@ async function switchExtensionBranch(extensionName, isGlobal, branch) { | ||
| 1476 | 1692 | * Installs a third-party extension via the API. |
| 1477 | 1693 | * @param {string} url Extension repository URL |
| 1478 | 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 | */ |
| 1481 | 1698 | export 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 | 1742 | console.debug('Extension installation started', url); |
| 1483 | 1743 | |
| 1484 | 1744 | toastr.info(t`Please wait...`, t`Installing extension`); |
| @@ -1497,11 +1757,11 @@ export async function installExtension(url, global, branch = '') { | ||
| 1497 | 1757 | const text = await request.text(); |
| 1498 | 1758 | toastr.warning(text || request.statusText, t`Extension installation failed`, { timeOut: 5000 }); |
| 1499 | 1759 | console.error('Extension installation failed', request.status, request.statusText, text); |
| 1500 | 1760 | return false; |
| 1501 | 1761 | } |
| 1502 | 1762 | |
| 1503 | 1763 | const response = await request.json(); |
| 1504 | 1764 | toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`); |
| 1505 | 1765 | console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`); |
| 1506 | 1766 | await loadExtensionSettings({}, false, false); |
| 1507 | 1767 | await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response); |
| @@ -1510,6 +1770,8 @@ export async function installExtension(url, global, branch = '') { | ||
| 1510 | 1770 | const extensionName = `third-party/${response.folderName}`; |
| 1511 | 1771 | await callExtensionHook(extensionName, 'install'); |
| 1512 | 1772 | } |
| 1773 | + | |
| 1774 | + return true; | |
| 1513 | 1775 | } |
| 1514 | 1776 | |
| 1515 | 1777 | /** |
| @@ -1589,7 +1851,11 @@ async function checkForUpdatesManual(sortFn, abortSignal) { | ||
| 1589 | 1851 | const promise = enqueueVersionCheck(async () => { |
| 1590 | 1852 | try { |
| 1591 | 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 | 1859 | if (extensionBlock && data) { |
| 1594 | 1860 | if (data.isUpToDate === false) { |
| 1595 | 1861 | const buttonElement = extensionBlock.querySelector('.btn_update'); |
| @@ -1620,6 +1886,18 @@ async function checkForUpdatesManual(sortFn, abortSignal) { | ||
| 1620 | 1886 | } |
| 1621 | 1887 | } |
| 1622 | 1888 | |
| 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 | 1901 | const versionElement = extensionBlock.querySelector('.extension_version'); |
| 1624 | 1902 | if (versionElement) { |
| 1625 | 1903 | versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`; |
| @@ -1672,6 +1950,9 @@ async function checkForExtensionUpdates(force) { | ||
| 1672 | 1950 | const promise = enqueueVersionCheck(async () => { |
| 1673 | 1951 | try { |
| 1674 | 1952 | const data = await getExtensionVersion(id.replace('third-party', '')); |
| 1953 | + if (!data) { | |
| 1954 | + return; | |
| 1955 | + } | |
| 1675 | 1956 | if (!data.isUpToDate) { |
| 1676 | 1957 | updatesAvailable.push(manifest.display_name); |
| 1677 | 1958 | } |
| @@ -1759,6 +2040,18 @@ export async function runGenerationInterceptors(chat, contextSize, type) { | ||
| 1759 | 2040 | } |
| 1760 | 2041 | |
| 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 | + */ | |
| 2052 | +export const UNSET_VALUE = '__@@UNSET@@__'; | |
| 2053 | + | |
| 2054 | +/** | |
| 1762 | 2055 | * Writes a field to the character's data extensions object. |
| 1763 | 2056 | * @param {number|string} characterId Index in the character array |
| 1764 | 2057 | * @param {string} key Field name |
| @@ -1772,13 +2065,23 @@ export async function writeExtensionField(characterId, key, value) { | ||
| 1772 | 2065 | console.warn('Character not found', characterId); |
| 1773 | 2066 | return; |
| 1774 | 2067 | } |
| 1775 | 2068 | const pathextensionPath = `data.extensions.${key}`; |
| 1776 | - setValueByPath(character, path, value); | |
| 2069 | + const isUnset = value === UNSET_VALUE; | |
| 2070 | + | |
| 2071 | + if (isUnset) { | |
| 2072 | + deleteValueByPath(character, extensionPath); | |
| 2073 | + } else { | |
| 2074 | + setValueByPath(character, extensionPath, value); | |
| 2075 | + } | |
| 1777 | 2076 | |
| 1778 | 2077 | // Process JSON data |
| 1779 | 2078 | if (character.json_data) { |
| 1780 | 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 | 2085 | character.json_data = JSON.stringify(jsonData); |
| 1783 | 2086 | |
| 1784 | 2087 | // 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 | } |
| 1809 | 2112 | |
| 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 | + */ | |
| 2143 | +export async function writeExtensionFieldBulk(avatars, key, value, { filterPath } = {}) { | |
| 2144 | + const context = getContext(); | |
| 2145 | + const extensionPath = `data.extensions.${key}`; | |
| 2146 | + const isUnset = value === UNSET_VALUE; | |
| 2147 | + | |
| 2148 | + // Build the server request | |
| 2149 | + const requestBody = { | |
| 2150 | + avatars: Array.isArray(avatars) && avatars.length > 0 ? avatars : [], | |
| 2151 | + data: { | |
| 2152 | + data: { | |
| 2153 | + extensions: { | |
| 2154 | + [key]: value, | |
| 2155 | + }, | |
| 2156 | + }, | |
| 2157 | + }, | |
| 2158 | + }; | |
| 2159 | + | |
| 2160 | + // Default filter: when unsetting, only touch characters that have the field | |
| 2161 | + const resolvedFilterPath = filterPath ?? (isUnset ? extensionPath : undefined); | |
| 2162 | + if (resolvedFilterPath) { | |
| 2163 | + requestBody.filter = { path: resolvedFilterPath }; | |
| 2164 | + } | |
| 2165 | + | |
| 2166 | + const mergeResponse = await fetch('/api/characters/merge-attributes', { | |
| 2167 | + method: 'POST', | |
| 2168 | + headers: getRequestHeaders(), | |
| 2169 | + body: JSON.stringify(requestBody), | |
| 2170 | + }); | |
| 2171 | + | |
| 2172 | + if (!mergeResponse.ok) { | |
| 2173 | + console.error('Bulk extension field update failed', mergeResponse.statusText); | |
| 2174 | + return { updated: [], skipped: [], failed: [] }; | |
| 2175 | + } | |
| 2176 | + | |
| 2177 | + /** @type {BulkExtensionFieldResult} */ | |
| 2178 | + const result = await mergeResponse.json(); | |
| 2179 | + | |
| 2180 | + // Sync in-memory character objects for successfully updated characters | |
| 2181 | + const updatedSet = new Set(result.updated); | |
| 2182 | + for (const character of context.characters) { | |
| 2183 | + if (!character || !updatedSet.has(character.avatar)) continue; | |
| 2184 | + | |
| 2185 | + if (isUnset) { | |
| 2186 | + deleteValueByPath(character, extensionPath); | |
| 2187 | + } else { | |
| 2188 | + setValueByPath(character, extensionPath, value); | |
| 2189 | + } | |
| 2190 | + | |
| 2191 | + // Keep json_data in sync | |
| 2192 | + if (character.json_data) { | |
| 2193 | + const jsonData = JSON.parse(character.json_data); | |
| 2194 | + if (isUnset) { | |
| 2195 | + deleteValueByPath(jsonData, extensionPath); | |
| 2196 | + } else { | |
| 2197 | + setValueByPath(jsonData, extensionPath, value); | |
| 2198 | + } | |
| 2199 | + character.json_data = JSON.stringify(jsonData); | |
| 2200 | + } | |
| 2201 | + } | |
| 2202 | + | |
| 2203 | + // If the currently active character was updated, sync the hidden input | |
| 2204 | + if (context.characterId !== undefined) { | |
| 2205 | + const activeChar = context.characters[context.characterId]; | |
| 2206 | + if (activeChar && updatedSet.has(activeChar.avatar) && activeChar.json_data) { | |
| 2207 | + $('#character_json_data').val(activeChar.json_data); | |
| 2208 | + } | |
| 2209 | + } | |
| 2210 | + | |
| 2211 | + return result; | |
| 2212 | +} | |
| 2213 | + | |
| 2214 | +/** | |
| 1811 | 2215 | * Prompts the user to enter the Git URL of the extension to import. |
| 1812 | 2216 | * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension. |
| 1813 | 2217 | * If the extension is imported successfully, a success message is displayed. |
| @@ -1853,6 +2257,39 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') { | ||
| 1853 | 2257 | await installExtension(url, global, branchName); |
| 1854 | 2258 | } |
| 1855 | 2259 | |
| 2260 | +/** | |
| 2261 | + * Sentinel value representing an empty author, used when author information cannot be extracted from a URL. | |
| 2262 | + * @type {{name: string, url: string}} | |
| 2263 | + */ | |
| 2264 | +export const EMPTY_AUTHOR = Object.freeze({ | |
| 2265 | + name: '', | |
| 2266 | + url: '', | |
| 2267 | +}); | |
| 2268 | + | |
| 2269 | +/** | |
| 2270 | + * Extracts the repository author from a given URL. | |
| 2271 | + * @param {string} url - The URL of the repository. | |
| 2272 | + * @returns {{name: string, url: string}} Object containing the author's name and URL, or empty strings if not found. | |
| 2273 | + */ | |
| 2274 | +export function getAuthorFromUrl(url) { | |
| 2275 | + const result = structuredClone(EMPTY_AUTHOR); | |
| 2276 | + | |
| 2277 | + try { | |
| 2278 | + const parsedUrl = new URL(url); | |
| 2279 | + const pathSegments = parsedUrl.pathname.split('/').filter(s => s.length > 0); | |
| 2280 | + | |
| 2281 | + // TODO: Handle non-GitHub URLs if needed | |
| 2282 | + if (parsedUrl.host === 'github.com' && pathSegments.length >= 2) { | |
| 2283 | + result.name = pathSegments[0]; | |
| 2284 | + result.url = `${parsedUrl.protocol}//${parsedUrl.hostname}/${result.name}`; | |
| 2285 | + } | |
| 2286 | + } catch (error) { | |
| 2287 | + console.debug('Error parsing URL:', error); | |
| 2288 | + } | |
| 2289 | + | |
| 2290 | + return result; | |
| 2291 | +} | |
| 2292 | + | |
| 1856 | 2293 | export async function initExtensions() { |
| 1857 | 2294 | await addExtensionsButtonAndMenu(); |
| 1858 | 2295 | $('#extensionsMenuButton').css('display', 'flex'); |
| @@ -1865,6 +2302,7 @@ export async function initExtensions() { | ||
| 1865 | 2302 | $(document).on('click', '.extensions_info .extension_block .toggle_enable', onEnableExtensionClick); |
| 1866 | 2303 | $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick); |
| 1867 | 2304 | $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick); |
| 2305 | + $(document).on('click', '.extensions_info .extension_block .btn_clean', onCleanClick); | |
| 1868 | 2306 | $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick); |
| 1869 | 2307 | $(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick); |
| 1870 | 2308 | |
| @@ -5,12 +5,12 @@ TODO: | ||
| 5 | 5 | |
| 6 | 6 | import { DOMPurify } from '../../../lib.js'; |
| 7 | 7 | import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js'; |
| 8 | 8 | import { deleteExtension, EMPTY_AUTHOR, extensionNames, getAuthorFromUrl, getContext, installExtension, renderExtensionTemplateAsync, isOfficialExtension } from '../../extensions.js'; |
| 9 | 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; |
| 10 | -import { executeSlashCommandsWithOptions } from '../../slash-commands.js'; | |
| 11 | 10 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 12 | 11 | import { escapeHtml, flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 13 | 12 | import { t, translate } from '../../i18n.js'; |
| 13 | +import { SlashCommandParser } from '/scripts/slash-commands/SlashCommandParser.js'; | |
| 14 | 14 | export { MODULE_NAME }; |
| 15 | 15 | |
| 16 | 16 | const MODULE_NAME = 'assets'; |
| @@ -60,100 +60,34 @@ const KNOWN_TYPES = { | ||
| 60 | 60 | 'blip': t`Blip sounds`, |
| 61 | 61 | }; |
| 62 | 62 | |
| 63 | -const 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 | */ |
| 73 | 70 | function getAuthorFromUrlcreateAssetButton(urlasset, assetType, index) { |
| 74 | - const result = structuredClone(EMPTY_AUTHOR); | |
| 71 | + const elemId = `assets_install_${assetType}_${index}`; | |
| 75 | - | |
| 72 | + const element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' }); | |
| 76 | - try { | |
| 77 | - const parsedUrl = new URL(url); | |
| 78 | - const pathSegments = parsedUrl.pathname.split('/').filter(s => s.length > 0); | |
| 79 | - | |
| 80 | - // TODO: Handle non-GitHub URLs if needed | |
| 81 | - if (parsedUrl.host === 'github.com' && pathSegments.length >= 2) { | |
| 82 | - result.name = pathSegments[0]; | |
| 83 | - result.url = `${parsedUrl.protocol}//${parsedUrl.hostname}/${result.name}`; | |
| 84 | - } | |
| 85 | - } catch (error) { | |
| 86 | - console.debug(DEBUG_PREFIX, 'Error parsing URL:', error); | |
| 87 | - } | |
| 88 | - | |
| 89 | - return result; | |
| 90 | -} | |
| 91 | - | |
| 92 | -async function downloadAssetsList(url) { | |
| 93 | - updateCurrentAssets().then(async function () { | |
| 94 | - fetch(url, { cache: 'no-cache' }) | |
| 95 | - .then(response => response.json()) | |
| 96 | - .then(async function (json) { | |
| 97 | - availableAssets = {}; | |
| 98 | - $('#assets_menu').empty(); | |
| 99 | - | |
| 100 | - console.debug(DEBUG_PREFIX, 'Received assets dictionary', json); | |
| 101 | - | |
| 102 | - for (const i of json) { | |
| 103 | - //console.log(DEBUG_PREFIX,i) | |
| 104 | - if (availableAssets[i.type] === undefined) | |
| 105 | - availableAssets[i.type] = []; | |
| 106 | - | |
| 107 | - availableAssets[i.type].push(i); | |
| 108 | - } | |
| 109 | - | |
| 110 | - console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets); | |
| 111 | - // First extensions, then everything else | |
| 112 | - const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0); | |
| 113 | - | |
| 114 | - $('#assets_type_select').empty(); | |
| 115 | - $('#assets_search').val(''); | |
| 116 | - $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); | |
| 117 | - | |
| 118 | - for (const type of assetTypes) { | |
| 119 | - const text = translate(KNOWN_TYPES[type] || type); | |
| 120 | - const option = $('<option />', { value: type, text: text }); | |
| 121 | - $('#assets_type_select').append(option); | |
| 122 | - } | |
| 123 | - | |
| 124 | - if (assetTypes.includes('extension')) { | |
| 125 | - $('#assets_type_select').val('extension'); | |
| 126 | - } | |
| 127 | - | |
| 128 | - $('#assets_type_select').off('change').on('change', filterAssets); | |
| 129 | - $('#assets_search').off('input').on('input', filterAssets); | |
| 130 | - | |
| 131 | - for (const assetType of assetTypes) { | |
| 132 | - let assetTypeMenu = $('<div />', { id: 'assets_audio_ambient_div', class: 'assets-list-div' }); | |
| 133 | - assetTypeMenu.attr('data-type', assetType); | |
| 134 | - assetTypeMenu.append(`<h3>${KNOWN_TYPES[assetType] || assetType}</h3>`).hide(); | |
| 135 | - | |
| 136 | - if (assetType == 'extension') { | |
| 137 | - assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation')); | |
| 138 | - } | |
| 139 | - | |
| 140 | - for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a.name.localeCompare(b.name))) { | |
| 141 | - const i = availableAssets[assetType].indexOf(asset); | |
| 142 | - const elemId = `assets_install_${assetType}_${i}`; | |
| 143 | - let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' }); | |
| 144 | 73 | const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>'); |
| 145 | 74 | element.append(label); |
| 146 | 75 | |
| 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 | 76 | console.debug(DEBUG_PREFIX, 'Checking asset', asset.id, asset.url); |
| 151 | 77 | |
| 152 | 78 | const assetInstall = async function () { |
| 153 | 79 | element.off('click'); |
| 154 | 80 | label.removeClass('fa-download'); |
| 155 | 81 | this.classList.add('asset-download-button-loading'); |
| 156 | 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 | 91 | label.addClass('fa-check'); |
| 158 | 92 | this.classList.remove('asset-download-button-loading'); |
| 159 | 93 | element.on('click', assetDelete); |
| @@ -171,7 +105,7 @@ async function downloadAssetsList(url) { | ||
| 171 | 105 | const assetDelete = async function () { |
| 172 | 106 | if (assetType === 'character') { |
| 173 | 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 | 109 | return; |
| 176 | 110 | } |
| 177 | 111 | element.off('click'); |
| @@ -204,6 +138,17 @@ async function downloadAssetsList(url) { | ||
| 204 | 138 | element.on('click', assetInstall); |
| 205 | 139 | } |
| 206 | 140 | |
| 141 | + return element; | |
| 142 | +} | |
| 143 | + | |
| 144 | +/** | |
| 145 | + * Creates the full visual block element for a single asset. | |
| 146 | + * @param {object} asset The asset data object, containing at least id, name, description and url fields | |
| 147 | + * @param {string} assetType Asset type, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip' | |
| 148 | + * @param {JQuery} element The button element from createAssetButton | |
| 149 | + * @returns {JQuery} The asset block element | |
| 150 | + */ | |
| 151 | +function createAssetBlock(asset, assetType, element) { | |
| 207 | 152 | console.debug(DEBUG_PREFIX, 'Created element for ', asset.id); |
| 208 | 153 | |
| 209 | 154 | const displayName = DOMPurify.sanitize(asset.name || asset.id); |
| @@ -214,23 +159,31 @@ async function downloadAssetsList(url) { | ||
| 214 | 159 | const toolTag = assetType === 'extension' && asset.tool; |
| 215 | 160 | const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR; |
| 216 | 161 | |
| 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); | |
| 234 | 187 | |
| 235 | 188 | assetBlock.find('.tag').on('click', function (e) { |
| 236 | 189 | const a = document.createElement('a'); |
| @@ -241,38 +194,128 @@ async function downloadAssetsList(url) { | ||
| 241 | 194 | |
| 242 | 195 | if (assetType === 'character') { |
| 243 | 196 | if (asset.highlight) { |
| 244 | 197 | assetBlock.find('.asset-name') nameSpan.append($('<i>', { class=": 'fa-solid fa-sm fa-trophy"></i>' })); |
| 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 | } |
| 248 | 201 | |
| 249 | 202 | assetBlock.addClass('asset-block'); |
| 203 | + return assetBlock; | |
| 204 | +} | |
| 250 | 205 | |
| 206 | +/** | |
| 207 | + * Builds and appends the menu section for a single asset type. | |
| 208 | + * @param {string} assetType Asset type, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip' | |
| 209 | + * @returns {Promise<void>} | |
| 210 | + */ | |
| 211 | +async function buildAssetTypeSection(assetType) { | |
| 212 | + const assetTypeMenu = $('<div />', { id: `assets_${assetType}_div`, class: 'assets-list-div' }); | |
| 213 | + assetTypeMenu.attr('data-type', assetType); | |
| 214 | + assetTypeMenu.append($('<h3>').text(KNOWN_TYPES[assetType] || assetType)).hide(); | |
| 215 | + | |
| 216 | + if (assetType == 'extension') { | |
| 217 | + assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation')); | |
| 218 | + } | |
| 219 | + | |
| 220 | + for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a.name.localeCompare(b.name))) { | |
| 221 | + const i = availableAssets[assetType].indexOf(asset); | |
| 222 | + const element = createAssetButton(asset, assetType, i); | |
| 223 | + const assetBlock = createAssetBlock(asset, assetType, element); | |
| 224 | + | |
| 225 | + if (assetType === 'extension') { | |
| 226 | + const extensionBlockList = isOfficialExtension(asset.url) | |
| 227 | + ? assetTypeMenu.find('.assets-list-extensions-official .assets-list-extensions') | |
| 228 | + : assetTypeMenu.find('.assets-list-extensions-community .assets-list-extensions'); | |
| 229 | + extensionBlockList.append(assetBlock); | |
| 230 | + } else { | |
| 251 | 231 | assetTypeMenu.append(assetBlock); |
| 252 | 232 | } |
| 233 | + } | |
| 234 | + | |
| 253 | 235 | assetTypeMenu.appendTo('#assets_menu'); |
| 254 | 236 | assetTypeMenu.on('click', 'a.asset_preview', previewAsset); |
| 255 | 237 | } |
| 256 | 238 | |
| 239 | +/** | |
| 240 | + * Parses the fetched assets JSON and renders the full assets menu. | |
| 241 | + * @param {object[]} json Array of asset objects, each containing at least id, name, description, url and type fields | |
| 242 | + */ | |
| 243 | +async function populateAssetsMenu(json) { | |
| 244 | + availableAssets = {}; | |
| 245 | + $('#assets_menu').empty(); | |
| 246 | + | |
| 247 | + console.debug(DEBUG_PREFIX, 'Received assets dictionary', json); | |
| 248 | + | |
| 249 | + for (const i of json) { | |
| 250 | + if (availableAssets[i.type] === undefined) | |
| 251 | + availableAssets[i.type] = []; | |
| 252 | + availableAssets[i.type].push(i); | |
| 253 | + } | |
| 254 | + | |
| 255 | + console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets); | |
| 256 | + // First extensions, then everything else | |
| 257 | + const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0); | |
| 258 | + | |
| 259 | + $('#assets_type_select').empty(); | |
| 260 | + $('#assets_search').val(''); | |
| 261 | + $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); | |
| 262 | + | |
| 263 | + for (const type of assetTypes) { | |
| 264 | + const text = translate(KNOWN_TYPES[type] || type); | |
| 265 | + const option = $('<option />', { value: type, text: text }); | |
| 266 | + $('#assets_type_select').append(option); | |
| 267 | + } | |
| 268 | + | |
| 269 | + if (assetTypes.includes('extension')) { | |
| 270 | + $('#assets_type_select').val('extension'); | |
| 271 | + } | |
| 272 | + | |
| 273 | + $('#assets_type_select').off('change').on('change', filterAssets); | |
| 274 | + $('#assets_search').off('input').on('input', filterAssets); | |
| 275 | + | |
| 276 | + for (const assetType of assetTypes) { | |
| 277 | + await buildAssetTypeSection(assetType); | |
| 278 | + } | |
| 279 | + | |
| 257 | 280 | filterAssets(); |
| 258 | 281 | $('#assets_filters').show(); |
| 259 | 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 :3 | |
| 285 | +/** | |
| 286 | + * Downloads the assets list from the given URL and populates the menu. Shows error message if something goes wrong. | |
| 287 | + * @param {URL} url URL to fetch from | |
| 288 | + */ | |
| 289 | +async function downloadAssetsList(url) { | |
| 290 | + await updateCurrentAssets(); | |
| 291 | + try { | |
| 292 | + const response = await fetch(url, { cache: 'no-cache' }); | |
| 293 | + if (!response.ok) { | |
| 294 | + throw new Error('Cannot download the assets list.'); | |
| 295 | + } | |
| 296 | + const json = await response.json(); | |
| 297 | + if (!Array.isArray(json)) { | |
| 298 | + throw new Error('Assets list is not an array'); | |
| 299 | + } | |
| 300 | + await populateAssetsMenu(json); | |
| 301 | + } catch (error) { | |
| 302 | + // Info hint if the user maybe... likely accidentally was trying to install an extension and we wanna help guide them? uwu :3 | |
| 263 | 303 | const installButton = $('#third_party_extension_button'); |
| 264 | 304 | flashHighlight(installButton, 10_000); |
| 265 | 305 | toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 }); |
| 266 | 306 | |
| 267 | 307 | // Error logged after, to appear on top |
| 268 | 308 | console.error(error); |
| 269 | 309 | toastr.error('Problem with assets URL', DEBUG_PREFIX + 'Cannot get assets list'); |
| 270 | 310 | $('#assets-connect-button').addClass('fa-plug-circle-exclamation'); |
| 271 | 311 | $('#assets-connect-button').addClass('redOverlayGlow'); |
| 272 | - }); | |
| 312 | + } | |
| 273 | - }); | |
| 274 | 313 | } |
| 275 | 314 | |
| 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 | + */ | |
| 276 | 319 | function previewAsset(e) { |
| 277 | 320 | const href = $(this).attr('href'); |
| 278 | 321 | const audioExtensions = ['.mp3', '.ogg', '.wav']; |
| @@ -295,6 +338,15 @@ function previewAsset(e) { | ||
| 295 | 338 | } |
| 296 | 339 | } |
| 297 | 340 | |
| 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 | + */ | |
| 298 | 350 | function isAssetInstalled(assetType, filename) { |
| 299 | 351 | let assetList = currentAssets[assetType]; |
| 300 | 352 | |
| @@ -316,15 +368,22 @@ function isAssetInstalled(assetType, filename) { | ||
| 316 | 368 | return false; |
| 317 | 369 | } |
| 318 | 370 | |
| 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 | + */ | |
| 319 | 378 | async function installAsset(url, assetType, filename) { |
| 320 | 379 | console.debug(DEBUG_PREFIX, 'Downloading ', url); |
| 321 | 380 | const category = assetType; |
| 322 | 381 | try { |
| 323 | 382 | if (category === 'extension') { |
| 324 | 383 | console.debug(DEBUG_PREFIX, 'Installing extension ', url); |
| 325 | 384 | const result = await installExtension(url, false); |
| 326 | 385 | console.debug(DEBUG_PREFIX, 'Extension installed.'); |
| 327 | 386 | return result; |
| 328 | 387 | } |
| 329 | 388 | |
| 330 | 389 | const body = { url, category, filename }; |
| @@ -340,16 +399,25 @@ async function installAsset(url, assetType, filename) { | ||
| 340 | 399 | console.debug(DEBUG_PREFIX, 'Importing character ', filename); |
| 341 | 400 | const blob = await result.blob(); |
| 342 | 401 | const file = new File([blob], filename, { type: blob.type }); |
| 343 | 402 | awaitconst processDroppedFilesfileNameMap = new Map([[file, filename]]); |
| 403 | + await processDroppedFiles([file], fileNameMap); | |
| 344 | 404 | console.debug(DEBUG_PREFIX, 'Character downloaded.'); |
| 345 | 405 | } |
| 406 | + return true; | |
| 346 | 407 | } |
| 408 | + return false; | |
| 347 | 409 | } catch (err) { |
| 348 | 410 | console.log(err); |
| 349 | 411 | return []false; |
| 350 | 412 | } |
| 351 | 413 | } |
| 352 | 414 | |
| 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 | + */ | |
| 353 | 421 | async function deleteAsset(assetType, filename) { |
| 354 | 422 | console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename); |
| 355 | 423 | const category = assetType; |
| @@ -358,6 +426,7 @@ async function deleteAsset(assetType, filename) { | ||
| 358 | 426 | console.debug(DEBUG_PREFIX, 'Deleting extension ', filename); |
| 359 | 427 | await deleteExtension(filename); |
| 360 | 428 | console.debug(DEBUG_PREFIX, 'Extension deleted.'); |
| 429 | + return true; | |
| 361 | 430 | } |
| 362 | 431 | |
| 363 | 432 | const body = { category, filename }; |
| @@ -369,19 +438,37 @@ async function deleteAsset(assetType, filename) { | ||
| 369 | 438 | }); |
| 370 | 439 | if (result.ok) { |
| 371 | 440 | console.debug(DEBUG_PREFIX, 'Deletion success.'); |
| 441 | + return true; | |
| 372 | 442 | } |
| 443 | + return false; | |
| 373 | 444 | } catch (err) { |
| 374 | 445 | console.log(err); |
| 375 | 446 | return []false; |
| 376 | 447 | } |
| 377 | 448 | } |
| 378 | 449 | |
| 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 | + */ | |
| 379 | 455 | async function openCharacterBrowser(forceDefault) { |
| 380 | 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 | 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 | 466 | const json = await fetchResult.json(); |
| 383 | - const characters = json.filter(x => x.type === 'character'); | |
| 467 | + if (!Array.isArray(json)) { | |
| 384 | - | |
| 468 | + toastr.error('Assets list is not an array'); | |
| 469 | + return; | |
| 470 | + } | |
| 471 | + const characters = json.filter(x => x && x.type === 'character'); | |
| 385 | 472 | if (!characters.length) { |
| 386 | 473 | toastr.error('No characters found in the assets list', 'Character browser'); |
| 387 | 474 | return; |
| @@ -398,12 +485,19 @@ async function openCharacterBrowser(forceDefault) { | ||
| 398 | 485 | |
| 399 | 486 | downloadButton.toggle(!isInstalled).on('click', async () => { |
| 400 | 487 | downloadButton.toggleClass('fa-download fa-spinner fa-spin'); |
| 401 | 488 | const result = await installAsset(character.url, 'character', character.id); |
| 489 | + if (result) { | |
| 402 | 490 | downloadButton.hide(); |
| 403 | 491 | checkMark.show(); |
| 492 | + } else { | |
| 493 | + downloadButton.toggleClass('fa-download fa-spinner fa-spin'); | |
| 494 | + } | |
| 404 | 495 | }); |
| 405 | 496 | |
| 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 | + }); | |
| 407 | 501 | |
| 408 | 502 | listElement.append(characterElement); |
| 409 | 503 | } |
| @@ -435,7 +529,7 @@ async function updateCurrentAssets() { | ||
| 435 | 529 | //#############################// |
| 436 | 530 | |
| 437 | 531 | // This function is called when the extension is loaded |
| 438 | 532 | jQuery(export async function init() => { |
| 439 | 533 | // This is an example of loading HTML from a file |
| 440 | 534 | const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {}); |
| 441 | 535 | const windowHtml = $(windowTemplate); |
| @@ -457,11 +551,16 @@ jQuery(async () => { | ||
| 457 | 551 | |
| 458 | 552 | const connectButton = windowHtml.find('#assets-connect-button'); |
| 459 | 553 | connectButton.on('click', async function () { |
| 460 | 554 | const urlurlString = DOMPurify.sanitize(String(assetsJsonUrl.val()).trim(); |
| 461 | - const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; | |
| 555 | + if (!isValidUrl(urlString)) { | |
| 556 | + toastr.error('Please enter a valid URL'); | |
| 557 | + return; | |
| 558 | + } | |
| 559 | + const url = new URL(urlString); | |
| 560 | + const rememberKey = `Assets_SkipConfirm_${getStringHash(url.href)}`; | |
| 462 | 561 | const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; |
| 463 | 562 | |
| 464 | 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 | 564 | customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], |
| 466 | 565 | onClose: popup => { |
| 467 | 566 | if (popup.result) { |
| @@ -480,7 +579,7 @@ jQuery(async () => { | ||
| 480 | 579 | connectButton.addClass('fa-plug-circle-check'); |
| 481 | 580 | } catch (error) { |
| 482 | 581 | console.error('Error:', error); |
| 483 | 582 | toastr.error(`Cannot get assets list from ${url.href}`); |
| 484 | 583 | connectButton.removeClass('fa-plug-circle-check'); |
| 485 | 584 | connectButton.addClass('fa-plug-circle-exclamation'); |
| 486 | 585 | connectButton.removeClass('redOverlayGlow'); |
| @@ -496,4 +595,4 @@ jQuery(async () => { | ||
| 496 | 595 | eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => { |
| 497 | 596 | openCharacterBrowser(forceDefault); |
| 498 | 597 | }); |
| 499 | 598 | }); |
| @@ -2,3 +2,21 @@ | ||
| 2 | 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 | 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 | 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> | |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "Keij#6799", |
| 9 | 9 | "version": "0.1.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -27,15 +27,20 @@ | ||
| 27 | 27 | margin-bottom: 0.25em; |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | +.assets-list-div h2 { | |
| 31 | + margin: 0; | |
| 32 | + font-size: 1.1em; | |
| 33 | +} | |
| 34 | + | |
| 30 | 35 | .assets-list-div h3 { |
| 31 | 36 | text-transform: capitalize; |
| 32 | 37 | } |
| 33 | 38 | |
| 34 | 39 | .assets-list-div i.asset-block a { |
| 35 | 40 | color: inherit; |
| 36 | 41 | } |
| 37 | 42 | |
| 38 | 43 | .assets-list-div>i .asset-block { |
| 39 | 44 | display: flex; |
| 40 | 45 | flex-direction: row; |
| 41 | 46 | align-items: center; |
| @@ -46,7 +51,7 @@ | ||
| 46 | 51 | border-bottom: 1px solid var(--SmartThemeBorderColor); |
| 47 | 52 | } |
| 48 | 53 | |
| 49 | 54 | .assets-list-div i.asset-block span:first-of-type { |
| 50 | 55 | font-weight: bold; |
| 51 | 56 | } |
| 52 | 57 | |
| @@ -198,3 +203,11 @@ | ||
| 198 | 203 | .asset-name>b { |
| 199 | 204 | font-weight: 600; |
| 200 | 205 | } |
| 206 | + | |
| 207 | +div:is(.assets-list-extensions-official, .assets-list-extensions-community):has(.assets-list-extensions:empty) { | |
| 208 | + display: none; | |
| 209 | +} | |
| 210 | + | |
| 211 | +div:is(.assets-list-extensions-official, .assets-list-extensions-community) { | |
| 212 | + margin-top: 10px; | |
| 213 | +} | |
| @@ -243,7 +243,7 @@ function handleCharacterRename(oldAvatar, newAvatar) { | ||
| 243 | 243 | } |
| 244 | 244 | } |
| 245 | 245 | |
| 246 | 246 | jQuery(export async function init() => { |
| 247 | 247 | eventSource.on(event_types.APP_READY, cleanUpAttachments); |
| 248 | 248 | eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments); |
| 249 | 249 | eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename); |
| @@ -407,4 +407,4 @@ jQuery(async () => { | ||
| 407 | 407 | }), |
| 408 | 408 | ], |
| 409 | 409 | })); |
| 410 | 410 | }); |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "Cohee1207", |
| 9 | 9 | "version": "1.0.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | 3 | import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtendedsubstituteParams } from '../../../script.js'; |
| 4 | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| 5 | 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 6 | +import { oai_settings } from '../../openai.js'; | |
| 6 | 7 | import { getMultimodalCaption } from '../shared.js'; |
| 7 | 8 | import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js'; |
| 8 | 9 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| @@ -99,7 +100,7 @@ async function wrapCaptionTemplate(caption) { | ||
| 99 | 100 | template += ' {{caption}}'; |
| 100 | 101 | } |
| 101 | 102 | |
| 102 | 103 | let messageText = substituteParamsExtendedsubstituteParams(template, { dynamicMacros: { caption: caption } }); |
| 103 | 104 | |
| 104 | 105 | if (extension_settings.caption.refine_mode) { |
| 105 | 106 | messageText = await Popup.show.input( |
| @@ -321,6 +322,8 @@ async function captionMultimodal(base64Img, externalPrompt) { | ||
| 321 | 322 | prompt = String(customPrompt).trim(); |
| 322 | 323 | } |
| 323 | 324 | |
| 325 | + prompt = substituteParams(prompt); | |
| 326 | + | |
| 324 | 327 | const caption = await getMultimodalCaption(base64Img, prompt); |
| 325 | 328 | return { caption }; |
| 326 | 329 | } |
| @@ -454,7 +457,7 @@ function isVideoCaptioningAvailable() { | ||
| 454 | 457 | return ['google', 'vertexai', 'zai'].includes(extension_settings.caption.multimodal_api); |
| 455 | 458 | } |
| 456 | 459 | |
| 457 | 460 | jQuery(export async function init() { |
| 458 | 461 | function addSendPictureButton() { |
| 459 | 462 | const sendButton = $(` |
| 460 | 463 | <div id="send_picture" class="list-group-item flex-container flexGap5"> |
| @@ -504,6 +507,7 @@ jQuery(async function () { | ||
| 504 | 507 | 'chutes': SECRET_KEYS.CHUTES, |
| 505 | 508 | 'electronhub': SECRET_KEYS.ELECTRONHUB, |
| 506 | 509 | 'pollinations': SECRET_KEYS.POLLINATIONS, |
| 510 | + 'workers_ai': SECRET_KEYS.WORKERS_AI, | |
| 507 | 511 | }; |
| 508 | 512 | |
| 509 | 513 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -580,7 +584,7 @@ jQuery(async function () { | ||
| 580 | 584 | } |
| 581 | 585 | |
| 582 | 586 | async function addRemoteEndpointModels() { |
| 583 | 587 | async function processEndpoint(api, url, additionalParams = {}) { |
| 584 | 588 | const dropdown = document.getElementById('caption_multimodal_model'); |
| 585 | 589 | if (!(dropdown instanceof HTMLSelectElement)) { |
| 586 | 590 | return; |
| @@ -591,7 +595,8 @@ jQuery(async function () { | ||
| 591 | 595 | const options = Array.from(dropdown.options); |
| 592 | 596 | const response = await fetch(url, { |
| 593 | 597 | method: 'POST', |
| 594 | 598 | headers: getRequestHeaders({ omitContentType: true }), |
| 599 | + body: JSON.stringify(additionalParams), | |
| 595 | 600 | }); |
| 596 | 601 | if (!response.ok) { |
| 597 | 602 | return; |
| @@ -620,6 +625,7 @@ jQuery(async function () { | ||
| 620 | 625 | await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral'); |
| 621 | 626 | await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai'); |
| 622 | 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 | } |
| 624 | 630 | |
| 625 | 631 | await addSettings(); |
| @@ -804,4 +810,4 @@ jQuery(async function () { | ||
| 804 | 810 | })); |
| 805 | 811 | |
| 806 | 812 | document.body.classList.add('caption'); |
| 807 | 813 | }); |
| @@ -9,5 +9,8 @@ | ||
| 9 | 9 | "css": "style.css", |
| 10 | 10 | "author": "Cohee#1207", |
| 11 | 11 | "version": "1.0.0", |
| 12 | 12 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 13 | + "hooks": { | |
| 14 | + "activate": "init" | |
| 15 | + } | |
| 13 | 16 | } |
| @@ -20,6 +20,7 @@ | ||
| 20 | 20 | <option value="aimlapi">AI/ML API</option> |
| 21 | 21 | <option value="chutes">Chutes</option> |
| 22 | 22 | <option value="anthropic">Claude</option> |
| 23 | + <option value="workers_ai">Cloudflare Workers AI</option> | |
| 23 | 24 | <option value="cohere">Cohere</option> |
| 24 | 25 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> |
| 25 | 26 | <option value="electronhub">Electron Hub</option> |
| @@ -53,7 +54,14 @@ | ||
| 53 | 54 | <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option> |
| 54 | 55 | <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option> |
| 55 | 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 | 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 | 65 | <option data-type="openai" value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option> |
| 58 | 66 | <option data-type="openai" value="gpt-5.2">gpt-5.2</option> |
| 59 | 67 | <option data-type="openai" value="gpt-5.2-2025-12-11">gpt-5.2-2025-12-11</option> |
| @@ -88,6 +96,7 @@ | ||
| 88 | 96 | <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option> |
| 89 | 97 | <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option> |
| 90 | 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 | 100 | <option data-type="anthropic" value="claude-opus-4-6">claude-opus-4-6</option> |
| 92 | 101 | <option data-type="anthropic" value="claude-opus-4-5">claude-opus-4-5</option> |
| 93 | 102 | <option data-type="anthropic" value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option> |
| @@ -144,6 +153,11 @@ | ||
| 144 | 153 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> |
| 145 | 154 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 146 | 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 | 161 | <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option> |
| 148 | 162 | <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option> |
| 149 | 163 | <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option> |
| @@ -174,6 +188,7 @@ | ||
| 174 | 188 | <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option> |
| 175 | 189 | <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option> |
| 176 | 190 | <option data-type="ollama" value="llama4">llama4</option> |
| 191 | + <option data-type="zai" value="glm-5v-turbo">glm-5v-turbo</option> | |
| 177 | 192 | <option data-type="zai" value="glm-4.6v">glm-4.6v</option> |
| 178 | 193 | <option data-type="zai" value="glm-4.6v-flashx">glm-4.6v-flashx</option> |
| 179 | 194 | <option data-type="zai" value="glm-4.6v-flash">glm-4.6v-flash</option> |
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | import { DOMPurify, Fuse } from '../../../lib.js'; |
| 2 | 2 | |
| 3 | 3 | import { activateSendButtons, deactivateSendButtons, event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js'; |
| 4 | 4 | import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 5 | 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 6 | 6 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 7 | 7 | import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js'; |
| @@ -9,11 +9,16 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from ' | ||
| 9 | 9 | import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 10 | 10 | import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js'; |
| 11 | 11 | import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 12 | +import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js'; | |
| 12 | 13 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 13 | 14 | import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js'; |
| 14 | 15 | import { collapseSpaces, getUniqueName, isFalseBoolean, isTrueBoolean, uuidv4, waitUntilCondition } from '../../utils.js'; |
| 15 | 16 | import { t } from '../../i18n.js'; |
| 16 | 17 | import { getSecretLabelById } from '../../secrets.js'; |
| 18 | +import { performFuzzySearch } from '/scripts/power-user.js'; | |
| 19 | +import { StreamingDisplay } from '/scripts/streaming-display.js'; | |
| 20 | +import { ConnectionManagerRequestService } from '../shared.js'; | |
| 21 | +import { formatReasoning } from '/scripts/reasoning.js'; | |
| 17 | 22 | |
| 18 | 23 | const MODULE_NAME = 'connection-manager'; |
| 19 | 24 | const NONE = '<None>'; |
| @@ -474,7 +479,223 @@ async function renderDetailsContent(detailsContent) { | ||
| 474 | 479 | } |
| 475 | 480 | } |
| 476 | 481 | |
| 477 | -(async function () { | |
| 482 | +/** | |
| 483 | + * Callback for the /profile-genstream command | |
| 484 | + * Generates text using Connection Manager with streaming display support. | |
| 485 | + * @param {object} args Named arguments | |
| 486 | + * @param {string} value Unnamed argument (the prompt) | |
| 487 | + * @returns {Promise<string>} The generated text, optionally with formatted reasoning | |
| 488 | + */ | |
| 489 | +async function generateStreamCallback(args, value) { | |
| 490 | + if (!value) { | |
| 491 | + console.warn('WARN: No argument provided for /profile-genstream command'); | |
| 492 | + return ''; | |
| 493 | + } | |
| 494 | + | |
| 495 | + // Check if Connection Manager is available | |
| 496 | + const context = getContext(); | |
| 497 | + if (context.extensionSettings.disabledExtensions.includes('connection-manager')) { | |
| 498 | + toastr.error(t`Connection Manager is required for /profile-genstream. Use /gen or /genraw instead.`); | |
| 499 | + return ''; | |
| 500 | + } | |
| 501 | + | |
| 502 | + const profileIdOrName = args?.profile; | |
| 503 | + const includeReasoning = isTrueBoolean(args?.reasoning); | |
| 504 | + const systemPrompt = typeof args?.system == 'string' ? args.system : ''; | |
| 505 | + const maxTokens = Number(args?.length ?? 2048) || 2048; | |
| 506 | + const lock = isTrueBoolean(args?.lock); | |
| 507 | + const generatingLabel = typeof args?.generating === 'string' ? args.generating : 'Generating...'; | |
| 508 | + const completedLabel = typeof args?.completed === 'string' ? args.completed : 'Generated'; | |
| 509 | + const enableStop = !isFalseBoolean(args?.stop); | |
| 510 | + const onStopClosure = args?.onStop instanceof SlashCommandClosure ? args.onStop : null; | |
| 511 | + const onCompleteClosure = args?.onComplete instanceof SlashCommandClosure ? args.onComplete : null; | |
| 512 | + | |
| 513 | + // Parse delay: 'infinite' or negative = null (stay open), number = delay in ms | |
| 514 | + let completeDelay = 3000; // Default 3 seconds | |
| 515 | + if (args?.delay !== undefined) { | |
| 516 | + if (typeof args.delay === 'string' && args.delay.toLowerCase() === 'infinite') { | |
| 517 | + completeDelay = null; // Stay until user closes | |
| 518 | + } else { | |
| 519 | + const parsed = Number(args.delay); | |
| 520 | + if (!isNaN(parsed) && parsed >= 0) { | |
| 521 | + completeDelay = parsed; | |
| 522 | + } else if (!isNaN(parsed) && parsed < 0) { | |
| 523 | + completeDelay = null; // Negative = infinite | |
| 524 | + } | |
| 525 | + } | |
| 526 | + } | |
| 527 | + | |
| 528 | + // Create abort controller for stop functionality (when stop is enabled) | |
| 529 | + const abortController = enableStop ? new AbortController() : null; | |
| 530 | + | |
| 531 | + // Compose the stop handler: abort the request + optionally invoke user closure | |
| 532 | + const onStopHandler = enableStop ? async () => { | |
| 533 | + abortController.abort(); | |
| 534 | + if (onStopClosure) { | |
| 535 | + try { | |
| 536 | + const localClosure = onStopClosure.getCopy(); | |
| 537 | + localClosure.onProgress = () => { }; | |
| 538 | + await localClosure.execute(); | |
| 539 | + } catch (e) { | |
| 540 | + console.error('[GenStream] Error executing onStop closure', e); | |
| 541 | + } | |
| 542 | + } | |
| 543 | + } : null; | |
| 544 | + | |
| 545 | + try { | |
| 546 | + if (lock) { | |
| 547 | + deactivateSendButtons(); | |
| 548 | + } | |
| 549 | + | |
| 550 | + // Determine which profile to use | |
| 551 | + // Use the currently selected profile if no profile specified | |
| 552 | + let effectiveProfileId = context.extensionSettings.connectionManager.selectedProfile; | |
| 553 | + | |
| 554 | + const profiles = context.extensionSettings.connectionManager.profiles; | |
| 555 | + | |
| 556 | + if (profileIdOrName) { | |
| 557 | + // Use try to find profile by id first, then fuse search | |
| 558 | + const profile = profiles.find(p => p.id === profileIdOrName); | |
| 559 | + if (profile) { | |
| 560 | + effectiveProfileId = profile.id; | |
| 561 | + } else { | |
| 562 | + const keys = [ | |
| 563 | + { name: 'name', weight: 10 }, | |
| 564 | + ]; | |
| 565 | + const fuseResults = performFuzzySearch('profile', profiles, keys, profileIdOrName); | |
| 566 | + if (fuseResults.length > 0) { | |
| 567 | + effectiveProfileId = fuseResults[0].item.id; | |
| 568 | + } else { | |
| 569 | + toastr.warning(t`Connection profile not found: ${profileIdOrName}`); | |
| 570 | + return ''; | |
| 571 | + } | |
| 572 | + } | |
| 573 | + } | |
| 574 | + | |
| 575 | + if (!effectiveProfileId) { | |
| 576 | + toastr.error(t`No connection profile specified or selected. Use profile= argument or select a profile in Connection Manager.`); | |
| 577 | + return ''; | |
| 578 | + } | |
| 579 | + | |
| 580 | + // Create streaming display | |
| 581 | + const display = new StreamingDisplay(); | |
| 582 | + display.show({ | |
| 583 | + label: generatingLabel, | |
| 584 | + icon: ConnectionManagerRequestService.getProfileIcon(effectiveProfileId), | |
| 585 | + onStop: onStopHandler, | |
| 586 | + }); | |
| 587 | + | |
| 588 | + const messages = [ | |
| 589 | + ...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []), | |
| 590 | + { role: 'user', content: value }, | |
| 591 | + ]; | |
| 592 | + | |
| 593 | + let finalText = ''; | |
| 594 | + let finalReasoning = ''; | |
| 595 | + | |
| 596 | + /** Gets the final (if requested, formatted) text to return for this command @returns {string} */ | |
| 597 | + function buildResultText() { | |
| 598 | + // Format output with reasoning if requested | |
| 599 | + if (includeReasoning && finalReasoning) { | |
| 600 | + const { formatted } = formatReasoning(finalReasoning, finalText); | |
| 601 | + return formatted; | |
| 602 | + } | |
| 603 | + | |
| 604 | + return finalText; | |
| 605 | + } | |
| 606 | + | |
| 607 | + try { | |
| 608 | + // Attempt streaming first | |
| 609 | + const streamResponse = await ConnectionManagerRequestService.sendRequest( | |
| 610 | + effectiveProfileId, | |
| 611 | + messages, | |
| 612 | + maxTokens, | |
| 613 | + { extractData: true, includePreset: true, stream: true, signal: abortController?.signal ?? undefined }, | |
| 614 | + ); | |
| 615 | + | |
| 616 | + if (typeof streamResponse === 'function') { | |
| 617 | + const generator = streamResponse(); | |
| 618 | + for await (const chunk of generator) { | |
| 619 | + finalText = chunk.text; | |
| 620 | + finalReasoning = chunk.state?.reasoning || ''; | |
| 621 | + display.updateReasoning(finalReasoning); | |
| 622 | + display.updateContent(finalText); | |
| 623 | + } | |
| 624 | + } else { | |
| 625 | + // Non-streaming fallback within the try block | |
| 626 | + const extracted = streamResponse; | |
| 627 | + finalText = extracted?.content || ''; | |
| 628 | + finalReasoning = extracted?.reasoning || ''; | |
| 629 | + if (finalReasoning) { | |
| 630 | + display.updateReasoning(finalReasoning); | |
| 631 | + } | |
| 632 | + display.updateContent(finalText); | |
| 633 | + } | |
| 634 | + } catch (error) { | |
| 635 | + // If the user clicked stop, don't retry — show stopped state and return empty | |
| 636 | + if (abortController?.signal?.aborted) { | |
| 637 | + display.markStopped({ label: `${generatingLabel} [Stopped]` }); | |
| 638 | + return buildResultText(); | |
| 639 | + } | |
| 640 | + | |
| 641 | + console.warn('[Slash Commands] Streaming failed, falling back to non-streaming:', error); | |
| 642 | + display.hide({ instant: true }); | |
| 643 | + | |
| 644 | + // Retry with non-streaming | |
| 645 | + const response = await ConnectionManagerRequestService.sendRequest( | |
| 646 | + effectiveProfileId, | |
| 647 | + messages, | |
| 648 | + maxTokens, | |
| 649 | + { extractData: true, includePreset: true, stream: false }, | |
| 650 | + ); | |
| 651 | + | |
| 652 | + const extracted = /** @type {import('../../custom-request.js').ExtractedData} */ (response); | |
| 653 | + finalText = extracted?.content || ''; | |
| 654 | + finalReasoning = extracted?.reasoning || ''; | |
| 655 | + | |
| 656 | + // Show quick non-streaming display | |
| 657 | + display.show({ | |
| 658 | + label: generatingLabel, | |
| 659 | + icon: ConnectionManagerRequestService.getProfileIcon(effectiveProfileId), | |
| 660 | + }); | |
| 661 | + if (finalReasoning) { | |
| 662 | + display.updateReasoning(finalReasoning); | |
| 663 | + } | |
| 664 | + display.updateContent(finalText); | |
| 665 | + } | |
| 666 | + | |
| 667 | + // Mark as complete with delay (null = stay open until user closes) | |
| 668 | + display.complete({ label: completedLabel, delay: completeDelay }); | |
| 669 | + | |
| 670 | + // Invoke onComplete closure if provided | |
| 671 | + if (onCompleteClosure) { | |
| 672 | + try { | |
| 673 | + const localClosure = onCompleteClosure.getCopy(); | |
| 674 | + localClosure.onProgress = () => { }; | |
| 675 | + await localClosure.execute(); | |
| 676 | + } catch (e) { | |
| 677 | + console.error('[GenStream] Error executing onComplete closure', e); | |
| 678 | + } | |
| 679 | + } | |
| 680 | + | |
| 681 | + if (!finalText) { | |
| 682 | + toastr.warning(t`Generation returned empty result`); | |
| 683 | + return ''; | |
| 684 | + } | |
| 685 | + | |
| 686 | + return buildResultText(); | |
| 687 | + } catch (err) { | |
| 688 | + console.error('Error on /genstream generation', err); | |
| 689 | + toastr.error(err.message, t`API Error`, { preventDuplicates: true }); | |
| 690 | + return ''; | |
| 691 | + } finally { | |
| 692 | + if (lock) { | |
| 693 | + activateSendButtons(); | |
| 694 | + } | |
| 695 | + } | |
| 696 | +} | |
| 697 | + | |
| 698 | +export async function init() { | |
| 478 | 699 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); |
| 479 | 700 | |
| 480 | 701 | for (const key of Object.keys(DEFAULT_SETTINGS)) { |
| @@ -824,4 +1045,114 @@ async function renderDetailsContent(detailsContent) { | ||
| 824 | 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 | +} | |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "Cohee1207", |
| 9 | 9 | "version": "1.0.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, | ||
| 4 | 4 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; |
| 5 | 5 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 6 | 6 | import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js'; |
| 7 | 7 | import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean, includesIgnoreCaseAndAccents } from '../../utils.js'; |
| 8 | 8 | import { hideMutedSprites, selected_group } from '../../group-chats.js'; |
| 9 | 9 | import { isJsonSchemaSupported } from '../../textgen-settings.js'; |
| 10 | 10 | import { debounce_timeout } from '../../constants.js'; |
| @@ -787,6 +787,32 @@ async function setSpriteSlashCommand({ type }, searchTerm) { | ||
| 787 | 787 | } |
| 788 | 788 | |
| 789 | 789 | /** |
| 790 | + * @param {string} expressionName - Label of the expression to set as fallback | |
| 791 | + */ | |
| 792 | +function setFallBackExpressionSlashCommand(args, expressionName) { | |
| 793 | + expressionName = expressionName.trim().toLowerCase(); | |
| 794 | + | |
| 795 | + if (!expressionName) return extension_settings?.expressions?.fallback_expression || ''; | |
| 796 | + | |
| 797 | + const select = /** @type {HTMLSelectElement} */(document.getElementById('expression_fallback')); | |
| 798 | + const fallbackExpressions = Array | |
| 799 | + .from(select?.options || []) | |
| 800 | + .map(option => option.value) | |
| 801 | + .filter(expression => expression?.length > 0); | |
| 802 | + | |
| 803 | + const expressionMatch = fallbackExpressions.find(expression => includesIgnoreCaseAndAccents(expression, expressionName)); | |
| 804 | + | |
| 805 | + if (!expressionMatch) { | |
| 806 | + toastr.warning(t`No expression found for search term ${expressionName}`, t`Set Fallback Expression`); | |
| 807 | + return ''; | |
| 808 | + } | |
| 809 | + | |
| 810 | + $(select).val(expressionMatch).trigger('change'); | |
| 811 | + | |
| 812 | + return expressionMatch; | |
| 813 | +} | |
| 814 | + | |
| 815 | +/** | |
| 790 | 816 | * Returns the sprite folder name (including override) for a character. |
| 791 | 817 | * @param {object} char Character object |
| 792 | 818 | * @param {string} char.avatar Avatar filename with extension |
| @@ -2140,7 +2166,7 @@ function migrateSettings() { | ||
| 2140 | 2166 | } |
| 2141 | 2167 | } |
| 2142 | 2168 | |
| 2143 | 2169 | (export async function init() { |
| 2144 | 2170 | function addExpressionImage() { |
| 2145 | 2171 | const html = ` |
| 2146 | 2172 | <div id="expression-wrapper"> |
| @@ -2317,6 +2343,42 @@ function migrateSettings() { | ||
| 2317 | 2343 | returns: 'The currently set expression label after setting it.', |
| 2318 | 2344 | })); |
| 2319 | 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 | 2382 | name: 'expression-folder-override', |
| 2321 | 2383 | aliases: ['spriteoverride', 'costume'], |
| 2322 | 2384 | callback: setSpriteFolderCommand, |
| @@ -2511,4 +2573,4 @@ function migrateSettings() { | ||
| 2511 | 2573 | </div> |
| 2512 | 2574 | `, |
| 2513 | 2575 | })); |
| 2514 | -})(); | |
| 2576 | +} | |
| @@ -9,5 +9,8 @@ | ||
| 9 | 9 | "css": "style.css", |
| 10 | 10 | "author": "Cohee#1207", |
| 11 | 11 | "version": "1.0.0", |
| 12 | 12 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 13 | + "hooks": { | |
| 14 | + "activate": "init" | |
| 15 | + } | |
| 13 | 16 | } |
| @@ -818,7 +818,7 @@ function addGalleryWandButton() { | ||
| 818 | 818 | } |
| 819 | 819 | |
| 820 | 820 | // On extension load, ensure the settings are initialized |
| 821 | 821 | (export async function init() { |
| 822 | 822 | initSettings(); |
| 823 | 823 | eventSource.on(event_types.CHARACTER_RENAMED, (oldAvatar, newAvatar) => { |
| 824 | 824 | const context = SillyTavern.getContext(); |
| @@ -850,4 +850,4 @@ function addGalleryWandButton() { | ||
| 850 | 850 | }), |
| 851 | 851 | ); |
| 852 | 852 | addGalleryWandButton(); |
| 853 | -})(); | |
| 853 | +} | |
| @@ -8,5 +8,8 @@ | ||
| 8 | 8 | "css": "style.css", |
| 9 | 9 | "author": "City-Unit", |
| 10 | 10 | "version": "1.5.0", |
| 11 | 11 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 12 | + "hooks": { | |
| 13 | + "activate": "init" | |
| 14 | + } | |
| 12 | 15 | } |
| @@ -1063,7 +1063,7 @@ function setupListeners() { | ||
| 1063 | 1063 | }); |
| 1064 | 1064 | } |
| 1065 | 1065 | |
| 1066 | 1066 | jQuery(export async function init() { |
| 1067 | 1067 | async function addExtensionControls() { |
| 1068 | 1068 | const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings }); |
| 1069 | 1069 | $('#summarize_container').append(settingsHtml); |
| @@ -1128,4 +1128,4 @@ jQuery(async function () { | ||
| 1128 | 1128 | () => summaryMacroHandler(), |
| 1129 | 1129 | 'Returns the latest memory/summary from the current chat.'); |
| 1130 | 1130 | } |
| 1131 | 1131 | }); |
| @@ -9,5 +9,8 @@ | ||
| 9 | 9 | "css": "style.css", |
| 10 | 10 | "author": "Cohee#1207", |
| 11 | 11 | "version": "1.0.0", |
| 12 | 12 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 13 | + "hooks": { | |
| 14 | + "activate": "init" | |
| 15 | + } | |
| 13 | 16 | } |
| @@ -169,7 +169,7 @@ const handleCharChange = () => { | ||
| 169 | 169 | settings.charConfig = charConfig; |
| 170 | 170 | }; |
| 171 | 171 | |
| 172 | 172 | const init =export async function init() => { |
| 173 | 173 | await loadSets(); |
| 174 | 174 | await loadSettings(); |
| 175 | 175 | log('settings: ', settings); |
| @@ -214,7 +214,8 @@ const init = async () => { | ||
| 214 | 214 | eventSource.on(event_types.APP_READY, async () => await finalizeInit()); |
| 215 | 215 | |
| 216 | 216 | globalThis.quickReplyApi = quickReplyApi; |
| 217 | 217 | }; |
| 218 | + | |
| 218 | 219 | const finalizeInit = async () => { |
| 219 | 220 | debug('executing startup'); |
| 220 | 221 | await autoExec.handleStartup(); |
| @@ -229,7 +230,7 @@ const finalizeInit = async () => { | ||
| 229 | 230 | isReady = true; |
| 230 | 231 | debug('READY'); |
| 231 | 232 | }; |
| 232 | -await init(); | |
| 233 | + | |
| 233 | 234 | |
| 234 | 235 | const purgeCharacterQuickReplySets = ({ character }) => { |
| 235 | 236 | // Remove the character's Quick Reply Sets from the settings. |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "RossAscends#1779", |
| 9 | 9 | "version": "2.0.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -1639,7 +1639,7 @@ async function checkCharEmbeddedRegexScripts() { | ||
| 1639 | 1639 | function notifyReloadCurrentChat(presetName) { |
| 1640 | 1640 | toastr.info( |
| 1641 | 1641 | t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>', |
| 1642 | 1642 | t`Preset '${escapeHtml(presetName)}' contains enabled regex scripts`, |
| 1643 | 1643 | { |
| 1644 | 1644 | timeOut: 5000, |
| 1645 | 1645 | escapeHtml: false, |
| @@ -1709,7 +1709,7 @@ function onPresetRenamed({ apiId, oldName, newName }) { | ||
| 1709 | 1709 | |
| 1710 | 1710 | // Workaround for loading in sequence with other extensions |
| 1711 | 1711 | // NOTE: Always puts extension at the top of the list, but this is fine since it's static |
| 1712 | 1712 | jQuery(export async function init() => { |
| 1713 | 1713 | if (!Array.isArray(extension_settings.regex)) { |
| 1714 | 1714 | extension_settings.regex = []; |
| 1715 | 1715 | } |
| @@ -2069,6 +2069,37 @@ jQuery(async () => { | ||
| 2069 | 2069 | helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.', |
| 2070 | 2070 | })); |
| 2071 | 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 | 2103 | name: 'regex-toggle', |
| 2073 | 2104 | callback: toggleRegexCallback, |
| 2074 | 2105 | returns: 'The name of the script that was toggled', |
| @@ -2123,4 +2154,4 @@ jQuery(async () => { | ||
| 2123 | 2154 | |
| 2124 | 2155 | presetManager.setupEventListeners(); |
| 2125 | 2156 | presetManager.registerSlashCommands(); |
| 2126 | 2157 | }); |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "kingbri", |
| 9 | 9 | "version": "1.0.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | import { CONNECT_API_MAP, createModelIcon, getRequestHeaders } from '../../script.js'; |
| 2 | 2 | import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js'; |
| 3 | 3 | import { t } from '../i18n.js'; |
| 4 | 4 | import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js'; |
| @@ -126,6 +126,10 @@ export async function getMultimodalCaption(base64Img, prompt) { | ||
| 126 | 126 | requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON; |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | + if (extension_settings.caption.multimodal_api === 'workers_ai') { | |
| 130 | + requestBody.workers_ai_account_id = oai_settings.workers_ai_account_id; | |
| 131 | + } | |
| 132 | + | |
| 129 | 133 | function getEndpointUrl() { |
| 130 | 134 | switch (extension_settings.caption.multimodal_api) { |
| 131 | 135 | case 'google': |
| @@ -283,6 +287,10 @@ function throwIfInvalidModel(useReverseProxy) { | ||
| 283 | 287 | if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) { |
| 284 | 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 | } |
| 287 | 295 | |
| 288 | 296 | /** |
| @@ -435,10 +443,12 @@ export class ConnectionManagerRequestService { | ||
| 435 | 443 | max_tokens: maxTokens, |
| 436 | 444 | model: profile.model, |
| 437 | 445 | chat_completion_source: selectedApiMap.source, |
| 446 | + secret_id: profile['secret-id'], | |
| 438 | 447 | custom_url: profile['api-url'], |
| 439 | 448 | vertexai_region: profile['api-url'], |
| 440 | 449 | zai_endpoint: profile['api-url'], |
| 441 | 450 | siliconflow_endpoint: profile['api-url'], |
| 451 | + minimax_endpoint: profile['api-url'], | |
| 442 | 452 | reverse_proxy: proxyPreset?.url, |
| 443 | 453 | proxy_password: proxyPreset?.password, |
| 444 | 454 | custom_prompt_post_processing: profile['prompt-post-processing'], |
| @@ -459,6 +469,7 @@ export class ConnectionManagerRequestService { | ||
| 459 | 469 | model: profile.model, |
| 460 | 470 | api_type: selectedApiMap.type, |
| 461 | 471 | api_server: profile['api-url'], |
| 472 | + secret_id: profile['secret-id'], | |
| 462 | 473 | ...overridePayload, |
| 463 | 474 | }, { |
| 464 | 475 | instructName: includeInstruct ? profile.instruct : undefined, |
| @@ -534,6 +545,29 @@ export class ConnectionManagerRequestService { | ||
| 534 | 545 | } |
| 535 | 546 | |
| 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 | 571 | * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile] |
| 538 | 572 | * @returns {boolean} |
| 539 | 573 | */ |
| @@ -2,7 +2,3 @@ | ||
| 2 | 2 | <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div> |
| 3 | 3 | <span data-i18n="Generate Image">Generate Image</span> |
| 4 | 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> | |
| @@ -62,18 +62,13 @@ import { t, translate } from '../../i18n.js'; | ||
| 62 | 62 | import { oai_settings } from '../../openai.js'; |
| 63 | 63 | import { power_user } from '/scripts/power-user.js'; |
| 64 | 64 | import { MacrosParser } from '/scripts/macros.js'; |
| 65 | +import { ActionLoaderHandle, loader } from '/scripts/action-loader.js'; | |
| 65 | 66 | |
| 66 | 67 | export { MODULE_NAME }; |
| 67 | 68 | |
| 68 | 69 | const MODULE_NAME = 'sd'; |
| 69 | 70 | // This is a 1x1 transparent PNG |
| 70 | 71 | const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; |
| 71 | -const CUSTOM_STOP_EVENT = 'sd_stop_generation'; | |
| 72 | - | |
| 73 | -// Generation tracking for status indicator | |
| 74 | -let activeGenerations = 0; | |
| 75 | -/** @type {JQuery<HTMLElement>|null} */ | |
| 76 | -let generationToast = null; | |
| 77 | 72 | |
| 78 | 73 | const sources = { |
| 79 | 74 | extras: 'extras', |
| @@ -99,6 +94,7 @@ const sources = { | ||
| 99 | 94 | google: 'google', |
| 100 | 95 | zai: 'zai', |
| 101 | 96 | openrouter: 'openrouter', |
| 97 | + workersai: 'workersai', | |
| 102 | 98 | }; |
| 103 | 99 | const comfyTypes = { |
| 104 | 100 | standard: 'standard', |
| @@ -1747,6 +1743,9 @@ async function loadSamplers() { | ||
| 1747 | 1743 | case sources.openrouter: |
| 1748 | 1744 | samplers = ['N/A']; |
| 1749 | 1745 | break; |
| 1746 | + case sources.workersai: | |
| 1747 | + samplers = ['N/A']; | |
| 1748 | + break; | |
| 1750 | 1749 | } |
| 1751 | 1750 | |
| 1752 | 1751 | for (const sampler of samplers) { |
| @@ -1815,6 +1814,34 @@ async function loadAutoSamplers() { | ||
| 1815 | 1814 | } |
| 1816 | 1815 | } |
| 1817 | 1816 | |
| 1817 | +async function loadSdcppModels() { | |
| 1818 | + if (!extension_settings.sd.sdcpp_url) { | |
| 1819 | + return [{ value: '', text: 'N/A' }]; | |
| 1820 | + } | |
| 1821 | + | |
| 1822 | + try { | |
| 1823 | + const result = await fetch('/api/sd/sdcpp/models', { | |
| 1824 | + method: 'POST', | |
| 1825 | + headers: getRequestHeaders(), | |
| 1826 | + body: JSON.stringify({ url: extension_settings.sd.sdcpp_url }), | |
| 1827 | + }); | |
| 1828 | + | |
| 1829 | + if (!result.ok) { | |
| 1830 | + return [{ value: '', text: 'N/A' }]; | |
| 1831 | + } | |
| 1832 | + | |
| 1833 | + const data = await result.json(); | |
| 1834 | + | |
| 1835 | + if (data?.data?.length > 0) { | |
| 1836 | + return data.data.map(model => ({ value: model.id, text: model.name || model.id })); | |
| 1837 | + } | |
| 1838 | + } catch (error) { | |
| 1839 | + console.error('Failed to load sd.cpp models:', error); | |
| 1840 | + } | |
| 1841 | + | |
| 1842 | + return [{ value: '', text: 'N/A' }]; | |
| 1843 | +} | |
| 1844 | + | |
| 1818 | 1845 | async function loadSdcppSamplers() { |
| 1819 | 1846 | // The sdcpp server does not provide an API for samplers, so we return the known list. |
| 1820 | 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 | 1937 | models = await loadAutoModels(); |
| 1911 | 1938 | break; |
| 1912 | 1939 | case sources.sdcpp: |
| 1913 | - models = [{ value: '', text: 'N/A' }]; | |
| 1940 | + models = await loadSdcppModels(); | |
| 1914 | 1941 | break; |
| 1915 | 1942 | case sources.drawthings: |
| 1916 | 1943 | models = await loadDrawthingsModels(); |
| @@ -1969,6 +1996,9 @@ async function loadModels() { | ||
| 1969 | 1996 | case sources.openrouter: |
| 1970 | 1997 | models = await loadOpenRouterModels(); |
| 1971 | 1998 | break; |
| 1999 | + case sources.workersai: | |
| 2000 | + models = await loadWorkersAIImageModels(); | |
| 2001 | + break; | |
| 1972 | 2002 | } |
| 1973 | 2003 | |
| 1974 | 2004 | if (extension_settings.sd.source === sources.electronhub) { |
| @@ -2096,6 +2126,33 @@ async function loadXAIModels() { | ||
| 2096 | 2126 | ]; |
| 2097 | 2127 | } |
| 2098 | 2128 | |
| 2129 | +async function loadWorkersAIImageModels() { | |
| 2130 | + $('#sd_cf_workers_key').toggleClass('success', !!secret_state[SECRET_KEYS.WORKERS_AI]); | |
| 2131 | + | |
| 2132 | + if (!secret_state[SECRET_KEYS.WORKERS_AI]) { | |
| 2133 | + return []; | |
| 2134 | + } | |
| 2135 | + | |
| 2136 | + if (!oai_settings.workers_ai_account_id) { | |
| 2137 | + toastr.warning('Workers AI account ID is required. Save it in the "API Connections" panel.', 'Image Generation'); | |
| 2138 | + return []; | |
| 2139 | + } | |
| 2140 | + | |
| 2141 | + const result = await fetch('/api/sd/workersai/models', { | |
| 2142 | + method: 'POST', | |
| 2143 | + headers: getRequestHeaders(), | |
| 2144 | + body: JSON.stringify({ | |
| 2145 | + account_id: oai_settings.workers_ai_account_id, | |
| 2146 | + }), | |
| 2147 | + }); | |
| 2148 | + | |
| 2149 | + if (result.ok) { | |
| 2150 | + return await result.json(); | |
| 2151 | + } | |
| 2152 | + | |
| 2153 | + return []; | |
| 2154 | +} | |
| 2155 | + | |
| 2099 | 2156 | async function loadPollinationsModels() { |
| 2100 | 2157 | $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]); |
| 2101 | 2158 | |
| @@ -2325,6 +2382,8 @@ async function loadDrawthingsModels() { | ||
| 2325 | 2382 | |
| 2326 | 2383 | async function loadOpenAiModels() { |
| 2327 | 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 | 2387 | { value: 'gpt-image-1.5', text: 'gpt-image-1.5' }, |
| 2329 | 2388 | { value: 'gpt-image-1-mini', text: 'gpt-image-1-mini' }, |
| 2330 | 2389 | { value: 'gpt-image-1', text: 'gpt-image-1' }, |
| @@ -2581,6 +2640,9 @@ async function loadSchedulers() { | ||
| 2581 | 2640 | case sources.openrouter: |
| 2582 | 2641 | schedulers = ['N/A']; |
| 2583 | 2642 | break; |
| 2643 | + case sources.workersai: | |
| 2644 | + schedulers = ['N/A']; | |
| 2645 | + break; | |
| 2584 | 2646 | } |
| 2585 | 2647 | |
| 2586 | 2648 | for (const scheduler of schedulers) { |
| @@ -2701,6 +2763,9 @@ async function loadVaes() { | ||
| 2701 | 2763 | case sources.openrouter: |
| 2702 | 2764 | vaes = ['N/A']; |
| 2703 | 2765 | break; |
| 2766 | + case sources.workersai: | |
| 2767 | + vaes = ['N/A']; | |
| 2768 | + break; | |
| 2704 | 2769 | } |
| 2705 | 2770 | |
| 2706 | 2771 | for (const vae of vaes) { |
| @@ -2913,62 +2978,6 @@ function ensureSelectionExists(setting, selector) { | ||
| 2913 | 2978 | } |
| 2914 | 2979 | |
| 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 | - */ | |
| 2919 | -function updateGenerationIndicator() { | |
| 2920 | - if (activeGenerations > 0) { | |
| 2921 | - const countText = activeGenerations > 1 ? ` (${activeGenerations})` : ''; | |
| 2922 | - const toastText = `<i class="fa-solid fa-spinner fa-spin"></i> ${t`Generating an image`}${countText}...`; | |
| 2923 | - | |
| 2924 | - // Show persistent toast if not already showing | |
| 2925 | - if (!generationToast) { | |
| 2926 | - generationToast = toastr.info( | |
| 2927 | - toastText, | |
| 2928 | - 'Image Generation', | |
| 2929 | - { | |
| 2930 | - timeOut: 0, | |
| 2931 | - extendedTimeOut: 0, | |
| 2932 | - tapToDismiss: true, | |
| 2933 | - escapeHtml: false, | |
| 2934 | - onHidden: () => { | |
| 2935 | - generationToast = null; | |
| 2936 | - }, | |
| 2937 | - }, | |
| 2938 | - ); | |
| 2939 | - } else if (activeGenerations > 1) { | |
| 2940 | - // Update count in existing toast | |
| 2941 | - const toastMessage = $(generationToast).find('.toast-message'); | |
| 2942 | - if (toastMessage.length) { | |
| 2943 | - toastMessage.html(toastText); | |
| 2944 | - } | |
| 2945 | - } | |
| 2946 | - } else { | |
| 2947 | - // Hide toast when done | |
| 2948 | - if (generationToast) { | |
| 2949 | - toastr.clear(generationToast); | |
| 2950 | - generationToast = null; | |
| 2951 | - } | |
| 2952 | - } | |
| 2953 | -} | |
| 2954 | - | |
| 2955 | -/** | |
| 2956 | - * Increments the active generation counter and updates indicators. | |
| 2957 | - */ | |
| 2958 | -function startGenerationTracking() { | |
| 2959 | - activeGenerations++; | |
| 2960 | - updateGenerationIndicator(); | |
| 2961 | -} | |
| 2962 | - | |
| 2963 | -/** | |
| 2964 | - * Decrements the active generation counter and updates indicators. | |
| 2965 | - */ | |
| 2966 | -function endGenerationTracking() { | |
| 2967 | - activeGenerations = Math.max(0, activeGenerations - 1); | |
| 2968 | - updateGenerationIndicator(); | |
| 2969 | -} | |
| 2970 | - | |
| 2971 | -/** | |
| 2972 | 2981 | * Generates an image based on the given trigger word. |
| 2973 | 2982 | * @param {string} initiator The initiator of the image generation |
| 2974 | 2983 | * @param {Record<string, object>} args Command arguments |
| @@ -3028,12 +3037,13 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 3028 | 3037 | |
| 3029 | 3038 | const dimensions = setTypeSpecificDimensions(generationType); |
| 3030 | 3039 | const abortController = new AbortController(); |
| 3031 | - const stopButton = document.getElementById('sd_stop_gen'); | |
| 3032 | 3040 | let negativePromptPrefix = args?.negative || ''; |
| 3033 | 3041 | let imagePath = ''; |
| 3034 | 3042 | |
| 3035 | 3043 | const stopListener = () => abortController.abort('Aborted by user'); |
| 3036 | 3044 | |
| 3045 | + let loaderHandle = ActionLoaderHandle.EMPTY; | |
| 3046 | + | |
| 3037 | 3047 | try { |
| 3038 | 3048 | const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); }; |
| 3039 | 3049 | |
| @@ -3046,16 +3056,19 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 3046 | 3056 | await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData); |
| 3047 | 3057 | prompt = eventData.prompt; // Allow extensions to modify the prompt |
| 3048 | 3058 | |
| 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 | 3059 | if (typeof args?._abortController?.addEventListener === 'function') { |
| 3056 | 3060 | args._abortController.addEventListener('abort', stopListener); |
| 3057 | 3061 | } |
| 3058 | 3062 | |
| 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 | 3072 | // generate the image |
| 3060 | 3073 | imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal); |
| 3061 | 3074 | } catch (err) { |
| @@ -3074,10 +3087,8 @@ async function generatePicture(initiator, args, trigger, message, callback) { | ||
| 3074 | 3087 | toastr.error(errorText, 'Image Generation'); |
| 3075 | 3088 | throw new Error(errorText); |
| 3076 | 3089 | } finally { |
| 3077 | - $(stopButton).hide(); | |
| 3078 | 3090 | restoreOriginalDimensions(dimensions); |
| 3079 | 3091 | eventSourceawait loaderHandle.removeListenerhide(CUSTOM_STOP_EVENT, stopListener); |
| 3080 | - endGenerationTracking(); | |
| 3081 | 3092 | } |
| 3082 | 3093 | |
| 3083 | 3094 | return imagePath; |
| @@ -3404,6 +3415,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 3404 | 3415 | case sources.openrouter: |
| 3405 | 3416 | result = await generateOpenRouterImage(prefixedPrompt, signal); |
| 3406 | 3417 | break; |
| 3418 | + case sources.workersai: | |
| 3419 | + result = await generateWorkersAIImage(prefixedPrompt, negativePrompt, signal); | |
| 3420 | + break; | |
| 3407 | 3421 | } |
| 3408 | 3422 | |
| 3409 | 3423 | if (!result.data) { |
| @@ -3858,6 +3872,7 @@ async function generateAutoImage(prompt, negativePrompt, signal) { | ||
| 3858 | 3872 | async function generateSdcppImage(prompt, negativePrompt, signal) { |
| 3859 | 3873 | const payload = { |
| 3860 | 3874 | url: extension_settings.sd.sdcpp_url, |
| 3875 | + model: extension_settings.sd.model || undefined, | |
| 3861 | 3876 | prompt: prompt, |
| 3862 | 3877 | negative_prompt: negativePrompt, |
| 3863 | 3878 | steps: extension_settings.sd.steps, |
| @@ -4062,7 +4077,7 @@ async function generateOpenAiImage(prompt, signal) { | ||
| 4062 | 4077 | |
| 4063 | 4078 | const isDalle2 = /dall-e-2/.test(extension_settings.sd.model); |
| 4064 | 4079 | const isDalle3 = /dall-e-3/.test(extension_settings.sd.model); |
| 4065 | 4080 | const isGptImg = /gpt-image-(1|2|latest)/.test(extension_settings.sd.model); |
| 4066 | 4081 | const isSora2 = /sora-2/.test(extension_settings.sd.model); |
| 4067 | 4082 | |
| 4068 | 4083 | if (isDalle2 && prompt.length > dalle2PromptLimit) { |
| @@ -4719,6 +4734,33 @@ async function generateOpenRouterImage(prompt, signal) { | ||
| 4719 | 4734 | throw new Error(text); |
| 4720 | 4735 | } |
| 4721 | 4736 | |
| 4737 | +async function generateWorkersAIImage(prompt, negativePrompt, signal) { | |
| 4738 | + const result = await fetch('/api/sd/workersai/generate', { | |
| 4739 | + method: 'POST', | |
| 4740 | + headers: getRequestHeaders(), | |
| 4741 | + signal: signal, | |
| 4742 | + body: JSON.stringify({ | |
| 4743 | + prompt: prompt, | |
| 4744 | + negative_prompt: negativePrompt, | |
| 4745 | + model: extension_settings.sd.model, | |
| 4746 | + width: extension_settings.sd.width, | |
| 4747 | + height: extension_settings.sd.height, | |
| 4748 | + steps: extension_settings.sd.steps, | |
| 4749 | + scale: extension_settings.sd.scale, | |
| 4750 | + seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, | |
| 4751 | + account_id: oai_settings.workers_ai_account_id, | |
| 4752 | + }), | |
| 4753 | + }); | |
| 4754 | + | |
| 4755 | + if (result.ok) { | |
| 4756 | + const data = await result.json(); | |
| 4757 | + return { format: data?.format, data: data?.image }; | |
| 4758 | + } else { | |
| 4759 | + const text = await result.text(); | |
| 4760 | + throw new Error(text); | |
| 4761 | + } | |
| 4762 | +} | |
| 4763 | + | |
| 4722 | 4764 | async function onComfyOpenWorkflowEditorClick() { |
| 4723 | 4765 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| 4724 | 4766 | method: 'POST', |
| @@ -5029,10 +5071,6 @@ async function addSDGenButtons() { | ||
| 5029 | 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 | } |
| 5037 | 5075 | |
| 5038 | 5076 | function isValidState() { |
| @@ -5091,6 +5129,8 @@ function isValidState() { | ||
| 5091 | 5129 | return secret_state[SECRET_KEYS.ZAI]; |
| 5092 | 5130 | case sources.openrouter: |
| 5093 | 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 | 5134 | default: |
| 5095 | 5135 | return false; |
| 5096 | 5136 | } |
| @@ -5115,10 +5155,6 @@ async function sdMessageButton($icon, { animate } = {}) { | ||
| 5115 | 5155 | $icon.toggleClass(classes.idle, !isBusy); |
| 5116 | 5156 | $icon.toggleClass(classes.busy, isBusy); |
| 5117 | 5157 | $media.toggleClass(classes.animation, isBusy); |
| 5118 | - | |
| 5119 | - // Update generation counter toast | |
| 5120 | - const trackingFunction = isBusy ? startGenerationTracking : endGenerationTracking; | |
| 5121 | - trackingFunction(); | |
| 5122 | 5158 | } |
| 5123 | 5159 | |
| 5124 | 5160 | let $media = jQuery(); |
| @@ -5233,7 +5269,6 @@ async function writePromptFields(characterId) { | ||
| 5233 | 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 | */ |
| 5235 | 5271 | async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, abortController = new AbortController()) { |
| 5236 | - const stopButton = document.getElementById('sd_stop_gen'); | |
| 5237 | 5272 | const stopListener = () => abortController.abort('Aborted by user'); |
| 5238 | 5273 | const generationType = mediaAttachment.generation_type ?? message?.extra?.generationType ?? generationMode.FREE; |
| 5239 | 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 | 5282 | source: MEDIA_SOURCE.GENERATED, |
| 5248 | 5283 | }; |
| 5249 | 5284 | |
| 5285 | + let loaderHandle = ActionLoaderHandle.EMPTY; | |
| 5286 | + | |
| 5250 | 5287 | try { |
| 5251 | - $(stopButton).show(); | |
| 5252 | - eventSource.once(CUSTOM_STOP_EVENT, stopListener); | |
| 5253 | 5288 | const callback = (_a, _b, _c, _d, _e, _f, format) => { result.type = isVideo(format) ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.IMAGE; }; |
| 5254 | 5289 | const savedPrompt = mediaAttachment.title ?? message.extra.title ?? ''; |
| 5255 | 5290 | const savedNegative = mediaAttachment.negative ?? message.extra.negative ?? ''; |
| @@ -5265,6 +5300,15 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, | ||
| 5265 | 5300 | ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString() |
| 5266 | 5301 | : context.characters[context.characterId]?.name; |
| 5267 | 5302 | |
| 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 | 5312 | onStart(); |
| 5269 | 5313 | result.url = await sendGenerationRequest(generationType, prompt, refineArgs.negative, characterName, callback, initiators.swipe, abortController.signal); |
| 5270 | 5314 | result.generation_type = generationType; |
| @@ -5276,11 +5320,10 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, | ||
| 5276 | 5320 | } |
| 5277 | 5321 | } finally { |
| 5278 | 5322 | onComplete(); |
| 5279 | - $(stopButton).hide(); | |
| 5280 | - eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener); | |
| 5281 | 5323 | restoreOriginalDimensions(dimensions); |
| 5282 | 5324 | extension_settings.sd.seed = extension_settings.sd.original_seed; |
| 5283 | 5325 | delete extension_settings.sd.original_seed; |
| 5326 | + await loaderHandle.hide(); | |
| 5284 | 5327 | } |
| 5285 | 5328 | |
| 5286 | 5329 | if (!result.url) { |
| @@ -5441,7 +5484,7 @@ function registerFunctionTool() { | ||
| 5441 | 5484 | }); |
| 5442 | 5485 | } |
| 5443 | 5486 | |
| 5444 | 5487 | jQuery(export async function init() => { |
| 5445 | 5488 | await addSDGenButtons(); |
| 5446 | 5489 | |
| 5447 | 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 | 5893 | extension_settings.sd.google_duration = Number($(this).val()); |
| 5851 | 5894 | saveSettingsDebounced(); |
| 5852 | 5895 | }); |
| 5896 | + $('#sd_models_refresh').on('click', async () => { | |
| 5897 | + await loadModels(); | |
| 5898 | + }); | |
| 5853 | 5899 | $('#sd_electronhub_quality').on('change', function () { |
| 5854 | 5900 | extension_settings.sd.electronhub_quality = String($(this).val()); |
| 5855 | 5901 | saveSettingsDebounced(); |
| @@ -5893,6 +5939,7 @@ jQuery(async () => { | ||
| 5893 | 5939 | [sources.aimlapi]: SECRET_KEYS.AIMLAPI, |
| 5894 | 5940 | [sources.comfy]: SECRET_KEYS.COMFY_RUNPOD, |
| 5895 | 5941 | [sources.pollinations]: SECRET_KEYS.POLLINATIONS, |
| 5942 | + [sources.workersai]: SECRET_KEYS.WORKERS_AI, | |
| 5896 | 5943 | }; |
| 5897 | 5944 | const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key); |
| 5898 | 5945 | if (!shouldReloadOptions) { |
| @@ -5948,4 +5995,4 @@ jQuery(async () => { | ||
| 5948 | 5995 | t`Character's negative Image Generation prompt prefix`, |
| 5949 | 5996 | ); |
| 5950 | 5997 | } |
| 5951 | 5998 | }); |
| @@ -10,5 +10,8 @@ | ||
| 10 | 10 | "css": "style.css", |
| 11 | 11 | "author": "Cohee#1207", |
| 12 | 12 | "version": "1.0.0", |
| 13 | 13 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 14 | + "hooks": { | |
| 15 | + "activate": "init" | |
| 16 | + } | |
| 14 | 17 | } |
| @@ -40,10 +40,11 @@ | ||
| 40 | 40 | <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span> |
| 41 | 41 | </label> |
| 42 | 42 | <label for="sd_source" data-i18n="Source">Source</label> |
| 43 | 43 | <select id="sd_source" class="text_pole"> |
| 44 | 44 | <option value="aimlapi">AI/ML API</option> |
| 45 | 45 | <option value="bfl">BFL (Black Forest Labs)</option> |
| 46 | 46 | <option value="chutes">Chutes</option> |
| 47 | + <option value="workersai">Cloudflare Workers AI</option> | |
| 47 | 48 | <option value="comfy">ComfyUI</option> |
| 48 | 49 | <option value="drawthings">DrawThings HTTP API</option> |
| 49 | 50 | <option value="electronhub">Electron Hub</option> |
| @@ -123,7 +124,7 @@ | ||
| 123 | 124 | <div class="flex-container" id="sd_electronhub_quality_row"> |
| 124 | 125 | <div class="flex1"> |
| 125 | 126 | <label for="sd_electronhub_quality" data-i18n="Image Quality">Image Quality</label> |
| 126 | 127 | <select id="sd_electronhub_quality" class="text_pole"></select> |
| 127 | 128 | </div> |
| 128 | 129 | </div> |
| 129 | 130 | </div> |
| @@ -191,14 +192,14 @@ | ||
| 191 | 192 | <div class="flex-container"> |
| 192 | 193 | <div data-sd-model="dall-e-3" class="flex1"> |
| 193 | 194 | <label for="sd_openai_style" data-i18n="Image Style">Image Style</label> |
| 194 | 195 | <select id="sd_openai_style" class="text_pole"> |
| 195 | 196 | <option value="vivid">Vivid</option> |
| 196 | 197 | <option value="natural">Natural</option> |
| 197 | 198 | </select> |
| 198 | 199 | </div> |
| 199 | 200 | <div data-sd-model="gpt-image" class="flex1"> |
| 200 | 201 | <label for="sd_openai_quality_gpt" data-i18n="Image Quality">Image Quality</label> |
| 201 | 202 | <select id="sd_openai_quality_gpt" class="text_pole"> |
| 202 | 203 | <option value="auto" data-i18n="Auto">Auto</option> |
| 203 | 204 | <option value="low" data-i18n="Low">Low</option> |
| 204 | 205 | <option value="medium" data-i18n="Medium">Medium</option> |
| @@ -207,7 +208,7 @@ | ||
| 207 | 208 | </div> |
| 208 | 209 | <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1"> |
| 209 | 210 | <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label> |
| 210 | 211 | <select id="sd_openai_quality" class="text_pole"> |
| 211 | 212 | <option value="standard" data-i18n="Standard">Standard</option> |
| 212 | 213 | <option value="hd" data-i18n="HD">HD</option> |
| 213 | 214 | </select> |
| @@ -216,7 +217,7 @@ | ||
| 216 | 217 | <div data-sd-model="sora-2,sora-2-pro" class="flex-container"> |
| 217 | 218 | <div class="flex1"> |
| 218 | 219 | <label for="sd_openai_duration" data-i18n="Duration">Duration</label> |
| 219 | 220 | <select id="sd_openai_duration" class="text_pole"> |
| 220 | 221 | <option value="4" data-i18n="Short (4 seconds)">Short (4 seconds)</option> |
| 221 | 222 | <option value="8" data-i18n="Medium (8 seconds)">Medium (8 seconds)</option> |
| 222 | 223 | <option value="12" data-i18n="Long (16 seconds)">Long (12 seconds)</option> |
| @@ -226,7 +227,7 @@ | ||
| 226 | 227 | </div> |
| 227 | 228 | <div data-sd-source="comfy"> |
| 228 | 229 | <label for="sd_comfy_type">Server Type</label> |
| 229 | 230 | <select id="sd_comfy_type" class="text_pole"> |
| 230 | 231 | <option value="standard">Standard Server</option> |
| 231 | 232 | <option value="runpod_serverless">RunPod Serverless Endpoint</option> |
| 232 | 233 | </select> |
| @@ -318,7 +319,7 @@ | ||
| 318 | 319 | <div class="flex-container"> |
| 319 | 320 | <div class="flex1"> |
| 320 | 321 | <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label> |
| 321 | 322 | <select id="sd_stability_style_preset" class="text_pole"> |
| 322 | 323 | <option value="anime">Anime</option> |
| 323 | 324 | <option value="3d-model">3D Model</option> |
| 324 | 325 | <option value="analog-film">Analog Film</option> |
| @@ -375,6 +376,20 @@ | ||
| 375 | 376 | </div> |
| 376 | 377 | </div> |
| 377 | 378 | |
| 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 | 393 | <div data-sd-source="google"> |
| 379 | 394 | <div class="flex-container"> |
| 380 | 395 | <div class="flex1"> |
| @@ -394,7 +409,7 @@ | ||
| 394 | 409 | </label> |
| 395 | 410 | <div class="flex1"> |
| 396 | 411 | <label for="sd_google_duration" data-i18n="Duration (Veo)">Duration (Veo)</label> |
| 397 | 412 | <select id="sd_google_duration" class="text_pole"> |
| 398 | 413 | <option value="4">Short (4 seconds)</option> |
| 399 | 414 | <option value="6">Medium (6 seconds)</option> |
| 400 | 415 | <option value="8">Long (8 seconds)</option> |
| @@ -405,37 +420,42 @@ | ||
| 405 | 420 | |
| 406 | 421 | <div class="flex-container"> |
| 407 | 422 | <div class="flex1"> |
| 408 | 423 | <label for="sd_model" data-i18nclass="Modelflex-container justifySpaceBetween">Model</label> |
| 409 | 424 | <selectspan iddata-i18n="sd_modelModel">Model</selectspan> |
| 425 | + <div id="sd_models_refresh" class="right_menu_button margin0 padding0" title="Refresh model list" data-i18n="[title]Refresh model list"> | |
| 426 | + <i class="fa-solid fa-sync"></i> | |
| 427 | + </div> | |
| 428 | + </label> | |
| 429 | + <select id="sd_model" class="text_pole"></select> | |
| 410 | 430 | </div> |
| 411 | 431 | |
| 412 | 432 | <div class="flex1" data-sd-source="comfy,auto"> |
| 413 | 433 | <label for="sd_vae">VAE</label> |
| 414 | 434 | <select id="sd_vae" class="text_pole"></select> |
| 415 | 435 | </div> |
| 416 | 436 | </div> |
| 417 | 437 | |
| 418 | 438 | <div class="flex-container"> |
| 419 | 439 | <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp"> |
| 420 | 440 | <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label> |
| 421 | 441 | <select id="sd_sampler" class="text_pole"></select> |
| 422 | 442 | </div> |
| 423 | 443 | |
| 424 | 444 | <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp"> |
| 425 | 445 | <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label> |
| 426 | 446 | <select id="sd_scheduler" class="text_pole"></select> |
| 427 | 447 | </div> |
| 428 | 448 | </div> |
| 429 | 449 | |
| 430 | 450 | <div class="flex-container"> |
| 431 | 451 | <div class="flex1"> |
| 432 | 452 | <label for="sd_resolution" data-i18n="Resolution">Resolution</label> |
| 433 | 453 | <select id="sd_resolution" class="text_pole"><!-- Populated in JS --></select> |
| 434 | 454 | </div> |
| 435 | 455 | |
| 436 | 456 | <div class="flex1" data-sd-source="auto,vlad,drawthings"> |
| 437 | 457 | <label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label> |
| 438 | 458 | <select id="sd_hr_upscaler" class="text_pole"></select> |
| 439 | 459 | </div> |
| 440 | 460 | </div> |
| 441 | 461 | |
| @@ -1,7 +1,3 @@ | ||
| 1 | -.sd_settings label:not(.checkbox_label) { | |
| 2 | - display: block; | |
| 3 | -} | |
| 4 | - | |
| 5 | 1 | #sd_dropdown { |
| 6 | 2 | z-index: 30000; |
| 7 | 3 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); |
| @@ -101,7 +101,7 @@ async function doCount() { | ||
| 101 | 101 | return count; |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | -jQuery(() => { | |
| 104 | +export function init() { | |
| 105 | 105 | const buttonHtml = ` |
| 106 | 106 | <div id="token_counter" class="list-group-item flex-container flexGap5"> |
| 107 | 107 | <div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>` + |
| @@ -115,4 +115,4 @@ jQuery(() => { | ||
| 115 | 115 | returns: 'number of tokens', |
| 116 | 116 | helpString: 'Counts the number of tokens in the current chat.', |
| 117 | 117 | })); |
| 118 | 118 | }); |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "Cohee#1207", |
| 9 | 9 | "version": "1.0.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -707,7 +707,7 @@ const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayTe | ||
| 707 | 707 | |
| 708 | 708 | globalThis.translate = translate; |
| 709 | 709 | |
| 710 | 710 | jQuery(export async function init() => { |
| 711 | 711 | const html = await renderExtensionTemplateAsync('translate', 'index'); |
| 712 | 712 | const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons'); |
| 713 | 713 | |
| @@ -801,4 +801,4 @@ jQuery(async () => { | ||
| 801 | 801 | }, |
| 802 | 802 | returns: ARGUMENT_TYPE.STRING, |
| 803 | 803 | })); |
| 804 | 804 | }); |
| @@ -7,5 +7,8 @@ | ||
| 7 | 7 | "css": "style.css", |
| 8 | 8 | "author": "Cohee#1207", |
| 9 | 9 | "version": "1.0.0", |
| 10 | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | + "hooks": { | |
| 12 | + "activate": "init" | |
| 13 | + } | |
| 11 | 14 | } |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js'; |
| 2 | 2 | import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | 3 | import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js'; |
| 4 | +import { accountStorage } from '../../util/AccountStorage.js'; | |
| 4 | 5 | import { EdgeTtsProvider } from './edge.js'; |
| 5 | 6 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; |
| 6 | 7 | import { SileroTtsProvider } from './silerotts.js'; |
| @@ -165,7 +166,7 @@ async function onNarrateOneMessage() { | ||
| 165 | 166 | } |
| 166 | 167 | |
| 167 | 168 | resetTtsPlayback(); |
| 168 | 169 | processAndQueueTtsMessage(message, Number(id), { manual: true }); |
| 169 | 170 | moduleWorker(); |
| 170 | 171 | } |
| 171 | 172 | |
| @@ -186,13 +187,20 @@ async function onNarrateText(args, text) { | ||
| 186 | 187 | ? voiceMap[DEFAULT_VOICE_MARKER] |
| 187 | 188 | : voiceMap[name]; |
| 188 | 189 | |
| 189 | 190 | if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) { |
| 191 | + toastr.info(`TTS voice for ${name} is disabled.`); | |
| 192 | + await initVoiceMap(false); | |
| 193 | + return; | |
| 194 | + } | |
| 195 | + | |
| 196 | + if (!voiceMapEntry) { | |
| 190 | 197 | toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`); |
| 198 | + await initVoiceMap(false); | |
| 191 | 199 | return; |
| 192 | 200 | } |
| 193 | 201 | |
| 194 | 202 | resetTtsPlayback(); |
| 195 | 203 | processAndQueueTtsMessage({ mes: text, name: name }, null, { manual: true }); |
| 196 | 204 | await moduleWorker(); |
| 197 | 205 | |
| 198 | 206 | // Return back to the chat voices |
| @@ -245,7 +253,7 @@ function isTtsProcessing() { | ||
| 245 | 253 | } |
| 246 | 254 | |
| 247 | 255 | /** |
| 248 | 256 | * @typedef {ChatMessage & { id?: number, manual?: boolean, segmentText?: string, segmentType?: string }} TtsMessage |
| 249 | 257 | */ |
| 250 | 258 | |
| 251 | 259 | /** |
| @@ -253,12 +261,15 @@ function isTtsProcessing() { | ||
| 253 | 261 | * (if enabled) and adds each part to the TTS job queue. |
| 254 | 262 | * @param {ChatMessage} message - The message object to be processed. |
| 255 | 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 | 266 | * @returns {void} |
| 257 | 267 | */ |
| 258 | 268 | function processAndQueueTtsMessage(message, messageId = null, { manual = false } = {}) { |
| 259 | 269 | /** @type {TtsMessage} */ |
| 260 | 270 | const clone = structuredClone(message); |
| 261 | 271 | clone.id = messageId ?? null; |
| 272 | + clone.manual = manual ?? false; | |
| 262 | 273 | |
| 263 | 274 | if (!extension_settings.tts.narrate_by_paragraphs) { |
| 264 | 275 | ttsJobQueue.push(clone); |
| @@ -408,9 +419,10 @@ function onAudioControlClicked() { | ||
| 408 | 419 | // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful |
| 409 | 420 | if (!audioElement.paused || isTtsProcessing()) { |
| 410 | 421 | resetTtsPlayback(); |
| 411 | - } else { | |
| 422 | + } else if (context?.chat?.length > 0) { | |
| 412 | 423 | // Default play behavior if not processing or playing is to play the last message. |
| 413 | 424 | processAndQueueTtsMessage(context.chat[const id = context.chat.length - 1]); |
| 425 | + processAndQueueTtsMessage(context.chat[id], id, { manual: true }); | |
| 414 | 426 | } |
| 415 | 427 | updateUiAudioPlayState(); |
| 416 | 428 | } |
| @@ -481,8 +493,10 @@ async function processAudioJobQueue() { | ||
| 481 | 493 | // TTS Control // |
| 482 | 494 | //################// |
| 483 | 495 | |
| 496 | +/** @type {TtsMessage[]} */ | |
| 484 | 497 | const ttsJobQueue = []; |
| 485 | -let currentTtsJob; // Null if nothing is currently being processed | |
| 498 | +/** @type {TtsMessage|null} */ | |
| 499 | +let currentTtsJob = null; // Null if nothing is currently being processed | |
| 486 | 500 | |
| 487 | 501 | function completeTtsJob() { |
| 488 | 502 | console.info(`Current TTS job for ${currentTtsJob?.name} completed.`); |
| @@ -621,7 +635,18 @@ async function processTtsQueue() { | ||
| 621 | 635 | |
| 622 | 636 | const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey]; |
| 623 | 637 | |
| 624 | 638 | if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) { |
| 639 | + const storageKey = `tts_disabled_warned_${char}`; | |
| 640 | + if (!accountStorage.getItem(storageKey) || currentTtsJob.manual) { | |
| 641 | + accountStorage.setItem(storageKey, 'true'); | |
| 642 | + toastr.info(`TTS voice for ${char} is disabled.`); | |
| 643 | + } | |
| 644 | + currentTtsJob = null; | |
| 645 | + setTimeout(() => wrapper.update(), 0); | |
| 646 | + return; | |
| 647 | + } | |
| 648 | + | |
| 649 | + if (!voiceMapEntry) { | |
| 625 | 650 | throw `${char} not in voicemap. Configure character in extension settings voice map`; |
| 626 | 651 | } |
| 627 | 652 | |
| @@ -649,7 +674,6 @@ async function processTtsQueue() { | ||
| 649 | 674 | text = substituteParams(text); |
| 650 | 675 | |
| 651 | 676 | if (extension_settings.tts.skip_codeblocks) { |
| 652 | - text = text.replace(/^\s{4}.*$/gm, '').trim(); | |
| 653 | 677 | text = text.replace(/```.*?```/gs, '').trim(); |
| 654 | 678 | text = text.replace(/~~~.*?~~~/gs, '').trim(); |
| 655 | 679 | } |
| @@ -724,6 +748,7 @@ async function processTtsQueue() { | ||
| 724 | 748 | mes: currentTtsJob.mes, |
| 725 | 749 | extra: currentTtsJob.extra, |
| 726 | 750 | id: currentTtsJob.id, |
| 751 | + manual: currentTtsJob.manual, | |
| 727 | 752 | }; |
| 728 | 753 | ttsJobQueue.unshift(segmentJob); |
| 729 | 754 | } |
| @@ -824,7 +849,7 @@ async function playFullConversation() { | ||
| 824 | 849 | |
| 825 | 850 | context.chat.forEach((msg, i) => { |
| 826 | 851 | if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') { |
| 827 | 852 | processAndQueueTtsMessage(msg, i, { manual: false }); |
| 828 | 853 | } |
| 829 | 854 | }); |
| 830 | 855 | |
| @@ -1164,7 +1189,7 @@ async function onMessageEvent(messageId, lastCharIndex) { | ||
| 1164 | 1189 | message.id = messageId; |
| 1165 | 1190 | ttsJobQueue.push(message); |
| 1166 | 1191 | } else { |
| 1167 | 1192 | processAndQueueTtsMessage(message, messageId, { manual: false }); |
| 1168 | 1193 | } |
| 1169 | 1194 | } |
| 1170 | 1195 | |
| @@ -1506,7 +1531,7 @@ async function initVoiceMapInternal(unrestricted) { | ||
| 1506 | 1531 | updateVoiceMap(); |
| 1507 | 1532 | } |
| 1508 | 1533 | |
| 1509 | 1534 | jQuery(export async function init() { |
| 1510 | 1535 | async function addExtensionControls() { |
| 1511 | 1536 | const settingsHtml = $(await renderExtensionTemplateAsync('tts', 'settings')); |
| 1512 | 1537 | $('#tts_container').append(settingsHtml); |
| @@ -1594,4 +1619,4 @@ jQuery(async function () { | ||
| 1594 | 1619 | })); |
| 1595 | 1620 | |
| 1596 | 1621 | document.body.appendChild(audioElement); |
| 1597 | 1622 | }); |
| @@ -11,5 +11,8 @@ | ||
| 11 | 11 | "css": "style.css", |
| 12 | 12 | "author": "Ouoertheo#7264", |
| 13 | 13 | "version": "1.0.0", |
| 14 | 14 | "homePage": "None", |
| 15 | + "hooks": { | |
| 16 | + "activate": "init" | |
| 17 | + } | |
| 15 | 18 | } |
| @@ -44,6 +44,7 @@ import { oai_settings } from '../../openai.js'; | ||
| 44 | 44 | * @property {string} text - The hashed message text |
| 45 | 45 | * @property {number} hash - The hash used as the vector key |
| 46 | 46 | * @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 | */ |
| 48 | 49 | |
| 49 | 50 | const MODULE_NAME = 'vectors'; |
| @@ -77,10 +78,13 @@ const settings = { | ||
| 77 | 78 | summarize_sent: false, |
| 78 | 79 | summary_source: 'main', |
| 79 | 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 | 83 | force_chunk_delimiter: '', |
| 81 | 84 | |
| 82 | 85 | // For chats |
| 83 | 86 | enabled_chats: false, |
| 87 | + keep_hidden: false, | |
| 84 | 88 | template: 'Past events:\n{{text}}', |
| 85 | 89 | depth: 2, |
| 86 | 90 | position: extension_prompt_types.IN_PROMPT, |
| @@ -117,10 +121,77 @@ const settings = { | ||
| 117 | 121 | |
| 118 | 122 | const moduleWorker = new ModuleWorkerWrapper(synchronizeChat); |
| 119 | 123 | const webllmProvider = new WebLlmVectorProvider(); |
| 124 | +/** | |
| 125 | + * Cache for storing summaries of messages by their hash. | |
| 126 | + * @type {Map<number, string>} | |
| 127 | + */ | |
| 120 | 128 | const cachedSummaries = new Map(); |
| 129 | +/** | |
| 130 | + * Hashes skipped this Vectorize All session (summary or embed failure). Cleared on next Vectorize All click. | |
| 131 | + * @type {Set<number>} | |
| 132 | + */ | |
| 133 | +const skippedHashes = new Set(); | |
| 134 | +/** | |
| 135 | + * Error causes treated as fatal — abort Vectorize All rather than skip. | |
| 136 | + * @type {Set<string>} | |
| 137 | + */ | |
| 138 | +const FATAL_CAUSES = new Set(['account_id_missing', 'api_key_missing', 'api_url_missing', 'api_model_missing', 'extras_module_missing', 'webllm_not_supported', 'summary_endpoint_invalid']); | |
| 121 | 139 | const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp']; |
| 122 | 140 | |
| 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>} */ | |
| 153 | +const remoteEmbeddingEndpoints = { | |
| 154 | + chutes: { | |
| 155 | + url: '/api/openai/chutes/models/embedding', | |
| 156 | + settingsKey: 'chutes_model', | |
| 157 | + selectId: 'vectors_chutes_model', | |
| 158 | + valueProperty: 'slug', | |
| 159 | + textProperty: 'name', | |
| 160 | + }, | |
| 161 | + nanogpt: { | |
| 162 | + url: '/api/openai/nanogpt/models/embedding', | |
| 163 | + settingsKey: 'nanogpt_model', | |
| 164 | + selectId: 'vectors_nanogpt_model', | |
| 165 | + textProperty: 'name', | |
| 166 | + }, | |
| 167 | + electronhub: { | |
| 168 | + url: '/api/openai/electronhub/models', | |
| 169 | + settingsKey: 'electronhub_model', | |
| 170 | + selectId: 'vectors_electronhub_model', | |
| 171 | + textProperty: 'name', | |
| 172 | + filter: models => models.filter(m => Array.isArray(m?.endpoints) && m.endpoints.includes('/v1/embeddings')), | |
| 173 | + }, | |
| 174 | + openrouter: { | |
| 175 | + url: '/api/openrouter/models/embedding', | |
| 176 | + settingsKey: 'openrouter_model', | |
| 177 | + selectId: 'vectors_openrouter_model', | |
| 178 | + textProperty: 'name', | |
| 179 | + }, | |
| 180 | + siliconflow: { | |
| 181 | + url: '/api/openai/siliconflow/models/embedding', | |
| 182 | + settingsKey: 'siliconflow_model', | |
| 183 | + selectId: 'vectors_siliconflow_model', | |
| 184 | + getBody: () => ({ siliconflow_endpoint: oai_settings.siliconflow_endpoint }), | |
| 185 | + }, | |
| 186 | + workers_ai: { | |
| 187 | + url: '/api/openai/workers-ai/models/embedding', | |
| 188 | + settingsKey: 'workers_ai_model', | |
| 189 | + selectId: 'vectors_workers_ai_model', | |
| 190 | + getBody: () => ({ workers_ai_account_id: oai_settings.workers_ai_account_id }), | |
| 191 | + }, | |
| 192 | +}; | |
| 193 | + | |
| 194 | +/** | |
| 124 | 195 | * Gets the Collection ID for a file embedded in the chat. |
| 125 | 196 | * @param {string} fileUrl URL of the file |
| 126 | 197 | * @returns {string} Collection ID |
| @@ -145,10 +216,12 @@ async function onVectorizeAllClick() { | ||
| 145 | 216 | // Clear all cached summaries to ensure that new ones are created |
| 146 | 217 | // upon request of a full vectorise |
| 147 | 218 | cachedSummaries.clear(); |
| 219 | + skippedHashes.clear(); | |
| 148 | 220 | |
| 149 | 221 | const batchSize = getBatchSize(); |
| 150 | 222 | const elapsedLog = []; |
| 151 | 223 | let finished = false; |
| 224 | + let initialPending = null; // total items pending at the start of this run — set on first sync return | |
| 152 | 225 | $('#vectorize_progress').show(); |
| 153 | 226 | $('#vectorize_progress_percent').text('0'); |
| 154 | 227 | $('#vectorize_progress_eta').text('...'); |
| @@ -162,16 +235,27 @@ async function onVectorizeAllClick() { | ||
| 162 | 235 | const startTime = Date.now(); |
| 163 | 236 | const remaining = await synchronizeChat(batchSize); |
| 164 | 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 | 244 | elapsedLog.push(elapsed); |
| 166 | 245 | finished = remaining <= 0; |
| 167 | 246 | |
| 168 | - const total = getContext().chat.length; | |
| 247 | + if (initialPending === null) { | |
| 169 | - const processed = total - remaining; | |
| 248 | + initialPending = Math.max(0, remaining + batchSize); | |
| 170 | - const processedPercent = Math.round((processed / total) * 100); // percentage of the work done | |
| 249 | + } | |
| 250 | + const pending = Math.max(0, remaining); | |
| 251 | + const processed = Math.max(0, initialPending - pending); | |
| 252 | + const processedPercent = initialPending > 0 | |
| 253 | + ? Math.min(100, Math.round((processed / initialPending) * 100)) | |
| 254 | + : 100; | |
| 171 | 255 | const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times |
| 172 | 256 | const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item |
| 173 | 257 | const pace = averageElapsed / batchSize; // time needed to process one item |
| 174 | 258 | const remainingTime = Math.round(pace * remainingpending / 1000); |
| 175 | 259 | |
| 176 | 260 | $('#vectorize_progress_percent').text(processedPercent); |
| 177 | 261 | $('#vectorize_progress_eta').text(remainingTime); |
| @@ -180,6 +264,9 @@ async function onVectorizeAllClick() { | ||
| 180 | 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 | 270 | } catch (error) { |
| 184 | 271 | console.error('Vectors: Failed to vectorize all', error); |
| 185 | 272 | } finally { |
| @@ -250,7 +337,7 @@ async function summarizeExtra(element) { | ||
| 250 | 337 | |
| 251 | 338 | if (apiResult.ok) { |
| 252 | 339 | const data = await apiResult.json(); |
| 253 | 340 | element.text = removeReasoningFromString(data.summary); |
| 254 | 341 | } |
| 255 | 342 | } catch (error) { |
| 256 | 343 | console.log(error); |
| @@ -282,45 +369,70 @@ async function summarizeWebLLM(element) { | ||
| 282 | 369 | } |
| 283 | 370 | |
| 284 | 371 | const messages = [{ role: 'system', content: settings.summary_prompt }, { role: 'user', content: element.text }]; |
| 285 | 372 | element.text = removeReasoningFromString(await generateWebLlmChatPrompt(messages)); |
| 286 | 373 | |
| 287 | 374 | return true; |
| 288 | 375 | } |
| 289 | 376 | |
| 290 | 377 | /** |
| 291 | 378 | * SummarizesRuns messagesone usingsummarization attempt for a single element via the chosen methodendpoint. |
| 292 | 379 | * @param {HashedMessage[]} hashedMessages Array of hashed messageselement |
| 293 | 380 | * @param {string} endpoint Type of endpoint to use |
| 294 | 381 | * @returns {Promise<HashedMessage[]boolean>} SummarizedWhether messagesthe attempt succeeded. |
| 295 | 382 | */ |
| 296 | 383 | async function summarizesummarizeOne(hashedMessageselement, endpoint = 'main') { |
| 297 | - for (const element of hashedMessages) { | |
| 298 | - const cachedSummary = cachedSummaries.get(element.hash); | |
| 299 | - if (!cachedSummary) { | |
| 300 | - let success = true; | |
| 301 | 384 | switch (endpoint) { |
| 302 | 385 | case 'main': |
| 303 | 386 | success = return await summarizeMain(element); |
| 304 | - break; | |
| 305 | 387 | case 'extras': |
| 306 | 388 | success = return await summarizeExtra(element); |
| 307 | - break; | |
| 308 | 389 | case 'webllm': |
| 309 | 390 | success = return await summarizeWebLLM(element); |
| 310 | - break; | |
| 311 | 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 | + */ | |
| 406 | +async function summarize(hashedMessages, endpoint = 'main', { skipOnFailure = false } = {}) { | |
| 407 | + const maxAttempts = Math.max(1, Number(settings.summary_retries) || 1); | |
| 408 | + for (const element of hashedMessages) { | |
| 409 | + const cachedSummary = cachedSummaries.get(element.hash); | |
| 410 | + if (cachedSummary) { | |
| 322 | 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 | 437 | return hashedMessages; |
| 326 | 438 | } |
| @@ -347,21 +459,43 @@ async function synchronizeChat(batchSize = 5) { | ||
| 347 | 459 | return -1; |
| 348 | 460 | } |
| 349 | 461 | |
| 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 | 464 | const hashesInCollection = await getSavedHashes(chatId); |
| 352 | 465 | |
| 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 | 469 | const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x)); |
| 355 | 470 | |
| 471 | + let batch = newVectorItems.slice(0, batchSize); | |
| 472 | + | |
| 356 | 473 | if (settings.summarize) { |
| 357 | 474 | newVectorItemsconst =minLength await= summarizeMath.max(newVectorItems0, Number(settings.summary_sourcesummary_threshold) || 0); |
| 475 | + const toSummarize = minLength > 0 ? batch.filter(x => x.text.length >= minLength) : batch; | |
| 476 | + if (toSummarize.length > 0) { | |
| 477 | + await summarize(toSummarize, settings.summary_source, { skipOnFailure: true }); | |
| 478 | + const failed = toSummarize.filter(x => x.summaryFailed); | |
| 479 | + if (failed.length > 0) { | |
| 480 | + for (const item of failed) skippedHashes.add(item.hash); | |
| 481 | + batch = batch.filter(x => !x.summaryFailed); | |
| 482 | + } | |
| 483 | + } | |
| 358 | 484 | } |
| 359 | 485 | |
| 360 | 486 | if (newVectorItemsbatch.length > 0) { |
| 361 | 487 | const chunkedBatch = splitByChunks(newVectorItems.slice(0, batchSize)batch); |
| 362 | 488 | |
| 363 | 489 | console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batchSizebatch.length}...`); |
| 490 | + try { | |
| 364 | 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 | } |
| 366 | 500 | |
| 367 | 501 | if (deletedHashes.length > 0) { |
| @@ -388,6 +522,12 @@ async function synchronizeChat(batchSize = 5) { | ||
| 388 | 522 | return 'Extras API must provide an "embeddings" module.'; |
| 389 | 523 | case 'webllm_not_supported': |
| 390 | 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 | 531 | default: |
| 392 | 532 | return 'Check server console for more details'; |
| 393 | 533 | } |
| @@ -397,7 +537,7 @@ async function synchronizeChat(batchSize = 5) { | ||
| 397 | 537 | |
| 398 | 538 | const message = getErrorMessage(error.cause); |
| 399 | 539 | toastr.error(message, 'Vectorization failed', { preventDuplicates: true }); |
| 400 | 540 | return -1null; |
| 401 | 541 | } finally { |
| 402 | 542 | syncBlocked = false; |
| 403 | 543 | } |
| @@ -771,7 +911,11 @@ async function getQueryText(chat, initiator) { | ||
| 771 | 911 | .slice(0, settings.query); |
| 772 | 912 | |
| 773 | 913 | if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) { |
| 774 | 914 | hashedMessagesconst =minLength await= summarizeMath.max(hashedMessages0, Number(settings.summary_sourcesummary_threshold) || 0); |
| 915 | + const toSummarize = minLength > 0 ? hashedMessages.filter(x => x.text.length >= minLength) : hashedMessages; | |
| 916 | + if (toSummarize.length > 0) { | |
| 917 | + await summarize(toSummarize, settings.summary_source, { skipOnFailure: true }); | |
| 918 | + } | |
| 775 | 919 | } |
| 776 | 920 | |
| 777 | 921 | const queryText = hashedMessages.map(x => x.text).join('\n'); |
| @@ -842,6 +986,10 @@ function getVectorsRequestBody(args = {}) { | ||
| 842 | 986 | body.model = extension_settings.vectors.siliconflow_model; |
| 843 | 987 | body.siliconflow_endpoint = oai_settings.siliconflow_endpoint; |
| 844 | 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 | 993 | default: |
| 846 | 994 | break; |
| 847 | 995 | } |
| @@ -935,6 +1083,7 @@ function throwIfSourceInvalid() { | ||
| 935 | 1083 | settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] || |
| 936 | 1084 | settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] || |
| 937 | 1085 | settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE] || |
| 1086 | + settings.source === 'workers_ai' && !secret_state[SECRET_KEYS.WORKERS_AI] || | |
| 938 | 1087 | settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) { |
| 939 | 1088 | throw new Error('Vectors: API key missing', { cause: 'api_key_missing' }); |
| 940 | 1089 | } |
| @@ -963,6 +1112,10 @@ function throwIfSourceInvalid() { | ||
| 963 | 1112 | if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) { |
| 964 | 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 | } |
| 967 | 1120 | |
| 968 | 1121 | /** |
| @@ -972,11 +1125,12 @@ function throwIfSourceInvalid() { | ||
| 972 | 1125 | * @returns {Promise<void>} |
| 973 | 1126 | */ |
| 974 | 1127 | async function deleteVectorItems(collectionId, hashes) { |
| 1128 | + const args = await getAdditionalArgs([]); | |
| 975 | 1129 | const response = await fetch('/api/vector/delete', { |
| 976 | 1130 | method: 'POST', |
| 977 | 1131 | headers: getRequestHeaders(), |
| 978 | 1132 | body: JSON.stringify({ |
| 979 | 1133 | ...getVectorsRequestBody(args), |
| 980 | 1134 | collectionId: collectionId, |
| 981 | 1135 | hashes: hashes, |
| 982 | 1136 | source: settings.source, |
| @@ -1154,211 +1308,77 @@ function toggleSettings() { | ||
| 1154 | 1308 | $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp'); |
| 1155 | 1309 | $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai'); |
| 1156 | 1310 | $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow'); |
| 1311 | + $('#workers_ai_vectorsModel').toggle(settings.source === 'workers_ai'); | |
| 1157 | 1312 | $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source)); |
| 1158 | 1313 | switchif (settings.source === 'webllm') { |
| 1159 | - case 'webllm': | |
| 1160 | 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 | - | |
| 1180 | -async function loadChutesModels() { | |
| 1181 | - try { | |
| 1182 | - const response = await fetch('/api/openai/chutes/models/embedding', { | |
| 1183 | - method: 'POST', | |
| 1184 | - headers: getRequestHeaders({ omitContentType: true }), | |
| 1185 | - }); | |
| 1186 | - if (!response.ok) { | |
| 1187 | - throw new Error(`HTTP ${response.status}`); | |
| 1188 | - } | |
| 1189 | - /** @type {Array<any>} */ | |
| 1190 | - const data = await response.json(); | |
| 1191 | - const models = Array.isArray(data) ? data : []; | |
| 1192 | - populateChutesModelSelect(models); | |
| 1193 | - } catch (err) { | |
| 1194 | - console.warn('Chutes models fetch failed', err); | |
| 1195 | - populateChutesModelSelect([]); | |
| 1196 | - } | |
| 1197 | -} | |
| 1198 | - | |
| 1199 | -function populateChutesModelSelect(models) { | |
| 1200 | - const select = $('#vectors_chutes_model'); | |
| 1201 | - select.empty(); | |
| 1202 | - for (const m of models) { | |
| 1203 | - const option = document.createElement('option'); | |
| 1204 | - option.value = m.slug; | |
| 1205 | - option.text = m.name; | |
| 1206 | - select.append(option); | |
| 1207 | - } | |
| 1208 | - if (!settings.chutes_model && models.length) { | |
| 1209 | - settings.chutes_model = models[0].slug; | |
| 1210 | - } | |
| 1211 | - $('#vectors_chutes_model').val(settings.chutes_model); | |
| 1212 | -} | |
| 1213 | - | |
| 1214 | -async function loadNanoGPTModels() { | |
| 1215 | - try { | |
| 1216 | - const response = await fetch('/api/openai/nanogpt/models/embedding', { | |
| 1217 | - method: 'POST', | |
| 1218 | - headers: getRequestHeaders({ omitContentType: true }), | |
| 1219 | - }); | |
| 1220 | - if (!response.ok) { | |
| 1221 | - throw new Error(`HTTP ${response.status}`); | |
| 1222 | - } | |
| 1223 | - /** @type {Array<any>} */ | |
| 1224 | - const data = await response.json(); | |
| 1225 | - const models = Array.isArray(data) ? data : []; | |
| 1226 | - populateNanoGPTModelSelect(models); | |
| 1227 | - } catch (err) { | |
| 1228 | - console.warn('NanoGPT models fetch failed', err); | |
| 1229 | - populateNanoGPTModelSelect([]); | |
| 1230 | - } | |
| 1231 | -} | |
| 1232 | - | |
| 1233 | -function populateNanoGPTModelSelect(models) { | |
| 1234 | - const select = $('#vectors_nanogpt_model'); | |
| 1235 | - select.empty(); | |
| 1236 | - for (const m of models) { | |
| 1237 | - const option = document.createElement('option'); | |
| 1238 | - option.value = m.id; | |
| 1239 | - option.text = m.name || m.id; | |
| 1240 | - select.append(option); | |
| 1241 | - } | |
| 1242 | - if (!settings.nanogpt_model && models.length) { | |
| 1243 | - settings.nanogpt_model = models[0].id; | |
| 1244 | - } | |
| 1245 | - $('#vectors_nanogpt_model').val(settings.nanogpt_model); | |
| 1246 | -} | |
| 1247 | - | |
| 1248 | -async function loadElectronHubModels() { | |
| 1249 | - try { | |
| 1250 | - const response = await fetch('/api/openai/electronhub/models', { | |
| 1251 | - method: 'POST', | |
| 1252 | - headers: getRequestHeaders({ omitContentType: true }), | |
| 1253 | - }); | |
| 1254 | - if (!response.ok) { | |
| 1255 | - throw new Error(`HTTP ${response.status}`); | |
| 1256 | - } | |
| 1257 | - /** @type {Array<any>} */ | |
| 1258 | - const data = await response.json(); | |
| 1259 | - // filter by embeddings endpoint | |
| 1260 | - const models = Array.isArray(data) ? data.filter(m => Array.isArray(m?.endpoints) && m.endpoints.includes('/v1/embeddings')) : []; | |
| 1261 | - populateElectronHubModelSelect(models); | |
| 1262 | - } catch (err) { | |
| 1263 | - console.warn('Electron Hub models fetch failed', err); | |
| 1264 | - populateElectronHubModelSelect([]); | |
| 1265 | 1317 | } |
| 1266 | 1318 | } |
| 1267 | 1319 | |
| 1268 | 1320 | /** |
| 1269 | 1321 | * PopulatesLoads themodels Electronfrom Huba modelremote embedding endpoint and populates the corresponding select element. |
| 1270 | 1322 | * @param {{string} id:source string,- name:The stringsource }[]}key modelsmatching Electrona HubremoteEmbeddingEndpoints modelsentry |
| 1271 | 1323 | */ |
| 1272 | 1324 | async function populateElectronHubModelSelectloadRemoteEmbeddingModels(modelssource) { |
| 1273 | 1325 | const selectconfig = $('#vectors_electronhub_model')remoteEmbeddingEndpoints[source]; |
| 1274 | - select.empty(); | |
| 1326 | + if (!config) { | |
| 1275 | - for (const m of models) { | |
| 1327 | + return; | |
| 1276 | - const option = document.createElement('option'); | |
| 1277 | - option.value = m.id; | |
| 1278 | - option.text = m.name || m.id; | |
| 1279 | - select.append(option); | |
| 1280 | - } | |
| 1281 | - if (!settings.electronhub_model && models.length) { | |
| 1282 | - settings.electronhub_model = models[0].id; | |
| 1283 | - } | |
| 1284 | - $('#vectors_electronhub_model').val(settings.electronhub_model); | |
| 1285 | 1328 | } |
| 1286 | 1329 | |
| 1287 | -async function loadOpenRouterModels() { | |
| 1330 | + const { url, settingsKey, selectId, getBody, filter } = config; | |
| 1288 | - try { | |
| 1331 | + const valueProperty = config.valueProperty || 'id'; | |
| 1289 | - const response = await fetch('/api/openrouter/models/embedding', { | |
| 1332 | + const textProperty = config.textProperty; | |
| 1290 | - method: 'POST', | |
| 1291 | - headers: getRequestHeaders({ omitContentType: true }), | |
| 1292 | - }); | |
| 1293 | - if (!response.ok) { | |
| 1294 | - throw new Error(`HTTP ${response.status}`); | |
| 1295 | - } | |
| 1296 | - /** @type {Array<any>} */ | |
| 1297 | - const data = await response.json(); | |
| 1298 | - const models = Array.isArray(data) ? data : []; | |
| 1299 | - populateOpenRouterModelSelect(models); | |
| 1300 | - } catch (err) { | |
| 1301 | - console.warn('OpenRouter models fetch failed', err); | |
| 1302 | - populateOpenRouterModelSelect([]); | |
| 1303 | - } | |
| 1304 | -} | |
| 1305 | 1333 | |
| 1306 | 1334 | /** |
| 1307 | 1335 | * Populates the OpenRouter model select element with the given models. |
| 1308 | - * @param {{ id: string, name: string }[]} models OpenRouter models | |
| 1336 | + * @param {any[]} models - Array of model objects | |
| 1309 | 1337 | */ |
| 1310 | 1338 | function populateOpenRouterModelSelectpopulateSelect(models) { |
| 1311 | 1339 | const select = $('`#vectors_openrouter_model'${selectId}`); |
| 1312 | 1340 | select.empty(); |
| 1313 | 1341 | for (const m of models) { |
| 1314 | 1342 | const option = document.createElement('option'); |
| 1315 | 1343 | option.value = m.id[valueProperty]; |
| 1316 | 1344 | option.text = textProperty ? (m.name[textProperty] || m.id[valueProperty]) : m[valueProperty]; |
| 1317 | 1345 | select.append(option); |
| 1318 | 1346 | } |
| 1319 | 1347 | if (!settings.openrouter_model[settingsKey] && models.length) { |
| 1320 | 1348 | settings.openrouter_model[settingsKey] = models[0].id[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 | } |
| 1324 | 1354 | |
| 1325 | -async function loadSiliconFlowModels() { | |
| 1326 | 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 | 1360 | method: 'POST', |
| 1329 | 1361 | headers: getRequestHeaders(), |
| 1330 | 1362 | body: JSON.stringify(body || {}), |
| 1331 | - siliconflow_endpoint: oai_settings.siliconflow_endpoint, | |
| 1363 | + }; | |
| 1332 | - }), | |
| 1333 | - }); | |
| 1334 | 1364 | |
| 1365 | + const response = await fetch(url, fetchOptions); | |
| 1335 | 1366 | if (!response.ok) { |
| 1336 | 1367 | throw new Error(`HTTP ${response.status}`); |
| 1337 | 1368 | } |
| 1338 | 1369 | |
| 1339 | 1370 | /** @type {Array<any>} */ |
| 1340 | 1371 | const data = await response.json(); |
| 1341 | 1372 | constlet models = Array.isArray(data) ? data : []; |
| 1342 | - populateSiliconFlowModelSelect(models); | |
| 1373 | + if (filter) { | |
| 1343 | - } catch (err) { | |
| 1374 | + models = filter(models); | |
| 1344 | - console.warn('SiliconFlow models fetch failed', err); | |
| 1345 | - populateSiliconFlowModelSelect([]); | |
| 1346 | - } | |
| 1347 | 1375 | } |
| 1348 | 1376 | |
| 1349 | -function populateSiliconFlowModelSelect(models) { | |
| 1377 | + populateSelect(models); | |
| 1350 | - const select = $('#vectors_siliconflow_model'); | |
| 1378 | + } catch (err) { | |
| 1351 | - select.empty(); | |
| 1379 | + console.warn(`${source} models fetch failed`, err); | |
| 1352 | - for (const m of models) { | |
| 1380 | + populateSelect([]); | |
| 1353 | - const option = document.createElement('option'); | |
| 1354 | - option.value = m.id; | |
| 1355 | - option.text = m.id; | |
| 1356 | - select.append(option); | |
| 1357 | - } | |
| 1358 | - if (!settings.siliconflow_model && models.length) { | |
| 1359 | - settings.siliconflow_model = models[0].id; | |
| 1360 | 1381 | } |
| 1361 | - $('#vectors_siliconflow_model').val(settings.siliconflow_model); | |
| 1362 | 1382 | } |
| 1363 | 1383 | |
| 1364 | 1384 | /** |
| @@ -1496,10 +1516,11 @@ async function onViewStatsClick() { | ||
| 1496 | 1516 | { timeOut: 10000, escapeHtml: false }, |
| 1497 | 1517 | ); |
| 1498 | 1518 | |
| 1519 | + $('#chat .mes.vectorized').removeClass('vectorized'); | |
| 1499 | 1520 | const chat = getContext().chat; |
| 1500 | 1521 | for (const message of chat) { |
| 1501 | 1522 | if (hashesInCollection.includes(getStringHash(substituteParams(message.mes)))) { |
| 1502 | 1523 | const messageElement = $(`#chat .mes[mesid="${chat.indexOf(message)}"]`); |
| 1503 | 1524 | messageElement.addClass('vectorized'); |
| 1504 | 1525 | } |
| 1505 | 1526 | } |
| @@ -1704,7 +1725,7 @@ async function activateWorldInfo(chat) { | ||
| 1704 | 1725 | await eventSource.emit(event_types.WORLDINFO_FORCE_ACTIVATE, activatedEntries); |
| 1705 | 1726 | } |
| 1706 | 1727 | |
| 1707 | 1728 | jQuery(export async function init() => { |
| 1708 | 1729 | if (!extension_settings.vectors) { |
| 1709 | 1730 | extension_settings.vectors = settings; |
| 1710 | 1731 | } |
| @@ -1726,6 +1747,11 @@ jQuery(async () => { | ||
| 1726 | 1747 | saveSettingsDebounced(); |
| 1727 | 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 | 1755 | $('#vectors_enabled_files').prop('checked', settings.enabled_files).on('input', () => { |
| 1730 | 1756 | settings.enabled_files = $('#vectors_enabled_files').prop('checked'); |
| 1731 | 1757 | Object.assign(extension_settings.vectors, settings); |
| @@ -1778,6 +1804,11 @@ jQuery(async () => { | ||
| 1778 | 1804 | Object.assign(extension_settings.vectors, settings); |
| 1779 | 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 | 1812 | $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => { |
| 1782 | 1813 | settings.openrouter_model = String($('#vectors_openrouter_model').val()); |
| 1783 | 1814 | Object.assign(extension_settings.vectors, settings); |
| @@ -1888,6 +1919,20 @@ jQuery(async () => { | ||
| 1888 | 1919 | saveSettingsDebounced(); |
| 1889 | 1920 | }); |
| 1890 | 1921 | |
| 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 | 1936 | $('#vectors_message_chunk_size').val(settings.message_chunk_size).on('input', () => { |
| 1892 | 1937 | settings.message_chunk_size = Number($('#vectors_message_chunk_size').val()); |
| 1893 | 1938 | Object.assign(extension_settings.vectors, settings); |
| @@ -2310,4 +2355,4 @@ jQuery(async () => { | ||
| 2310 | 2355 | } |
| 2311 | 2356 | await purgeAllVectorIndexes(); |
| 2312 | 2357 | }); |
| 2313 | 2358 | }); |
| @@ -10,5 +10,8 @@ | ||
| 10 | 10 | "css": "style.css", |
| 11 | 11 | "author": "Cohee#1207", |
| 12 | 12 | "version": "1.0.0", |
| 13 | 13 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 14 | + "hooks": { | |
| 15 | + "activate": "init" | |
| 16 | + } | |
| 14 | 17 | } |
| @@ -11,6 +11,7 @@ | ||
| 11 | 11 | </label> |
| 12 | 12 | <select id="vectors_source" class="text_pole"> |
| 13 | 13 | <option value="chutes">Chutes</option> |
| 14 | + <option value="workers_ai">Cloudflare Workers AI</option> | |
| 14 | 15 | <option value="cohere">Cohere</option> |
| 15 | 16 | <option value="electronhub">Electron Hub</option> |
| 16 | 17 | <option value="extras">Extras (deprecated)</option> |
| @@ -205,6 +206,16 @@ | ||
| 205 | 206 | </i> |
| 206 | 207 | </div> |
| 207 | 208 | |
| 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 | 219 | <div class="flex-container marginTopBot5"> |
| 209 | 220 | <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance."> |
| 210 | 221 | <label for="vectors_query"> |
| @@ -408,6 +419,10 @@ | ||
| 408 | 419 | <hr> |
| 409 | 420 | |
| 410 | 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 | 426 | <div id="vectors_advanced_settings"> |
| 412 | 427 | <label for="vectors_template" data-i18n="Injection Template"> |
| 413 | 428 | Injection Template |
| @@ -478,6 +493,16 @@ | ||
| 478 | 493 | <label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label> |
| 479 | 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 | 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 | 506 | </div> |
| 482 | 507 | </div> |
| 483 | 508 | <small data-i18n="Old messages are vectorized gradually as you chat. To process all previous messages, click the button below."> |
| @@ -289,6 +289,16 @@ export class FilterHelper { | ||
| 289 | 289 | return this.filterDataByState(data, state, isFolder); |
| 290 | 290 | } |
| 291 | 291 | |
| 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 | 302 | filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) { |
| 293 | 303 | if (isFilterState(state, FILTER_STATES.SELECTED)) { |
| 294 | 304 | return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag')); |
| @@ -414,7 +424,7 @@ export class FilterHelper { | ||
| 414 | 424 | typeScores.set(uid, score); |
| 415 | 425 | } |
| 416 | 426 | this.scoreCache.set(type, typeScores); |
| 417 | 427 | console.debug('search scores chachedcached', type, typeScores); |
| 418 | 428 | } |
| 419 | 429 | |
| 420 | 430 | /** |
| @@ -139,7 +139,8 @@ async function getLocaleData(language) { | ||
| 139 | 139 | function findLang(language) { |
| 140 | 140 | const supportedLang = langs.find(x => x.lang === language); |
| 141 | 141 | |
| 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 | 144 | console.warn(`Unsupported language: ${language}`); |
| 144 | 145 | } |
| 145 | 146 | return supportedLang; |
| @@ -597,7 +597,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, | ||
| 597 | 597 | function getSequence() { |
| 598 | 598 | // User impersonation prompt |
| 599 | 599 | if (isImpersonate) { |
| 600 | 600 | return instruct.last_input_sequence || instruct.input_sequence; |
| 601 | 601 | } |
| 602 | 602 | |
| 603 | 603 | // Neutral / system / quiet prompt |
| @@ -31,6 +31,8 @@ export async function loadItemizedPrompts(chatId) { | ||
| 31 | 31 | if (!itemizedPrompts) { |
| 32 | 32 | itemizedPrompts = []; |
| 33 | 33 | } |
| 34 | + | |
| 35 | + await eventSource.emit(event_types.ITEMIZED_PROMPTS_LOADED, { chatId: chatId }); | |
| 34 | 36 | } catch { |
| 35 | 37 | console.log('Error loading itemized prompts for chat', chatId); |
| 36 | 38 | itemizedPrompts = []; |
| @@ -48,6 +50,7 @@ export async function saveItemizedPrompts(chatId) { | ||
| 48 | 50 | } |
| 49 | 51 | |
| 50 | 52 | await promptStorage.setItem(chatId, itemizedPrompts); |
| 53 | + await eventSource.emit(event_types.ITEMIZED_PROMPTS_SAVED, { chatId: chatId }); | |
| 51 | 54 | } catch { |
| 52 | 55 | console.log('Error saving itemized prompts for chat', chatId); |
| 53 | 56 | } |
| @@ -84,6 +87,7 @@ export async function deleteItemizedPrompts(chatId) { | ||
| 84 | 87 | } |
| 85 | 88 | |
| 86 | 89 | await promptStorage.removeItem(chatId); |
| 90 | + await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { chatId: chatId, all: false }); | |
| 87 | 91 | } catch { |
| 88 | 92 | console.log('Error deleting itemized prompts for chat', chatId); |
| 89 | 93 | } |
| @@ -96,6 +100,7 @@ export async function clearItemizedPrompts() { | ||
| 96 | 100 | try { |
| 97 | 101 | await promptStorage.clear(); |
| 98 | 102 | itemizedPrompts = []; |
| 103 | + await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { all: true }); | |
| 99 | 104 | } catch { |
| 100 | 105 | console.log('Error clearing itemized prompts'); |
| 101 | 106 | } |
| @@ -202,6 +202,7 @@ export function getKoboldGenerationData(finalPrompt, settings, maxLength, maxCon | ||
| 202 | 202 | mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined, |
| 203 | 203 | use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined, |
| 204 | 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 | 206 | sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined, |
| 206 | 207 | api_server: kai_settings.api_server, |
| 207 | 208 | }; |
| @@ -28,6 +28,7 @@ export function showLoader() { | ||
| 28 | 28 | |
| 29 | 29 | // Create a blocking loader with no toast (matches old behavior) |
| 30 | 30 | legacyLoaderHandle = loader.show({ |
| 31 | + slug: 'legacy-loader', | |
| 31 | 32 | blocking: true, |
| 32 | 33 | toastMode: loader.ToastMode.NONE, |
| 33 | 34 | }); |
| @@ -58,10 +58,11 @@ export class MacrosParser { | ||
| 58 | 58 | * |
| 59 | 59 | * @param {string} method |
| 60 | 60 | * @param {string} replacement |
| 61 | + * @param {IArguments} [methodArgs=null] | |
| 61 | 62 | * @returns {void} |
| 62 | 63 | */ |
| 63 | 64 | static #logDeprecated(method, replacement, methodArgs = null) { |
| 64 | 65 | console.warn(`[DEPRECATED] MacrosParser.${method} is deprecated and will be removed in a future version. Use ${replacement} instead. Arguments:`, (methodArgs ?? 'none')); |
| 65 | 66 | } |
| 66 | 67 | |
| 67 | 68 | /** |
| @@ -155,7 +156,7 @@ export class MacrosParser { | ||
| 155 | 156 | * @returns {string|MacroFunction|undefined} The macro value |
| 156 | 157 | */ |
| 157 | 158 | static get(key) { |
| 158 | 159 | MacrosParser.#logDeprecated('get', 'macros.registry.getMacro (from scripts/macros/macro-system.js)', arguments); |
| 159 | 160 | return MacrosParser.#macros.get(key); |
| 160 | 161 | } |
| 161 | 162 | |
| @@ -165,7 +166,7 @@ export class MacrosParser { | ||
| 165 | 166 | * @returns {boolean} True if the macro is registered, false otherwise |
| 166 | 167 | */ |
| 167 | 168 | static has(key) { |
| 168 | 169 | MacrosParser.#logDeprecated('has', 'macros.registry.hasMacro (from scripts/macros/macro-system.js)', arguments); |
| 169 | 170 | if (power_user.experimental_macro_engine) { |
| 170 | 171 | return macroSystem.registry.hasMacro(key); |
| 171 | 172 | } |
| @@ -180,7 +181,7 @@ export class MacrosParser { | ||
| 180 | 181 | * @param {string} [description] Optional description of the macro |
| 181 | 182 | */ |
| 182 | 183 | static registerMacro(key, value, description = '') { |
| 183 | 184 | MacrosParser.#logDeprecated('registerMacro', 'macros.registry.registerMacro (from scripts/macros/macro-system.js) or substituteParams({ dynamicMacros })', arguments); |
| 184 | 185 | if (typeof key !== 'string') { |
| 185 | 186 | throw new Error('Macro key must be a string'); |
| 186 | 187 | } |
| @@ -223,7 +224,7 @@ export class MacrosParser { | ||
| 223 | 224 | * @param {string} key Macro name (key) |
| 224 | 225 | */ |
| 225 | 226 | static unregisterMacro(key) { |
| 226 | 227 | MacrosParser.#logDeprecated('unregisterMacro', 'macros.registry.unregisterMacro (from scripts/macros/macro-system.js)', arguments); |
| 227 | 228 | if (typeof key !== 'string') { |
| 228 | 229 | throw new Error('Macro key must be a string'); |
| 229 | 230 | } |
| @@ -47,6 +47,7 @@ import { SECRET_KEYS, secret_state, writeSecret } from './secrets.js'; | ||
| 47 | 47 | |
| 48 | 48 | import { getEventSourceStream } from './sse-stream.js'; |
| 49 | 49 | import { |
| 50 | + clamp, | |
| 50 | 51 | createThumbnail, |
| 51 | 52 | delay, |
| 52 | 53 | download, |
| @@ -79,7 +80,7 @@ import { t } from './i18n.js'; | ||
| 79 | 80 | import { ToolManager } from './tool-calling.js'; |
| 80 | 81 | import { accountStorage } from './util/AccountStorage.js'; |
| 81 | 82 | import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js'; |
| 82 | 83 | import { syncNanoGptProvidersForModel, syncOpenRouterProvidersForModel, updateNanoGptProvidersWarning, updateOpenRouterProvidersWarning } from './textgen-models.js'; |
| 83 | 84 | |
| 84 | 85 | export { |
| 85 | 86 | openai_messages_count, |
| @@ -196,6 +197,8 @@ export const chat_completion_sources = { | ||
| 196 | 197 | AZURE_OPENAI: 'azure_openai', |
| 197 | 198 | ZAI: 'zai', |
| 198 | 199 | SILICONFLOW: 'siliconflow', |
| 200 | + WORKERS_AI: 'workers_ai', | |
| 201 | + MINIMAX: 'minimax', | |
| 199 | 202 | }; |
| 200 | 203 | |
| 201 | 204 | const character_names_behavior = { |
| @@ -256,6 +259,7 @@ export const tool_reasoning_modes = { | ||
| 256 | 259 | // Providers that support interleaved reasoning forwarding in tool-call chains. |
| 257 | 260 | const interleaved_reasoning_providers = [ |
| 258 | 261 | chat_completion_sources.OPENROUTER, |
| 262 | + chat_completion_sources.CUSTOM, | |
| 259 | 263 | ]; |
| 260 | 264 | |
| 261 | 265 | export const ZAI_ENDPOINT = { |
| @@ -268,6 +272,11 @@ export const SILICONFLOW_ENDPOINT = { | ||
| 268 | 272 | CN: 'cn', |
| 269 | 273 | }; |
| 270 | 274 | |
| 275 | +export const MINIMAX_ENDPOINT = { | |
| 276 | + GLOBAL: 'global', | |
| 277 | + CN: 'cn', | |
| 278 | +}; | |
| 279 | + | |
| 271 | 280 | const sensitiveFields = [ |
| 272 | 281 | 'reverse_proxy', |
| 273 | 282 | 'proxy_password', |
| @@ -279,6 +288,7 @@ const sensitiveFields = [ | ||
| 279 | 288 | 'vertexai_express_project_id', |
| 280 | 289 | 'azure_base_url', |
| 281 | 290 | 'azure_deployment_name', |
| 291 | + 'workers_ai_account_id', | |
| 282 | 292 | ]; |
| 283 | 293 | |
| 284 | 294 | /** |
| @@ -296,12 +306,12 @@ export const settingsToUpdate = { | ||
| 296 | 306 | min_p: ['#min_p_openai', 'min_p_openai', false, false], |
| 297 | 307 | repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false], |
| 298 | 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 | 311 | openai_model: ['#model_openai_select', 'openai_model', false, true], |
| 300 | 312 | claude_model: ['#model_claude_select', 'claude_model', false, true], |
| 301 | 313 | openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true], |
| 302 | 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 | 315 | openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true], |
| 306 | 316 | openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true], |
| 307 | 317 | openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true], |
| @@ -313,13 +323,14 @@ export const settingsToUpdate = { | ||
| 313 | 323 | perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true], |
| 314 | 324 | groq_model: ['#model_groq_select', 'groq_model', false, true], |
| 315 | 325 | chutes_model: ['#model_chutes_select', 'chutes_model', false, true], |
| 316 | - chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true], | |
| 317 | 326 | siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true], |
| 318 | 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 | 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 | 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 | 334 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true], |
| 324 | 335 | aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true], |
| 325 | 336 | xai_model: ['#model_xai_select', 'xai_model', false, true], |
| @@ -337,6 +348,8 @@ export const settingsToUpdate = { | ||
| 337 | 348 | vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], |
| 338 | 349 | zai_model: ['#model_zai_select', 'zai_model', false, true], |
| 339 | 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 | 353 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], |
| 341 | 354 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], |
| 342 | 355 | names_behavior: ['#names_behavior', 'names_behavior', false, false], |
| @@ -369,6 +382,7 @@ export const settingsToUpdate = { | ||
| 369 | 382 | continue_prefill: ['#continue_prefill', 'continue_prefill', true, false], |
| 370 | 383 | continue_postfix: ['#continue_postfix', 'continue_postfix', false, false], |
| 371 | 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 | 386 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false], |
| 373 | 387 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false], |
| 374 | 388 | verbosity: ['#openai_verbosity', 'verbosity', false, false], |
| @@ -413,6 +427,8 @@ const default_settings = { | ||
| 413 | 427 | group_nudge_prompt: default_group_nudge_prompt, |
| 414 | 428 | scenario_format: default_scenario_format, |
| 415 | 429 | personality_format: default_personality_format, |
| 430 | + sort_models: 'alphabetically', | |
| 431 | + group_models: false, | |
| 416 | 432 | openai_model: 'gpt-4-turbo', |
| 417 | 433 | claude_model: 'claude-sonnet-4-5', |
| 418 | 434 | google_model: 'gemini-2.5-pro', |
| @@ -423,14 +439,15 @@ const default_settings = { | ||
| 423 | 439 | perplexity_model: 'sonar-pro', |
| 424 | 440 | groq_model: 'llama-3.3-70b-versatile', |
| 425 | 441 | chutes_model: 'deepseek-ai/DeepSeek-V3-0324', |
| 426 | - chutes_sort_models: 'alphabetically', | |
| 427 | 442 | siliconflow_model: 'deepseek-ai/DeepSeek-V3', |
| 428 | 443 | siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL, |
| 444 | + minimax_model: 'MiniMax-M2.7', | |
| 445 | + minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL, | |
| 429 | 446 | electronhub_model: 'gpt-4o-mini', |
| 430 | - electronhub_sort_models: 'alphabetically', | |
| 431 | - electronhub_group_models: false, | |
| 432 | 447 | nanogpt_model: 'gpt-4o-mini', |
| 433 | 448 | deepseek_modelnanogpt_provider: 'deepseek-chat', |
| 449 | + nanogpt_payg_override: false, | |
| 450 | + deepseek_model: 'deepseek-v4-flash', | |
| 434 | 451 | aimlapi_model: 'chatgpt-4o-latest', |
| 435 | 452 | xai_model: 'grok-3-beta', |
| 436 | 453 | pollinations_model: 'openai', |
| @@ -439,6 +456,8 @@ const default_settings = { | ||
| 439 | 456 | fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct', |
| 440 | 457 | zai_model: 'glm-4.6', |
| 441 | 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 | 461 | azure_base_url: '', |
| 443 | 462 | azure_deployment_name: '', |
| 444 | 463 | azure_api_version: '2024-02-15-preview', |
| @@ -450,8 +469,6 @@ const default_settings = { | ||
| 450 | 469 | custom_include_headers: '', |
| 451 | 470 | openrouter_model: openrouter_website_model, |
| 452 | 471 | openrouter_use_fallback: false, |
| 453 | - openrouter_group_models: false, | |
| 454 | - openrouter_sort_models: 'alphabetically', | |
| 455 | 472 | openrouter_providers: [], |
| 456 | 473 | openrouter_quantizations: [], |
| 457 | 474 | openrouter_allow_fallbacks: true, |
| @@ -474,6 +491,7 @@ const default_settings = { | ||
| 474 | 491 | bypass_status_check: false, |
| 475 | 492 | continue_prefill: false, |
| 476 | 493 | function_calling: false, |
| 494 | + tool_call_recurse_limit: 5, | |
| 477 | 495 | names_behavior: character_names_behavior.DEFAULT, |
| 478 | 496 | continue_postfix: continue_postfix_types.SPACE, |
| 479 | 497 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| @@ -597,8 +615,10 @@ function setOpenAIMessages(chat) { | ||
| 597 | 615 | const originApi = chat[j]?.extra?.api; |
| 598 | 616 | const originModel = chat[j]?.extra?.model; |
| 599 | 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 | 619 | const reasoningisOtherGroupMember = isSameModelselected_group ?&& String(chat[j]?.extra?.reasoning ?? '')name :!== ''name2; |
| 620 | + const signature = isSameModel && !isOtherGroupMember ? chat[j]?.extra?.reasoning_signature : null; | |
| 621 | + const reasoning = isSameModel && !isOtherGroupMember ? String(chat[j]?.extra?.reasoning ?? '') : ''; | |
| 602 | 622 | |
| 603 | 623 | // Remove reasoning metadata from invocations if the API/model don't match |
| 604 | 624 | if (Array.isArray(invocations) && invocations.length > 0) { |
| @@ -1013,8 +1033,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | ||
| 1013 | 1033 | const clone = structuredClone(invocation); |
| 1014 | 1034 | if (!reasoningIsEligible) { |
| 1015 | 1035 | delete clone.reasoning; |
| 1016 | 1036 | } else if (previousAssistantReasoning && !clone.reasoning) { |
| 1017 | 1037 | // PreferFall currentlyback editableto adjacent assistant-text reasoning basedonly onwhen forwardingthe modeinvocation overhas invocationnone snapshotof its own. |
| 1018 | 1038 | clone.reasoning = previousAssistantReasoning; |
| 1019 | 1039 | } |
| 1020 | 1040 | return clone; |
| @@ -1703,6 +1723,8 @@ export function getChatCompletionModel(settings = null) { | ||
| 1703 | 1723 | return settings.groq_model; |
| 1704 | 1724 | case chat_completion_sources.SILICONFLOW: |
| 1705 | 1725 | return settings.siliconflow_model; |
| 1726 | + case chat_completion_sources.MINIMAX: | |
| 1727 | + return settings.minimax_model; | |
| 1706 | 1728 | case chat_completion_sources.ELECTRONHUB: |
| 1707 | 1729 | return settings.electronhub_model; |
| 1708 | 1730 | case chat_completion_sources.CHUTES: |
| @@ -1727,6 +1749,8 @@ export function getChatCompletionModel(settings = null) { | ||
| 1727 | 1749 | return settings.azure_openai_model; |
| 1728 | 1750 | case chat_completion_sources.ZAI: |
| 1729 | 1751 | return settings.zai_model; |
| 1752 | + case chat_completion_sources.WORKERS_AI: | |
| 1753 | + return settings.workers_ai_model; | |
| 1730 | 1754 | default: |
| 1731 | 1755 | console.error(`Unknown chat completion source: ${source}`); |
| 1732 | 1756 | return ''; |
| @@ -1871,6 +1895,33 @@ function getChutesModelTemplate(option) { | ||
| 1871 | 1895 | `)); |
| 1872 | 1896 | } |
| 1873 | 1897 | |
| 1898 | +function calculateChutesCost() { | |
| 1899 | + if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) { | |
| 1900 | + return; | |
| 1901 | + } | |
| 1902 | + | |
| 1903 | + let cost = 'Unknown'; | |
| 1904 | + const model = model_list.find(x => x.id === oai_settings.chutes_model); | |
| 1905 | + | |
| 1906 | + if (model?.pricing) { | |
| 1907 | + const outputPrice = model.pricing?.output; | |
| 1908 | + const inputPrice = model.pricing?.input; | |
| 1909 | + | |
| 1910 | + if (outputPrice !== undefined && inputPrice !== undefined) { | |
| 1911 | + const outputCost = Number(outputPrice / 1000000); | |
| 1912 | + const inputCost = Number(inputPrice / 1000000); | |
| 1913 | + const outputTokens = oai_settings.openai_max_tokens; | |
| 1914 | + const inputTokens = (oai_settings.openai_max_context - outputTokens); | |
| 1915 | + const totalCost = (outputCost * outputTokens) + (inputCost * inputTokens); | |
| 1916 | + if (!isNaN(totalCost)) { | |
| 1917 | + cost = '$' + totalCost.toFixed(4); | |
| 1918 | + } | |
| 1919 | + } | |
| 1920 | + } | |
| 1921 | + | |
| 1922 | + $('#chutes_max_prompt_cost').text(cost); | |
| 1923 | +} | |
| 1924 | + | |
| 1874 | 1925 | function getNanoGptModelTemplate(option) { |
| 1875 | 1926 | const model = model_list.find(x => x.id === option?.element?.value); |
| 1876 | 1927 | |
| @@ -1880,10 +1931,9 @@ function getNanoGptModelTemplate(option) { | ||
| 1880 | 1931 | |
| 1881 | 1932 | const inputPrice = model.pricing?.prompt; |
| 1882 | 1933 | const outputPrice = model.pricing?.completion; |
| 1883 | - | |
| 1884 | 1934 | let price = 'Unknown'; |
| 1935 | + | |
| 1885 | 1936 | if (inputPrice !== undefined && outputPrice !== undefined) { |
| 1886 | - // Check if both prices are 0 (free model) | |
| 1887 | 1937 | if (inputPrice === 0 && outputPrice === 0) { |
| 1888 | 1938 | price = 'Free'; |
| 1889 | 1939 | } else { |
| @@ -1891,40 +1941,61 @@ function getNanoGptModelTemplate(option) { | ||
| 1891 | 1941 | } |
| 1892 | 1942 | } |
| 1893 | 1943 | |
| 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 | 1975 | const contextLength = model.context_length || 'Unknown'; |
| 1976 | + const modelName = model.name || model.id; | |
| 1895 | 1977 | |
| 1896 | 1978 | return $((` |
| 1897 | 1979 | <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}"> |
| 1898 | 1980 | <strong>${DOMPurify.sanitize(model.idmodelName)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities} |
| 1899 | 1981 | </div> |
| 1900 | 1982 | `)); |
| 1901 | 1983 | } |
| 1902 | 1984 | |
| 1903 | 1985 | function calculateChutesCostgetAimlapiModelTemplate(option) { |
| 1904 | - if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) { | |
| 1986 | + const model = model_list.find(x => x.id === option?.element?.value); | |
| 1905 | - return; | |
| 1906 | - } | |
| 1907 | - | |
| 1908 | - let cost = 'Unknown'; | |
| 1909 | - const model = model_list.find(x => x.id === oai_settings.chutes_model); | |
| 1910 | - | |
| 1911 | - if (model?.pricing) { | |
| 1912 | - const outputPrice = model.pricing?.output; | |
| 1913 | - const inputPrice = model.pricing?.input; | |
| 1914 | 1987 | |
| 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 | } |
| 1926 | 1991 | |
| 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 | } |
| 1929 | 2000 | |
| 1930 | 2001 | function saveModelList(data) { |
| @@ -1932,14 +2003,22 @@ function saveModelList(data) { | ||
| 1932 | 2003 | model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id)); |
| 1933 | 2004 | |
| 1934 | 2005 | if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) { |
| 1935 | 2006 | model_list = openRouterSortBysortModelsBy(model_list, oai_settings.openrouter_sort_modelssort_models, chat_completion_sources.OPENROUTER); |
| 1936 | - | |
| 1937 | 2007 | $('#model_openrouter_select').empty(); |
| 2008 | + $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` })); | |
| 1938 | 2009 | |
| 1939 | 2010 | if (true === oai_settings.openrouter_group_modelsgroup_models) { |
| 1940 | - appendOpenRouterOptions(openRouterGroupByVendor(model_list), oai_settings.openrouter_group_models); | |
| 2011 | + groupModelsByVendor(model_list, chat_completion_sources.OPENROUTER).forEach((models, vendor) => { | |
| 2012 | + const optgroup = $('<optgroup>').attr('label', vendor); | |
| 2013 | + models.forEach((model) => { | |
| 2014 | + optgroup.append($('<option>', { value: model.id, text: model.name })); | |
| 2015 | + }); | |
| 2016 | + $('#model_openrouter_select').append(optgroup); | |
| 2017 | + }); | |
| 1941 | 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 | } |
| 1944 | 2023 | |
| 1945 | 2024 | $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change'); |
| @@ -1979,13 +2058,26 @@ function saveModelList(data) { | ||
| 1979 | 2058 | } |
| 1980 | 2059 | |
| 1981 | 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 | 2063 | $('#model_aimlapi_select').empty(); |
| 1983 | - const chatModels = model_list.filter(m => m.type === 'chat-completion'); | |
| 1984 | 2064 | |
| 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 | + } | |
| 1986 | 2078 | |
| 1987 | 2079 | if (!oai_settings.aimlapi_model && chatModelsmodel_list.length > 0) { |
| 1988 | 2080 | oai_settings.aimlapi_model = chatModelsmodel_list[0].id; |
| 1989 | 2081 | } |
| 1990 | 2082 | |
| 1991 | 2083 | $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change'); |
| @@ -2008,13 +2100,22 @@ function saveModelList(data) { | ||
| 2008 | 2100 | |
| 2009 | 2101 | if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { |
| 2010 | 2102 | model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions')); |
| 2011 | - | |
| 2103 | + model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.ELECTRONHUB); | |
| 2012 | - model_list = electronHubSortBy(model_list, oai_settings.electronhub_sort_models); | |
| 2013 | - | |
| 2014 | 2104 | $('#model_electronhub_select').empty(); |
| 2015 | 2105 | |
| 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 | + } | |
| 2018 | 2119 | |
| 2019 | 2120 | const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model); |
| 2020 | 2121 | if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) { |
| @@ -2026,15 +2127,21 @@ function saveModelList(data) { | ||
| 2026 | 2127 | |
| 2027 | 2128 | if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) { |
| 2028 | 2129 | model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine')); |
| 2029 | - | |
| 2130 | + model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.CHUTES); | |
| 2030 | - model_list = chutesSortBy(model_list, oai_settings.chutes_sort_models); | |
| 2031 | - | |
| 2032 | 2131 | $('#model_chutes_select').empty(); |
| 2033 | 2132 | |
| 2034 | 2133 | forif (const model of model_listoai_settings.group_models) { |
| 2035 | - const option = $('<option>').val(model.id).text(model.id); | |
| 2134 | + groupModelsByVendor(model_list, chat_completion_sources.CHUTES).forEach((models, vendor) => { | |
| 2036 | - option.attr('data-model', JSON.stringify(model)); | |
| 2135 | + const optgroup = $('<optgroup>').attr('label', vendor); | |
| 2037 | - $('#model_chutes_select').append(option); | |
| 2136 | + models.forEach((model) => { | |
| 2137 | + optgroup.append($('<option>', { value: model.id, text: model.id })); | |
| 2138 | + }); | |
| 2139 | + $('#model_chutes_select').append(optgroup); | |
| 2140 | + }); | |
| 2141 | + } else { | |
| 2142 | + model_list.forEach((model) => { | |
| 2143 | + $('#model_chutes_select').append($('<option>', { value: model.id, text: model.id })); | |
| 2144 | + }); | |
| 2038 | 2145 | } |
| 2039 | 2146 | |
| 2040 | 2147 | const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model); |
| @@ -2046,14 +2153,22 @@ function saveModelList(data) { | ||
| 2046 | 2153 | } |
| 2047 | 2154 | |
| 2048 | 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 | 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 | 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 | + } | |
| 2057 | 2172 | |
| 2058 | 2173 | const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model); |
| 2059 | 2174 | if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) { |
| @@ -2066,11 +2181,7 @@ function saveModelList(data) { | ||
| 2066 | 2181 | if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { |
| 2067 | 2182 | $('#model_deepseek_select').empty(); |
| 2068 | 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 | }); |
| 2075 | 2186 | |
| 2076 | 2187 | const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model); |
| @@ -2084,11 +2195,7 @@ function saveModelList(data) { | ||
| 2084 | 2195 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) { |
| 2085 | 2196 | $('#model_pollinations_select').empty(); |
| 2086 | 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 | }); |
| 2093 | 2200 | |
| 2094 | 2201 | const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model); |
| @@ -2193,6 +2300,24 @@ function saveModelList(data) { | ||
| 2193 | 2300 | $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change'); |
| 2194 | 2301 | } |
| 2195 | 2302 | |
| 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 | 2321 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { |
| 2197 | 2322 | $('#model_cometapi_select').empty(); |
| 2198 | 2323 | |
| @@ -2259,181 +2384,134 @@ function saveModelList(data) { | ||
| 2259 | 2384 | } |
| 2260 | 2385 | } |
| 2261 | 2386 | |
| 2262 | -function appendOpenRouterOptions(model_list, groupModels = false, sort = false) { | |
| 2387 | +/** | |
| 2263 | - $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` })); | |
| 2388 | + * Sorts models by the specified property for the given source. | |
| 2264 | - | |
| 2389 | + * @param {object[]} data - Array of model objects | |
| 2265 | - const appendOption = (model, parent = null) => { | |
| 2390 | + * @param {string} property - Sort property ('alphabetically', 'context_length', 'pricing.prompt', 'pricing.completion') | |
| 2266 | - (parent || $('#model_openrouter_select')).append( | |
| 2391 | + * @param {string} source - Chat Completion source (e.g., 'openrouter', 'chutes', 'electronhub', 'nanogpt') | |
| 2267 | - $('<option>', { | |
| 2392 | + * @returns {object[]} Sorted array of model objects | |
| 2268 | - value: model.id, | |
| 2393 | + */ | |
| 2269 | - text: model.name, | |
| 2394 | +function sortModelsBy(data, property, source) { | |
| 2270 | - })); | |
| 2395 | + switch (source) { | |
| 2271 | - }; | |
| 2396 | + case chat_completion_sources.OPENROUTER: | |
| 2272 | - | |
| 2273 | - if (groupModels) { | |
| 2274 | - model_list.forEach((models, vendor) => { | |
| 2275 | - const optgroup = $(`<optgroup label="${vendor}">`); | |
| 2276 | - | |
| 2277 | - models.forEach((model) => { | |
| 2278 | - appendOption(model, optgroup); | |
| 2279 | - }); | |
| 2280 | - | |
| 2281 | - $('#model_openrouter_select').append(optgroup); | |
| 2282 | - }); | |
| 2283 | - } else { | |
| 2284 | - model_list.forEach((model) => { | |
| 2285 | - appendOption(model); | |
| 2286 | - }); | |
| 2287 | - } | |
| 2288 | -} | |
| 2289 | - | |
| 2290 | -const openRouterSortBy = (data, property = 'alphabetically') => { | |
| 2291 | 2397 | return data.sort((a, b) => { |
| 2292 | 2398 | if (property === 'context_length') { |
| 2293 | 2399 | return (b.context_length || 0) - (a.context_length || 0); |
| 2294 | 2400 | } else if (property === 'pricing.input' || property === 'pricing.prompt') { |
| 2295 | 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 | 2404 | } else { |
| 2297 | - // Alphabetically | |
| 2405 | + return a?.name && b?.name ? a.name.localeCompare(b.name) : 0; | |
| 2298 | - return a?.name && b?.name && a.name.localeCompare(b.name); | |
| 2299 | 2406 | } |
| 2300 | 2407 | }); |
| 2301 | -}; | |
| 2408 | + case chat_completion_sources.CHUTES: | |
| 2302 | - | |
| 2303 | -function openRouterGroupByVendor(array) { | |
| 2304 | - return array.reduce((acc, curr) => { | |
| 2305 | - const vendor = curr.id.split('/')[0]; | |
| 2306 | - | |
| 2307 | - if (!acc.has(vendor)) { | |
| 2308 | - acc.set(vendor, []); | |
| 2309 | - } | |
| 2310 | - | |
| 2311 | - acc.get(vendor).push(curr); | |
| 2312 | - | |
| 2313 | - return acc; | |
| 2314 | - }, new Map()); | |
| 2315 | -} | |
| 2316 | - | |
| 2317 | -function chutesSortBy(data, property = 'alphabetically') { | |
| 2318 | 2409 | return data.sort((a, b) => { |
| 2319 | 2410 | if (property === 'context_length') { |
| 2320 | 2411 | return (b.context_length || 0) - (a.context_length || 0); |
| 2321 | 2412 | } else if (property === 'pricing.input' || property === 'pricing.prompt') { |
| 2322 | 2413 | const return aPriceparseFloat(a.pricing?.input =|| 0) - parseFloat(ab.pricing?.input || 0); |
| 2323 | - const bPrice = parseFloat(b.pricing?.input || 0); | |
| 2414 | + } else if (property === 'pricing.output' || property === 'pricing.completion') { | |
| 2324 | - return aPrice - bPrice; | |
| 2415 | + return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0); | |
| 2325 | - } else if (property === 'pricing.output') { | |
| 2326 | - const aPrice = parseFloat(a.pricing?.output || 0); | |
| 2327 | - const bPrice = parseFloat(b.pricing?.output || 0); | |
| 2328 | - return aPrice - bPrice; | |
| 2329 | 2416 | } else { |
| 2330 | 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 | - | |
| 2335 | -function appendElectronHubOptions(model_list, groupModels = false) { | |
| 2336 | - const appendOption = (model, parent = null) => { | |
| 2337 | - (parent || $('#model_electronhub_select')).append( | |
| 2338 | - $('<option>', { | |
| 2339 | - value: model.id, | |
| 2340 | - text: model.name, | |
| 2341 | - })); | |
| 2342 | - }; | |
| 2343 | - | |
| 2344 | - if (groupModels) { | |
| 2345 | - model_list.forEach((models, vendor) => { | |
| 2346 | - const optgroup = $('<optgroup>').attr('label', vendor); | |
| 2347 | - | |
| 2348 | - models.forEach((model) => { | |
| 2349 | - appendOption(model, optgroup); | |
| 2350 | - }); | |
| 2351 | - | |
| 2352 | - $('#model_electronhub_select').append(optgroup); | |
| 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 | 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 | + }); | |
| 2360 | - | |
| 2444 | + case chat_completion_sources.AIMLAPI: | |
| 2361 | -function electronHubSortBy(data, property = 'alphabetically') { | |
| 2362 | 2445 | return data.sort((a, b) => { |
| 2363 | 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 | 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 | } |
| 2374 | 2457 | |
| 2375 | -function electronHubGroupByVendor(array) { | |
| 2458 | +/** | |
| 2459 | + * Groups models by vendor for the given source. If not supported, returns a map with a single entry containing all models. | |
| 2460 | + * @param {object[]} array Array of model objects | |
| 2461 | + * @param {string} source Chat Completion source (e.g., 'openrouter') | |
| 2462 | + * @returns {Map<string, object[]>} Map of vendor to array of models | |
| 2463 | + */ | |
| 2464 | +function groupModelsByVendor(array, source) { | |
| 2465 | + switch (source) { | |
| 2466 | + case chat_completion_sources.OPENROUTER: | |
| 2376 | 2467 | return array.reduce((acc, curr) => { |
| 2377 | 2468 | const vendor = String(curr?.name || curr?.id || 'Other').split(':/')[0].trim() || 'Other'; |
| 2378 | - | |
| 2379 | 2469 | if (!acc.has(vendor)) { |
| 2380 | 2470 | acc.set(vendor, []); |
| 2381 | 2471 | } |
| 2382 | - | |
| 2383 | 2472 | acc.get(vendor).push(curr); |
| 2384 | - | |
| 2385 | 2473 | return acc; |
| 2386 | 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 | } |
| 2388 | - | |
| 2481 | + acc.get(vendor).push(curr); | |
| 2389 | -function aimlapiGroupByVendor(array) { | |
| 2482 | + return acc; | |
| 2483 | + }, new Map()); | |
| 2484 | + case chat_completion_sources.NANOGPT: | |
| 2390 | 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]; | |
| 2392 | - | |
| 2487 | + const vendor = String(vendorPart?.trim()?.toLowerCase() || 'Other'); | |
| 2393 | 2488 | if (!acc.has(vendor)) { |
| 2394 | 2489 | acc.set(vendor, []); |
| 2395 | 2490 | } |
| 2396 | - | |
| 2397 | 2491 | acc.get(vendor).push(curr); |
| 2398 | - | |
| 2399 | 2492 | return acc; |
| 2400 | 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 | } |
| 2402 | - | |
| 2500 | + acc.get(vendor).push(curr); | |
| 2403 | -function appendAimlapiOptions(model_list) { | |
| 2501 | + return acc; | |
| 2404 | - const appendOption = (model, parent = null) => { | |
| 2502 | + }, new Map()); | |
| 2405 | - (parent || $('#model_aimlapi_select')).append( | |
| 2503 | + case chat_completion_sources.AIMLAPI: | |
| 2406 | - $('<option>', { | |
| 2504 | + return array.reduce((acc, curr) => { | |
| 2407 | - value: model.id, | |
| 2505 | + const vendor = curr.info?.developer || 'Other'; | |
| 2408 | - text: model.info?.name || model.name || model.id, | |
| 2506 | + if (!acc.has(vendor)) { | |
| 2409 | - })); | |
| 2507 | + acc.set(vendor, []); | |
| 2410 | - }; | |
| 2411 | - | |
| 2412 | - model_list.forEach((models, vendor) => { | |
| 2413 | - const optgroup = $(`<optgroup label="${vendor}">`); | |
| 2414 | - | |
| 2415 | - models.forEach((model) => { | |
| 2416 | - appendOption(model, optgroup); | |
| 2417 | - }); | |
| 2418 | - | |
| 2419 | - $('#model_aimlapi_select').append(optgroup); | |
| 2420 | - }); | |
| 2421 | 2508 | } |
| 2422 | - | |
| 2509 | + acc.get(vendor).push(curr); | |
| 2423 | -function getAimlapiModelTemplate(option) { | |
| 2510 | + return acc; | |
| 2424 | - const model = model_list.find(x => x.id === option?.element?.value); | |
| 2511 | + }, new Map()); | |
| 2425 | - | |
| 2512 | + default: | |
| 2426 | - if (!option.id || !model) { | |
| 2513 | + return new Map([['', array]]); | |
| 2427 | - return option.text; | |
| 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 | } |
| 2438 | 2516 | |
| 2439 | 2517 | /** |
| @@ -2459,6 +2537,7 @@ function getReasoningEffort(settings = null, model = null) { | ||
| 2459 | 2537 | chat_completion_sources.COMETAPI, |
| 2460 | 2538 | chat_completion_sources.ELECTRONHUB, |
| 2461 | 2539 | chat_completion_sources.CHUTES, |
| 2540 | + chat_completion_sources.DEEPSEEK, | |
| 2462 | 2541 | ]; |
| 2463 | 2542 | |
| 2464 | 2543 | if (!reasoningEffortSources.includes(settings.chat_completion_source)) { |
| @@ -2466,6 +2545,36 @@ function getReasoningEffort(settings = null, model = null) { | ||
| 2466 | 2545 | } |
| 2467 | 2546 | |
| 2468 | 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 | 2578 | switch (settings.reasoning_effort) { |
| 2470 | 2579 | case reasoning_effort_types.auto: |
| 2471 | 2580 | return undefined; |
| @@ -2474,9 +2583,16 @@ function getReasoningEffort(settings = null, model = null) { | ||
| 2474 | 2583 | return 'none'; |
| 2475 | 2584 | } |
| 2476 | 2585 | |
| 2477 | 2586 | returnif ([chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source) && /^gpt-5/.test(model) { |
| 2478 | - ? reasoning_effort_types.min | |
| 2587 | + if (/^gpt-5\.(4|5)/.test(model)) { | |
| 2479 | - : reasoning_effort_types.low; | |
| 2588 | + return 'none'; | |
| 2589 | + } | |
| 2590 | + if (/^gpt-5/.test(model)) { | |
| 2591 | + return reasoning_effort_types.min; | |
| 2592 | + } | |
| 2593 | + } | |
| 2594 | + | |
| 2595 | + return reasoning_effort_types.low; | |
| 2480 | 2596 | case reasoning_effort_types.max: |
| 2481 | 2597 | return reasoning_effort_types.high; |
| 2482 | 2598 | default: |
| @@ -2604,7 +2720,8 @@ export async function createGenerationParameters(settings, model, type, messages | ||
| 2604 | 2720 | ]; |
| 2605 | 2721 | |
| 2606 | 2722 | const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model); |
| 2607 | 2723 | const streamisWorkersAIJsonMode = settings.stream_openai && typechat_completion_source !=== 'quiet'chat_completion_sources.WORKERS_AI && !isO1jsonSchema; |
| 2724 | + const stream = settings.stream_openai && type !== 'quiet' && !isO1 && !isWorkersAIJsonMode; | |
| 2608 | 2725 | |
| 2609 | 2726 | const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue']; |
| 2610 | 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 | 2832 | generate_data.middleout = settings.openrouter_middleout; |
| 2716 | 2833 | } |
| 2717 | 2834 | |
| 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 | 2840 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) { |
| 2719 | 2841 | const stopStringsLimit = 5; |
| 2720 | 2842 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -2815,6 +2937,24 @@ export async function createGenerationParameters(settings, model, type, messages | ||
| 2815 | 2937 | generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL; |
| 2816 | 2938 | } |
| 2817 | 2939 | |
| 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 | 2958 | // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus |
| 2819 | 2959 | if (settings.chat_completion_source === chat_completion_sources.NANOGPT) { |
| 2820 | 2960 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -2870,7 +3010,7 @@ export async function createGenerationParameters(settings, model, type, messages | ||
| 2870 | 3010 | if (/gpt-5-chat-latest/.test(model)) { |
| 2871 | 3011 | delete generate_data.tools; |
| 2872 | 3012 | delete generate_data.tool_choice; |
| 2873 | 3013 | } else if (/gpt-5\.(1|2|3|4)/.test(model) && !/chat-latest/.test(model) && !generate_data.reasoning_effort) { |
| 2874 | 3014 | delete generate_data.frequency_penalty; |
| 2875 | 3015 | delete generate_data.presence_penalty; |
| 2876 | 3016 | delete generate_data.logit_bias; |
| @@ -3052,7 +3192,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov | ||
| 3052 | 3192 | } |
| 3053 | 3193 | }); |
| 3054 | 3194 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 3055 | 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 | 3196 | if (show_thoughts) { |
| 3057 | 3197 | state.reasoning += |
| 3058 | 3198 | data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ?? |
| @@ -4049,6 +4189,11 @@ function migrateChatCompletionSettings(settings) { | ||
| 4049 | 4189 | { oldKey: 'claude_use_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, |
| 4050 | 4190 | { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, |
| 4051 | 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 | ]; |
| 4053 | 4198 | |
| 4054 | 4199 | for (const migration of migrateMap) { |
| @@ -4150,9 +4295,11 @@ function loadOpenAISettings(data, settings) { | ||
| 4150 | 4295 | setNamesBehaviorControls(); |
| 4151 | 4296 | setContinuePostfixControls(); |
| 4152 | 4297 | setToolReasoningControls(); |
| 4298 | + ToolManager.RECURSE_LIMIT = oai_settings.tool_call_recurse_limit; | |
| 4153 | 4299 | |
| 4154 | 4300 | $('#openrouter_providers_chat').trigger('change'); |
| 4155 | 4301 | $('#openrouter_quantizations_chat').trigger('change'); |
| 4302 | + $('#nanogpt_provider').trigger('change'); | |
| 4156 | 4303 | $('#chat_completion_source').trigger('change'); |
| 4157 | 4304 | } |
| 4158 | 4305 | |
| @@ -4215,6 +4362,7 @@ async function getStatusOpen() { | ||
| 4215 | 4362 | chat_completion_sources.VERTEXAI, |
| 4216 | 4363 | chat_completion_sources.PERPLEXITY, |
| 4217 | 4364 | chat_completion_sources.ZAI, |
| 4365 | + chat_completion_sources.MINIMAX, | |
| 4218 | 4366 | ]; |
| 4219 | 4367 | if (noValidateSources.includes(oai_settings.chat_completion_source)) { |
| 4220 | 4368 | let status = t`Key saved; press \"Test Message\" to verify.`; |
| @@ -4272,6 +4420,14 @@ async function getStatusOpen() { | ||
| 4272 | 4420 | data.siliconflow_endpoint = oai_settings.siliconflow_endpoint; |
| 4273 | 4421 | } |
| 4274 | 4422 | |
| 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 | 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 | 4432 | if (canBypass) { |
| 4277 | 4433 | setOnlineStatus(t`Status check bypassed`); |
| @@ -4791,6 +4947,7 @@ function onSettingsPresetChange() { | ||
| 4791 | 4947 | $('#chat_completion_source').trigger('change'); |
| 4792 | 4948 | $('#openrouter_providers_chat').trigger('change'); |
| 4793 | 4949 | $('#openrouter_quantizations_chat').trigger('change'); |
| 4950 | + $('#nanogpt_provider').trigger('change'); | |
| 4794 | 4951 | } |
| 4795 | 4952 | |
| 4796 | 4953 | $('#openai_logit_bias_preset').trigger('change'); |
| @@ -4801,41 +4958,102 @@ function onSettingsPresetChange() { | ||
| 4801 | 4958 | }); |
| 4802 | 4959 | } |
| 4803 | 4960 | |
| 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 | + */ | |
| 4804 | 4966 | function getMaxContextOpenAI(value) { |
| 4805 | 4967 | if (oai_settings.max_context_unlocked) { |
| 4806 | 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 | 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 | + */ | |
| 5006 | +function getGeminiMaxContext(model, isUnlocked) { | |
| 5007 | + if (isUnlocked) { | |
| 5008 | + return unlocked_max; | |
| 5009 | + } | |
| 5010 | + | |
| 5011 | + if (Array.isArray(model_list) && model_list.length > 0) { | |
| 5012 | + const contextLength = model_list.find((record) => record.id === model)?.inputTokenLimit; | |
| 5013 | + if (Number.isFinite(contextLength) && contextLength > 0) { | |
| 5014 | + return contextLength; | |
| 5015 | + } | |
| 5016 | + } | |
| 5017 | + | |
| 5018 | + /** @type {[RegExp, number][]} */ | |
| 5019 | + const contextMap = [ | |
| 5020 | + [/gemini-2\.5-flash-image/, max_32k], | |
| 5021 | + [/gemini-3-pro-image/, max_64k], | |
| 5022 | + [/gemini-(?:3[.\d]*|2\.(?:5|0))-(pro|flash)/, max_1mil], | |
| 5023 | + [/(gemini-exp|learnlm-2\.0-flash|gemini-robotics)/, max_1mil], | |
| 5024 | + [/gemma-3-27b-it/, max_128k], | |
| 5025 | + [/gemma-3n-e4b-it/, max_8k], | |
| 5026 | + [/gemma-3/, max_32k], | |
| 5027 | + [/gemma-4/, max_256k], | |
| 5028 | + ]; | |
| 5029 | + | |
| 5030 | + for (const [regex, max] of contextMap) { | |
| 5031 | + if (regex.test(model)) { | |
| 5032 | + return max; | |
| 5033 | + } | |
| 5034 | + } | |
| 5035 | + | |
| 4820 | 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 | + */ | |
| 5044 | +function getGeminiMaxTemp(model) { | |
| 5045 | + if (Array.isArray(model_list) && model_list.length > 0) { | |
| 5046 | + const temp = model_list.find((record) => record.id === model)?.maxTemperature; | |
| 5047 | + if (Number.isFinite(temp) && temp > 0) { | |
| 5048 | + return temp; | |
| 5049 | + } | |
| 5050 | + } | |
| 5051 | + | |
| 5052 | + if (/(vision|ultra|gemma)/.test(model)) { | |
| 5053 | + return 1.0; | |
| 5054 | + } | |
| 5055 | + | |
| 5056 | + return 2.0; | |
| 4839 | 5057 | } |
| 4840 | 5058 | |
| 4841 | 5059 | /** |
| @@ -4921,6 +5139,7 @@ function getZaiMaxContext(model, isUnlocked) { | ||
| 4921 | 5139 | const contextMap = { |
| 4922 | 5140 | 'glm-5.1': max_200k, |
| 4923 | 5141 | 'glm-5-turbo': max_200k, |
| 5142 | + 'glm-5v-turbo': max_200k, | |
| 4924 | 5143 | 'glm-5': max_200k, |
| 4925 | 5144 | 'glm-4.7': max_200k, |
| 4926 | 5145 | 'glm-4.7-flash': max_200k, |
| @@ -5221,6 +5440,15 @@ async function onModelChange() { | ||
| 5221 | 5440 | oai_settings.siliconflow_model = value; |
| 5222 | 5441 | } |
| 5223 | 5442 | |
| 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 | 5452 | if ($(this).is('#model_electronhub_select')) { |
| 5225 | 5453 | if (!value || !hasModelsLoaded) { |
| 5226 | 5454 | console.debug('Null ElectronHub model selected. Ignoring.'); |
| @@ -5247,6 +5475,7 @@ async function onModelChange() { | ||
| 5247 | 5475 | |
| 5248 | 5476 | console.log('NanoGPT model changed to', value); |
| 5249 | 5477 | oai_settings.nanogpt_model = value; |
| 5478 | + syncNanoGptProvidersForModel(value, '#nanogpt_provider'); | |
| 5250 | 5479 | } |
| 5251 | 5480 | |
| 5252 | 5481 | if ($(this).is('#model_deepseek_select')) { |
| @@ -5328,29 +5557,21 @@ async function onModelChange() { | ||
| 5328 | 5557 | oai_settings.zai_model = value; |
| 5329 | 5558 | } |
| 5330 | 5559 | |
| 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 | 5561 | if (oai_settings.max_context_unlocked!value || !hasModelsLoaded) { |
| 5333 | - $('#openai_max_context').attr('max', max_2mil); | |
| 5562 | + console.debug('Null Workers AI model selected. Ignoring.'); | |
| 5334 | - } else if (value.includes('gemini-2.5-flash-image')) { | |
| 5563 | + return; | |
| 5335 | - $('#openai_max_context').attr('max', max_32k); | |
| 5564 | + } | |
| 5336 | - } else if (value.includes('gemini-3-pro-image')) { | |
| 5565 | + console.log('Workers AI model changed to', value); | |
| 5337 | - $('#openai_max_context').attr('max', max_64k); | |
| 5566 | + oai_settings.workers_ai_model = value; | |
| 5338 | - } else if (/gemini-3[.\d]*-(pro|flash)/.test(value) || /gemini-2.5-(pro|flash)/.test(value) || /gemini-2.0-(pro|flash)/.test(value)) { | |
| 5339 | - $('#openai_max_context').attr('max', max_1mil); | |
| 5340 | - } else if (value.includes('gemini-exp') || value.includes('learnlm-2.0-flash') || value.includes('gemini-robotics')) { | |
| 5341 | - $('#openai_max_context').attr('max', max_1mil); | |
| 5342 | - } else if (value.includes('gemma-3-27b-it')) { | |
| 5343 | - $('#openai_max_context').attr('max', max_128k); | |
| 5344 | - } else if (value.includes('gemma-3n-e4b-it')) { | |
| 5345 | - $('#openai_max_context').attr('max', max_8k); | |
| 5346 | - } else if (value.includes('gemma-3')) { | |
| 5347 | - $('#openai_max_context').attr('max', max_32k); | |
| 5348 | - } else { | |
| 5349 | - $('#openai_max_context').attr('max', max_32k); | |
| 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 | 5575 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 5355 | 5576 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| 5356 | 5577 | } |
| @@ -5383,7 +5604,7 @@ async function onModelChange() { | ||
| 5383 | 5604 | if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) { |
| 5384 | 5605 | if (oai_settings.max_context_unlocked) { |
| 5385 | 5606 | $('#openai_max_context').attr('max', unlocked_max); |
| 5386 | 5607 | } else if (/^claude-(sonnet-4-5|sonnet-4-6|opus-4-6|opus-4-7)/.test(value)) { |
| 5387 | 5608 | $('#openai_max_context').attr('max', max_1mil); |
| 5388 | 5609 | } else if (/^claude-(3|opus|haiku|sonnet)/.test(value)) { |
| 5389 | 5610 | $('#openai_max_context').attr('max', max_200k); |
| @@ -5533,19 +5754,27 @@ async function onModelChange() { | ||
| 5533 | 5754 | } |
| 5534 | 5755 | |
| 5535 | 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 | 5765 | if (oai_settings.max_context_unlocked) { |
| 5537 | 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 | 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 | 5773 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 5547 | 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 | } |
| 5550 | 5779 | |
| 5551 | 5780 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { |
| @@ -5637,6 +5866,15 @@ async function onModelChange() { | ||
| 5637 | 5866 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 5638 | 5867 | } |
| 5639 | 5868 | |
| 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 | 5878 | if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { |
| 5641 | 5879 | const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked); |
| 5642 | 5880 | $('#openai_max_context').attr('max', maxContext); |
| @@ -5653,18 +5891,6 @@ async function onModelChange() { | ||
| 5653 | 5891 | eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value); |
| 5654 | 5892 | } |
| 5655 | 5893 | |
| 5656 | -async function onOpenrouterModelSortChange() { | |
| 5657 | - await getStatusOpen(); | |
| 5658 | -} | |
| 5659 | - | |
| 5660 | -async function onChutesModelSortChange() { | |
| 5661 | - await getStatusOpen(); | |
| 5662 | -} | |
| 5663 | - | |
| 5664 | -async function onElectronHubModelSortChange() { | |
| 5665 | - await getStatusOpen(); | |
| 5666 | -} | |
| 5667 | - | |
| 5668 | 5894 | async function onNewPresetClick() { |
| 5669 | 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); |
| 5670 | 5896 | |
| @@ -5709,6 +5935,8 @@ async function onConnectButtonClick(e) { | ||
| 5709 | 5935 | [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true }, |
| 5710 | 5936 | [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false }, |
| 5711 | 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 | }; |
| 5713 | 5941 | |
| 5714 | 5942 | // Vertex AI Express version - use API key |
| @@ -5774,6 +6002,8 @@ function toggleChatCompletionForms() { | ||
| 5774 | 6002 | $('#model_chutes_select').trigger('change'); |
| 5775 | 6003 | } else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) { |
| 5776 | 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 | 6007 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { |
| 5778 | 6008 | $('#model_electronhub_select').trigger('change'); |
| 5779 | 6009 | } else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) { |
| @@ -5798,6 +6028,8 @@ function toggleChatCompletionForms() { | ||
| 5798 | 6028 | $('#azure_openai_model').trigger('change'); |
| 5799 | 6029 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { |
| 5800 | 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 | } |
| 5802 | 6034 | |
| 5803 | 6035 | $('[data-source]').each(function () { |
| @@ -5903,6 +6135,10 @@ export function isImageInliningSupported() { | ||
| 5903 | 6135 | 'gemini-exp-1206', |
| 5904 | 6136 | 'learnlm', |
| 5905 | 6137 | 'gemini-robotics', |
| 6138 | + 'gemma-3-27b', | |
| 6139 | + 'gemma-3-12b', | |
| 6140 | + 'gemma-3-4b', | |
| 6141 | + 'gemma-4', | |
| 5906 | 6142 | // MistralAI |
| 5907 | 6143 | 'mistral-small-2503', |
| 5908 | 6144 | 'mistral-small-2506', |
| @@ -5923,6 +6159,7 @@ export function isImageInliningSupported() { | ||
| 5923 | 6159 | // Z.AI (GLM) |
| 5924 | 6160 | 'glm-4.5v', |
| 5925 | 6161 | 'glm-4.6v', |
| 6162 | + 'glm-5v-turbo', | |
| 5926 | 6163 | 'autoglm-phone', |
| 5927 | 6164 | // SiliconFlow |
| 5928 | 6165 | 'Qwen/Qwen3-VL-32B-Instruct', |
| @@ -5978,6 +6215,10 @@ export function isImageInliningSupported() { | ||
| 5978 | 6215 | return visionSupportedModels.some(model => oai_settings.zai_model.includes(model)); |
| 5979 | 6216 | case chat_completion_sources.SILICONFLOW: |
| 5980 | 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 | 6222 | default: |
| 5982 | 6223 | return false; |
| 5983 | 6224 | } |
| @@ -6002,9 +6243,11 @@ export function isVideoInliningSupported() { | ||
| 6002 | 6243 | 'gemini-2.5', |
| 6003 | 6244 | 'gemini-exp-1206', |
| 6004 | 6245 | 'gemini-3', |
| 6246 | + 'gemma-4', | |
| 6005 | 6247 | // Z.AI (GLM) |
| 6006 | 6248 | 'glm-4.5v', |
| 6007 | 6249 | 'glm-4.6v', |
| 6250 | + 'glm-5v-turbo', | |
| 6008 | 6251 | ]; |
| 6009 | 6252 | |
| 6010 | 6253 | switch (oai_settings.chat_completion_source) { |
| @@ -6632,16 +6875,6 @@ export function initOpenAI() { | ||
| 6632 | 6875 | saveSettingsDebounced(); |
| 6633 | 6876 | }); |
| 6634 | 6877 | |
| 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 | 6878 | $('#openrouter_allow_fallbacks').on('input', function () { |
| 6646 | 6879 | oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked'); |
| 6647 | 6880 | updateOpenRouterProvidersWarning('#openrouter_providers_chat'); |
| @@ -6653,21 +6886,6 @@ export function initOpenAI() { | ||
| 6653 | 6886 | saveSettingsDebounced(); |
| 6654 | 6887 | }); |
| 6655 | 6888 | |
| 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 | 6889 | $('#squash_system_messages').on('input', function () { |
| 6672 | 6890 | oai_settings.squash_system_messages = !!$(this).prop('checked'); |
| 6673 | 6891 | saveSettingsDebounced(); |
| @@ -6695,6 +6913,13 @@ export function initOpenAI() { | ||
| 6695 | 6913 | saveSettingsDebounced(); |
| 6696 | 6914 | }); |
| 6697 | 6915 | |
| 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 | 6923 | $('#tool_reasoning_mode').on('input', function () { |
| 6699 | 6924 | oai_settings.tool_reasoning_mode = getToolReasoningMode({ |
| 6700 | 6925 | ...oai_settings, |
| @@ -6921,11 +7146,34 @@ export function initOpenAI() { | ||
| 6921 | 7146 | saveSettingsDebounced(); |
| 6922 | 7147 | }); |
| 6923 | 7148 | |
| 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 | 7160 | $('#bind_preset_to_connection').on('input', function () { |
| 6925 | 7161 | oai_settings.bind_preset_to_connection = !!$(this).prop('checked'); |
| 6926 | 7162 | saveSettingsDebounced(); |
| 6927 | 7163 | }); |
| 6928 | 7164 | |
| 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 | 7177 | $('#api_button_openai').on('click', onConnectButtonClick); |
| 6930 | 7178 | $('#openai_reverse_proxy').on('input', onReverseProxyInput); |
| 6931 | 7179 | $('#model_openai_select').on('change', onModelChange); |
| @@ -6949,15 +7197,18 @@ export function initOpenAI() { | ||
| 6949 | 7197 | oai_settings.siliconflow_endpoint = String($(this).val()); |
| 6950 | 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 | 7208 | $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange); |
| 6953 | 7209 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); |
| 6954 | 7210 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); |
| 6955 | 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 | 7212 | $('#model_ai21_select').on('change', onModelChange); |
| 6962 | 7213 | $('#model_mistralai_select').on('change', onModelChange); |
| 6963 | 7214 | $('#model_cohere_select').on('change', onModelChange); |
| @@ -6965,6 +7216,7 @@ export function initOpenAI() { | ||
| 6965 | 7216 | $('#model_groq_select').on('change', onModelChange); |
| 6966 | 7217 | $('#model_chutes_select').on('change', onModelChange); |
| 6967 | 7218 | $('#model_siliconflow_select').on('change', onModelChange); |
| 7219 | + $('#model_minimax_select').on('change', onModelChange); | |
| 6968 | 7220 | $('#model_electronhub_select').on('change', onModelChange); |
| 6969 | 7221 | $('#model_nanogpt_select').on('change', onModelChange); |
| 6970 | 7222 | $('#model_deepseek_select').on('change', onModelChange); |
| @@ -6977,6 +7229,7 @@ export function initOpenAI() { | ||
| 6977 | 7229 | $('#model_fireworks_select').on('change', onModelChange); |
| 6978 | 7230 | $('#azure_openai_model').on('change', onModelChange); |
| 6979 | 7231 | $('#model_zai_select').on('change', onModelChange); |
| 7232 | + $('#model_workers_ai_select').on('change', onModelChange); | |
| 6980 | 7233 | $('#settings_preset_openai').on('change', onSettingsPresetChange); |
| 6981 | 7234 | $('#new_oai_preset').on('click', onNewPresetClick); |
| 6982 | 7235 | $('#delete_oai_preset').on('click', onDeletePresetClick); |
| @@ -22,9 +22,35 @@ import { | ||
| 22 | 22 | setUserName, |
| 23 | 23 | this_chid, |
| 24 | 24 | } from '../script.js'; |
| 25 | 25 | import { persona_description_positions, power_user } from './power-user.js'; |
| 26 | 26 | import { getTokenCountAsync } from './tokenizers.js'; |
| 27 | -import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent } from './utils.js'; | |
| 27 | +import { | |
| 28 | + PAGINATION_TEMPLATE, | |
| 29 | + clearInfoBlock, | |
| 30 | + debounce, | |
| 31 | + delay, | |
| 32 | + download, | |
| 33 | + ensureImageFormatSupported, | |
| 34 | + flashHighlight, | |
| 35 | + getBase64Async, | |
| 36 | + getCharIndex, | |
| 37 | + isFalseBoolean, | |
| 38 | + isTrueBoolean, | |
| 39 | + onlyUnique, | |
| 40 | + parseJsonFile, | |
| 41 | + setInfoBlock, | |
| 42 | + localizePagination, | |
| 43 | + renderPaginationDropdown, | |
| 44 | + paginationDropdownChangeHandler, | |
| 45 | + addLongPressEvent, | |
| 46 | + stringToRange, | |
| 47 | + sortIgnoreCaseAndAccents, | |
| 48 | + equalsIgnoreCaseAndAccents, | |
| 49 | + uuidv4, | |
| 50 | + resolveAvatarData, | |
| 51 | + findPersona, | |
| 52 | + escapeHtml, | |
| 53 | +} from './utils.js'; | |
| 28 | 54 | import { debounce_timeout } from './constants.js'; |
| 29 | 55 | import { FILTER_TYPES, FilterHelper } from './filters.js'; |
| 30 | 56 | import { groups, selected_group } from './group-chats.js'; |
| @@ -36,10 +62,11 @@ import { saveMetadataDebounced } from './extensions.js'; | ||
| 36 | 62 | import { accountStorage } from './util/AccountStorage.js'; |
| 37 | 63 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 38 | 64 | import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; |
| 39 | 65 | import { commonEnumMatchProviders, commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 40 | 66 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 41 | 67 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 42 | 68 | import { isFirefox } from './browser-fixes.js'; |
| 69 | +import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; | |
| 43 | 70 | |
| 44 | 71 | /** |
| 45 | 72 | * @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 | 85 | * @property {boolean} locked.character - Whether the persona is locked to the currently open character or group |
| 59 | 86 | */ |
| 60 | 87 | |
| 88 | +export const persona_description_positions = { | |
| 89 | + IN_PROMPT: 0, | |
| 90 | + /** | |
| 91 | + * @deprecated Use persona_description_positions.IN_PROMPT instead. | |
| 92 | + */ | |
| 93 | + AFTER_CHAR: 1, | |
| 94 | + TOP_AN: 2, | |
| 95 | + BOTTOM_AN: 3, | |
| 96 | + AT_DEPTH: 4, | |
| 97 | + NONE: 9, | |
| 98 | +}; | |
| 99 | + | |
| 61 | 100 | const USER_AVATAR_PATH = 'User Avatars/'; |
| 62 | 101 | |
| 63 | 102 | let savePersonasPage = 0; |
| @@ -217,11 +256,12 @@ function getUserAvatarBlock(avatarId) { | ||
| 217 | 256 | /** |
| 218 | 257 | * Initialize missing personas in the power user settings. |
| 219 | 258 | * @param {string[]} avatarsList List of avatar file names |
| 259 | + * @returns {Promise<void>} | |
| 220 | 260 | */ |
| 221 | 261 | async function addMissingPersonas(avatarsList) { |
| 222 | 262 | for (const persona of avatarsList) { |
| 223 | 263 | if (!power_user.personas[persona]) { |
| 224 | 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 | } |
| 250 | 290 | |
| 251 | 291 | // If any persona is missing from the power user settings, we add it |
| 252 | 292 | await addMissingPersonas(allEntities); |
| 253 | 293 | // Before printing the personas, we check if we should enable/disable search sorting |
| 254 | 294 | verifyPersonaSearchSortRule(); |
| 255 | 295 | |
| @@ -429,7 +469,7 @@ export async function createPersona(avatarId) { | ||
| 429 | 469 | |
| 430 | 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 }); |
| 431 | 471 | |
| 432 | 472 | await initPersona(avatarId, personaName, personaDescription, ''); |
| 433 | 473 | if (power_user.persona_show_notifications) { |
| 434 | 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() { | ||
| 454 | 494 | |
| 455 | 495 | // Date + name (only ASCII) to make it unique |
| 456 | 496 | const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; |
| 457 | 497 | await initPersona(avatarId, personaName, '', personaTitle); |
| 458 | 498 | await uploadUserAvatar(default_user_avatar, avatarId); |
| 459 | 499 | } |
| 460 | 500 | |
| @@ -464,20 +504,36 @@ async function createDummyPersona() { | ||
| 464 | 504 | * @param {string} personaName Name for the persona |
| 465 | 505 | * @param {string} personaDescription Optional description for the persona |
| 466 | 506 | * @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 | */ |
| 469 | 515 | export 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 | 522 | power_user.personas[avatarId] = personaName; |
| 471 | 523 | power_user.persona_descriptions[avatarId] = { |
| 472 | 524 | description: personaDescription || '', |
| 473 | 525 | position: persona_description_positions.IN_PROMPTposition, |
| 474 | 526 | depth: DEFAULT_DEPTHdepth, |
| 475 | 527 | role: DEFAULT_ROLErole, |
| 476 | 528 | lorebook: ''lorebook, |
| 477 | 529 | title: personaTitle || '', |
| 478 | 530 | }; |
| 479 | 531 | |
| 480 | 532 | saveSettingsDebounced(); |
| 533 | + | |
| 534 | + if (!silent) { | |
| 535 | + await eventSource.emit(event_types.PERSONA_CREATED, { avatarId, name: personaName, description: personaDescription || '', title: personaTitle || '' }); | |
| 536 | + } | |
| 481 | 537 | } |
| 482 | 538 | |
| 483 | 539 | /** |
| @@ -540,6 +596,7 @@ export async function convertCharacterToPersona(characterId = null) { | ||
| 540 | 596 | } |
| 541 | 597 | |
| 542 | 598 | saveSettingsDebounced(); |
| 599 | + await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: overwriteName, name, description, title: '' }); | |
| 543 | 600 | |
| 544 | 601 | console.log('Persona for character created'); |
| 545 | 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 | 800 | * Automatically selects a persona based on the given name if a matching persona exists. |
| 744 | 801 | * @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 | 804 | * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise |
| 746 | 805 | */ |
| 747 | 806 | export 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 | 808 | if (value === namepersona) { |
| 750 | 809 | console.log(`Auto-selecting persona ${keypersona.avatar} for name ${name}`); |
| 751 | 810 | await setUserAvatar(keypersona.avatar); |
| 752 | 811 | return true; |
| 753 | 812 | } |
| 754 | - } | |
| 755 | 813 | return false; |
| 756 | 814 | } |
| 757 | 815 | |
| @@ -778,6 +836,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) { | ||
| 778 | 836 | delete power_user.persona_descriptions[avatarId].title; |
| 779 | 837 | await getUserAvatars(true, avatarId); |
| 780 | 838 | saveSettingsDebounced(); |
| 839 | + await eventSource.emit(event_types.PERSONA_UPDATED, avatarId); | |
| 781 | 840 | return; |
| 782 | 841 | } |
| 783 | 842 | |
| @@ -786,6 +845,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) { | ||
| 786 | 845 | console.log(`Updated persona title for ${avatarId} to ${newTitle}`); |
| 787 | 846 | await getUserAvatars(true, avatarId); |
| 788 | 847 | saveSettingsDebounced(); |
| 848 | + await eventSource.emit(event_types.PERSONA_UPDATED, avatarId); | |
| 789 | 849 | return; |
| 790 | 850 | } |
| 791 | 851 | } |
| @@ -821,6 +881,7 @@ async function renamePersona(avatarId) { | ||
| 821 | 881 | } |
| 822 | 882 | |
| 823 | 883 | saveSettingsDebounced(); |
| 884 | + await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName: currentName, newName }); | |
| 824 | 885 | await getUserAvatars(true, avatarId); |
| 825 | 886 | updatePersonaUIStates(); |
| 826 | 887 | setPersonaDescription(); |
| @@ -886,8 +947,9 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) { | ||
| 886 | 947 | const temporary = getPersonaTemporaryLockInfo(); |
| 887 | 948 | if (temporary.isTemporary) { |
| 888 | 949 | toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, { |
| 889 | 950 | preventDuplicates: true, onclick: () => { |
| 890 | - toastr.info(temporary.info.replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false }); | |
| 951 | + onclick: () => { | |
| 952 | + toastr.info(escapeHtml(temporary.info).replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false }); | |
| 891 | 953 | }, |
| 892 | 954 | }); |
| 893 | 955 | } |
| @@ -1013,6 +1075,7 @@ async function lockPersona(type = 'chat') { | ||
| 1013 | 1075 | connections: [], |
| 1014 | 1076 | title: '', |
| 1015 | 1077 | }; |
| 1078 | + await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: user_avatar, name: name1, description: '', title: '' }); | |
| 1016 | 1079 | } |
| 1017 | 1080 | |
| 1018 | 1081 | switch (type) { |
| @@ -1055,9 +1118,9 @@ async function lockPersona(type = 'chat') { | ||
| 1055 | 1118 | if (power_user.persona_show_notifications) { |
| 1056 | 1119 | let additional = ''; |
| 1057 | 1120 | if (unlinkedCharacters.length) |
| 1058 | 1121 | additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.map(escapeHtml).join(', ')}`}`; |
| 1059 | 1122 | if (additional || !isPersonaPanelOpen()) { |
| 1060 | 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 | } |
| 1072 | 1135 | |
| 1073 | 1136 | |
| 1137 | +/** | |
| 1138 | + * Click handler for the delete persona button. Delegates to deletePersona with the current user avatar. | |
| 1139 | + */ | |
| 1074 | 1140 | async function deleteUserAvatar() { |
| 1075 | - const avatarId = user_avatar; | |
| 1141 | + await deletePersona(user_avatar); | |
| 1142 | +} | |
| 1076 | 1143 | |
| 1144 | +/** | |
| 1145 | + * Deletes a persona by avatar id. | |
| 1146 | + * @param {string} avatarId The persona's avatar id to delete | |
| 1147 | + * @param {object} [options] Options | |
| 1148 | + * @param {boolean} [options.silent=false] If true, skips the confirmation popup and suppresses toast notifications | |
| 1149 | + * @returns {Promise<boolean>} True if the persona was deleted | |
| 1150 | + */ | |
| 1151 | +async function deletePersona(avatarId, { silent = false } = {}) { | |
| 1077 | 1152 | if (!avatarId) { |
| 1078 | 1153 | console.warn('No avatar id found'); |
| 1079 | 1154 | return false; |
| 1080 | 1155 | } |
| 1156 | + | |
| 1081 | 1157 | const name = power_user.personas[avatarId] || ''; |
| 1158 | + | |
| 1159 | + if (!silent) { | |
| 1082 | 1160 | const confirm = await Popup.show.confirm( |
| 1083 | 1161 | t`Delete Persona` + `: ${name}`, |
| 1084 | 1162 | t`Are you sure you want to delete this avatar?` + '<br />' + t`All information associated with its linked persona will be lost.`); |
| 1085 | 1163 | |
| 1086 | 1164 | if (!confirm) { |
| 1087 | 1165 | console.debug('User cancelled deleting avatar'); |
| 1088 | 1166 | return false; |
| 1167 | + } | |
| 1089 | 1168 | } |
| 1090 | 1169 | |
| 1091 | 1170 | const request = await fetch('/api/avatars/delete', { |
| @@ -1102,24 +1181,29 @@ async function deleteUserAvatar() { | ||
| 1102 | 1181 | delete power_user.persona_descriptions[avatarId]; |
| 1103 | 1182 | |
| 1104 | 1183 | if (avatarId === power_user.default_persona) { |
| 1105 | 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 | 1185 | power_user.default_persona = null; |
| 1107 | 1186 | } |
| 1108 | 1187 | |
| 1109 | 1188 | if (avatarId === chat_metadata.persona) { |
| 1110 | 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 | 1190 | delete chat_metadata.persona; |
| 1112 | 1191 | await saveMetadata(); |
| 1113 | 1192 | } |
| 1114 | 1193 | |
| 1115 | 1194 | saveSettingsDebounced(); |
| 1195 | + await eventSource.emit(event_types.PERSONA_DELETED, { avatarId, name }); | |
| 1116 | 1196 | |
| 1117 | 1197 | // 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 | 1199 | await loadPersonaForCurrentChat({ doRender: true }); |
| 1200 | + return true; | |
| 1119 | 1201 | } |
| 1202 | + | |
| 1203 | + return false; | |
| 1120 | 1204 | } |
| 1121 | 1205 | |
| 1122 | 1206 | async function onPersonaDescriptionInput() { |
| 1123 | 1207 | power_user.persona_description = String($('#persona_description').val()); |
| 1124 | 1208 | countPersonaDescriptionTokens(); |
| 1125 | 1209 | |
| @@ -1145,25 +1229,35 @@ function onPersonaDescriptionInput() { | ||
| 1145 | 1229 | .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text')) |
| 1146 | 1230 | .toggleClass('text_muted', !power_user.persona_description); |
| 1147 | 1231 | saveSettingsDebounced(); |
| 1232 | + | |
| 1233 | + if (power_user.personas[user_avatar]) { | |
| 1234 | + await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | |
| 1235 | + } | |
| 1148 | 1236 | } |
| 1149 | 1237 | |
| 1150 | 1238 | async function onPersonaDescriptionDepthValueInput() { |
| 1151 | 1239 | power_user.persona_description_depth = Number($('#persona_depth_value').val()); |
| 1152 | 1240 | |
| 1153 | 1241 | if (power_user.personas[user_avatar]) { |
| 1154 | 1242 | const object = getOrCreatePersonaDescriptor(); |
| 1155 | 1243 | object.depth = power_user.persona_description_depth; |
| 1244 | + saveSettingsDebounced(); | |
| 1245 | + await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | |
| 1246 | + return; | |
| 1156 | 1247 | } |
| 1157 | 1248 | |
| 1158 | 1249 | saveSettingsDebounced(); |
| 1159 | 1250 | } |
| 1160 | 1251 | |
| 1161 | 1252 | async function onPersonaDescriptionDepthRoleInput() { |
| 1162 | 1253 | power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val()); |
| 1163 | 1254 | |
| 1164 | 1255 | if (power_user.personas[user_avatar]) { |
| 1165 | 1256 | const object = getOrCreatePersonaDescriptor(); |
| 1166 | 1257 | object.role = power_user.persona_description_role; |
| 1258 | + saveSettingsDebounced(); | |
| 1259 | + await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | |
| 1260 | + return; | |
| 1167 | 1261 | } |
| 1168 | 1262 | |
| 1169 | 1263 | saveSettingsDebounced(); |
| @@ -1171,9 +1265,9 @@ function onPersonaDescriptionDepthRoleInput() { | ||
| 1171 | 1265 | |
| 1172 | 1266 | /** |
| 1173 | 1267 | * Opens a popup to set the lorebook for the current persona. |
| 1174 | 1268 | * @param {Pick<JQuery.ClickEvent, 'shiftKey' | 'altKey'>} event Click event |
| 1175 | 1269 | */ |
| 1176 | 1270 | async function onPersonaLoreButtonClick(event{ shiftKey, altKey }) { |
| 1177 | 1271 | const personaName = power_user.personas[user_avatar]; |
| 1178 | 1272 | const selectedLorebook = power_user.persona_description_lorebook; |
| 1179 | 1273 | |
| @@ -1182,7 +1276,7 @@ async function onPersonaLoreButtonClick(event) { | ||
| 1182 | 1276 | return; |
| 1183 | 1277 | } |
| 1184 | 1278 | |
| 1185 | 1279 | if (selectedLorebook && !event.shiftKey && !event.altKey) { |
| 1186 | 1280 | openWorldInfoEditor(selectedLorebook); |
| 1187 | 1281 | return; |
| 1188 | 1282 | } |
| @@ -1200,7 +1294,7 @@ async function onPersonaLoreButtonClick(event) { | ||
| 1200 | 1294 | worldSelect.append(option); |
| 1201 | 1295 | } |
| 1202 | 1296 | |
| 1203 | 1297 | worldSelect.on('change', async function () { |
| 1204 | 1298 | power_user.persona_description_lorebook = String($(this).val()); |
| 1205 | 1299 | |
| 1206 | 1300 | if (power_user.personas[user_avatar]) { |
| @@ -1210,12 +1304,16 @@ async function onPersonaLoreButtonClick(event) { | ||
| 1210 | 1304 | |
| 1211 | 1305 | $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); |
| 1212 | 1306 | saveSettingsDebounced(); |
| 1307 | + | |
| 1308 | + if (power_user.personas[user_avatar]) { | |
| 1309 | + await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | |
| 1310 | + } | |
| 1213 | 1311 | }); |
| 1214 | 1312 | |
| 1215 | 1313 | await callGenericPopup(template, POPUP_TYPE.TEXT); |
| 1216 | 1314 | } |
| 1217 | 1315 | |
| 1218 | 1316 | async function onPersonaDescriptionPositionInput() { |
| 1219 | 1317 | power_user.persona_description_position = Number( |
| 1220 | 1318 | $('#persona_description_position').find(':selected').val(), |
| 1221 | 1319 | ); |
| @@ -1223,6 +1321,10 @@ function onPersonaDescriptionPositionInput() { | ||
| 1223 | 1321 | if (power_user.personas[user_avatar]) { |
| 1224 | 1322 | const object = getOrCreatePersonaDescriptor(); |
| 1225 | 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 | } |
| 1227 | 1329 | |
| 1228 | 1330 | saveSettingsDebounced(); |
| @@ -1728,15 +1830,36 @@ async function onPersonasRestoreInput(e) { | ||
| 1728 | 1830 | $('#personas_restore_input').val(''); |
| 1729 | 1831 | } |
| 1730 | 1832 | |
| 1731 | -async function syncUserNameToPersona() { | |
| 1833 | +/** | |
| 1732 | - const confirmation = await Popup.show.confirm(t`Are you sure?`, t`All user-sent messages in this chat will be attributed to ${name1}.`); | |
| 1834 | + * Synchronizes user-sent messages in the chat to the current persona. | |
| 1835 | + * @param {object} [options={}] - Optional parameters | |
| 1836 | + * @param {number} [options.start=0] - Start index of the message range (inclusive) | |
| 1837 | + * @param {number} [options.end=chat.length - 1] - End index of the message range (inclusive) | |
| 1838 | + * @param {boolean} [options.quiet=false] - If true, skips the confirmation popup | |
| 1839 | + * @param {string} [options.nameFilter=''] - Filter messages by name (case-insensitive) | |
| 1840 | + * @returns {Promise<void>} | |
| 1841 | + */ | |
| 1842 | +async function syncUserNameToPersona({ start = 0, end = chat.length - 1, quiet = false, nameFilter = '' } = {}) { | |
| 1843 | + const isRangeAll = start === 0 && end === chat.length - 1; | |
| 1844 | + const hasNameFilter = nameFilter?.trim(); | |
| 1845 | + const confirmMessage = isRangeAll && !hasNameFilter | |
| 1846 | + ? t`All user-sent messages in this chat will be attributed to ${name1}.` | |
| 1847 | + : isRangeAll && hasNameFilter | |
| 1848 | + ? t`User-sent messages with name "${nameFilter}" will be attributed to ${name1}.` | |
| 1849 | + : !isRangeAll && !hasNameFilter | |
| 1850 | + ? t`User-sent messages in the specified range will be attributed to ${name1}.` | |
| 1851 | + : t`User-sent messages with name "${nameFilter}" in the specified range will be attributed to ${name1}.`; | |
| 1733 | 1852 | |
| 1853 | + if (!quiet) { | |
| 1854 | + const confirmation = await Popup.show.confirm(t`Are you sure?`, confirmMessage); | |
| 1734 | 1855 | if (!confirmation) { |
| 1735 | 1856 | return; |
| 1736 | 1857 | } |
| 1858 | + } | |
| 1737 | 1859 | |
| 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 | 1863 | mes.name = name1; |
| 1741 | 1864 | mes.force_avatar = getThumbnailUrl('persona', user_avatar); |
| 1742 | 1865 | } |
| @@ -1763,22 +1886,27 @@ export async function retriggerFirstMessageOnEmptyChat() { | ||
| 1763 | 1886 | |
| 1764 | 1887 | /** |
| 1765 | 1888 | * Duplicates a persona. |
| 1766 | 1889 | * @param {string} avatarId Source persona avatar id |
| 1767 | 1890 | * @returnsparam {Promise<void>object} [options] Options |
| 1891 | + * @param {boolean} [options.silent=false] If true, skips the confirmation popup | |
| 1892 | + * @param {boolean} [options.select=false] If true, selects/activates the duplicated persona | |
| 1893 | + * @returns {Promise<string>} The avatar id of the new persona, or empty string on failure/cancellation | |
| 1768 | 1894 | */ |
| 1769 | -async function duplicatePersona(avatarId) { | |
| 1895 | +async function duplicatePersona(avatarId, { silent = false, select = false } = {}) { | |
| 1770 | 1896 | const personaName = power_user.personas[avatarId]; |
| 1771 | 1897 | |
| 1772 | 1898 | if (!personaName) { |
| 1773 | 1899 | toastr.warning('t`Chosen avatar is not a persona'`, t`Persona Management`); |
| 1774 | 1900 | return ''; |
| 1775 | 1901 | } |
| 1776 | 1902 | |
| 1903 | + if (!silent) { | |
| 1777 | 1904 | const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName); |
| 1778 | 1905 | |
| 1779 | 1906 | if (!confirm) { |
| 1780 | 1907 | console.debug('User cancelled duplicating persona'); |
| 1781 | - return; | |
| 1908 | + return ''; | |
| 1909 | + } | |
| 1782 | 1910 | } |
| 1783 | 1911 | |
| 1784 | 1912 | const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; |
| @@ -1795,8 +1923,24 @@ async function duplicatePersona(avatarId) { | ||
| 1795 | 1923 | }; |
| 1796 | 1924 | |
| 1797 | 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 | 1936 | await getUserAvatars(true, newAvatarId); |
| 1799 | 1937 | saveSettingsDebounced(); |
| 1938 | + | |
| 1939 | + if (select) { | |
| 1940 | + await setUserAvatar(newAvatarId); | |
| 1941 | + } | |
| 1942 | + | |
| 1943 | + return newAvatarId; | |
| 1800 | 1944 | } |
| 1801 | 1945 | |
| 1802 | 1946 | /** |
| @@ -1807,12 +1951,418 @@ async function migrateNonPersonaUser() { | ||
| 1807 | 1951 | return; |
| 1808 | 1952 | } |
| 1809 | 1953 | |
| 1810 | 1954 | await initPersona(user_avatar, name1, '', '', { silent: true }); |
| 1811 | 1955 | setPersonaDescription(); |
| 1812 | 1956 | await getUserAvatars(true, user_avatar); |
| 1813 | 1957 | } |
| 1814 | 1958 | |
| 1815 | 1959 | |
| 1960 | +// #region Persona CRUD Slash Command Utilities | |
| 1961 | + | |
| 1962 | +/** | |
| 1963 | + * Mapping of human-readable position names to persona_description_positions enum values. | |
| 1964 | + * @type {Record<string, number>} | |
| 1965 | + */ | |
| 1966 | +const POSITION_NAME_MAP = Object.freeze({ | |
| 1967 | + 'inprompt': persona_description_positions.IN_PROMPT, | |
| 1968 | + 'topan': persona_description_positions.TOP_AN, | |
| 1969 | + 'bottoman': persona_description_positions.BOTTOM_AN, | |
| 1970 | + 'atdepth': persona_description_positions.AT_DEPTH, | |
| 1971 | + 'none': persona_description_positions.NONE, | |
| 1972 | +}); | |
| 1973 | + | |
| 1974 | +/** | |
| 1975 | + * Mapping of human-readable role names to numeric role values. | |
| 1976 | + * @type {Record<string, number>} | |
| 1977 | + */ | |
| 1978 | +const ROLE_NAME_MAP = Object.freeze({ | |
| 1979 | + 'system': 0, | |
| 1980 | + 'user': 1, | |
| 1981 | + 'assistant': 2, | |
| 1982 | +}); | |
| 1983 | + | |
| 1984 | +/** | |
| 1985 | + * Parses a persona description position from a string or number value. | |
| 1986 | + * @param {string|number|undefined} value Position value (name or number) | |
| 1987 | + * @returns {number|null} Parsed position value, or null if invalid/undefined | |
| 1988 | + */ | |
| 1989 | +function parsePersonaPosition(value) { | |
| 1990 | + if (value === undefined || value === null) return null; | |
| 1991 | + const strValue = String(value).toLowerCase(); | |
| 1992 | + if (strValue in POSITION_NAME_MAP) return POSITION_NAME_MAP[strValue]; | |
| 1993 | + const numValue = Number(value); | |
| 1994 | + if (!isNaN(numValue) && Object.values(persona_description_positions).includes(numValue)) return numValue; | |
| 1995 | + return null; | |
| 1996 | +} | |
| 1997 | + | |
| 1998 | +/** | |
| 1999 | + * Parses a persona description role from a string or number value. | |
| 2000 | + * @param {string|number|undefined} value Role value (name or number) | |
| 2001 | + * @returns {number|null} Parsed role value, or null if invalid/undefined | |
| 2002 | + */ | |
| 2003 | +function parsePersonaRole(value) { | |
| 2004 | + if (value === undefined || value === null) return null; | |
| 2005 | + const strValue = String(value).toLowerCase(); | |
| 2006 | + if (strValue in ROLE_NAME_MAP) return ROLE_NAME_MAP[strValue]; | |
| 2007 | + const numValue = Number(value); | |
| 2008 | + if (!isNaN(numValue) && numValue >= 0 && numValue <= 2) return numValue; | |
| 2009 | + return null; | |
| 2010 | +} | |
| 2011 | + | |
| 2012 | +/** | |
| 2013 | + * Uploads base64 avatar data to a persona, optionally showing a crop dialog. | |
| 2014 | + * @param {string} avatarId The persona's avatar file name | |
| 2015 | + * @param {string} base64Data Base64 data URL of the image | |
| 2016 | + * @param {object} [options] Options | |
| 2017 | + * @param {boolean} [options.resizePrompt=false] Whether to show the crop dialog | |
| 2018 | + * @returns {Promise<boolean>} True if upload was successful | |
| 2019 | + */ | |
| 2020 | +async function uploadPersonaAvatar(avatarId, base64Data, { resizePrompt = false } = {}) { | |
| 2021 | + if (!base64Data || !avatarId) return false; | |
| 2022 | + | |
| 2023 | + let finalImageData = base64Data; | |
| 2024 | + | |
| 2025 | + if (resizePrompt && !power_user.never_resize_avatars) { | |
| 2026 | + const dlg = new Popup(t`Set the crop position of the avatar image`, POPUP_TYPE.CROP, '', { cropImage: base64Data }); | |
| 2027 | + const croppedImage = await dlg.show(); | |
| 2028 | + if (!croppedImage) return false; | |
| 2029 | + finalImageData = String(croppedImage); | |
| 2030 | + } | |
| 2031 | + | |
| 2032 | + try { | |
| 2033 | + const response = await fetch(finalImageData); | |
| 2034 | + const blob = await response.blob(); | |
| 2035 | + const file = new File([blob], 'avatar.png', { type: 'image/png' }); | |
| 2036 | + const formData = new FormData(); | |
| 2037 | + formData.append('avatar', file); | |
| 2038 | + formData.append('overwrite_name', avatarId); | |
| 2039 | + | |
| 2040 | + const uploadResponse = await fetch('/api/avatars/upload', { | |
| 2041 | + method: 'POST', | |
| 2042 | + headers: getRequestHeaders({ omitContentType: true }), | |
| 2043 | + cache: 'no-cache', | |
| 2044 | + body: formData, | |
| 2045 | + }); | |
| 2046 | + | |
| 2047 | + if (!uploadResponse.ok) { | |
| 2048 | + throw new Error(`Upload failed: ${uploadResponse.statusText}`); | |
| 2049 | + } | |
| 2050 | + | |
| 2051 | + // Cache bust for the updated avatar | |
| 2052 | + await fetch(getUserAvatar(avatarId), { cache: 'reload' }); | |
| 2053 | + await fetch(getThumbnailUrl('persona', avatarId), { cache: 'reload' }); | |
| 2054 | + reloadUserAvatar(true); | |
| 2055 | + return true; | |
| 2056 | + } catch (error) { | |
| 2057 | + console.error('Error uploading persona avatar:', error); | |
| 2058 | + toastr.warning(t`Failed to upload avatar: ${error.message}`); | |
| 2059 | + return false; | |
| 2060 | + } | |
| 2061 | +} | |
| 2062 | + | |
| 2063 | +/** | |
| 2064 | + * Resolves a persona from the given argument or falls back to the currently active persona. | |
| 2065 | + * @param {string} [personaArg] Persona name or avatar key argument | |
| 2066 | + * @returns {import('./utils.js').PersonaViewModel|null} The resolved persona, or null if not found | |
| 2067 | + */ | |
| 2068 | +function getTargetPersona(personaArg) { | |
| 2069 | + if (personaArg) { | |
| 2070 | + const persona = findPersona({ name: personaArg }); | |
| 2071 | + if (!persona) { | |
| 2072 | + toastr.warning(t`Persona "${personaArg}" not found`); | |
| 2073 | + return null; | |
| 2074 | + } | |
| 2075 | + return persona; | |
| 2076 | + } | |
| 2077 | + | |
| 2078 | + // Fall back to currently active persona | |
| 2079 | + const persona = findPersona({ preferCurrentPersona: true }); | |
| 2080 | + if (!persona) { | |
| 2081 | + toastr.warning(t`No persona selected and no persona argument provided`); | |
| 2082 | + return null; | |
| 2083 | + } | |
| 2084 | + return persona; | |
| 2085 | +} | |
| 2086 | + | |
| 2087 | +// #endregion | |
| 2088 | + | |
| 2089 | +// #region Persona CRUD Slash Command Callbacks | |
| 2090 | + | |
| 2091 | +/** | |
| 2092 | + * Creates a new persona with the specified attributes. | |
| 2093 | + * @param {object} args Named arguments from the slash command | |
| 2094 | + * @returns {Promise<string>} Avatar key of the created persona, or empty string on failure | |
| 2095 | + */ | |
| 2096 | +async function createPersonaCallback(args) { | |
| 2097 | + const name = args.name; | |
| 2098 | + if (!name || typeof name !== 'string' || !name.trim()) { | |
| 2099 | + toastr.warning(t`Persona name is required`); | |
| 2100 | + return ''; | |
| 2101 | + } | |
| 2102 | + | |
| 2103 | + const trimmedName = name.trim(); | |
| 2104 | + const avatarId = `${Date.now()}-${trimmedName.replace(/[^a-zA-Z0-9]/g, '')}.png`; | |
| 2105 | + | |
| 2106 | + const description = args.description ?? ''; | |
| 2107 | + const title = args.title ?? ''; | |
| 2108 | + const position = parsePersonaPosition(args.descriptionPosition) ?? persona_description_positions.IN_PROMPT; | |
| 2109 | + const role = parsePersonaRole(args.descriptionRole) ?? DEFAULT_ROLE; | |
| 2110 | + const lorebook = args.lorebook ?? ''; | |
| 2111 | + | |
| 2112 | + let depth = args.descriptionDepth !== undefined ? Number(args.descriptionDepth) : DEFAULT_DEPTH; | |
| 2113 | + if (isNaN(depth)) { | |
| 2114 | + toastr.warning(t`Invalid description depth "${args.descriptionDepth}", defaulting to ${DEFAULT_DEPTH}`); | |
| 2115 | + depth = DEFAULT_DEPTH; | |
| 2116 | + } | |
| 2117 | + | |
| 2118 | + // Initialize persona data with all fields | |
| 2119 | + await initPersona(avatarId, trimmedName, description, title, { | |
| 2120 | + position, depth, role, lorebook, | |
| 2121 | + }); | |
| 2122 | + | |
| 2123 | + // Handle avatar upload | |
| 2124 | + const avatarData = args.avatar ? await resolveAvatarData(args.avatar) : null; | |
| 2125 | + if (avatarData) { | |
| 2126 | + const resizePrompt = !isFalseBoolean(args.avatarPromptResize ?? 'true'); | |
| 2127 | + const uploaded = await uploadPersonaAvatar(avatarId, avatarData, { resizePrompt }); | |
| 2128 | + if (!uploaded) { | |
| 2129 | + // Crop was cancelled or upload failed — use default avatar | |
| 2130 | + await uploadUserAvatar(default_user_avatar, avatarId); | |
| 2131 | + } | |
| 2132 | + } else { | |
| 2133 | + await uploadUserAvatar(default_user_avatar, avatarId); | |
| 2134 | + } | |
| 2135 | + | |
| 2136 | + saveSettingsDebounced(); | |
| 2137 | + await getUserAvatars(true, avatarId); | |
| 2138 | + | |
| 2139 | + // Select/activate if requested (default: true) | |
| 2140 | + if (!isFalseBoolean(args.select ?? 'true')) { | |
| 2141 | + await setUserAvatar(avatarId); | |
| 2142 | + } | |
| 2143 | + | |
| 2144 | + toastr.success(t`Persona "${trimmedName}" created successfully`); | |
| 2145 | + return avatarId; | |
| 2146 | +} | |
| 2147 | + | |
| 2148 | +/** | |
| 2149 | + * Updates an existing persona's attributes. | |
| 2150 | + * @param {object} args Named arguments from the slash command | |
| 2151 | + * @returns {Promise<string>} Avatar key of the updated persona, or empty string on failure | |
| 2152 | + */ | |
| 2153 | +async function updatePersonaCallback(args) { | |
| 2154 | + const persona = getTargetPersona(args.persona); | |
| 2155 | + if (!persona) return ''; | |
| 2156 | + | |
| 2157 | + const avatarId = persona.avatar; | |
| 2158 | + const descriptor = power_user.persona_descriptions[avatarId]; | |
| 2159 | + | |
| 2160 | + if (!descriptor) { | |
| 2161 | + toastr.warning(t`Persona data not found for "${persona.name}"`); | |
| 2162 | + return ''; | |
| 2163 | + } | |
| 2164 | + | |
| 2165 | + let hasUpdates = false; | |
| 2166 | + | |
| 2167 | + // Update name | |
| 2168 | + if (args.name !== undefined) { | |
| 2169 | + const newName = String(args.name).trim(); | |
| 2170 | + if (newName) { | |
| 2171 | + const oldName = power_user.personas[avatarId]; | |
| 2172 | + power_user.personas[avatarId] = newName; | |
| 2173 | + if (avatarId === user_avatar) { | |
| 2174 | + setUserName(newName); | |
| 2175 | + } | |
| 2176 | + await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName, newName }); | |
| 2177 | + hasUpdates = true; | |
| 2178 | + } | |
| 2179 | + } | |
| 2180 | + | |
| 2181 | + // Update description | |
| 2182 | + if (args.description !== undefined) { | |
| 2183 | + descriptor.description = args.description; | |
| 2184 | + if (avatarId === user_avatar) { | |
| 2185 | + power_user.persona_description = args.description; | |
| 2186 | + } | |
| 2187 | + hasUpdates = true; | |
| 2188 | + } | |
| 2189 | + | |
| 2190 | + // Update title | |
| 2191 | + if (args.title !== undefined) { | |
| 2192 | + descriptor.title = args.title; | |
| 2193 | + hasUpdates = true; | |
| 2194 | + } | |
| 2195 | + | |
| 2196 | + // Update description position | |
| 2197 | + if (args.descriptionPosition !== undefined) { | |
| 2198 | + const position = parsePersonaPosition(args.descriptionPosition); | |
| 2199 | + if (position !== null) { | |
| 2200 | + descriptor.position = position; | |
| 2201 | + if (avatarId === user_avatar) { | |
| 2202 | + power_user.persona_description_position = position; | |
| 2203 | + } | |
| 2204 | + hasUpdates = true; | |
| 2205 | + } | |
| 2206 | + } | |
| 2207 | + | |
| 2208 | + // Update description depth | |
| 2209 | + if (args.descriptionDepth !== undefined) { | |
| 2210 | + const depth = Number(args.descriptionDepth); | |
| 2211 | + if (!isNaN(depth)) { | |
| 2212 | + descriptor.depth = depth; | |
| 2213 | + if (avatarId === user_avatar) { | |
| 2214 | + power_user.persona_description_depth = depth; | |
| 2215 | + } | |
| 2216 | + hasUpdates = true; | |
| 2217 | + } | |
| 2218 | + } | |
| 2219 | + | |
| 2220 | + // Update description role | |
| 2221 | + if (args.descriptionRole !== undefined) { | |
| 2222 | + const role = parsePersonaRole(args.descriptionRole); | |
| 2223 | + if (role !== null) { | |
| 2224 | + descriptor.role = role; | |
| 2225 | + if (avatarId === user_avatar) { | |
| 2226 | + power_user.persona_description_role = role; | |
| 2227 | + } | |
| 2228 | + hasUpdates = true; | |
| 2229 | + } | |
| 2230 | + } | |
| 2231 | + | |
| 2232 | + // Update lorebook | |
| 2233 | + if (args.lorebook !== undefined) { | |
| 2234 | + descriptor.lorebook = args.lorebook; | |
| 2235 | + if (avatarId === user_avatar) { | |
| 2236 | + power_user.persona_description_lorebook = args.lorebook; | |
| 2237 | + } | |
| 2238 | + hasUpdates = true; | |
| 2239 | + } | |
| 2240 | + | |
| 2241 | + // Handle avatar | |
| 2242 | + const avatarData = args.avatar ? await resolveAvatarData(args.avatar) : null; | |
| 2243 | + if (avatarData) { | |
| 2244 | + const resizePrompt = !isFalseBoolean(args.avatarPromptResize ?? 'true'); | |
| 2245 | + const uploaded = await uploadPersonaAvatar(avatarId, avatarData, { resizePrompt }); | |
| 2246 | + if (uploaded) { | |
| 2247 | + hasUpdates = true; | |
| 2248 | + } | |
| 2249 | + } | |
| 2250 | + | |
| 2251 | + if (!hasUpdates) { | |
| 2252 | + toastr.info(t`No fields provided to update`); | |
| 2253 | + return avatarId; | |
| 2254 | + } | |
| 2255 | + | |
| 2256 | + saveSettingsDebounced(); | |
| 2257 | + await eventSource.emit(event_types.PERSONA_UPDATED, avatarId); | |
| 2258 | + | |
| 2259 | + // Refresh UI if the updated persona is the active one | |
| 2260 | + if (avatarId === user_avatar) { | |
| 2261 | + setPersonaDescription(); | |
| 2262 | + } | |
| 2263 | + await getUserAvatars(true, avatarId); | |
| 2264 | + updatePersonaUIStates(); | |
| 2265 | + | |
| 2266 | + toastr.success(t`Persona "${power_user.personas[avatarId]}" updated successfully`); | |
| 2267 | + return avatarId; | |
| 2268 | +} | |
| 2269 | + | |
| 2270 | +/** | |
| 2271 | + * Retrieves persona data or a specific field. | |
| 2272 | + * @param {object} args Named arguments from the slash command | |
| 2273 | + * @returns {Promise<string>} The persona data or field value | |
| 2274 | + */ | |
| 2275 | +async function getPersonaDataCallback(args) { | |
| 2276 | + const persona = getTargetPersona(args.persona); | |
| 2277 | + if (!persona) return ''; | |
| 2278 | + | |
| 2279 | + const avatarId = persona.avatar; | |
| 2280 | + const descriptor = power_user.persona_descriptions[avatarId] ?? {}; | |
| 2281 | + | |
| 2282 | + if (args.field) { | |
| 2283 | + /** @type {Record<string, unknown>} */ | |
| 2284 | + const fieldMap = { | |
| 2285 | + name: power_user.personas[avatarId] ?? '', | |
| 2286 | + description: descriptor.description ?? '', | |
| 2287 | + title: descriptor.title ?? '', | |
| 2288 | + position: descriptor.position ?? persona_description_positions.IN_PROMPT, | |
| 2289 | + depth: descriptor.depth ?? DEFAULT_DEPTH, | |
| 2290 | + role: descriptor.role ?? DEFAULT_ROLE, | |
| 2291 | + lorebook: descriptor.lorebook ?? '', | |
| 2292 | + avatar: avatarId, | |
| 2293 | + default: power_user.default_persona === avatarId, | |
| 2294 | + connections: descriptor.connections ?? [], | |
| 2295 | + }; | |
| 2296 | + | |
| 2297 | + const value = fieldMap[args.field]; | |
| 2298 | + if (value === undefined) { | |
| 2299 | + toastr.warning(t`Unknown persona field "${args.field}"`); | |
| 2300 | + return ''; | |
| 2301 | + } | |
| 2302 | + | |
| 2303 | + return await slashCommandReturnHelper.doReturn( | |
| 2304 | + args.return ?? 'pipe', value, | |
| 2305 | + { objectToStringFunc: x => typeof x === 'object' ? JSON.stringify(x) : String(x) }, | |
| 2306 | + ); | |
| 2307 | + } | |
| 2308 | + | |
| 2309 | + // Return full persona data | |
| 2310 | + const personaData = { | |
| 2311 | + avatar: avatarId, | |
| 2312 | + name: power_user.personas[avatarId] ?? '', | |
| 2313 | + description: descriptor.description ?? '', | |
| 2314 | + title: descriptor.title ?? '', | |
| 2315 | + position: descriptor.position ?? persona_description_positions.IN_PROMPT, | |
| 2316 | + depth: descriptor.depth ?? DEFAULT_DEPTH, | |
| 2317 | + role: descriptor.role ?? DEFAULT_ROLE, | |
| 2318 | + lorebook: descriptor.lorebook ?? '', | |
| 2319 | + default: power_user.default_persona === avatarId, | |
| 2320 | + connections: descriptor.connections ?? [], | |
| 2321 | + }; | |
| 2322 | + | |
| 2323 | + return await slashCommandReturnHelper.doReturn( | |
| 2324 | + args.return ?? 'pipe', personaData, | |
| 2325 | + { objectToStringFunc: x => JSON.stringify(x, null, 2) }, | |
| 2326 | + ); | |
| 2327 | +} | |
| 2328 | + | |
| 2329 | +/** | |
| 2330 | + * Deletes a persona via slash command. | |
| 2331 | + * @param {object} args Named arguments from the slash command | |
| 2332 | + * @returns {Promise<string>} 'true' if deleted, 'false' otherwise | |
| 2333 | + */ | |
| 2334 | +async function deletePersonaCallback(args) { | |
| 2335 | + const persona = getTargetPersona(args.persona); | |
| 2336 | + if (!persona) return 'false'; | |
| 2337 | + | |
| 2338 | + const silent = isTrueBoolean(args.silent); | |
| 2339 | + const success = await deletePersona(persona.avatar, { silent }); | |
| 2340 | + return String(success); | |
| 2341 | +} | |
| 2342 | + | |
| 2343 | +/** | |
| 2344 | + * Duplicates a persona via slash command. | |
| 2345 | + * @param {object} args Named arguments from the slash command | |
| 2346 | + * @returns {Promise<string>} Avatar key of the duplicated persona, or empty string on failure | |
| 2347 | + */ | |
| 2348 | +async function duplicatePersonaCallback(args) { | |
| 2349 | + const persona = getTargetPersona(args.persona); | |
| 2350 | + if (!persona) return ''; | |
| 2351 | + | |
| 2352 | + const shouldSelect = isTrueBoolean(args.select); | |
| 2353 | + const newAvatarId = await duplicatePersona(persona.avatar, { silent: true, select: shouldSelect }); | |
| 2354 | + | |
| 2355 | + if (!newAvatarId) { | |
| 2356 | + toastr.error(t`Failed to duplicate persona`); | |
| 2357 | + return ''; | |
| 2358 | + } | |
| 2359 | + | |
| 2360 | + toastr.success(t`Persona "${power_user.personas[newAvatarId]}" duplicated successfully`); | |
| 2361 | + return newAvatarId; | |
| 2362 | +} | |
| 2363 | + | |
| 2364 | +// #endregion | |
| 2365 | + | |
| 1816 | 2366 | /** |
| 1817 | 2367 | * Locks or unlocks the persona of the current chat. |
| 1818 | 2368 | * @param {{type: string}} _args Named arguments |
| @@ -1870,10 +2420,9 @@ async function setNameCallback({ mode = 'all' }, name) { | ||
| 1870 | 2420 | |
| 1871 | 2421 | // If the name matches a persona avatar, or a name, auto-select it |
| 1872 | 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 | 2424 | if (persona) { |
| 1876 | 2425 | await autoSelectPersona(persona.name, { personaKey: persona.avatar }); |
| 1877 | 2426 | return ''; |
| 1878 | 2427 | } else if (mode === 'lookup') { |
| 1879 | 2428 | toastr.warning(`Persona ${name} not found`); |
| @@ -1889,28 +2438,375 @@ async function setNameCallback({ mode = 'all' }, name) { | ||
| 1889 | 2438 | return ''; |
| 1890 | 2439 | } |
| 1891 | 2440 | |
| 1892 | 2441 | async 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 | 2456 | return ''; |
| 1895 | 2457 | } |
| 1896 | 2458 | |
| 2459 | +/** | |
| 2460 | + * Returns all unique user message names in the current chat for enum autocomplete. | |
| 2461 | + * @returns {SlashCommandEnumValue[]} | |
| 2462 | + */ | |
| 2463 | +function userMessageNamesEnumProvider() { | |
| 2464 | + return chat | |
| 2465 | + .filter(mes => mes.is_user) | |
| 2466 | + .map(mes => mes.name) | |
| 2467 | + .filter(onlyUnique) | |
| 2468 | + .sort(sortIgnoreCaseAndAccents) | |
| 2469 | + .map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.persona)); | |
| 2470 | +} | |
| 2471 | + | |
| 1897 | 2472 | function 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 | 2781 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1899 | 2782 | name: 'persona-lock', |
| 1900 | 2783 | aliases: ['lock', 'bind'], |
| 1901 | 2784 | callback: lockPersonaCallback, |
| 1902 | 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 | 2800 | namedArgumentList: [ |
| 1905 | 2801 | SlashCommandNamedArgument.fromProps({ |
| 1906 | 2802 | name: 'type', |
| 1907 | 2803 | description: 't`The type of the lock, where it should apply to'`, |
| 1908 | 2804 | typeList: [ARGUMENT_TYPE.STRING], |
| 1909 | 2805 | defaultValue: 'chat', |
| 1910 | 2806 | enumList: [ |
| 1911 | 2807 | new SlashCommandEnumValue('chat', 't`Lock the persona to the current chat.'`), |
| 1912 | 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 | 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 | 2822 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1926 | 2823 | name: 'persona-set', |
| 1927 | 2824 | callback: setNameCallback, |
| 1928 | 2825 | aliases: ['persona', 'name'], |
| 1929 | 2826 | namedArgumentList: [ |
| 1930 | 2827 | new SlashCommandNamedArgument.fromProps({ |
| 1931 | - 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)', | |
| 2828 | + name: 'mode', | |
| 1932 | - [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'], | |
| 2829 | + description: t`The mode for persona selection`, | |
| 1933 | - ), | |
| 2830 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 2831 | + defaultValue: 'all', | |
| 2832 | + enumList: [ | |
| 2833 | + new SlashCommandEnumValue('lookup', t`Search for an existing persona only`), | |
| 2834 | + new SlashCommandEnumValue('temp', t`Set a temporary name only (no persona lookup)`), | |
| 2835 | + new SlashCommandEnumValue('all', t`Try persona lookup first, fall back to temporary name`), | |
| 2836 | + ], | |
| 2837 | + }), | |
| 1934 | 2838 | ], |
| 1935 | 2839 | unnamedArgumentList: [ |
| 1936 | 2840 | SlashCommandArgument.fromProps({ |
| 1937 | 2841 | description: 'persona name', |
| 1938 | 2842 | typeList: [ARGUMENT_TYPE.STRING], |
| 1939 | 2843 | isRequired: true, |
| 1940 | 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 | 2864 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1946 | 2865 | name: 'persona-sync', |
| 1947 | 2866 | aliases: ['sync'], |
| 1948 | 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 | } |
| 1952 | 2914 | |
| @@ -2004,7 +2966,7 @@ export async function initPersonas() { | ||
| 2004 | 2966 | debouncedPersonaSearch(searchQuery); |
| 2005 | 2967 | }); |
| 2006 | 2968 | |
| 2007 | 2969 | $('#sync_name_button').on('click', async () => await syncUserNameToPersona()); |
| 2008 | 2970 | $('#avatar_upload_file').on('change', changeUserAvatar); |
| 2009 | 2971 | |
| 2010 | 2972 | $(document).on('click', '#user_avatar_block .avatar-container', async function () { |
| @@ -2,7 +2,7 @@ import dialogPolyfill from '../lib/dialog-polyfill.esm.js'; | ||
| 2 | 2 | import { shouldSendOnEnter } from './RossAscends-mods.js'; |
| 3 | 3 | import { t } from './i18n.js'; |
| 4 | 4 | import { power_user, toastPositionClasses } from './power-user.js'; |
| 5 | 5 | import { clamp, removeFromArray, runAfterAnimation, uuidv4 } from './utils.js'; |
| 6 | 6 | |
| 7 | 7 | /** @readonly */ |
| 8 | 8 | /** @enum {Number} */ |
| @@ -79,8 +79,12 @@ export const POPUP_RESULT = { | ||
| 79 | 79 | * @property {string} label - The label text for the input |
| 80 | 80 | * @property {string?} [tooltip=null] - Optional tooltip to be displayed. Default placeholder in input controls, tooltip icon behind the checkbox for those. |
| 81 | 81 | * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set) |
| 82 | 82 | * @property {('checkbox'|'text'|'textarea'|'number')?} [type='checkbox'] - The type of the input (default is checkbox) |
| 83 | 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 | */ |
| 85 | 89 | |
| 86 | 90 | /** |
| @@ -331,6 +335,7 @@ export class Popup { | ||
| 331 | 335 | inputElement.type = 'checkbox'; |
| 332 | 336 | inputElement.id = input.id; |
| 333 | 337 | inputElement.checked = Boolean(input.defaultState ?? false); |
| 338 | + inputElement.disabled = Boolean(input.disabled ?? false); | |
| 334 | 339 | label.appendChild(inputElement); |
| 335 | 340 | const labelText = document.createElement('span'); |
| 336 | 341 | labelText.innerText = input.label; |
| @@ -356,6 +361,7 @@ export class Popup { | ||
| 356 | 361 | inputElement.id = input.id; |
| 357 | 362 | inputElement.value = String(input.defaultState ?? ''); |
| 358 | 363 | inputElement.placeholder = input.tooltip ?? ''; |
| 364 | + inputElement.disabled = Boolean(input.disabled ?? false); | |
| 359 | 365 | setTitleFromTooltip(inputElement, input.tooltip); |
| 360 | 366 | |
| 361 | 367 | const labelText = document.createElement('span'); |
| @@ -377,6 +383,7 @@ export class Popup { | ||
| 377 | 383 | inputElement.value = String(input.defaultState ?? ''); |
| 378 | 384 | inputElement.rows = input.rows ?? 1; |
| 379 | 385 | inputElement.placeholder = input.tooltip ?? ''; |
| 386 | + inputElement.disabled = Boolean(input.disabled ?? false); | |
| 380 | 387 | setTitleFromTooltip(inputElement, input.tooltip); |
| 381 | 388 | |
| 382 | 389 | const labelText = document.createElement('span'); |
| @@ -387,8 +394,47 @@ export class Popup { | ||
| 387 | 394 | label.appendChild(inputElement); |
| 388 | 395 | |
| 389 | 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 | 436 | } else { |
| 391 | 437 | console.warn('Unknown custom input type. Only checkbox, text, number and textarea are supported.', input); |
| 392 | 438 | return; |
| 393 | 439 | } |
| 394 | 440 | }); |
| @@ -672,6 +718,10 @@ export class Popup { | ||
| 672 | 718 | } |
| 673 | 719 | } |
| 674 | 720 | |
| 721 | + if (!control) { | |
| 722 | + return; | |
| 723 | + } | |
| 724 | + | |
| 675 | 725 | if (applyAutoFocus) { |
| 676 | 726 | control.setAttribute('autofocus', ''); |
| 677 | 727 | // 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 | 770 | this.inputResults = new Map(this.customInputs.map(input => { |
| 721 | 771 | /** @type {HTMLInputElement} */ |
| 722 | 772 | const inputControl = this.dlg.querySelector(`#${input.id}`); |
| 723 | 773 | const value = ['text', 'textarea', 'number'].includes(input.type) ? inputControl.value : inputControl.checked; |
| 724 | 774 | return [inputControl.id, value]; |
| 725 | 775 | })); |
| 726 | 776 | } |
| @@ -68,6 +68,7 @@ import { bindModelTemplates } from './chat-templates.js'; | ||
| 68 | 68 | import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js'; |
| 69 | 69 | import { t } from './i18n.js'; |
| 70 | 70 | import { getBackgroundPath, isCustomBackgroundUrl } from './backgrounds.js'; |
| 71 | +import { persona_description_positions as _persona_description_positions } from './personas.js'; | |
| 71 | 72 | |
| 72 | 73 | export const toastPositionClasses = [ |
| 73 | 74 | 'toast-top-left', |
| @@ -110,17 +111,7 @@ export const send_on_enter_options = { | ||
| 110 | 111 | ENABLED: 1, |
| 111 | 112 | }; |
| 112 | 113 | |
| 113 | 114 | export 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 | -}; | |
| 124 | 115 | |
| 125 | 116 | export const power_user = { |
| 126 | 117 | charListGrid: false, |
| @@ -134,6 +134,7 @@ export function extractReasoningFromData(data, { | ||
| 134 | 134 | case chat_completion_sources.NANOGPT: |
| 135 | 135 | case chat_completion_sources.SILICONFLOW: |
| 136 | 136 | case chat_completion_sources.ZAI: |
| 137 | + case chat_completion_sources.WORKERS_AI: | |
| 137 | 138 | case chat_completion_sources.CUSTOM: { |
| 138 | 139 | return data?.choices?.[0]?.message?.reasoning_content |
| 139 | 140 | ?? data?.choices?.[0]?.message?.reasoning |
| @@ -1010,6 +1011,44 @@ function registerReasoningSlashCommands() { | ||
| 1010 | 1011 | }, |
| 1011 | 1012 | })); |
| 1012 | 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 | 1052 | name: 'reasoning-template', |
| 1014 | 1053 | aliases: ['reasoning-formatting', 'reasoning-preset'], |
| 1015 | 1054 | callback: selectReasoningTemplateCallback, |
| @@ -1082,7 +1121,7 @@ function registerReasoningSlashCommands() { | ||
| 1082 | 1121 | helpString: t`Collapse the reasoning block of a message or range of messages.`, |
| 1083 | 1122 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1084 | 1123 | callback: (_args, value) => { |
| 1085 | 1124 | const details = getReasoningDetailsElements(value.toString()); |
| 1086 | 1125 | if (details) details.removeAttr('open'); |
| 1087 | 1126 | return ''; |
| 1088 | 1127 | }, |
| @@ -1094,7 +1133,7 @@ function registerReasoningSlashCommands() { | ||
| 1094 | 1133 | helpString: t`Expand the reasoning block of a message or range of messages.`, |
| 1095 | 1134 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1096 | 1135 | callback: (_args, value) => { |
| 1097 | 1136 | const details = getReasoningDetailsElements(value.toString()); |
| 1098 | 1137 | if (details) details.attr('open', ''); |
| 1099 | 1138 | return ''; |
| 1100 | 1139 | }, |
| @@ -1106,7 +1145,7 @@ function registerReasoningSlashCommands() { | ||
| 1106 | 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 | 1146 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1108 | 1147 | callback: (_args, value) => { |
| 1109 | 1148 | const details = getReasoningDetailsElements(value.toString()); |
| 1110 | 1149 | if (!details) return ''; |
| 1111 | 1150 | details.each(function () { |
| 1112 | 1151 | const $el = $(this); |
| @@ -1411,6 +1450,36 @@ export function parseReasoningFromString(str, { strict = true } = {}, template = | ||
| 1411 | 1450 | } |
| 1412 | 1451 | |
| 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 | + */ | |
| 1463 | +export function formatReasoning(reasoning, content, template = null) { | |
| 1464 | + template = template ?? power_user.reasoning; | |
| 1465 | + | |
| 1466 | + // If no reasoning provided, return content only | |
| 1467 | + if (!reasoning || !template.prefix || !template.suffix) { | |
| 1468 | + return { formatted: content, contentOnly: content }; | |
| 1469 | + } | |
| 1470 | + | |
| 1471 | + // Substitute macros in template parts | |
| 1472 | + const prefix = substituteParams(template.prefix || ''); | |
| 1473 | + const suffix = substituteParams(template.suffix || ''); | |
| 1474 | + const separator = substituteParams(template.separator || ''); | |
| 1475 | + | |
| 1476 | + // Build the formatted string: prefix + reasoning + suffix + separator + content | |
| 1477 | + const formatted = `${prefix}${reasoning}${suffix}${separator}${content}`; | |
| 1478 | + | |
| 1479 | + return { formatted, contentOnly: content }; | |
| 1480 | +} | |
| 1481 | + | |
| 1482 | +/** | |
| 1414 | 1483 | * Parse reasoning in an array of swipe strings if auto-parsing is enabled. |
| 1415 | 1484 | * @param {string[]} swipes Array of swipe strings |
| 1416 | 1485 | * @param {{extra: Partial<ReasoningMessageExtra>}[]} swipeInfoArray Array of swipe info objects |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { DOMPurify, moment, sha256 } from '../lib.js'; |
| 2 | 2 | import { event_types, eventSource, getRequestHeaders, saveSettings } from '../script.js'; |
| 3 | 3 | import { t } from './i18n.js'; |
| 4 | 4 | import { chat_completion_sources } from './openai.js'; |
| 5 | 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| @@ -12,7 +12,9 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | ||
| 12 | 12 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 13 | 13 | import { renderTemplateAsync } from './templates.js'; |
| 14 | 14 | import { textgen_types } from './textgen-settings.js'; |
| 15 | 15 | import { copyText, isTrueBooleangetCurrentUserHandle } from './utilsuser.js'; |
| 16 | +import { copyText, isTrueBoolean, uuidv4 } from './utils.js'; | |
| 17 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 16 | 18 | |
| 17 | 19 | export const SECRET_KEYS = { |
| 18 | 20 | HORDE: 'api_key_horde', |
| @@ -76,6 +78,7 @@ export const SECRET_KEYS = { | ||
| 76 | 78 | POLLINATIONS: 'api_key_pollinations', |
| 77 | 79 | VOLCENGINE_APP_ID: 'volcengine_app_id', |
| 78 | 80 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', |
| 81 | + WORKERS_AI: 'api_key_workers_ai', | |
| 79 | 82 | }; |
| 80 | 83 | |
| 81 | 84 | const FRIENDLY_NAMES = { |
| @@ -129,7 +132,7 @@ const FRIENDLY_NAMES = { | ||
| 129 | 132 | [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)', |
| 130 | 133 | [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)', |
| 131 | 134 | [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)', |
| 132 | 135 | [SECRET_KEYS.MINIMAX]: 'MiniMax TTS', |
| 133 | 136 | [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID', |
| 134 | 137 | [SECRET_KEYS.MOONSHOT]: 'Moonshot AI', |
| 135 | 138 | [SECRET_KEYS.COMETAPI]: 'CometAPI', |
| @@ -140,6 +143,7 @@ const FRIENDLY_NAMES = { | ||
| 140 | 143 | [SECRET_KEYS.POLLINATIONS]: 'Pollinations', |
| 141 | 144 | [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID', |
| 142 | 145 | [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key', |
| 146 | + [SECRET_KEYS.WORKERS_AI]: 'Cloudflare Workers AI', | |
| 143 | 147 | }; |
| 144 | 148 | |
| 145 | 149 | const INPUT_MAP = { |
| @@ -182,8 +186,9 @@ const INPUT_MAP = { | ||
| 182 | 186 | [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai', |
| 183 | 187 | [SECRET_KEYS.ZAI]: '#api_key_zai', |
| 184 | 188 | [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow', |
| 185 | 189 | [SECRET_KEYS.COMFY_RUNPODMINIMAX]: '#api_key_comfy_runpodapi_key_minimax', |
| 186 | 190 | [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations', |
| 191 | + [SECRET_KEYS.WORKERS_AI]: '#api_key_workers_ai', | |
| 187 | 192 | }; |
| 188 | 193 | |
| 189 | 194 | const getLabel = () => moment().format('L LT'); |
| @@ -414,7 +419,6 @@ export async function readSecretState() { | ||
| 414 | 419 | secret_state = await response.json(); |
| 415 | 420 | updateSecretDisplay(); |
| 416 | 421 | updateInputDataLists(); |
| 417 | - await checkOpenRouterAuth(); | |
| 418 | 422 | } |
| 419 | 423 | } catch { |
| 420 | 424 | console.error('Could not read secrets file'); |
| @@ -495,6 +499,25 @@ export async function renameSecret(key, id, label) { | ||
| 495 | 499 | } |
| 496 | 500 | |
| 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 | + */ | |
| 506 | +const getVerifierKey = (source) => `${getCurrentUserHandle()}_${source}_code_verifier`; | |
| 507 | + | |
| 508 | +/** | |
| 509 | + * Generates a code challenge for PKCE authentication flows. | |
| 510 | + * @param {string} input Input secret string to generate the code challenge from. | |
| 511 | + * @returns {string} S256 code challenge generated from the input string, encoded in base64url format. | |
| 512 | + */ | |
| 513 | +const generateChallenge = (input) => { | |
| 514 | + const encoder = new TextEncoder(); | |
| 515 | + const data = encoder.encode(input); | |
| 516 | + const hashBytes = sha256.array(data); | |
| 517 | + return btoa(String.fromCharCode(...hashBytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); | |
| 518 | +}; | |
| 519 | + | |
| 520 | +/** | |
| 498 | 521 | * Redirects the user to authorize OpenRouter. |
| 499 | 522 | */ |
| 500 | 523 | async function authorizeOpenRouter() { |
| @@ -505,8 +528,15 @@ async function authorizeOpenRouter() { | ||
| 505 | 528 | } |
| 506 | 529 | } |
| 507 | 530 | |
| 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 | 538 | const redirectUrl = new URL('/callback/openrouter', window.location.origin); |
| 509 | 539 | const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(redirectUrl.toString())}&code_challenge=${codeChallenge}&code_challenge_method=S256`; |
| 510 | 540 | location.href = openRouterUrl; |
| 511 | 541 | } |
| 512 | 542 | |
| @@ -514,16 +544,32 @@ async function authorizeOpenRouter() { | ||
| 514 | 544 | * Checks if the OpenRouter authorization code is present in the URL, and if so, exchanges it for an API key. |
| 515 | 545 | * @returns {Promise<void>} |
| 516 | 546 | */ |
| 517 | 547 | export async function checkOpenRouterAuth() { |
| 518 | 548 | const params = new URLSearchParams(location.search); |
| 519 | 549 | const source = params.get('source'); |
| 520 | 550 | if (source === 'openrouter') { |
| 521 | 551 | const query = new URLSearchParams(params.get('query')); |
| 522 | - const code = query.get('code'); | |
| 523 | 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 | 563 | const response = await fetch('https://openrouter.ai/api/v1/auth/keys', { |
| 525 | 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 | }); |
| 528 | 574 | |
| 529 | 575 | if (!response.ok) { |
| @@ -539,18 +585,22 @@ async function checkOpenRouterAuth() { | ||
| 539 | 585 | |
| 540 | 586 | if (secret_state[SECRET_KEYS.OPENROUTER]) { |
| 541 | 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 | 588 | } else { |
| 547 | 589 | throw new Error('OpenRouter token not saved'); |
| 548 | 590 | } |
| 549 | 591 | } catch (err) { |
| 550 | 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 | } |
| 555 | 605 | |
| 556 | 606 | /** |
| @@ -1118,5 +1168,126 @@ export async function initSecrets() { | ||
| 1118 | 1168 | warningElement.toggle(value.length > 0); |
| 1119 | 1169 | }); |
| 1120 | 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 | 1292 | registerSecretSlashCommands(); |
| 1122 | 1293 | } |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { Fuse, DOMPurify } from '../lib.js'; |
| 2 | 2 | import { canUseNegativeLookbehind, copyText, findPersona, flashHighlight, getBase64Async, ensureImageFormatSupported, supportedImageMimeTypes, isExternalUrlresolveAvatarData } from './utils.js'; |
| 3 | 3 | |
| 4 | 4 | import { |
| 5 | 5 | Generate, |
| @@ -70,7 +70,7 @@ import { hideChatMessageRange } from './chats.js'; | ||
| 70 | 70 | import { getContext, saveMetadataDebounced } from './extensions.js'; |
Diff truncated