Merge pull request #5591 from SillyTavern/staging Staging
Signed| @@ -19,7 +19,7 @@ jobs: | |||
| 19 | - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 | 19 | - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 |
| 20 | with: | 20 | with: |
| 21 | node-version: 24 | 21 | node-version: 24 |
| 22 | - run: npm ci | 22 | - run: npm ci --omit=dev --ignore-scripts |
| 23 | 23 | ||
| 24 | publish-npm: | 24 | publish-npm: |
| 25 | needs: build | 25 | needs: build |
| @@ -30,5 +30,5 @@ jobs: | |||
| 30 | with: | 30 | with: |
| 31 | node-version: 24 | 31 | node-version: 24 |
| 32 | registry-url: https://registry.npmjs.org/ | 32 | registry-url: https://registry.npmjs.org/ |
| 33 | - run: npm ci | 33 | - run: npm ci --omit=dev --ignore-scripts |
| 34 | - run: npm publish | 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 | RUN \ | 20 | RUN \ |
| 21 | echo "*** Install npm packages ***" && \ | 21 | echo "*** Install npm packages ***" && \ |
| 22 | npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force | 22 | npm ci --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts && npm cache clean --force |
| 23 | 23 | ||
| 24 | # Create config directory and link config.yaml. Added hardcoded dirs(constants.js?) | 24 | # Create config directory and link config.yaml. Added hardcoded dirs(constants.js?) |
| 25 | # that must be present for Non-Root Mode and volumeless docker runs. | 25 | # that must be present for Non-Root Mode and volumeless docker runs. |
| @@ -1,7 +1,7 @@ | |||
| 1 | @echo off | 1 | @echo off |
| 2 | pushd %~dp0 | 2 | pushd %~dp0 |
| 3 | set NODE_ENV=production | 3 | set NODE_ENV=production |
| 4 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev | 4 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts |
| 5 | node server.js %* | 5 | node server.js %* |
| 6 | pause | 6 | pause |
| 7 | popd | 7 | popd |
| @@ -20,7 +20,7 @@ if %errorlevel% neq 0 ( | |||
| 20 | ) | 20 | ) |
| 21 | ) | 21 | ) |
| 22 | set NODE_ENV=production | 22 | set NODE_ENV=production |
| 23 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev | 23 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts |
| 24 | node server.js %* | 24 | node server.js %* |
| 25 | :end | 25 | :end |
| 26 | pause | 26 | pause |
| @@ -102,7 +102,7 @@ if %errorlevel% neq 0 ( | |||
| 102 | 102 | ||
| 103 | echo Installing npm packages and starting server | 103 | echo Installing npm packages and starting server |
| 104 | set NODE_ENV=production | 104 | set NODE_ENV=production |
| 105 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev | 105 | call npm install --no-save --no-audit --no-fund --loglevel=error --no-progress --omit=dev --ignore-scripts |
| 106 | node server.js %* | 106 | node server.js %* |
| 107 | 107 | ||
| 108 | :end | 108 | :end |
| @@ -41,6 +41,9 @@ port: 8000 | |||
| 41 | # Interval in seconds to write a heartbeat file. Set to 0 to disable. | 41 | # Interval in seconds to write a heartbeat file. Set to 0 to disable. |
| 42 | # This is used primarily for Docker healthchecks. | 42 | # This is used primarily for Docker healthchecks. |
| 43 | heartbeatInterval: 0 | 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 | # -- SSL options -- | 47 | # -- SSL options -- |
| 45 | ssl: | 48 | ssl: |
| 46 | # Enable SSL/TLS encryption | 49 | # Enable SSL/TLS encryption |
| @@ -55,7 +58,7 @@ ssl: | |||
| 55 | # -- SECURITY CONFIGURATION -- | 58 | # -- SECURITY CONFIGURATION -- |
| 56 | # Toggle whitelist mode | 59 | # Toggle whitelist mode |
| 57 | whitelistMode: true | 60 | whitelistMode: true |
| 58 | # Whitelist will also verify IP in X-Forwarded-For / X-Real-IP headers | 61 | # When enabled, whitelist will also verify IP in headers enabled in `forwardedHeaders` section. |
| 59 | enableForwardedWhitelist: true | 62 | enableForwardedWhitelist: true |
| 60 | # Whitelist of allowed IP addresses | 63 | # Whitelist of allowed IP addresses |
| 61 | whitelist: | 64 | whitelist: |
| @@ -127,6 +130,13 @@ sso: | |||
| 127 | # as that used for authentik. (Ensure the username in authentik | 130 | # as that used for authentik. (Ensure the username in authentik |
| 128 | # is an exact match in lowercase with that in sillytavern). | 131 | # is an exact match in lowercase with that in sillytavern). |
| 129 | authentikAuth: false | 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 | # Host whitelist configuration. Recommended if you're using a listen mode | 141 | # Host whitelist configuration. Recommended if you're using a listen mode |
| 132 | hostWhitelist: | 142 | hostWhitelist: |
| @@ -141,6 +151,26 @@ hostWhitelist: | |||
| 141 | # - .trycloudflare.com | 151 | # - .trycloudflare.com |
| 142 | hosts: [] | 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 | # User session timeout *in seconds* (defaults to 24 hours). | 174 | # User session timeout *in seconds* (defaults to 24 hours). |
| 145 | ## Set to a positive number to expire session after a certain time of inactivity | 175 | ## Set to a positive number to expire session after a certain time of inactivity |
| 146 | ## Set to 0 to expire session when the browser is closed | 176 | ## Set to 0 to expire session when the browser is closed |
| @@ -159,9 +189,24 @@ logging: | |||
| 159 | minLogLevel: 0 | 189 | minLogLevel: 0 |
| 160 | # -- RATE LIMITING CONFIGURATION -- | 190 | # -- RATE LIMITING CONFIGURATION -- |
| 161 | rateLimiting: | 191 | rateLimiting: |
| 162 | # Use X-Real-IP header instead of socket IP for rate limiting | 192 | # Use any of the enabled headers in the `forwardedHeaders` section to identify the client IP for rate limiting. |
| 163 | # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy) | 193 | # If disabled, only the socket IP will be used, which may not work correctly if you are behind a reverse proxy. |
| 164 | preferRealIpHeader: false | 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 | ## BACKUP CONFIGURATION | 211 | ## BACKUP CONFIGURATION |
| 167 | backups: | 212 | backups: |
| @@ -2,11 +2,11 @@ | |||
| 2 | <html> | 2 | <html> |
| 3 | 3 | ||
| 4 | <head> | 4 | <head> |
| 5 | <title>Not found</title> | 5 | <title>Not Found</title> |
| 6 | </head> | 6 | </head> |
| 7 | 7 | ||
| 8 | <body> | 8 | <body> |
| 9 | <h1>Not found</h1> | 9 | <h1>Not Found</h1> |
| 10 | <p> | 10 | <p> |
| 11 | The requested URL was not found on this server. | 11 | The requested URL was not found on this server. |
| 12 | </p> | 12 | </p> |
| @@ -642,5 +642,37 @@ | |||
| 642 | { | 642 | { |
| 643 | "filename": "presets/reasoning/Think XML.json", | 643 | "filename": "presets/reasoning/Think XML.json", |
| 644 | "type": "reasoning" | 644 | "type": "reasoning" |
| 645 | }, | ||
| 646 | { | ||
| 647 | "filename": "presets/reasoning/Gemma 4.json", | ||
| 648 | "type": "reasoning" | ||
| 649 | }, | ||
| 650 | { | ||
| 651 | "filename": "presets/instruct/Gemma 4.json", | ||
| 652 | "type": "instruct" | ||
| 653 | }, | ||
| 654 | { | ||
| 655 | "filename": "presets/context/Gemma 4.json", | ||
| 656 | "type": "context" | ||
| 657 | }, | ||
| 658 | { | ||
| 659 | "filename": "user.css", | ||
| 660 | "type": "stylesheet" | ||
| 661 | }, | ||
| 662 | { | ||
| 663 | "filename": "errors/forbidden-by-whitelist.html", | ||
| 664 | "type": "error_page" | ||
| 665 | }, | ||
| 666 | { | ||
| 667 | "filename": "errors/host-not-allowed.html", | ||
| 668 | "type": "error_page" | ||
| 669 | }, | ||
| 670 | { | ||
| 671 | "filename": "errors/unauthorized.html", | ||
| 672 | "type": "error_page" | ||
| 673 | }, | ||
| 674 | { | ||
| 675 | "filename": "errors/url-not-found.html", | ||
| 676 | "type": "error_page" | ||
| 645 | } | 677 | } |
| 646 | ] | 678 | ] |
| @@ -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 | "mistralai_model": "mistral-large-latest", | 10 | "mistralai_model": "mistral-large-latest", |
| 11 | "chutes_model": "deepseek-ai/DeepSeek-V3-0324", | 11 | "chutes_model": "deepseek-ai/DeepSeek-V3-0324", |
| 12 | "chutes_sort_models": "alphabetically", | 12 | "chutes_sort_models": "alphabetically", |
| 13 | "minimax_model": "MiniMax-M2.7", | ||
| 14 | "minimax_endpoint": "global", | ||
| 13 | "electronhub_model": "gpt-4o-mini", | 15 | "electronhub_model": "gpt-4o-mini", |
| 14 | "electronhub_sort_models": "alphabetically", | 16 | "electronhub_sort_models": "alphabetically", |
| 15 | "electronhub_group_models": false, | 17 | "electronhub_group_models": false, |
| @@ -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 | #!/bin/sh | 1 | #!/bin/sh |
| 2 | 2 | ||
| 3 | # Function to handle startup logic (Config check + Postinstall + Start) | 3 | # Function to handle startup logic (Config check + init + Start) |
| 4 | start_sillytavern() { | 4 | start_sillytavern() { |
| 5 | local PREFIX="$1" | 5 | local PREFIX="$1" |
| 6 | shift # Remove the first argument (PREFIX) so $@ contains the rest | 6 | shift # Remove the first argument (PREFIX) so $@ contains the rest |
| @@ -11,8 +11,8 @@ start_sillytavern() { | |||
| 11 | $PREFIX cp "default/config.yaml" "config/config.yaml" | 11 | $PREFIX cp "default/config.yaml" "config/config.yaml" |
| 12 | fi | 12 | fi |
| 13 | 13 | ||
| 14 | # Execute postinstall to auto-populate config.yaml with missing values | 14 | # Execute init script to auto-populate config.yaml with missing values |
| 15 | $PREFIX npm run postinstall | 15 | $PREFIX npm run init |
| 16 | 16 | ||
| 17 | # Start the server | 17 | # Start the server |
| 18 | exec $PREFIX node server.js --listen "$@" | 18 | exec $PREFIX node server.js --listen "$@" |
| @@ -42,6 +42,10 @@ declare global { | |||
| 42 | */ | 42 | */ |
| 43 | handle: string | null; | 43 | handle: string | null; |
| 44 | /** | 44 | /** |
| 45 | * Account version tag: shake256 derivative of password hash and salt. | ||
| 46 | */ | ||
| 47 | version: string | null; | ||
| 48 | /** | ||
| 45 | * Last time the session was extended. | 49 | * Last time the session was extended. |
| 46 | */ | 50 | */ |
| 47 | touch: number; | 51 | touch: number; |
| @@ -1,13 +1,12 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "sillytavern", | 2 | "name": "sillytavern", |
| 3 | "version": "1.17.0", | 3 | "version": "1.18.0", |
| 4 | "lockfileVersion": 3, | 4 | "lockfileVersion": 3, |
| 5 | "requires": true, | 5 | "requires": true, |
| 6 | "packages": { | 6 | "packages": { |
| 7 | "": { | 7 | "": { |
| 8 | "name": "sillytavern", | 8 | "name": "sillytavern", |
| 9 | "version": "1.17.0", | 9 | "version": "1.18.0", |
| 10 | "hasInstallScript": true, | ||
| 11 | "license": "AGPL-3.0", | 10 | "license": "AGPL-3.0", |
| 12 | "dependencies": { | 11 | "dependencies": { |
| 13 | "@adobe/css-tools": "^4.4.4", | 12 | "@adobe/css-tools": "^4.4.4", |
| @@ -39,13 +38,14 @@ | |||
| 39 | "@mozilla/readability": "^0.6.0", | 38 | "@mozilla/readability": "^0.6.0", |
| 40 | "@popperjs/core": "^2.11.8", | 39 | "@popperjs/core": "^2.11.8", |
| 41 | "@zeldafan0225/ai_horde": "^5.2.0", | 40 | "@zeldafan0225/ai_horde": "^5.2.0", |
| 41 | "agent-base": "^7.1.3", | ||
| 42 | "archiver": "^7.0.1", | 42 | "archiver": "^7.0.1", |
| 43 | "bing-translate-api": "^4.1.0", | 43 | "bing-translate-api": "^4.1.0", |
| 44 | "body-parser": "^1.20.2", | 44 | "body-parser": "^1.20.2", |
| 45 | "bowser": "^2.12.1", | 45 | "bowser": "^2.12.1", |
| 46 | "bytes": "^3.1.2", | 46 | "bytes": "^3.1.2", |
| 47 | "chalk": "^5.6.0", | 47 | "chalk": "^5.6.0", |
| 48 | "chevrotain": "^11.1.1", | 48 | "chevrotain": "^11.2.0", |
| 49 | "command-exists": "^1.2.9", | 49 | "command-exists": "^1.2.9", |
| 50 | "compression": "^1.8.1", | 50 | "compression": "^1.8.1", |
| 51 | "cookie-parser": "^1.4.6", | 51 | "cookie-parser": "^1.4.6", |
| @@ -54,7 +54,7 @@ | |||
| 54 | "crc": "^4.3.2", | 54 | "crc": "^4.3.2", |
| 55 | "csrf-sync": "^4.2.1", | 55 | "csrf-sync": "^4.2.1", |
| 56 | "diff-match-patch": "^1.0.5", | 56 | "diff-match-patch": "^1.0.5", |
| 57 | "dompurify": "^3.2.6", | 57 | "dompurify": "^3.4.2", |
| 58 | "droll": "^0.2.1", | 58 | "droll": "^0.2.1", |
| 59 | "env-paths": "^3.0.0", | 59 | "env-paths": "^3.0.0", |
| 60 | "express": "^4.21.0", | 60 | "express": "^4.21.0", |
| @@ -74,8 +74,9 @@ | |||
| 74 | "ipaddr.js": "^2.2.0", | 74 | "ipaddr.js": "^2.2.0", |
| 75 | "is-docker": "^3.0.0", | 75 | "is-docker": "^3.0.0", |
| 76 | "isomorphic-git": "^1.36.3", | 76 | "isomorphic-git": "^1.36.3", |
| 77 | "js-sha256": "^0.11.1", | ||
| 77 | "localforage": "^1.10.0", | 78 | "localforage": "^1.10.0", |
| 78 | "lodash": "^4.17.21", | 79 | "lodash": "^4.18.1", |
| 79 | "mime-types": "^3.0.2", | 80 | "mime-types": "^3.0.2", |
| 80 | "moment": "^2.30.1", | 81 | "moment": "^2.30.1", |
| 81 | "morphdom": "^2.7.7", | 82 | "morphdom": "^2.7.7", |
| @@ -109,7 +110,7 @@ | |||
| 109 | "sillytavern": "src/server-global.js" | 110 | "sillytavern": "src/server-global.js" |
| 110 | }, | 111 | }, |
| 111 | "devDependencies": { | 112 | "devDependencies": { |
| 112 | "@chevrotain/types": "^11.0.3", | 113 | "@chevrotain/types": "^11.2.0", |
| 113 | "@types/archiver": "^6.0.3", | 114 | "@types/archiver": "^6.0.3", |
| 114 | "@types/bytes": "^3.1.5", | 115 | "@types/bytes": "^3.1.5", |
| 115 | "@types/command-exists": "^1.2.3", | 116 | "@types/command-exists": "^1.2.3", |
| @@ -123,7 +124,7 @@ | |||
| 123 | "@types/jquery-cropper": "^1.0.4", | 124 | "@types/jquery-cropper": "^1.0.4", |
| 124 | "@types/jquery.transit": "^0.9.33", | 125 | "@types/jquery.transit": "^0.9.33", |
| 125 | "@types/jqueryui": "^1.12.24", | 126 | "@types/jqueryui": "^1.12.24", |
| 126 | "@types/lodash": "^4.17.20", | 127 | "@types/lodash": "^4.17.24", |
| 127 | "@types/mime-types": "^3.0.1", | 128 | "@types/mime-types": "^3.0.1", |
| 128 | "@types/multer": "^2.1.0", | 129 | "@types/multer": "^2.1.0", |
| 129 | "@types/node": "^18.19.84", | 130 | "@types/node": "^18.19.84", |
| @@ -139,7 +140,8 @@ | |||
| 139 | "eslint": "^8.57.1", | 140 | "eslint": "^8.57.1", |
| 140 | "eslint-plugin-jest": "^27.9.0", | 141 | "eslint-plugin-jest": "^27.9.0", |
| 141 | "eslint-plugin-jsdoc": "^48.10.0", | 142 | "eslint-plugin-jsdoc": "^48.10.0", |
| 142 | "eslint-plugin-playwright": "^2.3.0" | 143 | "eslint-plugin-playwright": "^2.3.0", |
| 144 | "typescript": "^5.9.3" | ||
| 143 | }, | 145 | }, |
| 144 | "engines": { | 146 | "engines": { |
| 145 | "node": ">= 20" | 147 | "node": ">= 20" |
| @@ -177,42 +179,42 @@ | |||
| 177 | "license": "Apache-2.0" | 179 | "license": "Apache-2.0" |
| 178 | }, | 180 | }, |
| 179 | "node_modules/@chevrotain/cst-dts-gen": { | 181 | "node_modules/@chevrotain/cst-dts-gen": { |
| 180 | "version": "11.1.1", | 182 | "version": "11.2.0", |
| 181 | "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", | 183 | "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.2.0.tgz", |
| 182 | "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", | 184 | "integrity": "sha512-ssJFvn/UXhQQeICw3SR/fZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==", |
| 183 | "license": "Apache-2.0", | 185 | "license": "Apache-2.0", |
| 184 | "dependencies": { | 186 | "dependencies": { |
| 185 | "@chevrotain/gast": "11.1.1", | 187 | "@chevrotain/gast": "11.2.0", |
| 186 | "@chevrotain/types": "11.1.1", | 188 | "@chevrotain/types": "11.2.0", |
| 187 | "lodash-es": "4.17.23" | 189 | "lodash-es": "4.17.23" |
| 188 | } | 190 | } |
| 189 | }, | 191 | }, |
| 190 | "node_modules/@chevrotain/gast": { | 192 | "node_modules/@chevrotain/gast": { |
| 191 | "version": "11.1.1", | 193 | "version": "11.2.0", |
| 192 | "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", | 194 | "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.2.0.tgz", |
| 193 | "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", | 195 | "integrity": "sha512-c+KoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==", |
| 194 | "license": "Apache-2.0", | 196 | "license": "Apache-2.0", |
| 195 | "dependencies": { | 197 | "dependencies": { |
| 196 | "@chevrotain/types": "11.1.1", | 198 | "@chevrotain/types": "11.2.0", |
| 197 | "lodash-es": "4.17.23" | 199 | "lodash-es": "4.17.23" |
| 198 | } | 200 | } |
| 199 | }, | 201 | }, |
| 200 | "node_modules/@chevrotain/regexp-to-ast": { | 202 | "node_modules/@chevrotain/regexp-to-ast": { |
| 201 | "version": "11.1.1", | 203 | "version": "11.2.0", |
| 202 | "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", | 204 | "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.2.0.tgz", |
| 203 | "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", | 205 | "integrity": "sha512-lG73pBFqbXODTbXhdZwv0oyUaI+3Irm+uOv5/W79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==", |
| 204 | "license": "Apache-2.0" | 206 | "license": "Apache-2.0" |
| 205 | }, | 207 | }, |
| 206 | "node_modules/@chevrotain/types": { | 208 | "node_modules/@chevrotain/types": { |
| 207 | "version": "11.1.1", | 209 | "version": "11.2.0", |
| 208 | "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", | 210 | "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.2.0.tgz", |
| 209 | "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", | 211 | "integrity": "sha512-vBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+kzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==", |
| 210 | "license": "Apache-2.0" | 212 | "license": "Apache-2.0" |
| 211 | }, | 213 | }, |
| 212 | "node_modules/@chevrotain/utils": { | 214 | "node_modules/@chevrotain/utils": { |
| 213 | "version": "11.1.1", | 215 | "version": "11.2.0", |
| 214 | "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", | 216 | "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.2.0.tgz", |
| 215 | "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", | 217 | "integrity": "sha512-+7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==", |
| 216 | "license": "Apache-2.0" | 218 | "license": "Apache-2.0" |
| 217 | }, | 219 | }, |
| 218 | "node_modules/@es-joy/jsdoccomment": { | 220 | "node_modules/@es-joy/jsdoccomment": { |
| @@ -549,7 +551,6 @@ | |||
| 549 | "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", | 551 | "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", |
| 550 | "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", | 552 | "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", |
| 551 | "license": "MIT", | 553 | "license": "MIT", |
| 552 | "peer": true, | ||
| 553 | "dependencies": { | 554 | "dependencies": { |
| 554 | "@jimp/core": "^0.22.12" | 555 | "@jimp/core": "^0.22.12" |
| 555 | } | 556 | } |
| @@ -1011,7 +1012,6 @@ | |||
| 1011 | "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", | 1012 | "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", |
| 1012 | "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", | 1013 | "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", |
| 1013 | "license": "MIT", | 1014 | "license": "MIT", |
| 1014 | "peer": true, | ||
| 1015 | "dependencies": { | 1015 | "dependencies": { |
| 1016 | "@jimp/core": "1.6.0", | 1016 | "@jimp/core": "1.6.0", |
| 1017 | "@jimp/types": "1.6.0", | 1017 | "@jimp/types": "1.6.0", |
| @@ -1043,7 +1043,6 @@ | |||
| 1043 | "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", | 1043 | "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", |
| 1044 | "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", | 1044 | "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", |
| 1045 | "license": "MIT", | 1045 | "license": "MIT", |
| 1046 | "peer": true, | ||
| 1047 | "dependencies": { | 1046 | "dependencies": { |
| 1048 | "@jimp/utils": "^0.22.12" | 1047 | "@jimp/utils": "^0.22.12" |
| 1049 | }, | 1048 | }, |
| @@ -1139,7 +1138,6 @@ | |||
| 1139 | "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", | 1138 | "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", |
| 1140 | "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", | 1139 | "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", |
| 1141 | "license": "MIT", | 1140 | "license": "MIT", |
| 1142 | "peer": true, | ||
| 1143 | "dependencies": { | 1141 | "dependencies": { |
| 1144 | "@jimp/utils": "^0.22.12" | 1142 | "@jimp/utils": "^0.22.12" |
| 1145 | }, | 1143 | }, |
| @@ -1176,7 +1174,6 @@ | |||
| 1176 | "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", | 1174 | "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", |
| 1177 | "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", | 1175 | "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", |
| 1178 | "license": "MIT", | 1176 | "license": "MIT", |
| 1179 | "peer": true, | ||
| 1180 | "dependencies": { | 1177 | "dependencies": { |
| 1181 | "@jimp/utils": "^0.22.12", | 1178 | "@jimp/utils": "^0.22.12", |
| 1182 | "tinycolor2": "^1.6.0" | 1179 | "tinycolor2": "^1.6.0" |
| @@ -1220,7 +1217,6 @@ | |||
| 1220 | "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", | 1217 | "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", |
| 1221 | "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", | 1218 | "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", |
| 1222 | "license": "MIT", | 1219 | "license": "MIT", |
| 1223 | "peer": true, | ||
| 1224 | "dependencies": { | 1220 | "dependencies": { |
| 1225 | "@jimp/utils": "^0.22.12" | 1221 | "@jimp/utils": "^0.22.12" |
| 1226 | }, | 1222 | }, |
| @@ -1308,7 +1304,6 @@ | |||
| 1308 | "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", | 1304 | "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", |
| 1309 | "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", | 1305 | "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", |
| 1310 | "license": "MIT", | 1306 | "license": "MIT", |
| 1311 | "peer": true, | ||
| 1312 | "dependencies": { | 1307 | "dependencies": { |
| 1313 | "@jimp/utils": "^0.22.12" | 1308 | "@jimp/utils": "^0.22.12" |
| 1314 | }, | 1309 | }, |
| @@ -1321,7 +1316,6 @@ | |||
| 1321 | "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", | 1316 | "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", |
| 1322 | "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", | 1317 | "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", |
| 1323 | "license": "MIT", | 1318 | "license": "MIT", |
| 1324 | "peer": true, | ||
| 1325 | "dependencies": { | 1319 | "dependencies": { |
| 1326 | "@jimp/utils": "^0.22.12" | 1320 | "@jimp/utils": "^0.22.12" |
| 1327 | }, | 1321 | }, |
| @@ -1922,7 +1916,6 @@ | |||
| 1922 | "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", | 1916 | "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", |
| 1923 | "dev": true, | 1917 | "dev": true, |
| 1924 | "license": "MIT", | 1918 | "license": "MIT", |
| 1925 | "peer": true, | ||
| 1926 | "dependencies": { | 1919 | "dependencies": { |
| 1927 | "@types/body-parser": "*", | 1920 | "@types/body-parser": "*", |
| 1928 | "@types/express-serve-static-core": "^4.17.33", | 1921 | "@types/express-serve-static-core": "^4.17.33", |
| @@ -2018,9 +2011,9 @@ | |||
| 2018 | } | 2011 | } |
| 2019 | }, | 2012 | }, |
| 2020 | "node_modules/@types/lodash": { | 2013 | "node_modules/@types/lodash": { |
| 2021 | "version": "4.17.20", | 2014 | "version": "4.17.24", |
| 2022 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", | 2015 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", |
| 2023 | "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", | 2016 | "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", |
| 2024 | "dev": true, | 2017 | "dev": true, |
| 2025 | "license": "MIT" | 2018 | "license": "MIT" |
| 2026 | }, | 2019 | }, |
| @@ -2612,7 +2605,6 @@ | |||
| 2612 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", | 2605 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", |
| 2613 | "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", | 2606 | "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", |
| 2614 | "license": "MIT", | 2607 | "license": "MIT", |
| 2615 | "peer": true, | ||
| 2616 | "bin": { | 2608 | "bin": { |
| 2617 | "acorn": "bin/acorn" | 2609 | "acorn": "bin/acorn" |
| 2618 | }, | 2610 | }, |
| @@ -2799,9 +2791,9 @@ | |||
| 2799 | } | 2791 | } |
| 2800 | }, | 2792 | }, |
| 2801 | "node_modules/archiver-utils/node_modules/brace-expansion": { | 2793 | "node_modules/archiver-utils/node_modules/brace-expansion": { |
| 2802 | "version": "2.0.2", | 2794 | "version": "2.1.0", |
| 2803 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | 2795 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", |
| 2804 | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | 2796 | "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", |
| 2805 | "license": "MIT", | 2797 | "license": "MIT", |
| 2806 | "dependencies": { | 2798 | "dependencies": { |
| 2807 | "balanced-match": "^1.0.0" | 2799 | "balanced-match": "^1.0.0" |
| @@ -3021,14 +3013,23 @@ | |||
| 3021 | } | 3013 | } |
| 3022 | }, | 3014 | }, |
| 3023 | "node_modules/axios": { | 3015 | "node_modules/axios": { |
| 3024 | "version": "1.13.5", | 3016 | "version": "1.15.2", |
| 3025 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", | 3017 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", |
| 3026 | "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", | 3018 | "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", |
| 3027 | "license": "MIT", | 3019 | "license": "MIT", |
| 3028 | "dependencies": { | 3020 | "dependencies": { |
| 3029 | "follow-redirects": "^1.15.11", | 3021 | "follow-redirects": "^1.15.11", |
| 3030 | "form-data": "^4.0.5", | 3022 | "form-data": "^4.0.5", |
| 3031 | "proxy-from-env": "^1.1.0" | 3023 | "proxy-from-env": "^2.1.0" |
| 3024 | } | ||
| 3025 | }, | ||
| 3026 | "node_modules/axios/node_modules/proxy-from-env": { | ||
| 3027 | "version": "2.1.0", | ||
| 3028 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", | ||
| 3029 | "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", | ||
| 3030 | "license": "MIT", | ||
| 3031 | "engines": { | ||
| 3032 | "node": ">=10" | ||
| 3032 | } | 3033 | } |
| 3033 | }, | 3034 | }, |
| 3034 | "node_modules/b4a": { | 3035 | "node_modules/b4a": { |
| @@ -3083,9 +3084,9 @@ | |||
| 3083 | } | 3084 | } |
| 3084 | }, | 3085 | }, |
| 3085 | "node_modules/basic-ftp": { | 3086 | "node_modules/basic-ftp": { |
| 3086 | "version": "5.2.0", | 3087 | "version": "5.3.1", |
| 3087 | "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", | 3088 | "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", |
| 3088 | "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", | 3089 | "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", |
| 3089 | "license": "MIT", | 3090 | "license": "MIT", |
| 3090 | "engines": { | 3091 | "engines": { |
| 3091 | "node": ">=10.0.0" | 3092 | "node": ">=10.0.0" |
| @@ -3190,9 +3191,9 @@ | |||
| 3190 | "license": "MIT" | 3191 | "license": "MIT" |
| 3191 | }, | 3192 | }, |
| 3192 | "node_modules/brace-expansion": { | 3193 | "node_modules/brace-expansion": { |
| 3193 | "version": "1.1.12", | 3194 | "version": "1.1.14", |
| 3194 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", | 3195 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", |
| 3195 | "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", | 3196 | "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", |
| 3196 | "dev": true, | 3197 | "dev": true, |
| 3197 | "license": "MIT", | 3198 | "license": "MIT", |
| 3198 | "dependencies": { | 3199 | "dependencies": { |
| @@ -3232,7 +3233,6 @@ | |||
| 3232 | } | 3233 | } |
| 3233 | ], | 3234 | ], |
| 3234 | "license": "MIT", | 3235 | "license": "MIT", |
| 3235 | "peer": true, | ||
| 3236 | "dependencies": { | 3236 | "dependencies": { |
| 3237 | "baseline-browser-mapping": "^2.9.0", | 3237 | "baseline-browser-mapping": "^2.9.0", |
| 3238 | "caniuse-lite": "^1.0.30001759", | 3238 | "caniuse-lite": "^1.0.30001759", |
| @@ -3503,16 +3503,16 @@ | |||
| 3503 | } | 3503 | } |
| 3504 | }, | 3504 | }, |
| 3505 | "node_modules/chevrotain": { | 3505 | "node_modules/chevrotain": { |
| 3506 | "version": "11.1.1", | 3506 | "version": "11.2.0", |
| 3507 | "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", | 3507 | "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.2.0.tgz", |
| 3508 | "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", | 3508 | "integrity": "sha512-mHCHTxM51nCklUw9RzRVc0DLjAh/SAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==", |
| 3509 | "license": "Apache-2.0", | 3509 | "license": "Apache-2.0", |
| 3510 | "dependencies": { | 3510 | "dependencies": { |
| 3511 | "@chevrotain/cst-dts-gen": "11.1.1", | 3511 | "@chevrotain/cst-dts-gen": "11.2.0", |
| 3512 | "@chevrotain/gast": "11.1.1", | 3512 | "@chevrotain/gast": "11.2.0", |
| 3513 | "@chevrotain/regexp-to-ast": "11.1.1", | 3513 | "@chevrotain/regexp-to-ast": "11.2.0", |
| 3514 | "@chevrotain/types": "11.1.1", | 3514 | "@chevrotain/types": "11.2.0", |
| 3515 | "@chevrotain/utils": "11.1.1", | 3515 | "@chevrotain/utils": "11.2.0", |
| 3516 | "lodash-es": "4.17.23" | 3516 | "lodash-es": "4.17.23" |
| 3517 | } | 3517 | } |
| 3518 | }, | 3518 | }, |
| @@ -4322,13 +4322,10 @@ | |||
| 4322 | } | 4322 | } |
| 4323 | }, | 4323 | }, |
| 4324 | "node_modules/dompurify": { | 4324 | "node_modules/dompurify": { |
| 4325 | "version": "3.3.2", | 4325 | "version": "3.4.2", |
| 4326 | "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", | 4326 | "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", |
| 4327 | "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", | 4327 | "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", |
| 4328 | "license": "(MPL-2.0 OR Apache-2.0)", | 4328 | "license": "(MPL-2.0 OR Apache-2.0)", |
| 4329 | "engines": { | ||
| 4330 | "node": ">=20" | ||
| 4331 | }, | ||
| 4332 | "optionalDependencies": { | 4329 | "optionalDependencies": { |
| 4333 | "@types/trusted-types": "^2.0.7" | 4330 | "@types/trusted-types": "^2.0.7" |
| 4334 | } | 4331 | } |
| @@ -4567,7 +4564,6 @@ | |||
| 4567 | "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", | 4564 | "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", |
| 4568 | "dev": true, | 4565 | "dev": true, |
| 4569 | "license": "MIT", | 4566 | "license": "MIT", |
| 4570 | "peer": true, | ||
| 4571 | "dependencies": { | 4567 | "dependencies": { |
| 4572 | "@eslint-community/eslint-utils": "^4.2.0", | 4568 | "@eslint-community/eslint-utils": "^4.2.0", |
| 4573 | "@eslint-community/regexpp": "^4.6.1", | 4569 | "@eslint-community/regexpp": "^4.6.1", |
| @@ -5209,9 +5205,9 @@ | |||
| 5209 | "license": "ISC" | 5205 | "license": "ISC" |
| 5210 | }, | 5206 | }, |
| 5211 | "node_modules/follow-redirects": { | 5207 | "node_modules/follow-redirects": { |
| 5212 | "version": "1.15.11", | 5208 | "version": "1.16.0", |
| 5213 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", | 5209 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", |
| 5214 | "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", | 5210 | "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", |
| 5215 | "funding": [ | 5211 | "funding": [ |
| 5216 | { | 5212 | { |
| 5217 | "type": "individual", | 5213 | "type": "individual", |
| @@ -6378,6 +6374,12 @@ | |||
| 6378 | "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", | 6374 | "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", |
| 6379 | "license": "BSD-3-Clause" | 6375 | "license": "BSD-3-Clause" |
| 6380 | }, | 6376 | }, |
| 6377 | "node_modules/js-sha256": { | ||
| 6378 | "version": "0.11.1", | ||
| 6379 | "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.1.tgz", | ||
| 6380 | "integrity": "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==", | ||
| 6381 | "license": "MIT" | ||
| 6382 | }, | ||
| 6381 | "node_modules/js-yaml": { | 6383 | "node_modules/js-yaml": { |
| 6382 | "version": "4.1.1", | 6384 | "version": "4.1.1", |
| 6383 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", | 6385 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", |
| @@ -6614,9 +6616,9 @@ | |||
| 6614 | } | 6616 | } |
| 6615 | }, | 6617 | }, |
| 6616 | "node_modules/lodash": { | 6618 | "node_modules/lodash": { |
| 6617 | "version": "4.17.23", | 6619 | "version": "4.18.1", |
| 6618 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", | 6620 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", |
| 6619 | "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", | 6621 | "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", |
| 6620 | "license": "MIT" | 6622 | "license": "MIT" |
| 6621 | }, | 6623 | }, |
| 6622 | "node_modules/lodash-es": { | 6624 | "node_modules/lodash-es": { |
| @@ -7926,9 +7928,9 @@ | |||
| 7926 | } | 7928 | } |
| 7927 | }, | 7929 | }, |
| 7928 | "node_modules/readdir-glob/node_modules/brace-expansion": { | 7930 | "node_modules/readdir-glob/node_modules/brace-expansion": { |
| 7929 | "version": "2.0.2", | 7931 | "version": "2.1.0", |
| 7930 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | 7932 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", |
| 7931 | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | 7933 | "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", |
| 7932 | "license": "MIT", | 7934 | "license": "MIT", |
| 7933 | "dependencies": { | 7935 | "dependencies": { |
| 7934 | "balanced-match": "^1.0.0" | 7936 | "balanced-match": "^1.0.0" |
| @@ -8125,7 +8127,6 @@ | |||
| 8125 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", | 8127 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", |
| 8126 | "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", | 8128 | "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", |
| 8127 | "license": "MIT", | 8129 | "license": "MIT", |
| 8128 | "peer": true, | ||
| 8129 | "dependencies": { | 8130 | "dependencies": { |
| 8130 | "fast-deep-equal": "^3.1.3", | 8131 | "fast-deep-equal": "^3.1.3", |
| 8131 | "fast-uri": "^3.0.1", | 8132 | "fast-uri": "^3.0.1", |
| @@ -9143,7 +9144,6 @@ | |||
| 9143 | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", | 9144 | "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", |
| 9144 | "dev": true, | 9145 | "dev": true, |
| 9145 | "license": "Apache-2.0", | 9146 | "license": "Apache-2.0", |
| 9146 | "peer": true, | ||
| 9147 | "bin": { | 9147 | "bin": { |
| 9148 | "tsc": "bin/tsc", | 9148 | "tsc": "bin/tsc", |
| 9149 | "tsserver": "bin/tsserver" | 9149 | "tsserver": "bin/tsserver" |
| @@ -29,13 +29,14 @@ | |||
| 29 | "@mozilla/readability": "^0.6.0", | 29 | "@mozilla/readability": "^0.6.0", |
| 30 | "@popperjs/core": "^2.11.8", | 30 | "@popperjs/core": "^2.11.8", |
| 31 | "@zeldafan0225/ai_horde": "^5.2.0", | 31 | "@zeldafan0225/ai_horde": "^5.2.0", |
| 32 | "agent-base": "^7.1.3", | ||
| 32 | "archiver": "^7.0.1", | 33 | "archiver": "^7.0.1", |
| 33 | "bing-translate-api": "^4.1.0", | 34 | "bing-translate-api": "^4.1.0", |
| 34 | "body-parser": "^1.20.2", | 35 | "body-parser": "^1.20.2", |
| 35 | "bowser": "^2.12.1", | 36 | "bowser": "^2.12.1", |
| 36 | "bytes": "^3.1.2", | 37 | "bytes": "^3.1.2", |
| 37 | "chalk": "^5.6.0", | 38 | "chalk": "^5.6.0", |
| 38 | "chevrotain": "^11.1.1", | 39 | "chevrotain": "^11.2.0", |
| 39 | "command-exists": "^1.2.9", | 40 | "command-exists": "^1.2.9", |
| 40 | "compression": "^1.8.1", | 41 | "compression": "^1.8.1", |
| 41 | "cookie-parser": "^1.4.6", | 42 | "cookie-parser": "^1.4.6", |
| @@ -44,7 +45,7 @@ | |||
| 44 | "crc": "^4.3.2", | 45 | "crc": "^4.3.2", |
| 45 | "csrf-sync": "^4.2.1", | 46 | "csrf-sync": "^4.2.1", |
| 46 | "diff-match-patch": "^1.0.5", | 47 | "diff-match-patch": "^1.0.5", |
| 47 | "dompurify": "^3.2.6", | 48 | "dompurify": "^3.4.2", |
| 48 | "droll": "^0.2.1", | 49 | "droll": "^0.2.1", |
| 49 | "env-paths": "^3.0.0", | 50 | "env-paths": "^3.0.0", |
| 50 | "express": "^4.21.0", | 51 | "express": "^4.21.0", |
| @@ -64,8 +65,9 @@ | |||
| 64 | "ipaddr.js": "^2.2.0", | 65 | "ipaddr.js": "^2.2.0", |
| 65 | "is-docker": "^3.0.0", | 66 | "is-docker": "^3.0.0", |
| 66 | "isomorphic-git": "^1.36.3", | 67 | "isomorphic-git": "^1.36.3", |
| 68 | "js-sha256": "^0.11.1", | ||
| 67 | "localforage": "^1.10.0", | 69 | "localforage": "^1.10.0", |
| 68 | "lodash": "^4.17.21", | 70 | "lodash": "^4.18.1", |
| 69 | "mime-types": "^3.0.2", | 71 | "mime-types": "^3.0.2", |
| 70 | "moment": "^2.30.1", | 72 | "moment": "^2.30.1", |
| 71 | "morphdom": "^2.7.7", | 73 | "morphdom": "^2.7.7", |
| @@ -113,8 +115,9 @@ | |||
| 113 | "type": "git", | 115 | "type": "git", |
| 114 | "url": "https://github.com/SillyTavern/SillyTavern.git" | 116 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 115 | }, | 117 | }, |
| 116 | "version": "1.17.0", | 118 | "version": "1.18.0", |
| 117 | "scripts": { | 119 | "scripts": { |
| 120 | "init": "node src/server-init.js", | ||
| 118 | "start": "node server.js", | 121 | "start": "node server.js", |
| 119 | "debug": "node --inspect server.js", | 122 | "debug": "node --inspect server.js", |
| 120 | "start:global": "node server.js --global", | 123 | "start:global": "node server.js --global", |
| @@ -122,7 +125,6 @@ | |||
| 122 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", | 125 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", |
| 123 | "start:bun": "bun server.js", | 126 | "start:bun": "bun server.js", |
| 124 | "start:no-csrf": "node server.js --disableCsrf", | 127 | "start:no-csrf": "node server.js --disableCsrf", |
| 125 | "postinstall": "node post-install.js", | ||
| 126 | "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js", | 128 | "lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js", |
| 127 | "lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix", | 129 | "lint:fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix", |
| 128 | "plugins:update": "node plugins update", | 130 | "plugins:update": "node plugins update", |
| @@ -137,7 +139,7 @@ | |||
| 137 | }, | 139 | }, |
| 138 | "main": "server.js", | 140 | "main": "server.js", |
| 139 | "devDependencies": { | 141 | "devDependencies": { |
| 140 | "@chevrotain/types": "^11.0.3", | 142 | "@chevrotain/types": "^11.2.0", |
| 141 | "@types/archiver": "^6.0.3", | 143 | "@types/archiver": "^6.0.3", |
| 142 | "@types/bytes": "^3.1.5", | 144 | "@types/bytes": "^3.1.5", |
| 143 | "@types/command-exists": "^1.2.3", | 145 | "@types/command-exists": "^1.2.3", |
| @@ -151,7 +153,7 @@ | |||
| 151 | "@types/jquery-cropper": "^1.0.4", | 153 | "@types/jquery-cropper": "^1.0.4", |
| 152 | "@types/jquery.transit": "^0.9.33", | 154 | "@types/jquery.transit": "^0.9.33", |
| 153 | "@types/jqueryui": "^1.12.24", | 155 | "@types/jqueryui": "^1.12.24", |
| 154 | "@types/lodash": "^4.17.20", | 156 | "@types/lodash": "^4.17.24", |
| 155 | "@types/mime-types": "^3.0.1", | 157 | "@types/mime-types": "^3.0.1", |
| 156 | "@types/multer": "^2.1.0", | 158 | "@types/multer": "^2.1.0", |
| 157 | "@types/node": "^18.19.84", | 159 | "@types/node": "^18.19.84", |
| @@ -167,6 +169,7 @@ | |||
| 167 | "eslint": "^8.57.1", | 169 | "eslint": "^8.57.1", |
| 168 | "eslint-plugin-jest": "^27.9.0", | 170 | "eslint-plugin-jest": "^27.9.0", |
| 169 | "eslint-plugin-jsdoc": "^48.10.0", | 171 | "eslint-plugin-jsdoc": "^48.10.0", |
| 170 | "eslint-plugin-playwright": "^2.3.0" | 172 | "eslint-plugin-playwright": "^2.3.0", |
| 173 | "typescript": "^5.9.3" | ||
| 171 | } | 174 | } |
| 172 | } | 175 | } |
| @@ -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 | font-size: 1.05em; | 97 | font-size: 1.05em; |
| 98 | } | 98 | } |
| 99 | 99 | ||
| 100 | .extensions_info .extension_version { | 100 | .extensions_info :is(.extension_version, .extension_author) { |
| 101 | opacity: 0.8; | 101 | opacity: 0.8; |
| 102 | font-size: 0.8em; | 102 | font-size: 0.8em; |
| 103 | font-weight: normal; | 103 | font-weight: normal; |
| 104 | margin-left: 2px; | 104 | margin-left: 2px; |
| 105 | } | 105 | } |
| 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 | .extensions_info .extension_block a { | 117 | .extensions_info .extension_block a { |
| 108 | color: var(--SmartThemeBodyColor); | 118 | color: var(--SmartThemeBodyColor); |
| 109 | } | 119 | } |
| @@ -161,4 +171,4 @@ input.extension_missing[type="checkbox"] { | |||
| 161 | z-index: 1; | 171 | z-index: 1; |
| 162 | margin-bottom: 10px; | 172 | margin-bottom: 10px; |
| 163 | padding: 5px; | 173 | padding: 5px; |
| 164 | } | ||
| 164 | \ No newline at end of file | \ No newline at end of file | |
| 174 | } | ||
| @@ -500,6 +500,11 @@ | |||
| 500 | .horde_multiple_hint { | 500 | .horde_multiple_hint { |
| 501 | display: none; | 501 | display: none; |
| 502 | } | 502 | } |
| 503 | |||
| 504 | select[multiple] { | ||
| 505 | max-height: 50px; | ||
| 506 | overflow-y: auto; | ||
| 507 | } | ||
| 503 | } | 508 | } |
| 504 | 509 | ||
| 505 | /*landscape mode phones and ipads*/ | 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 | display: none; | 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 | #adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) { | 572 | #adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) { |
| 569 | display: none; | 573 | display: none; |
| 570 | } | 574 | } |
| @@ -17,6 +17,7 @@ | |||
| 17 | .welcomePanel.recentHidden .welcomeRecent, | 17 | .welcomePanel.recentHidden .welcomeRecent, |
| 18 | .welcomePanel.recentHidden .recentChatsTitle, | 18 | .welcomePanel.recentHidden .recentChatsTitle, |
| 19 | .welcomePanel.recentHidden .hideRecentChats, | 19 | .welcomePanel.recentHidden .hideRecentChats, |
| 20 | .welcomePanel.recentHidden .recentChatsSettings, | ||
| 20 | .welcomePanel:not(.recentHidden) .showRecentChats { | 21 | .welcomePanel:not(.recentHidden) .showRecentChats { |
| 21 | display: none; | 22 | display: none; |
| 22 | } | 23 | } |
| @@ -1,78 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | 2 | <svg id="svg2" width="660.14" height="621.01" version="1.1" viewBox="0 0 660.14 621.01" xmlns="http://www.w3.org/2000/svg"> |
| 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" | 5 | </g> |
| 6 | id="svg2" | ||
| 7 | width="682.66669" | ||
| 8 | height="682.66669" | ||
| 9 | viewBox="0 0 682.66669 682.66669" | ||
| 10 | sodipodi:docname="kobold.svg" | ||
| 11 | inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | ||
| 12 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 13 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 14 | xmlns="http://www.w3.org/2000/svg" | ||
| 15 | xmlns:svg="http://www.w3.org/2000/svg"> | ||
| 16 | <defs | ||
| 17 | id="defs6" /> | ||
| 18 | <sodipodi:namedview | ||
| 19 | id="namedview4" | ||
| 20 | pagecolor="#ffffff" | ||
| 21 | bordercolor="#000000" | ||
| 22 | borderopacity="0.25" | ||
| 23 | inkscape:showpageshadow="2" | ||
| 24 | inkscape:pageopacity="0.0" | ||
| 25 | inkscape:pagecheckerboard="0" | ||
| 26 | inkscape:deskcolor="#d1d1d1" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="1.5043945" | ||
| 29 | inkscape:cx="460.98281" | ||
| 30 | inkscape:cy="311.08862" | ||
| 31 | inkscape:window-width="2560" | ||
| 32 | inkscape:window-height="1377" | ||
| 33 | inkscape:window-x="2552" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g8" /> | ||
| 37 | <g | ||
| 38 | inkscape:groupmode="layer" | ||
| 39 | inkscape:label="Image" | ||
| 40 | id="g8"> | ||
| 41 | <path | ||
| 42 | style="stroke:none;stroke-width:1.33333" | ||
| 43 | d="m 171.32749,47.331392 v 4 c 12.46,9.0412 4.032,24.8744 4.91067,37.33333 0.60933,8.61773 4.48133,15.097198 3.08933,23.999998 -9.768,-1.0508 -17.05467,6.59294 -24,12.62774 -15.61067,13.5656 -27.25574,32.5056 -43.14654,45.2576 -6.2576,5.02133 -14.829587,3.284 -22.186787,2.55733 -13.6492,-1.34666 -27.88494,-0.35466 -41.33334,-3.10933 -15.8956,27.97867 -27.41493,57.04 -36,88 13.74,10.83867 35.03,39.1 53.33334,38.66667 l -1.33334,6.66667 -5.33333,-2.66667 2.66667,14.66667 -4,-2.66667 -1.33334,6.66667 h 6.66667 l -1.33333,25.33333 h -1.33334 l -5.33333,-8 c 1.2756,13.55867 6.9132,37.864 2.5692,50.54 -2.02707,5.91467 -8.7172,10.132 -13.23613,14.05067 -10.5992,9.19067 -20.984,18.72 -31.99974,27.40933 l 25.33334,69.33334 h 1.33333 l 1.33333,-10.66667 17.33334,28 -2.66667,-5.33333 30.66667,4 1.33333,-6.66667 8,5.33334 -2.66667,-5.33334 c 33.997197,-5.85866 39.588927,35.29201 66.666667,47.46401 6.416,2.88533 16.03067,0.56933 22.66667,-0.79734 -0.58267,3.59734 -2.02267,9.11867 1.24133,11.92667 4.892,4.20667 11.732,-3.67467 17.35467,-1.764 14.21733,4.83467 19.67333,28.956 37.404,16.504 3.70133,14.85067 13.68667,53.584 34.496,53.04533 8.328,-0.216 10.988,-14.944 12.73867,-21.04533 5.44933,-18.99333 5.108,-41.92533 30.09866,-42.62667 20.65067,-0.58 53.08801,-5.31333 69.00801,10.92534 10.052,10.25333 8.66666,27.47066 15.73333,39.69333 2.42933,4.20133 6.73733,11.54 12.48667,11.09867 5.58133,-0.42934 9.27466,-8.18267 11.60533,-12.424 7.32533,-13.32667 10.54,-27.56534 11.16667,-42.66667 21.444,13.56267 26.752,-8.56 45.33333,-14.99333 5.74,-1.98667 13.94134,4.92 18.68,1.088 3.448,-2.788 0.0213,-8.492 -1.34666,-11.428 11.49466,1.196 36.74933,2.12 46.66667,-3.89067 13.856,-8.39867 22.12,-24.84267 33.33333,-36.10934 l -1.3333,-5.33333 41.3333,1.33333 c 1.3253,7.79467 6.2707,7.924 13.3333,8 v -6.66666 l 5.3334,5.33333 h 2.6666 c 10.9174,-18.93867 20.5987,-39.24933 27.5454,-60 2.1786,-6.504 6.8373,-15.692 5.808,-22.64934 -2.6627,-18.00933 -34.1347,-27.24133 -42.4814,-42.756 -4.5173,-8.39333 0.028,-24.07866 0.8147,-33.26133 1.5,-17.472 -3,-34.648 -2.5173,-51.99734 0.5226,-18.74133 33.68,-29.71733 42.796,-44.60933 3.9746,-6.49467 -1.3827,-16.932 -3.5227,-23.39334 -6.516,-19.676 -14.056,-42.93333 -26.5373,-59.73733 -4.7547,-6.40133 -10.9067,-4.772 -17.9054,-4.38667 -12.3666,0.68134 -33.6346,6.424 -45.12,1.564 -7.93997,-3.36133 -13.2693,-13.912 -18.93463,-20.06666 -12.87067,-13.98401 -27.26667,-25.57214 -41.10534,-38.47241 -15.90933,-14.829598 2.85333,-45.371998 -3.62133,-63.770668 -3.06134,-8.700397 -20.756,-14.264397 -28.552,-17.694131 -13.49867,-5.9384 -27.244,-11.378134 -41.33334,-15.735334 -5.22,-1.6144 -11.668,-4.60664 -16.88933,-1.585333 -16.16,9.350267 -22.56667,33.947998 -39.556,41.756398 -9.47467,4.3548 -27.90934,-1.2064 -38.22134,-1.7456 -15.03333,-0.786 -36.49067,6.88534 -50.54,2.33574 -5.92267,-1.9176 -10.304,-8.63254 -14.20267,-13.12894 -9.148,-10.549598 -18.88133,-20.827998 -27.25733,-31.999732 -31.076,6.852134 -58.048,21.650001 -86.66667,34.666672 m 297.33334,37.33333 v 1.33333 h -4 l 4,-1.33333 m 4,0 -1.33333,1.33333 1.33333,-1.33333 m -253.33334,1.33333 -1.33333,1.33334 1.33333,-1.33334 m 192.00001,44.000008 -1.33334,1.33333 1.33334,-1.33333 m 159.99997,136 h -1.3333 c -6.54533,-9.72 -13.10133,-19.16666 -22.67333,-26.17733 -6.84667,-5.01334 -14.52267,-8.54934 -22.66001,-10.896 -64.19333,-18.52 -94.28267,58.57733 -101.33333,109.07333 -4.65867,-12.744 -2.66667,-27.976 -2.66667,-41.33333 0,-26.22133 -0.096,-52.44534 0.003,-78.66667 0.048,-12.81733 0.236,-30.37867 10.848,-39.352 11.536,-9.75467 35,-9.336 49.14933,-7.74 50.02934,5.64267 90.66531,43.30267 90.66671,95.092 M 271.32782,319.3314 h -1.33333 c -8.73067,-49.15734 -41.592,-109.81467 -100,-92.31201 -8.24267,2.46934 -15.92267,6.46267 -22.66401,11.82 -8.93866,7.10267 -15.3944,16.20534 -21.336,25.82534 -14.76986,-48.75467 53.38134,-80.68134 90.66667,-85.05067 10.04267,-1.176 23.616,-4.14533 33.25467,0.0387 11.71067,5.08533 16.88267,22.98533 19.47067,34.34533 7.42133,32.56134 7.61067,72.44534 1.94133,105.33334 m -46.66667,-96.00001 c 4.99867,13.94667 17.988,23.35334 25.46001,36.00001 7.37733,12.484 9.68933,32.056 22.54,40 -3.912,-24.796 -18.472,-72.13734 -48.00001,-76.00001 m 200.00001,77.33334 c 10.88133,-7.50267 13.66933,-23.524 19.86667,-34.66667 7.652,-13.75733 22.06133,-25.04533 26.8,-40 -27.912,4.53067 -44.212,50.15067 -46.66667,74.66667 m -220.00034,-54.6667 v 1.33334 l -45.33333,8 c 5.248,-20.64801 30.736,-15.41734 45.33333,-9.33334 m 340.00002,22.66667 c -18.28667,-11.04667 -31.34001,-18.64267 -53.33334,-18.66667 19.048,-16.336 44.54933,-0.956 53.33334,18.66667 m -62.66667,-21.33333 -5.33334,8 5.33334,-8 m -330.66668,9.33333 v 9.33333 h -2.66667 l 2.66667,-9.33333 m 33.33333,10.66667 c -10.14666,28.372 -20.33733,64.79466 -3.732,93.332 23.412,40.23467 69.13467,19.88133 79.73201,-17.332 l -37.33334,12 c 7.55333,-29.95734 6.71333,-51.172 -5.33333,-80 l -4,1.33333 2.66666,-9.33333 c 25.18534,13.21066 33.43067,41.44266 45.02534,65.33333 5.99867,12.36134 12.33733,26.51467 2.584,39.25867 -4.64,6.06267 -15.18,9.508 -22.276,11.536 -21.07067,6.024 -63.98134,7.04267 -81.088,-8.61333 -13.28934,-12.164 -18.45467,-33.68534 -24.24534,-50.18134 l -4,1.33333 -5.33333,-17.33333 c 24.27867,-2.78133 33.91467,-33.52133 57.33333,-41.33333 m 328.00001,1.33333 c 13.32267,7.76533 22.768,16.98133 33.40401,27.99867 6.94666,7.19466 13.224,14.832 23.92933,13.33466 l 1.3333,1.33334 -5.3333,24 h -1.33333 l -2.66667,-9.33334 h -1.33333 c -8.71867,20.86401 -12.74534,50.58534 -37.33334,58.46534 -21.72,6.96133 -56.508,7.996 -77.33333,-1.67467 -10.91334,-5.068 -22.63334,-13.23333 -20.69734,-27.45733 0.92267,-6.78667 6.69067,-14.096 10.03067,-20 h 1.33333 c 0,8.11867 -0.77733,16.57467 3.116,24 22.06534,42.08667 74.49067,26.58933 82.21734,-16 l -33.33334,8 c 2.796,-20.556 10.612,-60.17867 -10.66666,-73.33334 l -2.66667,6.66667 h -1.33333 c 3.22666,-14.35067 15.708,-19.72133 27.70266,-8.79467 6.95467,6.336 9.74534,15.98934 12.044,24.79334 0.59067,2.264 2.49067,13.96 7.14667,10.308 8.94933,-7.02 -8.35467,-33.472 -8.22667,-42.30667 M 215.32749,311.3314 c -13.772,4.168 -8.14267,19.588 -8,30.66666 -7.48933,-6.38666 -6.66267,-16.27333 -6.66667,-25.33333 -18.06266,-3.052 -5.47733,-24.708 8.75467,-13.59333 2.832,2.212 4.33867,5.13333 5.912,8.26 m 261.33334,1.33333 v 2.66667 c -12.91733,5.19066 -8.896,15.32533 -6.66666,26.66666 -5.38134,-3.632 -15.92667,-20.34133 -17.05334,-26.78266 -1.472,-8.40667 8.44,-10.26267 14.36134,-7.90534 3.31866,1.32267 6.33733,3.48134 9.35866,5.35467 m -413.333337,2.66667 -1.33333,1.33333 1.33333,-1.33333 m -6.66667,13.33333 4,2.66667 v -1.33334 l -4,-1.33333 m 18.66667,180.00001 v 4 h -1.33333 l 1.33333,-4 m 349.333337,62.66667 c 0,17.77733 -2.26,32.136 -10.66667,48 -9.4,-5.81067 -23.30933,-33.76267 -13.44266,-43.90267 5.54,-5.69333 16.94133,-4.096 24.10933,-4.09733 m -145.33334,4 -8,46.66666 c -16.29333,-9.21866 -22.78666,-30.32 -24,-48 l 32,1.33334 m 124.00001,17.33333 v 10.66667 h 1.33333 z" | ||
| 44 | id="path972" /> | ||
| 45 | <path | ||
| 46 | style="fill:#ffffff" | ||
| 47 | id="path1031" | ||
| 48 | d="m 171.07018,314.24465 c 1.43533,2.55362 3.43647,4.86204 6.02366,6.31063 15.51628,8.6877 9.94595,7.11918 18.45758,9.09921 2.9676,0.68502 6.00726,0.33711 9.00575,0.4851 0.51777,0.0256 1.97943,0.5002 1.53847,0.22762 -12.3291,-7.62122 -8.46459,-9.40075 -13.47067,-6.74985 -0.81161,0.51959 -1.60672,1.06589 -2.43482,1.55876 -4.52165,2.69122 -9.22362,5.08101 -13.79908,7.67767 -5.00626,2.84116 -5.79872,3.37745 -10.6807,6.35696 -3.21994,2.00581 -5.34709,3.29487 -8.43672,5.34334 -0.93485,0.61982 -1.86152,1.25236 -2.77524,1.90291 -0.53177,0.3786 -1.12785,0.69454 -1.56007,1.18372 -0.23297,0.26368 -0.67179,0.8249 -0.36192,0.99157 4.80104,2.58231 9.91238,4.5407 14.86857,6.81105 12.64758,-5.66419 24.4458,-13.03247 36.15953,-20.38753 8.83007,-5.82628 17.49966,-11.90043 26.13449,-18.01368 0.12258,-0.10977 0.51254,-0.25112 0.36775,-0.32931 -4.67223,-2.52289 -9.13452,-5.66802 -14.23142,-7.15676 -1.20748,-0.35269 -2.10539,1.37742 -3.14865,2.08026 -4.89213,3.29582 -9.54755,6.82945 -14.1897,10.46637 -9.49744,7.36804 -17.9992,15.83825 -26.01509,24.76432 -6.30592,7.59525 -8.14218,8.23949 10.2995,13.47521 1.78331,0.50629 3.3167,-1.66017 4.9328,-2.56834 5.73793,-3.22441 9.53881,-5.73893 15.19815,-9.3143 13.24893,-8.54994 25.96694,-17.90399 37.94901,-28.15332 1.04824,-0.91395 2.1079,-1.81498 3.14472,-2.74187 0.79084,-0.70699 1.56569,-1.43176 2.33769,-2.15926 0.53118,-0.50056 1.12786,-0.94513 1.57156,-1.52464 0.198,-0.25861 0.59124,-0.77034 0.30618,-0.92788 -14.71815,-8.13451 -10.1663,-9.48815 -17.93001,-5.21701 -11.41265,6.6473 -22.20287,14.27124 -32.75212,22.19736 -10.66088,8.29117 -20.89797,17.10296 -30.98403,26.07851 -0.98028,0.86051 -2.03371,1.64422 -2.94083,2.58153 -5.39678,5.57637 8.13193,7.60867 14.30926,9.09646 2.00094,0.48192 3.79185,-1.6031 5.66556,-2.45469 5.07424,-2.30622 9.60823,-4.57573 14.62099,-7.03268 11.14435,-5.70338 22.44245,-11.13252 33.38489,-17.22113 5.46451,-0.58006 14.20537,-1.41277 -14.20952,-6.74516 -1.73723,-0.32601 -2.8735,2.0593 -4.29587,3.10863 -5.86769,4.32876 -11.61624,8.8152 -17.40775,13.24456 -0.88721,0.84917 -1.97162,1.53157 -2.66162,2.54751 -0.28753,0.42334 -0.56545,1.30065 -0.10868,1.53142 5.12688,2.59017 10.33852,5.33462 15.9671,6.48047 1.71551,0.34924 3.17757,-1.47245 4.73846,-2.26529 4.90381,-2.49085 6.99171,-3.77194 11.81268,-6.53639 1.94264,-1.1845 3.88822,-2.3642 5.82792,-3.55351 4.47851,-2.74595 8.99301,-5.47352 13.30943,-8.47306 0.65181,-0.45295 2.58101,-1.00805 1.90658,-1.4266 -4.79779,-2.9775 -10.29254,-4.64766 -15.43882,-6.97148 -11.72135,5.99277 -22.91179,12.94688 -34.23961,19.6353 -6.60264,3.95287 -13.30201,7.72362 -20.18434,11.16672 -9.33663,4.8581 -9.99423,3.7481 8.12157,11.42393 0.83219,0.3526 1.70531,-0.59973 2.5527,-0.91405 1.02537,-0.38033 2.04352,-0.77986 3.06527,-1.1698 6.88738,-3.16305 13.93717,-5.90731 21.04709,-8.52021 4.06204,-1.52457 8.09972,-3.2178 12.32549,-4.25606 1.68407,-0.41377 2.88582,-0.56931 4.57605,-0.83571 -1.4108,-2.79602 3.18988,0.90932 2.44908,0.52434 -4.45476,-2.31509 -8.49787,-5.5392 -13.24768,-7.16519 -0.90867,-0.31106 -1.4379,1.27434 -2.1802,1.8838 -2.94314,2.41647 -3.50484,2.75165 -6.63769,5.0276 -6.26617,4.40862 -12.714,8.5456 -19.11454,12.75349 -4.45769,4.93043 -2.50115,2.00475 11.93444,10.09798 0.63232,0.3545 1.41442,-0.31883 2.11844,-0.49163 2.98621,-0.73295 5.93525,-1.60178 8.89459,-2.4344 5.27098,-1.7298 10.76117,-2.53812 16.20623,-3.48887 4.60406,-0.82453 9.1585,-1.85176 13.62925,-3.22747 3.11796,-1.15071 6.24676,-2.27099 9.18927,-3.82602 1.66601,-0.96562 3.51589,-1.59353 5.16478,-2.58761 0.59316,-0.3576 1.14403,-0.78107 1.71659,-1.1708 1.74687,-1.35545 2.65746,-2.35985 3.62271,-4.37357 0.84126,-2.07386 1.06124,-4.30847 1.19133,-6.52137 0.0789,-1.7605 0.0646,-3.52332 0.0576,-5.28505 0.42359,-6.7192 3.03353,-1.77248 -13.2615,-9.57841 -0.44856,-0.21487 -0.88179,0.46044 -1.3207,0.69441 -0.54734,0.29178 -1.09201,0.58855 -1.63801,0.88282 -3.72432,2.34742 -7.18224,5.08426 -10.82303,7.54849 -3.13256,2.27465 -6.59511,3.9843 -10.07352,5.64923 -2.68913,1.31556 -5.50527,2.2938 -8.12552,3.76052 4.28068,2.88035 8.26686,6.25599 12.84202,8.64104 0.64153,0.33443 1.30296,-0.62985 1.9647,-0.92223 4.26541,-1.8846 1.57695,-0.53142 6.36978,-2.80944 1.13947,-0.54159 6.36523,-3.15514 7.52569,-3.73405 2.81628,-1.45312 5.68135,-2.8072 8.50641,-4.24271 0.33246,-0.16893 1.29943,-0.30491 0.99237,-0.51652 -12.71423,-8.76181 -8.74434,-10.10314 -15.04117,-6.92273 -6.79382,3.53204 -13.74745,6.74262 -20.52354,10.31127 -7.5434,3.95521 -15.43438,7.12357 -23.44161,9.9938 -4.93666,1.93963 -10.00951,3.44204 -15.13808,4.76283 -0.61961,0.37578 -4.11298,0.37957 -4.53103,0.75257 -0.23898,0.21323 -0.7467,0.65064 -0.48212,0.83114 4.26858,2.91201 8.88087,5.28514 13.32131,7.92771 3.06017,-0.26036 6.1004,-0.66469 9.16565,-0.88282 3.5349,-0.27157 7.08772,-0.15372 10.62077,-0.41487 2.34311,-0.20178 4.68416,-0.42837 7.03502,-0.51429 1.98063,0.0101 3.95881,-0.2504 5.90142,-0.62775 1.98537,-0.26974 3.8929,-0.84394 5.80038,-1.43236 1.70419,-0.58082 3.50678,-0.50524 5.26603,-0.74464 2.53961,-0.30944 5.00901,-1.04243 7.50092,-1.60707 1.79673,-0.39005 3.62652,-0.69197 5.40419,-1.16894 0.77169,-0.20705 0.73152,-0.48877 0.6584,0.0322 -15.02092,-10.25008 -8.42834,-9.62098 -17.23578,-6.65309 -1.91185,0.77525 -3.84198,1.48065 -5.76047,2.23372 -2.1782,0.94213 -4.52971,1.33577 -6.77528,2.06463 -1.92286,0.62993 -3.95096,0.6523 -5.85054,1.34594 -1.43592,0.6249 -2.95828,0.80537 -4.50244,0.90949 -1.69973,0.41292 -3.45038,0.54237 -5.19138,0.64517 -1.88828,0.13475 -3.82163,0.27962 -5.61684,0.9172 -0.0211,-0.0295 -0.0943,-0.10741 -0.0634,-0.0885 4.4075,2.68861 8.5688,5.82092 13.20029,8.10215 0.50696,0.2497 0.95543,-0.60426 1.44328,-0.88951 2.47696,-1.44829 4.88574,-2.95444 7.29325,-4.51881 4.43353,-2.70197 8.70382,-5.65959 12.75546,-8.90627 1.32906,-0.98398 2.49335,-2.14207 3.62575,-3.33846 0,0 -12.90598,-7.87335 -12.90598,-7.87335 v 0 c -1.10475,1.13152 -2.24531,2.21955 -3.51853,3.16535 -4.0487,3.17935 -8.28306,6.10197 -12.67783,8.78408 -1.64902,1.06545 -3.39232,2.21147 -5.07695,3.22299 -0.72249,0.43381 -1.46465,0.83405 -2.19248,1.25885 -0.47497,0.27721 -1.81706,0.47105 -1.41578,0.8471 10.42477,9.76936 7.5619,11.33669 12.0127,8.76713 1.78097,-0.40206 3.61469,-0.5307 5.43243,-0.65354 1.74871,-0.15261 3.47842,-0.41518 5.21287,-0.6753 1.60693,-0.20434 3.15791,-0.59502 4.6777,-1.16108 1.93936,-0.48218 3.95267,-0.655 5.84512,-1.338 2.30159,-0.64532 4.63,-1.21698 6.8368,-2.15547 1.91913,-0.72053 3.81715,-1.49554 5.74076,-2.20447 8.62643,-3.25519 5.68409,-1.80659 -7.41768,-10.83793 -0.57951,0.42283 -1.36317,0.47509 -2.05091,0.62728 -1.43747,0.31811 -2.87464,0.63797 -4.31681,0.93429 -2.44658,0.58032 -4.88995,1.23651 -7.40174,1.46555 -1.83742,0.16771 -3.67129,0.3449 -5.42332,0.97071 -1.86505,0.56113 -3.76192,0.9855 -5.68941,1.27195 -1.92476,0.2718 -3.85014,0.48183 -5.79717,0.48098 -2.35121,0.13789 -4.69492,0.37057 -7.04479,0.52789 -3.54525,0.15356 -7.09771,0.15487 -10.63737,0.43806 -3.05823,0.25769 -6.10045,0.66447 -9.16265,0.87861 4.10095,2.6678 8.35865,5.10888 12.30286,8.00339 0.22243,0.16323 -0.50266,0.42091 -0.45351,0.69239 0.0141,0.0781 4.37054,-0.75142 4.58956,-0.83694 5.16506,-1.33006 10.23089,-2.98806 15.21856,-4.88087 8.04727,-2.92727 15.97342,-6.16211 23.55564,-10.16528 6.79245,-3.54284 13.74759,-6.75919 20.5298,-10.32082 6.1694,-3.46088 3.5939,-1.2335 -10.25015,-9.64727 -0.31437,-0.19106 -0.65421,0.33667 -0.98284,0.50202 -3.77307,1.89844 -0.71516,0.34579 -5.12115,2.54028 -1.12963,0.56264 -2.25547,1.13287 -3.38321,1.69931 -3.64669,1.81381 -7.27715,3.65196 -11.01842,5.26635 -0.92249,0.39807 -1.84114,0.80968 -2.78853,1.14419 -0.5191,0.18329 -2.01711,0.0453 -1.60091,0.40566 3.71695,3.21784 7.99353,5.72576 11.99029,8.58864 2.63436,-1.37598 5.4429,-2.34498 8.08645,-3.70104 3.53151,-1.71393 7.01231,-3.53031 10.19911,-5.84269 3.562,-2.48006 7.00535,-5.14327 10.72295,-7.3938 0.5256,-0.26341 1.03573,-0.56029 1.57681,-0.79022 0.34553,-0.14683 1.37754,-0.11453 1.07482,-0.33657 -13.82664,-10.14164 -11.55582,-12.30727 -12.54762,-6.01864 0.009,1.73726 0.0275,3.47508 -0.0202,5.21196 -0.0821,2.02459 -0.20817,4.07984 -0.90984,6.00091 -0.81376,1.75352 -1.64737,2.5454 -3.20333,3.67747 -2.14295,1.42279 -4.50639,2.42113 -6.78341,3.60728 -2.91093,1.43792 -5.96998,2.53986 -9.03631,3.59764 -4.44558,1.29448 -8.96449,2.28607 -13.52651,3.07895 -5.48707,0.94315 -10.98138,1.90216 -16.29401,3.61552 -1.12137,0.3136 -4.98063,1.40492 -6.11919,1.67531 -0.86475,0.20537 -1.72806,0.45221 -2.61364,0.5278 -0.54137,0.0462 -2.04418,-0.50768 -1.62151,-0.16625 11.42704,9.23077 8.40436,11.68934 12.60187,7.50083 6.42684,-4.20992 12.88092,-8.38087 19.1554,-12.81757 2.49735,-1.83513 4.3837,-3.14306 6.72175,-5.16166 0.83724,-0.72285 3.09224,-1.40127 2.41497,-2.27578 -6.33848,-8.18436 -7.96984,-10.4425 -14.66541,-9.60946 -5.89378,1.07124 -11.51294,3.08172 -17.0598,5.32199 -7.10879,2.62566 -14.12733,5.44994 -21.05306,8.52907 -1.01221,0.37581 -2.01669,0.77314 -3.03664,1.12742 -0.81369,0.28264 -3.03652,0.14035 -2.46272,0.78279 11.80339,13.21539 6.99615,10.9296 11.92284,8.12062 1.31947,-0.7523 3.74865,-1.97655 5.01135,-2.62453 6.89121,-3.49042 13.61336,-7.28283 20.24024,-11.25236 11.27734,-6.67828 22.43811,-13.59569 34.1506,-19.49953 -3.31035,-3.15832 -6.16118,-6.88237 -9.93104,-9.47496 -0.63445,-0.43632 -1.23051,0.92684 -1.86559,1.36225 -1.15761,0.79367 -2.33243,1.56218 -3.51117,2.32412 -5.12389,3.31211 -10.35273,6.45766 -15.57115,9.61718 -4.38295,2.49441 -7.24864,4.21481 -11.74375,6.43131 -1.51803,0.74853 -5.44101,0.61334 -4.62479,2.09606 2.12153,3.85393 7.02265,5.3155 10.31252,8.23619 0.20792,0.18458 -0.98326,0.41742 -0.80971,0.20021 0.73463,-0.91941 1.71836,-1.60849 2.57754,-2.41273 5.78153,-4.42323 11.52997,-8.88866 17.38712,-13.21198 1.42972,-1.05531 5.0733,-1.52866 4.31223,-3.13445 -5.81826,-12.27593 -6.3744,-11.75565 -11.23511,-9.58703 -10.94552,6.03635 -22.20646,11.4862 -33.34269,17.15676 -4.22937,2.06502 -10.22531,5.0476 -14.57292,6.96022 -1.81778,0.79967 -6.27975,0.40599 -5.52019,2.24089 1.70084,4.10876 6.3992,6.24034 10.07003,8.75027 1.49441,1.02179 0.88047,-9.44405 3.83393,-3.84665 10.06538,-8.93869 20.2702,-17.72751 30.91727,-25.97198 10.50559,-7.86626 21.24325,-15.45041 32.62871,-22.00546 7.36761,-3.8532 3.96829,-0.86418 -8.13522,-10.03306 -0.061,-0.0462 0.28008,-0.0735 0.22901,-0.0165 -0.47916,0.5348 -1.02792,1.00305 -1.55236,1.49352 -0.76503,0.71545 -1.53338,1.4276 -2.31581,2.12398 -1.03519,0.92135 -2.09105,1.81921 -3.13658,2.72881 -11.95628,10.17483 -24.62464,19.47989 -37.83252,27.9695 -4.36546,2.74513 -10.44576,6.69692 -15.10959,9.17839 -1.49209,0.79389 -5.49167,0.65909 -4.61365,2.10329 2.38531,3.9234 6.89231,6.21587 10.96295,8.34018 1.93356,1.00905 0.87576,-8.92908 3.97258,-5.19905 7.97197,-8.85114 16.43325,-17.24046 25.87613,-24.53933 4.62049,-3.60275 9.25775,-7.09819 14.12437,-10.36353 1.0398,-0.69767 3.88038,-1.05567 3.13843,-2.06435 -8.3794,-11.39171 -7.52997,-11.87727 -11.60778,-8.80552 -8.62141,6.08752 -17.25943,12.16238 -26.08925,17.94303 -11.65263,7.29252 -23.38273,14.63386 -36.00522,20.15284 3.76123,2.73954 7.65708,5.30322 11.28369,8.21863 0.17317,0.13921 -0.64943,0.598 -0.45933,0.48299 0.54064,-0.3271 1.0066,-0.76466 1.52415,-1.12721 3.62633,-2.54031 7.40811,-4.8487 11.16951,-7.18018 4.99607,-3.03314 5.5246,-3.4057 10.64708,-6.30516 4.6037,-2.6058 9.38129,-4.97264 13.86718,-7.78308 0.88771,-0.55616 1.72092,-1.19491 2.58138,-1.79236 0.50182,-0.59773 2.09084,-1.27701 1.50547,-1.79319 -3.87871,-3.42027 -8.57482,-5.79813 -13.05103,-8.38775 -0.47089,-0.27242 -1.08498,-0.086 -1.62866,-0.10489 -2.94034,-0.10204 -5.89671,-0.0548 -8.77687,-0.79164 -6.84974,-2.01396 -3.16404,-1.19536 7.6358,5.56497 0.6805,0.42597 -1.44013,-0.72013 -2.10585,-1.16884 -1.12844,-0.76059 -2.34725,-1.94327 -3.22517,-2.98418 -0.37019,-0.43891 -0.67583,-0.92844 -1.01374,-1.39266 0,0 -13.77387,-6.23173 -13.77387,-6.23173 z" /> | ||
| 49 | <path | ||
| 50 | style="fill:#ffffff" | ||
| 51 | id="path1033" | ||
| 52 | d="m 232.73967,343.98425 c -0.16239,-4.21792 -0.38636,-8.43294 -0.53272,-12.65164 -0.17653,-6.95973 -1.85631,-4.87751 -13.29234,-10.63359 -0.04,-0.0201 -3.53227,3.33793 -3.78645,3.58181 -1.99028,1.91133 -4.00381,3.86285 -6.2969,5.4174 -1.06135,0.71952 -1.93388,1.15314 -3.0558,1.75665 -1.95113,0.78708 -4.0813,1.77702 -6.2486,1.72206 -0.41992,-0.0106 -0.83194,-0.11686 -1.24792,-0.1753 -0.48134,-0.10515 -0.97296,-0.17113 -1.44404,-0.31546 -1.88325,-0.57698 -3.69696,-1.49191 -5.50071,-2.26758 -2.1798,-0.9374 -2.2769,-0.95062 -4.52683,-1.82148 -4.44489,-1.59935 -9.05198,-2.51641 -13.72931,-3.06968 -0.51119,-0.087 -1.04311,-0.0927 -1.53355,-0.26109 -0.28832,-0.099 -1.01299,-0.67747 -0.75025,-0.5229 4.24182,2.49551 8.37529,5.17918 12.68969,7.54702 0.17035,0.0935 0.32998,-0.20543 0.49213,-0.31253 0.47218,-0.31192 0.93292,-0.64129 1.41076,-0.94447 0.64704,-0.41054 1.30876,-0.79748 1.96314,-1.19621 5.32786,-2.88263 10.80956,-5.47437 16.22858,-8.18209 4.78718,-1.95989 9.10461,-4.78507 13.46258,-7.52548 2.62671,-1.56346 5.19283,-3.22119 7.80177,-4.81406 1.1227,-0.93327 3.56593,-1.49092 4.53824,-2.47123 0.16716,-0.16853 0.24952,-0.40389 0.37428,-0.60583 -4.23667,-2.89754 -8.32988,-6.01688 -12.71002,-8.69261 -0.29187,-0.1783 -0.64392,0.2312 -0.96946,0.33612 -2.15826,0.69562 -4.30644,1.33446 -6.42473,2.15203 -4.29152,1.63988 -8.41307,3.66717 -12.6077,5.53018 -4.82936,2.0624 -9.56932,4.30241 -14.24575,6.68873 -3.90778,2.11644 -7.73683,4.3642 -11.5033,6.72178 -1.61315,0.98308 -3.112,2.13675 -4.70891,3.14424 -0.37871,0.23893 -0.78785,0.42969 -1.15472,0.68643 -0.77674,0.54356 -1.56314,1.30623 -2.2582,1.94248 -1.3674,1.53824 -3.49987,3.52541 -3.14491,5.83895 0.0542,0.35357 0.23681,0.67509 0.35521,1.01264 4.611,2.65737 9.10124,5.53626 13.833,7.9721 0.39531,0.2035 0.88276,-0.1075 1.32296,-0.17003 2.35331,-0.33431 4.41661,-1.16882 6.57287,-2.14245 4.66944,-2.08131 9.10602,-4.67138 13.44997,-7.35827 4.57957,-2.99451 9.14835,-6.0171 13.558,-9.25944 2.82993,-2.279 5.77344,-4.40642 8.73187,-6.51307 1.93406,-1.45357 3.91458,-2.83753 5.84743,-4.29188 1.50721,-1.13725 2.80699,-2.51181 4.20715,-3.77294 0.53578,-0.55497 1.54467,-0.85008 1.93694,-1.55022 2.9445,-5.25545 -8.98814,-6.22301 -12.95355,-8.48667 -2.46378,1.36153 -4.50197,3.20247 -6.39761,5.26251 -0.39857,0.48953 -5.58042,6.12839 -4.84005,6.67322 4.2012,3.09163 8.7864,5.62424 13.1796,8.43636 3.68207,-1.12239 6.2577,-3.8927 8.89656,-6.52432 0.0606,-0.0662 1.62195,-1.787 1.8513,-1.9885 0.12384,-0.10881 0.28737,-0.16161 0.43105,-0.24242 -4.39526,-2.2892 -8.63135,-4.91417 -13.18578,-6.86759 -0.34145,-0.14645 -0.2515,0.70012 -0.40601,1.03799 -0.22397,0.48975 -0.48546,0.96145 -0.72819,1.44218 -0.86092,1.6686 -2.0156,3.14343 -3.23225,4.56183 -0.24595,0.28673 -0.47983,0.58649 -0.7558,0.84446 -0.0265,0.0247 -0.071,-0.11872 -0.0392,-0.10134 4.40101,2.40648 8.77951,4.85392 13.16926,7.28089 1.46759,-2.4309 3.48191,-4.44214 4.9583,-6.86589 1.06137,-2.32718 0.93692,-4.83266 0.0823,-7.19189 -2.80578,-4.83762 -12.6773,-7.80282 -14.85026,-8.84928 -0.31655,-0.15245 -3.53109,1.16694 -3.87595,1.30947 -4.47781,1.99153 -8.79044,4.33462 -13.22011,6.43083 -4.21998,2.0963 -8.51473,4.03792 -12.87256,5.82964 -2.77042,1.16583 -5.6843,1.88722 -8.58772,2.62102 -2.80453,0.92069 -5.70723,1.44571 -8.56832,2.15075 -2.29694,0.62103 -4.58347,1.319 -6.66594,2.49128 -0.12684,0.0518 -0.49398,0.0785 -0.38052,0.15532 4.23064,2.86275 8.3752,5.89525 12.88037,8.30297 0.43136,0.23053 0.84219,-0.49762 1.26527,-0.74304 2.97907,-1.72813 5.83126,-3.60518 8.70066,-5.51101 5.75823,-3.98884 11.70446,-7.70199 17.74697,-11.24239 2.80788,-1.4419 5.6684,-2.82242 8.61115,-3.96542 2.70305,-1.02336 5.36367,-2.15739 8.10939,-3.06346 1.86988,-0.63702 3.74282,-1.3128 5.66466,-1.77909 1.44074,-0.18382 2.82028,-0.62291 4.25689,-0.83578 0.96481,-0.11847 1.939,-0.1301 2.9096,-0.15324 0.20012,-0.005 0.40765,-0.0685 0.60035,-0.0143 0.0912,0.0256 0.11119,0.15329 0.16679,0.22994 -12.44464,-7.68644 -11.33159,-11.89752 -15.32577,-3.63822 -0.92044,2.08126 -1.91653,4.06144 -3.30388,5.86997 -1.67673,1.99068 -4.14201,2.87169 -6.44358,3.91089 -5.85047,2.07862 -6.41927,-0.30481 11.67311,7.81678 0.39119,0.1756 0.56205,-0.64848 0.86246,-0.95446 0.8056,-0.82054 1.65794,-1.59387 2.48089,-2.39702 2.13255,-1.90308 3.73829,-3.81663 4.58765,-6.56018 0.51997,-2.17666 0.50849,-4.42627 0.51403,-6.65016 -0.0365,-1.98098 0.0282,-3.96596 -0.0874,-5.94446 -0.0469,-1.31854 -0.54398,-2.4502 -1.18941,-3.56455 -11.86794,-7.43097 -10.44561,-9.61528 -19.54019,-6.64195 -2.90504,1.06197 -5.64842,2.5092 -8.47323,3.76296 -2.82972,1.2514 -5.55757,2.71314 -8.35105,4.03951 -2.73802,1.46585 -5.62337,2.6416 -8.46532,3.89092 -2.93432,1.44932 -5.95304,2.71572 -8.93136,4.07021 -2.61697,1.18126 -5.17726,2.46451 -7.68626,3.85548 -0.43106,0.29155 -1.18643,0.74935 -1.55054,1.17364 -3.52989,4.11327 7.88695,6.33505 13.16143,8.67144 0.33506,0.14842 0.66895,-0.29947 1.00343,-0.44921 1.5692,-0.85195 3.27529,-1.39841 4.82461,-2.29156 0.80789,-0.46573 1.57825,-0.99384 2.37385,-1.48028 2.34583,-1.58221 4.30424,-3.63646 6.41685,-5.49806 1.46344,-1.30423 2.65707,-2.81893 3.34364,-4.6575 0.25683,-1.25844 0.81728,-2.81572 0.29801,-4.09133 -0.10581,-0.25993 -0.31332,-0.46569 -0.46998,-0.69854 -4.55348,-2.53758 -9.0634,-5.15497 -13.66045,-7.61274 -0.17399,-0.093 -0.39472,-0.015 -0.59186,-0.007 -0.12411,0.005 -0.47422,-0.0219 -0.36984,0.0455 4.24259,2.73583 8.50906,5.4383 12.85552,8.0059 0.073,0.0431 0.4994,-0.37586 0.75985,-0.61595 1.05025,-1.01942 1.86431,-2.28026 2.42797,-3.62806 0.31654,-0.52272 0.40322,-1.30266 0.96533,-1.64806 0.0979,-0.0602 0.21352,-0.0853 0.32028,-0.12794 0.13562,-0.0966 0.27123,-0.19324 0.40684,-0.28986 5.19693,-2.57523 10.31369,3.11931 -12.05308,-8.24825 -0.31398,-0.15958 -0.57926,0.4008 -0.86889,0.60121 -2.16543,1.33756 -4.16339,2.92389 -6.13481,4.52914 -1.66623,1.5478 -3.16695,3.26179 -4.80545,4.83609 -4.54417,4.78544 2.02375,3.79484 12.61783,9.20028 0.0449,0.0229 3.73072,-1.71529 3.99793,-1.84236 2.68136,-1.48749 5.35558,-2.96967 8.08898,-4.35972 1.77149,-0.97442 3.61795,-1.83865 5.50693,-2.55921 1.83273,-0.61603 3.6719,-1.20408 5.51169,-1.79859 1.74554,-0.54089 3.55893,-0.73243 5.34083,-1.11531 1.46277,-0.3253 2.95799,-0.3803 4.44952,-0.41387 1.62889,-0.0231 3.25805,-0.0161 4.88705,-0.0128 0.7882,0.19692 0.44532,0.0265 1.04462,0.47783 0,0 -11.91856,-9.30063 -11.91856,-9.30063 v 0 c -0.87451,-0.0778 -0.42265,-0.0541 -1.35592,-0.0645 -1.64103,8e-5 -3.28289,-0.005 -4.92286,0.0623 -1.54681,0.0866 -3.08424,0.26499 -4.59518,0.61963 -1.81956,0.37014 -3.67292,0.59739 -5.42732,1.241 -1.87635,0.58373 -3.73576,1.22084 -5.57977,1.8995 -1.8899,0.82 -3.77812,1.64933 -5.57518,2.66212 -2.72347,1.37718 -5.34241,2.93712 -8.06232,4.31931 -0.6843,0.3143 -2.00098,0.94025 -2.73657,1.19988 -0.32907,0.11614 -1.28367,0.058 -1.01018,0.27479 13.7453,10.89314 8.53254,11.61904 12.61923,7.42493 1.58097,-1.56706 3.0709,-3.22812 4.73039,-4.71521 1.96252,-1.57136 3.95718,-3.10421 6.08414,-4.44895 0.30082,-0.21807 1.18637,-0.41454 0.90245,-0.65421 -7.7916,-6.57739 -8.31092,-11.32876 -13.4836,-7.94085 -1.05349,0.71525 -1.69754,1.6303 -2.14331,2.82068 -0.43797,1.15738 -1.16584,2.19887 -2.07857,3.03463 -0.0545,0.0481 -0.92528,0.80376 -0.89054,0.83075 3.96643,3.08211 8.18217,5.82945 12.29744,8.70984 0.21262,0.14882 0.55613,0.0335 0.73193,0.26553 -4.11791,-2.3947 -8.23583,-4.78939 -12.35375,-7.18409 0.11825,0.0189 1.04835,0.11235 1.20019,0.31114 0.361,0.47262 -0.13437,2.13833 -0.19015,2.6962 -0.51158,1.63225 -1.70596,2.86555 -2.95481,3.99408 -2.02917,1.82599 -3.95843,3.79249 -6.27205,5.26938 -2.2578,1.37928 -4.6055,2.54372 -7.05381,3.5521 -5.58612,1.85709 -0.0223,-0.24329 11.68112,7.64689 0.40851,0.27541 -1.63103,1.30748 0.076,0.15975 0.2753,-0.18511 0.5646,-0.3485 0.84691,-0.52274 2.4821,-1.36587 5.04373,-2.57855 7.62031,-3.75523 2.97736,-1.34282 5.97456,-2.64164 8.92032,-4.05358 2.86232,-1.24381 5.72956,-2.48833 8.49579,-3.93692 2.76914,-1.35042 5.50943,-2.76014 8.32879,-4.00587 2.76516,-1.25474 5.47188,-2.67247 8.36843,-3.60722 1.26633,-0.36215 2.56448,-0.78342 3.89756,-0.80012 0.85192,-0.0107 0.82765,0.0752 1.53593,0.42768 -4.10255,-2.46371 -8.2051,-4.92742 -12.30765,-7.39113 0.87022,0.63506 1.41465,1.66666 1.57425,2.72657 0.29538,1.91362 0.0477,3.87804 0.15627,5.8075 0.009,2.09402 0.0596,4.20901 -0.31768,6.27765 -0.62923,2.46598 -2.34965,4.1958 -4.19368,5.88001 -0.32766,0.3196 -3.65762,3.49777 -3.50955,3.62853 10.25275,9.05415 7.97742,10.62804 13.96992,8.18094 2.5228,-1.13814 5.07757,-2.32734 6.8435,-4.54726 1.43052,-1.90044 2.50357,-3.95606 3.45643,-6.13263 3.80252,-7.93496 6.00519,-2.82532 -9.84222,-12.99277 -1.31575,0.30131 -2.7269,0.0687 -4.0426,0.43813 -1.44785,0.29793 -2.89095,0.62174 -4.3327,0.94508 -1.92223,0.5696 -3.83482,1.18028 -5.7206,1.8611 -2.74705,0.97129 -5.44361,2.07764 -8.158,3.13566 -2.96416,1.26301 -5.91996,2.56526 -8.73087,4.14719 -6.05698,3.565 -12.00387,7.32161 -17.79115,11.31094 -2.0647,1.36328 -4.22082,2.82155 -6.34676,4.10232 -0.74734,0.45023 -1.50867,0.8775 -2.2773,1.29035 -0.36773,0.19752 -1.45346,0.27595 -1.12997,0.53975 11.25958,9.18223 7.85353,10.77408 12.66198,8.30613 2.05871,-0.98645 4.25118,-1.63592 6.4617,-2.18772 2.86526,-0.69453 5.75463,-1.28469 8.57389,-2.15883 2.96319,-0.74043 5.89479,-1.61773 8.71335,-2.8061 4.36341,-1.83564 8.67479,-3.78933 12.91544,-5.89422 4.38121,-2.11024 8.66513,-4.43844 13.14946,-6.32484 0.79723,-0.27088 1.6527,-0.63379 2.51458,-0.67988 0.2792,-0.0149 1.07008,0.25495 0.83172,0.10879 -18.76731,-11.50759 -15.88476,-8.71313 -10.93879,-6.55684 1.1779,1.68282 1.33452,3.92655 0.61867,5.83 -1.51283,2.47276 -3.62336,4.50648 -5.0562,7.03708 4.23053,2.78584 8.38879,5.68466 12.6916,8.35752 0.11663,0.0725 0.19532,-0.19325 0.28736,-0.29514 0.26485,-0.29315 0.5285,-0.58756 0.78305,-0.88969 1.27543,-1.51383 2.4844,-3.08757 3.40139,-4.851 0.26271,-0.51611 0.54183,-1.02418 0.78813,-1.54831 0.2028,-0.43159 0.93409,-1.0235 0.55835,-1.31712 -12.01227,-9.38683 -10.01885,-11.64406 -15.33077,-5.90598 -1.79042,1.77947 -4.35818,4.82031 -7.00038,5.20786 -0.29162,0.0428 -0.58062,-0.10179 -0.87093,-0.15268 4.02282,2.65442 7.9953,5.38677 12.06847,7.96326 0.11339,0.0717 -0.15575,-0.27006 -0.0969,-0.39064 0.21338,-0.43739 0.56456,-0.79318 0.86224,-1.17819 1.14105,-1.47578 2.35791,-2.89629 3.60821,-4.2798 1.58596,-1.68233 3.44224,-3.73251 5.76525,-4.30599 -4.2131,-2.54932 -8.56527,-4.88182 -12.6393,-7.64798 -0.21311,-0.1447 0.33388,-0.3983 0.45002,-0.62823 0.0471,-0.0933 -0.15595,0.13926 -0.23661,0.20573 -0.55561,0.45787 -1.15351,0.8601 -1.69495,1.33579 -1.35883,1.24037 -2.64174,2.57235 -4.13464,3.65708 -1.919,1.45056 -3.90613,2.80573 -5.82514,4.25672 -2.96436,2.10935 -5.88464,4.27737 -8.74246,6.52975 -4.39397,3.19928 -8.92679,6.20958 -13.49223,9.15664 -4.30697,2.6285 -8.69454,5.17349 -13.33203,7.17839 -1.61461,0.66836 -3.19119,1.33514 -4.94353,1.54626 -5.13769,0.61898 -13.74732,-7.46153 9.72713,7.05026 -0.23995,-0.0713 -0.52011,-0.0631 -0.71985,-0.21402 -1.18523,-0.89541 1.85209,-3.97341 2.42794,-4.54589 2.44199,-2.14394 5.31075,-3.71203 7.98142,-5.55112 3.74114,-2.32298 7.54254,-4.54338 11.43148,-6.61142 4.66388,-2.36268 9.40496,-4.55199 14.20446,-6.62622 4.15951,-1.87582 8.27828,-3.84601 12.55452,-5.45065 1.64953,-0.59544 3.32117,-1.23265 5.04263,-1.59795 0.36175,-0.0768 0.73109,-0.18172 1.09941,-0.14867 0.18958,0.017 0.32738,0.19427 0.49107,0.29141 -4.04564,-2.7953 -8.09128,-5.59059 -12.13692,-8.38589 -1.49052,0.86633 -3.08899,1.54686 -4.54602,2.47941 -2.60577,1.60644 -5.19029,3.24617 -7.81092,4.82826 -4.32192,2.71934 -8.65468,5.41931 -13.38153,7.40097 -5.45576,2.72376 -10.99132,5.30639 -16.30537,8.29999 -0.95554,0.60573 -2.68974,1.62849 -3.56714,2.44843 -0.384,0.35884 -1.40022,0.87251 -1.00295,1.21659 7.44833,6.45106 8.6187,8.38054 15.11935,9.26037 4.61007,0.64895 9.14931,1.68107 13.50224,3.3662 3.83153,1.54046 7.61457,3.58519 11.77229,4.0085 2.81893,-0.10125 5.47716,-0.90361 7.97168,-2.23334 3.55204,-2.01968 6.71321,-4.56746 9.56112,-7.50291 0.0581,-0.0554 2.15763,-2.09521 2.47518,-2.27343 0.0932,-0.0523 0.3483,0.24732 0.25797,0.19025 -4.10953,-2.59651 -8.21085,-5.206 -12.31627,-7.80901 0.0837,0.25546 0.21511,0.49997 0.251,0.76638 0.0637,0.47319 0.0301,0.95446 0.0451,1.4317 0.13767,4.2041 0.32794,8.40601 0.55578,12.6062 0,0 13.47024,6.86343 13.47024,6.86343 z" /> | ||
| 53 | <path | ||
| 54 | style="fill:#ffffff" | ||
| 55 | id="path1035" | ||
| 56 | d="m 185.8578,280.25304 c 2.1647,-1.03493 4.30367,-2.1219 6.44766,-3.19929 1.27197,-0.62913 2.53847,-1.12777 3.94755,-1.32128 1.43773,-0.12953 2.88474,-0.10256 4.32689,-0.10046 1.74211,0.005 3.48422,0.0109 5.22634,0.0137 1.74919,0.003 3.49839,0.003 5.24759,0.003 1.44315,9e-5 2.8863,0 4.32945,-3e-5 1.33089,-4e-5 2.66179,-5e-5 3.99269,-5e-5 1.32776,0 2.65551,0 3.98326,0 1.32931,0 2.65862,0 3.98793,0 0.17489,0.0539 0.34978,0.10779 0.52467,0.16169 0,0 -12.10961,-9.05049 -12.10961,-9.05049 v 0 c -0.21526,8.9e-4 -0.43052,0.002 -0.64578,0.003 -1.32931,0 -2.65861,0 -3.98792,0 -1.32776,0 -2.65552,0 -3.98327,10e-6 -1.33091,1e-5 -2.66181,3e-5 -3.99271,7e-5 -1.44308,3e-5 -2.88616,9e-5 -4.32924,-2.5e-4 -1.74869,-5.5e-4 -3.49737,-10e-4 -5.24605,-0.005 -1.74413,-0.004 -3.48827,-0.0127 -5.23239,-0.005 -1.50245,0.0167 -3.01946,0.0159 -4.49505,0.33689 -1.46312,0.37423 -2.82923,0.98965 -4.17862,1.66268 -2.15021,1.10101 -4.33465,2.13332 -6.47881,3.24688 0,0 12.66542,8.25477 12.66542,8.25477 z" /> | ||
| 57 | <path | ||
| 58 | style="fill:#ffffff" | ||
| 59 | id="path1037" | ||
| 60 | d="m 517.46462,337.23029 c -2.56265,4.0164 -4.88261,8.17784 -7.31927,12.26999 -1.7766,3.09546 -4.32727,5.482 -7.18992,7.54762 -2.68734,1.95262 -5.15996,4.16998 -7.5556,6.46553 -1.38721,1.40056 -2.95482,2.59485 -4.53481,3.76485 -1.22963,0.91055 -2.3367,1.76135 -3.77132,2.29409 -5.09097,-1.74318 -1.08156,-0.40133 12.37559,6.98305 0.0258,0.0142 -0.004,-0.0636 0.0139,-0.0874 0.1837,-0.24854 0.37344,-0.49383 0.58333,-0.72068 0.75786,-0.81912 1.45438,-1.37948 2.30908,-2.12616 4.70831,-3.64505 10.04801,-6.34425 15.17505,-9.33458 2.03876,-1.27739 4.2988,-2.28601 6.19949,-3.78479 0.32219,-0.25406 0.62927,-0.53694 0.87598,-0.8648 0.15744,-0.20924 0.5329,-0.57258 0.31596,-0.71924 -4.29597,-2.90429 -8.86989,-5.37458 -13.30483,-8.06188 -3.57739,1.68493 -6.85132,3.90699 -10.27248,5.87217 -2.74604,1.62213 -5.6826,2.87125 -8.61663,4.10562 -2.65401,1.21151 -5.20027,2.67454 -7.58553,4.35282 -2.14447,1.40823 -4.28086,2.81275 -6.3615,4.31081 -1.40627,0.71348 -2.30929,1.77998 -2.62065,3.32068 0.81016,7.36832 12.11254,9.3282 18.52914,9.04592 2.50936,-0.31243 5.04454,-0.61344 7.40166,-1.56914 1.97441,-0.61026 2.76446,-1.84839 3.76012,-3.5379 1.07739,-2.05927 1.9625,-4.20328 2.99711,-6.28434 1.3492,-2.74011 3.16209,-5.22591 4.86933,-7.75101 1.18101,-1.65339 2.46708,-3.2623 3.46898,-5.03427 1.7824,-6.11494 -7.84664,-6.6416 -13.30612,-9.6205 -0.2292,-0.12506 -3.29355,0.22879 -3.63397,0.27387 -2.89195,0.30971 -5.62076,1.31686 -8.29068,2.41542 -2.82065,1.13197 -5.46628,2.58099 -8.06971,4.13143 -2.53638,1.22945 -4.83578,2.84226 -7.17486,4.3939 -1.3709,0.9908 -2.65439,2.08728 -3.75772,3.36967 -1.68219,1.5319 -3.2942,3.1344 -4.9593,4.68411 -1.45269,1.52497 -2.83742,3.11478 -4.17917,4.73738 -3.63798,5.37456 3.79556,4.99992 12.33474,9.71531 0.36277,0.20032 0.74858,-0.35639 1.13043,-0.51739 2.83931,-1.19711 0.0772,0.11931 3.2847,-1.4869 2.05827,-1.08132 4.03317,-2.30736 5.99544,-3.55117 0.47837,-0.30323 2.68062,-1.74462 3.32498,-2.03614 0.35437,-0.16032 0.74954,-0.2081 1.1243,-0.31215 5.21693,1.88088 0.36376,0.16898 -12.03101,-6.67223 -0.15964,-0.0881 0.50681,0.0961 0.47276,0.27524 -0.0452,0.23791 -0.50356,0.73864 -0.59892,0.8559 -1.01692,1.01725 -2.09668,1.99757 -3.23289,2.88304 -1.13366,0.77062 -1.78521,1.99288 -2.66487,3.00144 -0.4999,0.5181 -1.82578,2.76169 -0.85978,3.33392 4.5024,2.66709 9.1647,5.0545 13.74706,7.58175 2.23462,0.21341 4.48944,-0.29226 6.57794,-1.06304 0.183,-0.12081 0.39287,-0.20847 0.549,-0.36244 0.24654,-0.24315 0.89597,-1.50801 0.63823,-1.67783 -10.79379,-7.11175 -8.78038,-7.42141 -15.10327,-7.57337 -2.15751,-0.16966 -4.25743,0.16954 -6.33775,0.69945 -1.47207,0.22532 -2.86569,0.7409 -4.32815,0.98616 -1.4266,0.0627 -2.75764,0.56693 -4.1574,0.79381 -1.34956,0.0761 -2.77182,0.34036 -4.09815,0.0146 -0.19362,-0.0476 -0.39408,-0.0731 -0.58087,-0.14283 -0.44722,-0.167 -1.67781,-0.9967 -1.05789,-0.6456 20.98616,11.88561 15.60495,9.05661 11.16471,6.32842 -1.5196,-0.99201 -1.64758,-2.44567 -1.34593,-4.11665 0.81182,-3.10827 3.25991,-5.22887 5.61134,-7.23229 3.34059,-2.85841 7.1928,-4.9601 11.03557,-7.04621 4.20603,-2.0403 8.36157,-4.17644 12.43734,-6.46651 2.83986,-1.81873 5.72411,-3.57291 8.44219,-5.57327 1.02334,-0.93747 3.59337,-2.12394 4.07012,-3.46338 0.1339,-0.37621 0.1713,-0.78006 0.25695,-1.17009 -4.58492,-2.39842 -9.04567,-5.05089 -13.75477,-7.19526 -0.3497,-0.15925 -0.7063,0.30296 -1.06207,0.44814 -2.01467,0.82213 -3.9665,1.70254 -5.91589,2.67225 -3.65467,2.01385 -7.46814,3.79404 -10.77826,6.35973 -0.21249,0.34647 -0.5386,0.64517 -0.63747,1.03939 -1.30502,5.20313 13.83595,8.79595 14.32339,9.01748 0.61437,0.27922 1.34531,-0.12016 2.0121,-0.22411 1.77354,-0.2765 3.17125,-0.64859 4.92228,-1.06644 4.5451,-1.30218 8.81106,-3.4264 12.94588,-5.69043 1.97421,-0.92375 3.81889,-2.13756 5.873,-2.87661 1.77393,-1.01584 3.70407,-1.74284 5.64601,-2.36653 0.2903,-0.0996 0.5646,-0.27948 0.8709,-0.29874 0.14174,-0.009 0.48783,0.29097 0.36421,0.22107 -4.36595,-2.4686 -8.555,-5.27784 -13.09134,-7.41726 -0.35331,-0.16663 -0.51614,0.58655 -0.77843,0.87603 -1.08393,1.19626 -1.25521,1.36145 -2.40611,2.57297 -3.24235,3.30458 -6.48143,6.60383 -9.86178,9.77025 -3.28404,3.17757 -6.24306,6.66774 -9.44409,9.92313 -2.01291,2.02617 -4.18264,3.87968 -6.23082,5.8679 -1.9223,1.70425 -3.86185,3.39422 -5.88858,4.9743 -0.96722,0.65082 -1.83726,1.617 -2.96371,2.01994 -0.16514,0.0591 -0.37401,0.12202 -0.52514,0.033 -0.1206,-0.071 -0.22959,-0.47479 -0.10781,-0.4058 4.37015,2.47548 8.64001,5.12397 12.96002,7.68596 0.53794,-0.73321 1.39731,-1.28776 1.82599,-2.1042 2.47982,-4.72287 -10.9966,-8.27065 -13.75481,-9.82443 -3.01935,-0.16368 -6.00541,0.25868 -8.9999,0.5565 -3.33136,0.25062 -6.66491,0.53234 -9.97631,0.97952 -1.49207,0.0622 -2.91511,0.57342 -4.40233,0.66511 -0.31252,0.0193 -0.63635,0.0538 -0.93899,-0.0266 -0.22732,-0.0604 -0.79953,-0.49519 -0.59419,-0.3805 4.19573,2.34367 8.37764,4.712 12.56647,7.06801 -0.12644,-0.0812 -0.29432,-0.11966 -0.37932,-0.24357 -0.73362,-1.06945 0.52294,-2.95045 0.98506,-3.83506 1.54566,-2.92927 3.75683,-5.3607 6.04032,-7.72501 3.25479,-3.46003 6.89274,-6.53596 10.49318,-9.62639 3.01741,-2.51586 6.22504,-4.77774 9.2122,-7.33076 2.43349,-2.03466 5.14772,-3.71373 7.78144,-5.47881 7.74668,-5.34821 5.26549,-2.15496 -8.93837,-11.02194 -3.67679,2.35575 -7.28617,4.81658 -10.95286,7.1879 -3.60708,2.14166 -6.78641,4.82897 -9.89312,7.62168 -1.86144,1.77073 -3.57985,3.68242 -5.41099,5.4821 -1.46318,1.5278 -2.96981,3.07905 -4.72685,4.26969 -1.60606,1.0531 -3.23293,2.05513 -4.91002,2.98816 -1.48842,0.87657 -2.93256,1.85966 -4.62833,2.26668 -2.29749,0.73424 -4.56561,1.54956 -6.89065,2.19388 -0.21725,0.033 -1.44035,0.30317 -1.78598,0.0997 -0.1635,-0.0962 -0.41263,-0.60608 -0.24764,-0.51244 4.26109,2.41814 8.34115,5.14836 12.62053,7.53399 0.13307,0.0742 0.031,-0.30774 0.0903,-0.44808 0.14456,-0.342 0.34422,-0.658 0.51633,-0.987 2.37142,-3.19148 5.06218,-6.13906 7.75268,-9.063 2.78598,-3.28539 5.95741,-6.19831 9.12416,-9.10549 2.81779,-2.65813 5.52878,-5.42524 8.32563,-8.10749 2.35912,-2.2275 4.9302,-4.20401 7.51479,-6.15787 2.21682,-1.57455 4.70767,-2.64853 7.23257,-3.61266 2.59322,-1.05976 5.31693,-1.74942 7.95483,-2.67784 6.95804,-2.23836 5.26274,-0.70784 -10.5794,-8.877 -0.38723,-0.19968 -0.77405,0.40021 -1.15813,0.6059 -0.57211,0.30639 -1.14022,0.62019 -1.71034,0.93029 -5.10523,2.95137 -10.1952,5.93597 -15.4318,8.64977 -3.75271,2.05164 -7.48656,4.12742 -11.25821,6.14133 -2.71694,1.28113 -5.35381,2.71843 -8.01141,4.11619 -1.86867,0.9152 -3.66552,1.95908 -5.47922,2.98047 -1.72455,1.10921 -3.54098,2.13062 -5.05664,3.51931 -0.90463,0.82401 -2.04789,2.27228 -3.2307,2.66682 1.2635,-5.34205 -0.16936,0.0679 12.96906,7.26428 0.17418,0.0954 0.22262,-0.32904 0.32813,-0.49729 0.11736,-0.18717 0.22653,-0.37935 0.34031,-0.56871 0.0591,-0.0985 0.27684,-0.23544 0.17847,-0.2947 -4.39251,-2.6466 -8.90136,-5.09514 -13.35205,-7.6427 -1.49531,0.85332 -2.7992,1.97088 -4.22888,2.92082 -4.9037,3.89088 -8.1625,-1.2946 11.33893,8.0153 0.21258,0.10149 0.0789,-0.46569 0.14649,-0.69135 0.0927,-0.30958 0.22227,-0.60693 0.33341,-0.91039 0.9684,-2.27791 2.17116,-4.41805 3.44801,-6.53272 1.83613,-2.66105 4.2652,-4.84603 6.6056,-7.05325 2.98662,-2.55678 6.31032,-4.67369 9.50952,-6.94733 2.50895,-1.86606 5.26592,-3.33396 7.90531,-4.98838 2.47853,-1.36936 5.0954,-2.41932 7.72677,-3.44606 3.54649,-1.27142 7.2593,-1.95785 10.88689,-2.95161 3.44504,-1.09078 6.90807,-2.11655 10.36057,-3.18415 1.57896,-0.50775 3.15215,-1.01407 4.66702,-1.69465 0.22148,-0.0995 0.40862,-0.34535 0.65075,-0.32727 0.13052,0.01 -0.0964,0.24337 -0.14462,0.36505 -4.28721,-2.811 -8.39676,-5.91369 -12.86162,-8.433 -0.33959,-0.19162 -0.65762,0.4197 -0.9957,0.61396 -2.34749,1.34885 -4.80653,2.53706 -7.25277,3.6926 -4.36193,1.75711 -8.68282,3.60481 -12.99058,5.49322 -4.10098,1.72429 -7.9938,3.8697 -11.98214,5.82283 -2.74984,1.24947 -5.53582,2.41306 -8.32154,3.58056 -2.33776,0.90325 -4.64698,1.81777 -6.82899,3.06087 -1.6298,0.95465 -2.93181,2.35203 -4.53805,3.32872 -1.22767,0.5776 -2.9154,2.1317 -4.32374,1.80128 -2.79977,-4.49808 -0.18699,-0.17007 12.40537,6.86221 0.13592,0.0759 -0.24331,-0.20851 -0.31037,-0.34901 -0.3676,-0.7701 -0.29072,-1.79017 -0.29405,-2.60894 0.27412,-1.91104 1.29442,-3.60878 2.49962,-5.08165 1.12269,-1.14938 2.65266,-1.66837 3.92767,-2.62115 1.45993,-0.82313 2.77198,-1.84586 4.11828,-2.83995 1.71524,-1.19027 3.37924,-2.44494 5.19567,-3.48489 2.614,-1.40322 5.09913,-3.01589 7.68692,-4.46278 2.75467,-1.40031 5.54523,-2.81309 8.46488,-3.83629 3.572,-1.39268 7.35073,-2.15702 11.06617,-3.06688 2.1504,-0.43081 4.24068,-1.17674 6.24799,-2.0536 1.84007,-0.61386 3.57214,-1.48886 5.38903,-2.15968 2.39709,-0.73828 4.7544,-1.60292 7.1525,-2.33873 2.13542,-0.69731 4.39264,-0.70615 6.57979,-1.09077 1.97808,-0.16946 3.86686,-0.73435 5.77371,-1.23803 2.24307,-0.87141 4.58412,-1.44007 6.8694,-2.18694 2.22625,-0.79063 3.47755,-2.45154 4.24552,-4.59789 0.49708,-1.74364 0.66891,-3.12814 -0.47933,-4.63119 -3.93217,0.9071 -9.47789,-8.04187 -15.22808,-8.02582 -0.90173,0.003 -1.78593,0.33746 -2.64717,0.56315 -2.16621,0.70967 -3.8994,2.23159 -5.77133,3.47988 -1.28133,0.95004 -2.63044,1.80516 -3.90936,2.7585 -0.35558,0.26506 -0.71563,0.52642 -1.04581,0.82251 -0.19353,0.17354 -0.73737,0.43927 -0.51972,0.58138 4.38614,2.86373 9.04269,5.29022 13.56403,7.93533 2.86923,-1.07989 5.45916,-2.74922 7.90565,-4.5716 1.30728,-1.18837 2.73972,-2.28872 3.7411,-3.76068 0.69028,-1.09515 0.57253,-2.17863 0.22624,-3.35306 0,0 -13.75609,-6.27088 -13.75609,-6.27088 v 0 c 0.43127,0.58847 0.90259,1.25446 0.5011,1.95547 -0.93702,1.30814 -2.28597,2.27716 -3.47973,3.33299 -2.35429,1.69155 -4.84173,3.25548 -7.6231,4.14233 3.94379,2.81864 7.78552,5.78584 11.83137,8.45591 0.15408,0.10169 0.25521,-0.2681 0.39804,-0.38505 1.55286,-1.27142 3.28779,-2.30435 4.90031,-3.49462 1.73096,-1.16737 3.36424,-2.58064 5.46172,-3.01518 0.62992,-0.11694 0.95322,-0.2188 1.59062,-0.18113 4.62466,0.27329 16.94781,10.99054 -10.17189,-6.59562 1.74126,0.70663 1.64617,1.19544 1.27065,2.96111 -0.59489,1.92717 -1.5473,2.7829 -3.50056,3.38484 -2.28542,0.69029 -4.56126,1.40392 -6.83314,2.13394 -1.86368,0.50835 -3.75489,0.86129 -5.67334,1.09378 -2.26937,0.26543 -4.53808,0.57399 -6.71882,1.29155 -2.40682,0.79019 -4.81647,1.57331 -7.20528,2.41681 -1.80044,0.7305 -3.58032,1.50925 -5.40406,2.1821 -2.00043,0.75722 -4.01242,1.47936 -6.12371,1.87071 -3.75896,0.93132 -7.56641,1.75112 -11.16582,3.21306 -2.93909,1.16982 -5.82135,2.51337 -8.59563,4.03386 -2.57918,1.47872 -5.10173,3.05203 -7.70477,4.49029 -1.81837,1.12195 -3.53915,2.37807 -5.2793,3.6155 -1.32898,0.99565 -2.69698,1.91207 -4.09599,2.80484 -1.43419,0.93394 -3.04899,1.61757 -4.15253,2.98549 -1.3792,1.75296 -2.51107,3.72858 -2.9716,5.9322 -0.42607,6.72358 2.58546,13.89204 13.95641,11.7794 1.70484,-0.52484 3.23516,-1.48185 4.74126,-2.41844 1.50416,-1.05667 2.85238,-2.33425 4.47183,-3.22635 2.17907,-1.14349 4.47115,-1.99876 6.74447,-2.93278 2.80281,-1.1927 5.62687,-2.33761 8.37308,-3.65834 3.94777,-1.99116 7.87216,-4.0231 11.94217,-5.76243 4.31945,-1.89631 8.68307,-3.6801 13.01855,-5.53542 1.95914,-0.9522 3.90805,-1.926 5.82546,-2.96058 0.50397,-0.27193 0.99884,-0.56038 1.49829,-0.84052 0.31277,-0.17543 1.20979,-0.29179 0.93838,-0.52618 -3.74847,-3.2371 -7.92031,-5.94879 -11.88046,-8.92318 -1.22719,1.18526 -4.01355,1.49621 -5.55291,2.18899 -3.43466,1.07096 -6.88468,2.09121 -10.32725,3.13547 -3.67765,0.9652 -7.42888,1.70351 -10.98125,3.09056 -2.69832,1.05992 -5.36095,2.21186 -7.87351,3.66898 -2.70171,1.61585 -5.40577,3.22217 -7.95915,5.07237 -3.26329,2.2977 -6.63032,4.47577 -9.62334,7.12899 -2.43197,2.30605 -4.93949,4.60371 -6.81524,7.40375 -1.324,2.1933 -2.56344,4.41585 -3.58314,6.77064 -0.28262,0.72486 -0.78535,1.96378 -0.70523,2.0225 11.11095,8.14252 9.2911,11.16909 14.84179,7.04588 1.36792,-0.96609 2.68778,-2.00505 4.16026,-2.81433 -4.13289,-2.7284 -8.2024,-5.55535 -12.39869,-8.18521 -0.0147,-0.009 -0.14428,0.25714 -0.49677,0.8256 -0.14461,0.23321 -0.65593,0.52359 -0.4415,0.6948 11.86492,9.47378 8.42352,11.94948 12.84847,8.4416 1.25222,-0.84217 2.34972,-1.89283 3.41529,-2.95596 1.52098,-1.23338 3.20569,-2.25517 4.88718,-3.25333 1.81755,-0.98999 3.586,-2.06616 5.45951,-2.95033 2.66572,-1.38772 5.30731,-2.8214 8.0166,-4.1241 3.77769,-1.99676 7.47864,-4.12937 11.25287,-6.13319 5.2221,-2.75618 10.32081,-5.73451 15.44415,-8.66863 0.5681,-0.30701 1.13636,-0.61371 1.70429,-0.92104 0.39362,-0.213 1.51892,-0.34725 1.18035,-0.63995 -10.78271,-9.32163 -7.87059,-10.07916 -14.60754,-7.84877 -2.67927,0.86748 -5.3831,1.67329 -7.99425,2.73628 -2.62213,1.04815 -5.18008,2.27437 -7.45988,3.96169 -2.62589,1.99186 -5.21853,4.03187 -7.60812,6.30749 -2.7874,2.6763 -5.48264,5.44509 -8.30843,8.07963 -3.19517,2.93828 -6.36993,5.90573 -9.19109,9.21472 -2.77132,3.02267 -5.56044,6.06177 -7.9508,9.39909 -0.21109,0.3984 -0.44372,0.7861 -0.63328,1.19518 -0.17102,0.3691 -0.76758,0.88555 -0.44676,1.13567 8.73674,6.81144 8.79946,9.68501 15.48409,8.02983 2.31854,-0.69686 4.60328,-1.49915 6.91256,-2.22598 1.69473,-0.6526 3.23281,-1.61021 4.7984,-2.5221 1.71744,-0.96571 3.37745,-2.03192 5.0069,-3.13882 1.74325,-1.34381 3.35258,-2.85474 4.85028,-4.46755 1.79202,-1.80288 3.519,-3.67001 5.3695,-5.41483 3.06276,-2.73341 6.23067,-5.31534 9.76881,-7.42471 3.56377,-2.32805 7.04548,-4.81206 10.75925,-6.89388 -13.9328,-9.40918 -8.69904,-10.77058 -16.32658,-5.58395 -2.66559,1.80994 -5.43462,3.49608 -7.86903,5.61743 -3.01558,2.52213 -6.21582,4.80304 -9.21838,7.34065 -3.63424,3.12382 -7.29004,6.24902 -10.5699,9.75083 -2.36345,2.48192 -4.64404,5.04026 -6.25701,8.09181 -0.98025,1.86575 -1.87764,3.74138 -1.49605,5.91216 4.47978,2.60314 8.89349,5.32345 13.43933,7.80943 0.21105,0.11542 1.77486,-0.0717 1.89732,-0.0878 1.46508,-0.19215 2.91168,-0.51867 4.38649,-0.64271 3.31234,-0.38091 6.63266,-0.6784 9.95549,-0.95017 2.84468,-0.29162 5.80106,-0.75883 8.61293,-0.0364 -4.01164,-2.39741 -8.13559,-4.6162 -12.03493,-7.19225 -0.57156,-0.37759 1.09357,-0.4585 0.87991,-0.0213 -0.43108,0.88221 -1.47212,1.41354 -1.93076,2.28225 11.59732,8.66545 9.22916,11.9968 16.51576,6.29329 2.04341,-1.63486 4.03926,-3.33374 5.96812,-5.10217 2.06707,-1.98767 4.24691,-3.85399 6.25376,-5.90532 3.16428,-3.26173 6.1361,-6.70905 9.40961,-9.867 3.39505,-3.19207 6.64747,-6.51931 9.90836,-9.84623 0.25916,-0.27451 3.50217,-3.69601 3.39004,-3.79676 -9.96672,-8.9553 -7.81314,-10.57163 -13.77855,-8.49854 -1.94142,0.70676 -3.84608,1.49186 -5.68861,2.42977 -2.04138,0.89469 -3.9767,2.02298 -5.96838,3.02078 -4.06108,2.16276 -8.23356,4.22156 -12.71629,5.34929 -1.62184,0.34891 -3.11936,0.73218 -4.77701,0.8605 -0.61269,0.0474 -2.33845,-0.37035 -1.84358,-0.006 3.71143,2.73254 7.973,4.64748 11.74493,7.29587 0.25756,0.18084 -0.59442,0.45068 -0.8848,0.32936 -0.20091,-0.0839 0.22928,-0.37022 0.34392,-0.55533 3.31339,-2.409 7.02441,-4.18858 10.62438,-6.12136 1.49899,-0.72828 2.99183,-1.4498 4.55192,-2.04062 0.40737,-0.15427 0.8126,-0.31832 1.23226,-0.43509 0.27664,-0.077 1.09197,0.009 0.84932,-0.1441 -4.08456,-2.58476 -8.36799,-4.841 -12.55199,-7.26149 0.23631,-0.26817 0.46018,-0.54785 0.70893,-0.80451 0.0633,-0.0653 -0.085,0.16444 -0.15015,0.22795 -0.20966,0.20434 -0.43979,0.38689 -0.66838,0.5698 -0.49416,0.39542 -1.39427,1.05108 -1.89772,1.41656 -0.35135,0.25507 -0.70466,0.50742 -1.05699,0.76112 -2.69606,1.9333 -5.52364,3.67906 -8.3446,5.4215 -4.08556,2.2643 -8.25282,4.36573 -12.4297,6.4548 -3.91946,2.15218 -7.81879,4.36688 -11.20893,7.31806 -2.56409,2.24927 -5.13013,4.66762 -6.08973,8.05732 -0.12254,0.45699 -0.29555,0.90336 -0.36763,1.37098 -0.21991,1.42675 0.1583,3.14531 0.98221,4.31256 0.6152,0.57294 0.40284,0.43897 1.13778,0.86491 4.4268,2.56565 9.66767,7.64816 15.51758,8.2066 1.45832,-0.0239 2.92651,-0.0724 4.35816,-0.37589 1.38534,-0.3112 2.76572,-0.60664 4.17151,-0.81405 1.44323,-0.36329 2.89038,-0.69179 4.34497,-1.01042 2.00379,-0.416 4.03855,-0.52536 6.07966,-0.33812 5.72181,0.63545 5.81025,4.57384 -11.08516,-7.05781 -0.29961,-0.20627 0.46661,-0.55834 0.69003,-0.84538 0.0199,-0.0256 0.0697,-0.0944 0.0376,-0.0895 -0.14156,0.0215 -0.2695,0.0969 -0.40425,0.14532 -1.96016,0.55559 -4.04613,0.95541 -6.03614,0.31552 4.10167,2.38174 8.28145,4.63379 12.30502,7.14522 0.18644,0.11637 -0.43218,0.0811 -0.64597,0.1321 -0.13909,0.0332 -0.29574,0.20363 -0.41181,0.12012 -0.14738,-0.10602 0.68335,-1.16831 0.78671,-1.28219 0.79287,-0.96803 1.45595,-2.04923 2.51794,-2.76208 1.18285,-1.00593 2.39933,-1.99452 3.41218,-3.17648 0.54333,-0.72825 1.11914,-1.48461 1.24315,-2.42177 0.72239,-5.45934 -9.27368,-9.76863 -14.60467,-8.23205 -3.72115,1.5986 -6.82861,4.35869 -10.52914,6.02585 -0.79037,0.35684 -2.17456,1.06117 -3.10237,1.21728 -0.22721,0.0382 -0.8739,-0.22497 -0.68489,-0.0932 19.74632,13.76521 9.39646,11.90022 12.61258,7.28013 1.31409,-1.56503 2.64049,-3.12515 4.08106,-4.57625 1.69193,-1.58653 3.36292,-3.19567 5.0329,-4.80455 1.06326,-1.15314 2.25451,-2.16265 3.55446,-3.04299 2.30645,-1.56012 4.65827,-3.0362 7.13059,-4.32514 2.54667,-1.50998 5.18658,-2.83633 7.93981,-3.93423 2.59206,-1.0032 5.24439,-1.8682 8.03951,-2.05215 0.75935,-0.0307 1.59431,-0.11006 2.35351,0.0289 0.2913,0.0533 1.09313,0.43888 0.84307,0.28023 -4.01992,-2.55051 -8.1716,-4.89054 -12.1736,-7.46908 -0.12926,-0.0833 0.30923,-0.13404 0.45394,-0.082 0.0848,0.0305 0.0364,0.17647 0.0545,0.26471 -0.97645,1.73866 -2.26935,3.29571 -3.40716,4.92792 -1.74567,2.58014 -3.58934,5.12616 -4.97015,7.92547 -1.01904,2.04947 -1.90463,4.1522 -2.94347,6.19086 -0.97594,1.65484 -1.31084,2.04841 -3.20104,2.64489 -2.30959,0.69141 -4.70391,1.0518 -7.11217,1.15573 -1.14872,-0.0364 -1.5685,-0.0119 -2.68256,-0.18722 -0.36652,-0.0577 -0.73474,-0.11952 -1.08936,-0.22867 -0.27354,-0.0842 -1.02827,-0.49867 -0.78483,-0.34817 12.1521,7.51284 16.98049,7.11315 11.74075,7.44753 -0.64524,-0.69029 1.54481,-1.79198 1.90769,-2.06977 2.0632,-1.5008 4.23889,-2.83145 6.34049,-4.2778 2.35593,-1.56803 4.81573,-2.99305 7.42314,-4.10393 2.96865,-1.28918 5.94312,-2.576 8.71726,-4.2586 2.65138,-1.54923 0.9872,-0.56128 3.718,-2.22274 1.63477,-0.99462 3.31862,-2.11829 5.12482,-2.79026 0.3178,-0.11823 0.66178,-0.14818 0.99267,-0.22226 -4.06444,-2.69254 -8.12888,-5.38507 -12.19331,-8.07762 -0.0247,-0.0164 0.0948,0.0277 0.0743,0.0492 -0.22712,0.23843 -0.47605,0.45804 -0.74216,0.65199 -1.93532,1.41053 -4.11286,2.46482 -6.15034,3.70855 -5.2118,3.0227 -10.63123,5.78809 -15.33673,9.58923 -1.13626,1.02541 -2.40527,2.04636 -3.20845,3.3799 -0.23462,0.38955 -0.96384,0.95945 -0.59618,1.22707 11.84803,8.6244 8.4217,9.58039 13.95895,7.68437 3.19911,-1.63845 5.94733,-4.08316 8.58378,-6.49278 2.36984,-2.23814 4.80586,-4.40541 7.4576,-6.30866 2.96125,-2.25307 5.63225,-4.7928 7.49983,-8.05914 2.42312,-4.07031 4.73001,-8.20967 7.28048,-12.20339 0,0 -13.04362,-7.64316 -13.04362,-7.64316 z" /> | ||
| 61 | <path | ||
| 62 | style="fill:#ffffff" | ||
| 63 | id="path1039" | ||
| 64 | d="m 432.49953,344.24829 c -0.74992,1.88333 -1.06676,4.9053 -2.27069,6.52441 17.7842,9.09169 10.87195,10.15007 14.63577,2.53805 1.10268,-1.69406 2.3453,-3.29642 3.58552,-4.89165 1.1227,-1.38745 2.25262,-2.77369 3.29055,-4.22698 1.32978,-2.11295 2.84636,-4.09666 4.29583,-6.1268 1.30305,-1.75714 2.50915,-3.58428 3.74208,-5.39123 1.56571,-1.86607 2.88779,-3.92042 4.5192,-5.73014 1.88665,-1.74545 3.98152,-3.25993 6.12942,-4.66512 2.9896,-2.09562 6.07519,-4.03418 8.97079,-6.26338 2.78,-1.954 5.25025,-4.28817 8.0846,-6.16612 2.11189,-1.2452 4.15986,-2.59054 6.24117,-3.88429 3.02352,-1.90978 5.76591,-4.21725 8.62645,-6.35471 1.48877,-1.16753 3.03884,-2.25756 4.59377,-3.33481 0.2987,-0.20694 0.56223,-0.49503 0.91007,-0.60017 0.10854,-0.0328 0.32024,0.31389 0.22153,0.25812 -4.34332,-2.4539 -8.64841,-4.97486 -12.97261,-7.46229 -2.62513,4.04578 -5.83026,7.70019 -8.9005,11.41297 -3.66383,4.80372 -8.12475,8.87004 -12.73052,12.74179 -5.91272,4.63036 -8.29371,2.67995 10.5926,8.76614 0.72744,0.23442 0.95585,-1.19331 1.45579,-1.7714 1.41805,-1.63974 2.66603,-2.92715 4.16048,-4.51228 2.07947,-2.31538 4.40674,-4.3784 6.66784,-6.50762 1.84986,-1.74198 3.62603,-3.51822 5.55816,-5.17079 2.44558,-1.96438 4.85201,-4.07114 7.7316,-5.37452 1.77224,-0.39347 3.46115,-1.03221 5.19071,-1.59061 2.31711,-0.81022 4.51877,-2.02167 6.60839,-3.31065 1.39458,-0.88305 2.74941,-1.91401 4.27263,-2.56811 0.19353,-0.1003 0.38287,-0.20912 0.58059,-0.3009 0.59457,-0.27597 0.58438,-0.16429 1.12628,-0.58611 0.45898,-0.35728 0.79797,-0.75022 1.30061,-1.05309 5.14148,-4.96325 -7.95067,-9.73777 -13.28583,-9.85314 -2.48876,0.35892 -4.69207,1.55828 -6.90425,2.67053 -2.3624,0.89905 -3.73647,2.37469 -4.94941,4.5448 -1.13905,2.59111 -2.308,5.17539 -3.35541,7.80576 -1.96526,6.34677 5.52532,5.98779 13.1748,9.82243 0.28349,0.14211 3.63407,-1.76581 4.01264,-1.97846 2.35819,-1.33273 4.68655,-2.71263 6.98133,-4.15154 1.37572,-0.7433 2.66223,-1.6029 3.75923,-2.71805 1.17204,-1.09559 1.77792,-2.49721 2.12557,-4.03109 0.0998,-1.18885 -0.16945,-2.73124 -1.30979,-3.41737 -13.34308,-8.02843 -8.61169,-6.96412 -14.89582,-8.20796 -2.60759,-0.54437 -5.18738,-0.18878 -7.72525,0.49502 -0.65085,0.12605 -1.04631,0.33591 -1.66437,0.0414 -0.177,-0.0843 -0.61222,-0.48509 -0.44157,-0.38856 4.20664,2.42581 8.41328,4.85163 12.61992,7.27744 -0.0556,-0.0879 -0.16461,-0.15979 -0.1668,-0.2638 -0.005,-0.23223 0.098,-0.45412 0.14694,-0.68117 0.55462,-1.72933 1.86737,-2.87785 3.2017,-4.01207 0.89484,-1.03596 2.11945,-1.6709 3.12761,-2.57143 0.86027,-0.47119 1.53234,-0.96433 1.98432,-1.83939 -10.36867,-6.55283 -10.56003,-10.30222 -16.32628,-5.43659 -1.3875,0.80231 -2.64757,1.80472 -3.95489,2.73368 -1.67535,0.85692 -3.39189,1.62876 -5.04528,2.53289 -1.98355,0.85518 -3.9245,1.76609 -5.9385,2.55617 -2.6027,1.25889 -5.19179,2.53692 -7.73292,3.91622 -3.16884,1.42333 -6.13113,3.23939 -9.20069,4.85676 -3.01773,1.63467 -5.94816,3.43762 -8.7288,5.45006 -1.8161,1.09651 -3.20033,2.68211 -4.6919,4.14933 -2.56301,2.52117 0.62871,-0.80843 -1.82713,1.78993 -1.29257,1.17689 -2.33137,2.57077 -3.45106,3.89804 -1.30688,1.49913 -2.76578,2.85213 -4.2452,4.17729 -1.67526,1.40297 -3.27521,2.85848 -4.81531,4.40468 -1.11976,1.32692 -1.82601,2.96903 -2.59642,4.51708 -0.89521,1.49446 -1.60042,3.07987 -2.33327,4.65731 -0.55628,0.99643 -1.16583,1.96813 -1.65121,3.00157 -0.53699,0.94305 -0.94412,1.93177 -1.55569,2.83258 -0.8921,1.16304 -1.39503,2.55479 -2.18277,3.77523 -0.75855,1.14369 -1.22847,2.45654 -1.66572,3.75099 -0.47382,1.2505 -0.77063,2.5524 -1.27452,3.79267 0.0176,-0.0459 0.01,-0.16167 0.0529,-0.13767 15.20424,8.50293 10.5337,11.06607 15.80147,3.6489 2.08695,-2.75768 3.99725,-5.64466 5.97647,-8.48077 1.92629,-2.76638 3.80319,-5.56446 5.65316,-8.38206 1.26769,-2.14829 2.54516,-4.29976 3.72202,-6.4994 0.99127,-1.696 1.86944,-3.43898 2.60941,-5.25851 0.6295,-1.85568 1.18095,-3.73524 1.94295,-5.54209 0.89682,-2.17472 2.06346,-4.22311 3.20854,-6.27422 1.00575,-1.47324 1.84774,-3.08799 2.93752,-4.50437 0.10494,-0.13639 1.21585,-1.33809 1.34628,-1.47981 0.9082,-1.31448 2.09267,-2.36626 3.17242,-3.52376 0.95213,-0.94011 1.9487,-1.7963 2.92104,-2.71484 1.11996,-1.04095 2.26946,-2.03134 3.34625,-3.11929 1.36036,-1.04378 2.73501,-2.1089 4.26504,-2.89286 1.62924,-1.00996 3.42299,-1.69537 5.08542,-2.63746 1.4904,-0.68881 3.08098,-1.11573 4.60632,-1.70803 1.61959,-0.626 3.03985,-1.70627 4.57116,-2.53507 1.26489,-0.46675 2.54065,-0.95411 3.84876,-1.29477 1.16488,-0.23001 2.35876,-0.2251 3.54126,-0.23621 0.64204,0.066 1.31618,-0.0895 1.95561,4.1e-4 0.38998,0.0548 0.54234,0.17649 0.85356,0.35797 -4.23079,-2.43099 -8.46158,-4.86198 -12.69237,-7.29297 -0.23596,0.40922 0.32876,-0.0608 0.27332,0.0117 -0.23121,0.30203 -1.16696,0.73465 -1.48098,0.91793 -1.89828,0.86389 -3.80655,1.66682 -5.80597,2.2709 -2.38683,0.81515 -4.90251,1.25962 -7.18253,2.35472 -1.59845,0.71242 -3.19309,1.38261 -4.74426,2.19516 -2.17518,1.09322 -4.14076,2.57229 -6.05109,4.07209 -1.04757,0.75941 -1.86421,1.78884 -2.84381,2.62126 -0.47353,0.40237 -1.00615,0.72951 -1.50758,1.09652 -2.60957,1.82293 -5.07268,3.85707 -7.44319,5.9788 -2.38247,2.05357 -4.89087,3.94947 -7.26141,6.01746 -1.45747,1.27654 -2.72386,2.74505 -3.98576,4.21007 -1.17894,1.2966 -2.3584,2.58174 -3.34479,4.03392 -0.80492,1.04949 -1.52848,2.15792 -2.2549,3.2618 -0.56841,1.04014 -0.90533,2.19537 -1.26485,3.32241 -0.20666,1.14317 -0.70444,2.17858 -1.1385,3.24297 -0.37682,1.24532 -0.9082,2.43344 -1.34007,3.65859 -0.59614,1.23685 -1.07713,2.51619 -1.55984,3.79939 -0.60561,1.64317 -1.22029,3.28304 -1.72453,4.96068 -0.49888,1.22534 -0.83747,2.50404 -1.31075,3.73744 -0.40444,1.06336 -0.97991,2.04175 -1.55375,3.01889 0,0 13.08691,7.56878 13.08691,7.56878 v 0 c 0.61569,-1.04805 1.22928,-2.09904 1.67579,-3.23392 0.49152,-1.27241 0.84263,-2.58551 1.37098,-3.84669 0.46931,-1.6472 1.08207,-3.24556 1.66603,-4.85454 0.50216,-1.29407 0.94652,-2.60845 1.58598,-3.84657 0.4,-1.21115 0.96582,-2.36112 1.30393,-3.59348 0.42896,-1.10903 1.01951,-2.16153 1.20003,-3.35429 0.28527,-0.99865 0.6179,-2.00144 1.04898,-2.94116 0.72768,-1.09373 1.44377,-2.19601 2.24241,-3.24048 0.96693,-1.3969 2.13628,-2.61995 3.25929,-3.89028 1.2158,-1.40982 2.45249,-2.8098 3.87076,-4.02235 2.38823,-2.05378 4.89907,-3.95769 7.27043,-6.03197 2.36178,-2.07152 4.79443,-4.06893 7.37306,-5.86698 1.54036,-1.15559 2.87935,-2.51821 4.3653,-3.73979 1.87881,-1.41018 3.76354,-2.83918 5.90154,-3.83977 1.53809,-0.76523 3.13304,-1.40357 4.69438,-2.11969 2.31022,-0.90327 4.75639,-1.41584 7.09887,-2.23031 2.062,-0.71132 4.09072,-1.52659 6.00735,-2.575 1.15677,-0.75487 2.31614,-1.57824 2.36727,-3.09574 -11.84239,-7.15983 -8.79952,-8.19681 -16.23653,-7.94552 -1.27385,0.0591 -2.55519,0.15442 -3.77266,0.56575 -1.36356,0.46811 -2.76869,0.85036 -4.03011,1.56676 -1.44448,0.83593 -2.83281,1.8206 -4.43538,2.33025 -1.60897,0.54133 -3.21588,1.11756 -4.72908,1.89291 -1.7058,0.87233 -3.44756,1.67232 -5.09656,2.65452 -1.58254,0.9473 -3.11251,2.01939 -4.48952,3.24809 -1.07479,1.06783 -2.23755,2.0338 -3.32634,3.0869 -0.97659,0.93843 -2.02953,1.78709 -2.95848,2.77592 -1.11653,1.14012 -2.228,2.27157 -3.18959,3.55198 -1.73615,1.91381 -3.12213,4.07803 -4.46152,6.28494 -1.17125,2.09783 -2.36412,4.19304 -3.28797,6.41396 -0.77999,1.81247 -1.36826,3.69143 -1.96005,5.57265 -0.69501,1.77895 -1.57036,3.46126 -2.52724,5.11323 -1.14995,2.16186 -2.41205,4.27023 -3.65327,6.38065 -1.84142,2.80594 -3.71367,5.58986 -5.63123,8.34468 -1.97876,2.83242 -3.8863,5.71663 -5.9713,8.47202 -5.09974,7.21931 -3.61585,3.58041 10.14902,11.86734 0.10559,0.0636 0.13753,-0.20454 0.2063,-0.30682 0.54562,-1.27233 0.85593,-2.60811 1.33519,-3.90196 0.366,-1.18393 0.79847,-2.37723 1.47685,-3.41749 0.76962,-1.22092 1.29148,-2.58159 2.16466,-3.74426 0.64823,-0.97619 1.10877,-2.02653 1.6781,-3.04528 0.45874,-1.04165 1.10992,-1.99296 1.6475,-2.99498 0.7231,-1.56766 1.42248,-3.13956 2.31322,-4.62198 0.69376,-1.40359 1.32111,-2.91229 2.35041,-4.10035 1.51226,-1.5356 3.15699,-2.90965 4.77261,-4.33535 1.51521,-1.38933 3.03002,-2.786 4.35719,-4.36096 1.07589,-1.31874 2.15268,-2.63092 3.39427,-3.80416 1.98924,-2.04024 3.98674,-4.10902 6.36889,-5.69823 2.76588,-1.9319 5.63882,-3.7109 8.62332,-5.28656 3.05605,-1.64619 6.06258,-3.38186 9.20471,-4.86291 2.50272,-1.33987 5.04539,-2.60674 7.63563,-3.76923 2.04517,-0.82102 4.03382,-1.76494 6.03055,-2.69514 1.72296,-0.88646 3.50857,-1.66371 5.15022,-2.6955 1.33179,-0.93952 2.63362,-1.91845 4.00061,-2.80598 5.75132,-4.27305 6.41889,-0.18499 -9.91136,-9.53506 0.0988,-0.15669 0.22735,-0.29822 0.29653,-0.47009 0.0309,-0.0768 -0.11478,0.11966 -0.17731,0.17392 -0.45656,0.39618 -1.06226,0.60574 -1.48578,1.04994 -1.07663,0.82836 -2.19514,1.6074 -3.14048,2.59206 -1.53745,1.34942 -2.95155,2.80683 -3.65012,4.78163 -0.0188,0.0593 -0.65643,1.94397 -0.51994,2.04078 7.75805,5.50234 9.22853,9.3143 15.73862,7.79935 2.42682,-0.47812 4.85872,-0.64526 7.24894,0.15555 5.24405,2.34611 1.12226,0.54529 -11.09364,-6.66691 -0.24934,-0.14721 0.51196,0.27175 0.75737,0.42541 0.62106,0.38887 1.13479,0.73189 1.24929,1.49492 -0.19447,1.28451 -0.67305,2.36229 -1.67066,3.23761 -1.0601,0.99375 -2.34252,1.69237 -3.58241,2.43713 -2.28455,1.40297 -4.58146,2.78316 -6.93152,4.07482 -0.6401,0.33075 -1.7557,0.97424 -2.50893,1.12733 -0.29072,0.0591 -1.13609,-0.18506 -0.88979,-0.0197 3.99496,2.68242 8.18171,5.06786 12.24564,7.64459 0.11606,0.0736 -0.36688,0.007 -0.3917,-0.1286 -0.0517,-0.28183 0.14352,-0.55483 0.21528,-0.83225 1.0086,-2.57511 2.17547,-5.08853 3.25754,-7.633 1.14069,-2.06393 2.33352,-2.87066 4.47439,-3.77341 1.99682,-0.99435 4.09811,-2.12487 6.39726,-1.94691 5.54739,1.31252 -1.16486,-0.19796 -11.70509,-7.15668 -0.22944,-0.15148 1.03266,-0.87795 0.47926,-0.42008 -0.1142,0.0945 -0.23753,0.17734 -0.3563,0.26601 -0.91035,0.69602 -1.797,1.34558 -2.88727,1.75467 -1.48835,0.81922 -2.91672,1.74573 -4.3402,2.6716 -2.04124,1.18747 -4.14532,2.33613 -6.41512,3.0194 -1.78134,0.56187 -3.56442,1.13378 -5.31489,1.77606 -2.89473,1.52569 -5.43227,3.61724 -7.94047,5.7005 -4.2817,3.73703 -8.32405,7.76259 -12.30794,11.8115 -1.41228,1.50514 -2.88298,3.03195 -4.21881,4.60891 -0.54545,0.64392 -2.16213,1.37043 -1.57814,1.97962 10.37811,10.8258 9.09373,11.67867 15.32582,6.79575 4.65474,-3.95024 9.15169,-8.10228 12.87166,-12.97238 3.18286,-3.85479 6.53093,-7.63942 9.17583,-11.89157 -4.19727,-2.86373 -8.27973,-5.90343 -12.59181,-8.59118 -0.077,-0.048 -1.09652,0.65829 -1.56463,0.98599 -1.5592,1.09152 -3.11333,2.19347 -4.61332,3.36563 -2.81974,2.1259 -5.54732,4.39017 -8.5582,6.24779 -2.08506,1.3181 -4.17789,2.62318 -6.27025,3.9296 -2.81241,1.94701 -5.33289,4.26353 -8.12848,6.23518 -2.89927,2.17261 -5.95114,4.11376 -8.92117,6.18537 -2.22251,1.51205 -4.43203,3.08505 -6.33475,4.9951 -1.64219,1.88227 -3.03883,3.95761 -4.61661,5.89356 -1.22912,1.79711 -2.42749,3.61668 -3.72627,5.3644 -1.44811,2.02933 -2.9611,4.01363 -4.29362,6.12305 -1.03598,1.43422 -2.15766,2.80499 -3.26514,4.18412 -1.28435,1.68579 -2.61731,3.34228 -3.73394,5.1467 -0.61541,1.23637 -1.10338,2.39149 -1.42745,3.7376 -1.42213,5.90724 -0.30903,0.87064 12.96295,8.86478 1.37512,-2.07493 1.70669,-4.65994 2.52212,-6.96893 0,0 -13.28274,-7.2196 -13.28274,-7.2196 z" /> | ||
| 65 | <path | ||
| 66 | style="fill:#ffffff" | ||
| 67 | id="path1041" | ||
| 68 | d="m 444.95379,346.65456 c 1.06642,-2.60177 2.26717,-5.1388 3.53885,-7.64477 0.80062,-1.72005 1.70753,-3.33379 2.78034,-4.89383 0.99874,-1.25158 1.79112,-2.64198 2.78939,-3.89223 0.97549,-1.20036 1.66709,-2.60021 2.6938,-3.75713 1.13014,-1.5117 1.88641,-3.2593 2.68308,-4.96014 0.7471,-1.60342 1.60226,-3.14777 2.40761,-4.72157 1.18872,-1.66559 2.27151,-3.4063 3.48455,-5.05396 0.96809,-1.31722 1.78836,-2.73014 2.77318,-4.03489 0.90823,-1.43352 1.91835,-2.76684 3.06487,-4.0174 1.43521,-1.23262 2.86359,-2.45013 4.14673,-3.84264 1.10736,-0.88478 2.1079,-1.91275 3.22743,-2.78438 1.26625,-0.92328 2.40104,-2.02565 3.74382,-2.83749 1.36021,-0.84114 2.47965,-1.99587 3.75402,-2.93834 1.17313,-0.82096 2.1014,-1.91914 3.19251,-2.83265 0.96511,-1.06249 1.9825,-2.0389 3.15071,-2.87458 1.46588,-1.3037 3.03026,-2.5058 4.41761,-3.8948 1.26476,-0.99496 2.67815,-1.75513 3.97292,-2.70979 1.23962,-0.91909 2.67716,-1.37512 4.09945,-1.89879 1.53746,-0.34526 2.98,-0.81959 4.436,-1.41426 0.95752,-0.25952 2.32209,-0.8001 3.33156,-0.62574 0.20945,0.0362 0.38685,0.17626 0.58027,0.26439 -4.20114,-2.33375 -8.40228,-4.6675 -12.60343,-7.00126 0,0 13.96868,5.78186 13.96868,5.78186 v 0 c -4.4863,-2.61747 -8.9726,-5.23493 -13.4589,-7.8524 -1.51335,0.0943 -2.98338,0.40961 -4.37883,1.01983 -1.4393,0.56639 -2.96156,0.82534 -4.40858,1.37347 -1.5382,0.55268 -3.00033,1.2792 -4.32591,2.24462 -1.37284,0.95626 -2.85935,1.75693 -4.09307,2.90202 -1.38146,1.30479 -2.85379,2.51476 -4.31435,3.72921 -1.15751,0.90413 -2.18874,1.92435 -3.21309,2.97405 -1.03083,0.94401 -2.01396,1.93579 -3.14828,2.76194 -1.19514,0.98762 -2.37284,1.99266 -3.68464,2.82915 -1.32344,0.92776 -2.55283,1.98338 -3.82992,2.9733 -1.12752,0.96567 -2.25289,1.94069 -3.33352,2.95626 -1.30383,1.37931 -2.79466,2.54328 -4.15785,3.86097 -1.16319,1.29631 -2.19199,2.67816 -3.13817,4.13963 -0.9771,1.31712 -1.81169,2.72753 -2.78204,4.04984 -1.24707,1.72053 -2.39712,3.51107 -3.60412,5.25971 -0.815,1.58975 -1.6765,3.15157 -2.43517,4.77007 -0.73319,1.57868 -1.42437,3.21086 -2.47717,4.60741 -0.99785,1.20674 -1.75357,2.57945 -2.72447,3.80831 -1.00142,1.28731 -1.83229,2.69059 -2.83871,3.9755 -1.1137,1.6144 -2.04714,3.29226 -2.88163,5.06874 -1.2825,2.52926 -2.50723,5.08361 -3.57702,7.71214 0,0 13.17149,7.42062 13.17149,7.42062 z" /> | ||
| 69 | <path | ||
| 70 | style="fill:#ffffff" | ||
| 71 | id="path1043" | ||
| 72 | d="m 47.723738,302.48814 c -0.235582,2.57035 -0.413918,5.14543 -0.527844,7.72396 -0.04401,1.66877 -0.637615,3.23133 -0.971978,4.84849 -0.49334,2.0254 -0.803411,4.07474 -0.962727,6.15115 -0.22051,2.38235 -0.149823,4.7319 0.269472,7.08396 0.481444,2.35269 0.997451,4.69608 1.45286,7.05476 0.457995,2.16877 0.573854,4.38236 0.790824,6.58142 0.233663,1.60072 0.456482,3.20102 0.57548,4.81477 0.08973,1.43491 0.08843,2.87328 0.09188,4.31034 -0.0031,5.94595 -0.872531,1.79721 13.666195,7.35427 0.230286,0.088 0.424137,-2.99784 0.437609,-3.20433 0.09063,-2.46503 0.06323,-4.93249 0.04974,-7.39861 -0.08329,-2.38251 0.008,-4.7492 0.31058,-7.11289 0.318125,-3.12377 1.109164,-6.1645 1.882127,-9.19858 0.603361,-2.38574 1.199296,-4.775 1.831907,-7.15334 0.5698,-2.1975 1.009163,-4.42888 1.423266,-6.66069 0.356849,-2.02356 0.443261,-4.078 0.494757,-6.12769 0.10103,-1.42529 0.383836,-2.82762 0.53435,-4.24816 -0.0029,-1.13487 0.180083,-2.1682 0.603434,-3.21105 0.321111,-0.5869 0.15321,-0.2786 0.503328,-0.92512 0,0 -13.113313,-7.52296 -13.113313,-7.52296 v 0 c -0.373806,0.69103 -0.177993,0.33257 -0.588169,1.07497 -0.466058,1.17181 -0.891486,2.30042 -0.810581,3.59475 -0.118838,1.46005 -0.383281,2.8963 -0.58493,4.34604 -0.03272,1.97659 -0.0796,3.95891 -0.384361,5.91626 -0.38509,2.19498 -0.834288,4.38156 -1.361672,6.54616 -0.65643,2.37946 -1.223845,4.78566 -1.843449,7.17462 -0.780965,3.08634 -1.636353,6.16979 -1.961447,9.34711 -0.358177,2.4239 -0.514696,4.85383 -0.409938,7.30476 0.01422,2.44019 0.03976,4.88083 -0.0099,7.32079 -0.02942,0.76513 -0.03695,1.29897 -0.112712,2.05085 -0.02471,0.24528 -0.307475,0.60077 -0.09933,0.73287 4.278007,2.71486 8.786753,5.0478 13.181546,7.56923 0.0572,0.0328 -0.153475,-0.0392 -0.168712,-0.10336 -0.04652,-0.19592 4.77e-4,-0.40274 7.16e-4,-0.60411 -7.7e-5,-1.46409 0.0069,-2.92885 -0.05054,-4.3921 -0.08299,-1.64226 -0.248525,-3.27751 -0.534015,-4.89805 -0.334753,-2.2089 -0.271582,-4.47121 -0.7496,-6.66552 -0.430658,-2.36726 -0.924448,-4.71652 -1.449337,-7.062 -0.472671,-2.26866 -0.697763,-4.4124 -0.444127,-6.73588 0.115274,-2.02246 0.364088,-4.02381 0.877321,-5.98869 0.294207,-1.70188 0.964594,-3.3091 1.075721,-5.04646 0.09428,-2.55627 0.265712,-5.10876 0.493001,-7.65667 0,0 -13.40746,-6.98527 -13.40746,-6.98527 z" /> | ||
| 73 | <path | ||
| 74 | style="fill:#ffffff" | ||
| 75 | id="path1045" | ||
| 76 | d="m 638.45033,529.45411 c 0.32043,-1.43279 0.87109,-2.79362 1.51319,-4.10875 0.49898,-1.07666 1.0692,-2.11787 1.58949,-3.18392 0.3604,-0.79469 0.72883,-1.65852 0.63073,-2.55458 -0.0213,-0.19465 -0.0803,-0.38329 -0.12051,-0.57493 -12.05402,-7.29915 -8.97396,-7.82949 -16.48178,-7.87355 -1.51954,-0.003 -3.03908,0.007 -4.55861,0.0125 -0.43502,-0.13711 -1.85991,0.26465 -2.19604,0.10033 -0.0753,-0.0368 -0.16683,-0.27616 -0.0951,-0.23283 4.27548,2.58135 8.49877,5.24815 12.74816,7.87222 -0.0323,0.11502 -0.19798,0.28158 -0.0968,0.34508 0.11127,0.0698 0.23042,-0.12621 0.34594,-0.18875 0.82367,-0.44601 1.64573,-0.88641 2.48418,-1.30455 1.04859,-0.43593 2.07827,-0.92742 3.18607,-1.19426 0.1154,-0.0333 1.46149,-0.43299 1.45532,-0.21803 -0.003,0.12184 -0.0928,0.22543 -0.13915,0.33815 -15.99901,-11.90341 -7.73872,-9.62675 -14.63806,-7.60889 -1.12229,0.32922 -2.27318,0.57267 -3.42629,0.77165 -0.8597,0.13995 -1.69864,0.34359 -2.57144,0.3832 -0.54215,-0.0101 -1.09515,0.074 -1.62901,-0.0558 -0.24961,-0.0607 -0.48864,-0.15896 -0.73526,-0.23084 -0.0894,-0.0566 -0.19053,-0.098 -0.26821,-0.16984 -0.0706,-0.0653 -0.25175,-0.28213 -0.16846,-0.23411 4.25969,2.45608 8.54644,4.87008 12.7194,7.47078 0.0386,0.0241 -0.62896,0.70858 -0.14215,0.34641 0.41198,-0.21773 0.79311,-0.50185 1.21319,-0.7044 0.241,-0.1162 0.49677,-0.19887 0.74453,-0.29986 0.64051,-0.18906 1.25438,-0.4587 1.9201,-0.54617 0.53439,-0.0211 1.07056,0.004 1.60554,0.0114 0.66172,0.20645 1.36743,0.21542 2.0551,0.24269 0.2059,0.0384 0.41918,-0.0195 0.62534,0.0116 0.0937,0.0141 0.17952,0.0618 0.27192,0.0828 0.32123,0.0728 0.65864,0.0923 0.98578,0.12092 0,0 -12.12893,-9.02458 -12.12893,-9.02458 v 0 c -0.64258,-0.0911 -1.27712,-0.13195 -1.92456,-0.15915 -0.70608,-0.0683 -1.41657,-0.216 -2.12625,-0.14532 -0.60794,0.0224 -1.21803,0.0643 -1.79888,0.26164 -0.70334,0.23112 -1.4064,0.47243 -2.0796,0.78154 -0.8134,0.39578 -1.61239,0.85829 -2.26694,1.48977 -4.40189,5.22506 7.05828,9.21158 12.81615,9.58956 0.87601,0.1034 1.76219,0.0157 2.63569,-0.0834 0.88485,-0.14403 1.76111,-0.3377 2.64498,-0.48765 1.20051,-0.2558 2.40354,-0.52668 3.55497,-0.95904 7.22662,-2.97311 -0.75811,-4.03385 -9.72884,-10.2551 -0.10392,0.0865 -0.19775,0.18687 -0.31174,0.25961 -0.14271,0.0911 -0.37549,0.11873 -0.53047,0.15979 -0.37663,0.0998 -0.74429,0.23119 -1.11935,0.33636 -1.13859,0.40564 -2.2512,0.88549 -3.336,1.41811 -0.68468,0.35565 -1.37148,0.71028 -2.0284,1.11615 -0.26605,0.16437 -0.84138,0.52704 -1.05722,0.77979 -0.20267,0.23735 -0.35044,0.51655 -0.52566,0.77483 4.25429,2.7014 8.45898,5.48255 12.76287,8.1042 0.1058,0.0645 0.23191,-0.0911 0.35356,-0.11452 0.71214,-0.1371 1.45642,-0.0262 2.17386,-0.0697 1.50973,-0.005 3.02005,-0.019 4.52929,0.0278 0.63448,0.0341 1.28759,0.049 1.90849,0.20211 0.17207,0.0424 0.34359,0.0927 0.50543,0.16496 0.13833,0.0617 0.51975,0.31137 0.38936,0.2343 -4.17013,-2.46505 -8.33904,-4.93215 -12.50856,-7.39822 0.13533,-0.0252 0.26836,-0.0779 0.406,-0.0756 0.69198,0.0116 0.0239,1.25024 -0.0802,1.48859 -0.51442,1.06625 -1.09068,2.1018 -1.58591,3.17763 -0.69439,1.40539 -1.30774,2.85802 -1.66838,4.38859 0,0 13.29807,7.19133 13.29807,7.19133 z" /> | ||
| 77 | </g> | ||
| 78 | </svg> | 6 | </svg> |
| @@ -1,78 +1,6 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | 2 | <svg id="svg2" width="660.14" height="621.01" version="1.1" viewBox="0 0 660.14 621.01" xmlns="http://www.w3.org/2000/svg"> |
| 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" | 5 | </g> |
| 6 | id="svg2" | ||
| 7 | width="682.66669" | ||
| 8 | height="682.66669" | ||
| 9 | viewBox="0 0 682.66669 682.66669" | ||
| 10 | sodipodi:docname="kobold.svg" | ||
| 11 | inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | ||
| 12 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 13 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 14 | xmlns="http://www.w3.org/2000/svg" | ||
| 15 | xmlns:svg="http://www.w3.org/2000/svg"> | ||
| 16 | <defs | ||
| 17 | id="defs6" /> | ||
| 18 | <sodipodi:namedview | ||
| 19 | id="namedview4" | ||
| 20 | pagecolor="#ffffff" | ||
| 21 | bordercolor="#000000" | ||
| 22 | borderopacity="0.25" | ||
| 23 | inkscape:showpageshadow="2" | ||
| 24 | inkscape:pageopacity="0.0" | ||
| 25 | inkscape:pagecheckerboard="0" | ||
| 26 | inkscape:deskcolor="#d1d1d1" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="1.5043945" | ||
| 29 | inkscape:cx="460.98281" | ||
| 30 | inkscape:cy="311.08862" | ||
| 31 | inkscape:window-width="2560" | ||
| 32 | inkscape:window-height="1377" | ||
| 33 | inkscape:window-x="2552" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="g8" /> | ||
| 37 | <g | ||
| 38 | inkscape:groupmode="layer" | ||
| 39 | inkscape:label="Image" | ||
| 40 | id="g8"> | ||
| 41 | <path | ||
| 42 | style="stroke:none;stroke-width:1.33333" | ||
| 43 | d="m 171.32749,47.331392 v 4 c 12.46,9.0412 4.032,24.8744 4.91067,37.33333 0.60933,8.61773 4.48133,15.097198 3.08933,23.999998 -9.768,-1.0508 -17.05467,6.59294 -24,12.62774 -15.61067,13.5656 -27.25574,32.5056 -43.14654,45.2576 -6.2576,5.02133 -14.829587,3.284 -22.186787,2.55733 -13.6492,-1.34666 -27.88494,-0.35466 -41.33334,-3.10933 -15.8956,27.97867 -27.41493,57.04 -36,88 13.74,10.83867 35.03,39.1 53.33334,38.66667 l -1.33334,6.66667 -5.33333,-2.66667 2.66667,14.66667 -4,-2.66667 -1.33334,6.66667 h 6.66667 l -1.33333,25.33333 h -1.33334 l -5.33333,-8 c 1.2756,13.55867 6.9132,37.864 2.5692,50.54 -2.02707,5.91467 -8.7172,10.132 -13.23613,14.05067 -10.5992,9.19067 -20.984,18.72 -31.99974,27.40933 l 25.33334,69.33334 h 1.33333 l 1.33333,-10.66667 17.33334,28 -2.66667,-5.33333 30.66667,4 1.33333,-6.66667 8,5.33334 -2.66667,-5.33334 c 33.997197,-5.85866 39.588927,35.29201 66.666667,47.46401 6.416,2.88533 16.03067,0.56933 22.66667,-0.79734 -0.58267,3.59734 -2.02267,9.11867 1.24133,11.92667 4.892,4.20667 11.732,-3.67467 17.35467,-1.764 14.21733,4.83467 19.67333,28.956 37.404,16.504 3.70133,14.85067 13.68667,53.584 34.496,53.04533 8.328,-0.216 10.988,-14.944 12.73867,-21.04533 5.44933,-18.99333 5.108,-41.92533 30.09866,-42.62667 20.65067,-0.58 53.08801,-5.31333 69.00801,10.92534 10.052,10.25333 8.66666,27.47066 15.73333,39.69333 2.42933,4.20133 6.73733,11.54 12.48667,11.09867 5.58133,-0.42934 9.27466,-8.18267 11.60533,-12.424 7.32533,-13.32667 10.54,-27.56534 11.16667,-42.66667 21.444,13.56267 26.752,-8.56 45.33333,-14.99333 5.74,-1.98667 13.94134,4.92 18.68,1.088 3.448,-2.788 0.0213,-8.492 -1.34666,-11.428 11.49466,1.196 36.74933,2.12 46.66667,-3.89067 13.856,-8.39867 22.12,-24.84267 33.33333,-36.10934 l -1.3333,-5.33333 41.3333,1.33333 c 1.3253,7.79467 6.2707,7.924 13.3333,8 v -6.66666 l 5.3334,5.33333 h 2.6666 c 10.9174,-18.93867 20.5987,-39.24933 27.5454,-60 2.1786,-6.504 6.8373,-15.692 5.808,-22.64934 -2.6627,-18.00933 -34.1347,-27.24133 -42.4814,-42.756 -4.5173,-8.39333 0.028,-24.07866 0.8147,-33.26133 1.5,-17.472 -3,-34.648 -2.5173,-51.99734 0.5226,-18.74133 33.68,-29.71733 42.796,-44.60933 3.9746,-6.49467 -1.3827,-16.932 -3.5227,-23.39334 -6.516,-19.676 -14.056,-42.93333 -26.5373,-59.73733 -4.7547,-6.40133 -10.9067,-4.772 -17.9054,-4.38667 -12.3666,0.68134 -33.6346,6.424 -45.12,1.564 -7.93997,-3.36133 -13.2693,-13.912 -18.93463,-20.06666 -12.87067,-13.98401 -27.26667,-25.57214 -41.10534,-38.47241 -15.90933,-14.829598 2.85333,-45.371998 -3.62133,-63.770668 -3.06134,-8.700397 -20.756,-14.264397 -28.552,-17.694131 -13.49867,-5.9384 -27.244,-11.378134 -41.33334,-15.735334 -5.22,-1.6144 -11.668,-4.60664 -16.88933,-1.585333 -16.16,9.350267 -22.56667,33.947998 -39.556,41.756398 -9.47467,4.3548 -27.90934,-1.2064 -38.22134,-1.7456 -15.03333,-0.786 -36.49067,6.88534 -50.54,2.33574 -5.92267,-1.9176 -10.304,-8.63254 -14.20267,-13.12894 -9.148,-10.549598 -18.88133,-20.827998 -27.25733,-31.999732 -31.076,6.852134 -58.048,21.650001 -86.66667,34.666672 m 297.33334,37.33333 v 1.33333 h -4 l 4,-1.33333 m 4,0 -1.33333,1.33333 1.33333,-1.33333 m -253.33334,1.33333 -1.33333,1.33334 1.33333,-1.33334 m 192.00001,44.000008 -1.33334,1.33333 1.33334,-1.33333 m 159.99997,136 h -1.3333 c -6.54533,-9.72 -13.10133,-19.16666 -22.67333,-26.17733 -6.84667,-5.01334 -14.52267,-8.54934 -22.66001,-10.896 -64.19333,-18.52 -94.28267,58.57733 -101.33333,109.07333 -4.65867,-12.744 -2.66667,-27.976 -2.66667,-41.33333 0,-26.22133 -0.096,-52.44534 0.003,-78.66667 0.048,-12.81733 0.236,-30.37867 10.848,-39.352 11.536,-9.75467 35,-9.336 49.14933,-7.74 50.02934,5.64267 90.66531,43.30267 90.66671,95.092 M 271.32782,319.3314 h -1.33333 c -8.73067,-49.15734 -41.592,-109.81467 -100,-92.31201 -8.24267,2.46934 -15.92267,6.46267 -22.66401,11.82 -8.93866,7.10267 -15.3944,16.20534 -21.336,25.82534 -14.76986,-48.75467 53.38134,-80.68134 90.66667,-85.05067 10.04267,-1.176 23.616,-4.14533 33.25467,0.0387 11.71067,5.08533 16.88267,22.98533 19.47067,34.34533 7.42133,32.56134 7.61067,72.44534 1.94133,105.33334 m -46.66667,-96.00001 c 4.99867,13.94667 17.988,23.35334 25.46001,36.00001 7.37733,12.484 9.68933,32.056 22.54,40 -3.912,-24.796 -18.472,-72.13734 -48.00001,-76.00001 m 200.00001,77.33334 c 10.88133,-7.50267 13.66933,-23.524 19.86667,-34.66667 7.652,-13.75733 22.06133,-25.04533 26.8,-40 -27.912,4.53067 -44.212,50.15067 -46.66667,74.66667 m -220.00034,-54.6667 v 1.33334 l -45.33333,8 c 5.248,-20.64801 30.736,-15.41734 45.33333,-9.33334 m 340.00002,22.66667 c -18.28667,-11.04667 -31.34001,-18.64267 -53.33334,-18.66667 19.048,-16.336 44.54933,-0.956 53.33334,18.66667 m -62.66667,-21.33333 -5.33334,8 5.33334,-8 m -330.66668,9.33333 v 9.33333 h -2.66667 l 2.66667,-9.33333 m 33.33333,10.66667 c -10.14666,28.372 -20.33733,64.79466 -3.732,93.332 23.412,40.23467 69.13467,19.88133 79.73201,-17.332 l -37.33334,12 c 7.55333,-29.95734 6.71333,-51.172 -5.33333,-80 l -4,1.33333 2.66666,-9.33333 c 25.18534,13.21066 33.43067,41.44266 45.02534,65.33333 5.99867,12.36134 12.33733,26.51467 2.584,39.25867 -4.64,6.06267 -15.18,9.508 -22.276,11.536 -21.07067,6.024 -63.98134,7.04267 -81.088,-8.61333 -13.28934,-12.164 -18.45467,-33.68534 -24.24534,-50.18134 l -4,1.33333 -5.33333,-17.33333 c 24.27867,-2.78133 33.91467,-33.52133 57.33333,-41.33333 m 328.00001,1.33333 c 13.32267,7.76533 22.768,16.98133 33.40401,27.99867 6.94666,7.19466 13.224,14.832 23.92933,13.33466 l 1.3333,1.33334 -5.3333,24 h -1.33333 l -2.66667,-9.33334 h -1.33333 c -8.71867,20.86401 -12.74534,50.58534 -37.33334,58.46534 -21.72,6.96133 -56.508,7.996 -77.33333,-1.67467 -10.91334,-5.068 -22.63334,-13.23333 -20.69734,-27.45733 0.92267,-6.78667 6.69067,-14.096 10.03067,-20 h 1.33333 c 0,8.11867 -0.77733,16.57467 3.116,24 22.06534,42.08667 74.49067,26.58933 82.21734,-16 l -33.33334,8 c 2.796,-20.556 10.612,-60.17867 -10.66666,-73.33334 l -2.66667,6.66667 h -1.33333 c 3.22666,-14.35067 15.708,-19.72133 27.70266,-8.79467 6.95467,6.336 9.74534,15.98934 12.044,24.79334 0.59067,2.264 2.49067,13.96 7.14667,10.308 8.94933,-7.02 -8.35467,-33.472 -8.22667,-42.30667 M 215.32749,311.3314 c -13.772,4.168 -8.14267,19.588 -8,30.66666 -7.48933,-6.38666 -6.66267,-16.27333 -6.66667,-25.33333 -18.06266,-3.052 -5.47733,-24.708 8.75467,-13.59333 2.832,2.212 4.33867,5.13333 5.912,8.26 m 261.33334,1.33333 v 2.66667 c -12.91733,5.19066 -8.896,15.32533 -6.66666,26.66666 -5.38134,-3.632 -15.92667,-20.34133 -17.05334,-26.78266 -1.472,-8.40667 8.44,-10.26267 14.36134,-7.90534 3.31866,1.32267 6.33733,3.48134 9.35866,5.35467 m -413.333337,2.66667 -1.33333,1.33333 1.33333,-1.33333 m -6.66667,13.33333 4,2.66667 v -1.33334 l -4,-1.33333 m 18.66667,180.00001 v 4 h -1.33333 l 1.33333,-4 m 349.333337,62.66667 c 0,17.77733 -2.26,32.136 -10.66667,48 -9.4,-5.81067 -23.30933,-33.76267 -13.44266,-43.90267 5.54,-5.69333 16.94133,-4.096 24.10933,-4.09733 m -145.33334,4 -8,46.66666 c -16.29333,-9.21866 -22.78666,-30.32 -24,-48 l 32,1.33334 m 124.00001,17.33333 v 10.66667 h 1.33333 z" | ||
| 44 | id="path972" /> | ||
| 45 | <path | ||
| 46 | style="fill:#ffffff" | ||
| 47 | id="path1031" | ||
| 48 | d="m 171.07018,314.24465 c 1.43533,2.55362 3.43647,4.86204 6.02366,6.31063 15.51628,8.6877 9.94595,7.11918 18.45758,9.09921 2.9676,0.68502 6.00726,0.33711 9.00575,0.4851 0.51777,0.0256 1.97943,0.5002 1.53847,0.22762 -12.3291,-7.62122 -8.46459,-9.40075 -13.47067,-6.74985 -0.81161,0.51959 -1.60672,1.06589 -2.43482,1.55876 -4.52165,2.69122 -9.22362,5.08101 -13.79908,7.67767 -5.00626,2.84116 -5.79872,3.37745 -10.6807,6.35696 -3.21994,2.00581 -5.34709,3.29487 -8.43672,5.34334 -0.93485,0.61982 -1.86152,1.25236 -2.77524,1.90291 -0.53177,0.3786 -1.12785,0.69454 -1.56007,1.18372 -0.23297,0.26368 -0.67179,0.8249 -0.36192,0.99157 4.80104,2.58231 9.91238,4.5407 14.86857,6.81105 12.64758,-5.66419 24.4458,-13.03247 36.15953,-20.38753 8.83007,-5.82628 17.49966,-11.90043 26.13449,-18.01368 0.12258,-0.10977 0.51254,-0.25112 0.36775,-0.32931 -4.67223,-2.52289 -9.13452,-5.66802 -14.23142,-7.15676 -1.20748,-0.35269 -2.10539,1.37742 -3.14865,2.08026 -4.89213,3.29582 -9.54755,6.82945 -14.1897,10.46637 -9.49744,7.36804 -17.9992,15.83825 -26.01509,24.76432 -6.30592,7.59525 -8.14218,8.23949 10.2995,13.47521 1.78331,0.50629 3.3167,-1.66017 4.9328,-2.56834 5.73793,-3.22441 9.53881,-5.73893 15.19815,-9.3143 13.24893,-8.54994 25.96694,-17.90399 37.94901,-28.15332 1.04824,-0.91395 2.1079,-1.81498 3.14472,-2.74187 0.79084,-0.70699 1.56569,-1.43176 2.33769,-2.15926 0.53118,-0.50056 1.12786,-0.94513 1.57156,-1.52464 0.198,-0.25861 0.59124,-0.77034 0.30618,-0.92788 -14.71815,-8.13451 -10.1663,-9.48815 -17.93001,-5.21701 -11.41265,6.6473 -22.20287,14.27124 -32.75212,22.19736 -10.66088,8.29117 -20.89797,17.10296 -30.98403,26.07851 -0.98028,0.86051 -2.03371,1.64422 -2.94083,2.58153 -5.39678,5.57637 8.13193,7.60867 14.30926,9.09646 2.00094,0.48192 3.79185,-1.6031 5.66556,-2.45469 5.07424,-2.30622 9.60823,-4.57573 14.62099,-7.03268 11.14435,-5.70338 22.44245,-11.13252 33.38489,-17.22113 5.46451,-0.58006 14.20537,-1.41277 -14.20952,-6.74516 -1.73723,-0.32601 -2.8735,2.0593 -4.29587,3.10863 -5.86769,4.32876 -11.61624,8.8152 -17.40775,13.24456 -0.88721,0.84917 -1.97162,1.53157 -2.66162,2.54751 -0.28753,0.42334 -0.56545,1.30065 -0.10868,1.53142 5.12688,2.59017 10.33852,5.33462 15.9671,6.48047 1.71551,0.34924 3.17757,-1.47245 4.73846,-2.26529 4.90381,-2.49085 6.99171,-3.77194 11.81268,-6.53639 1.94264,-1.1845 3.88822,-2.3642 5.82792,-3.55351 4.47851,-2.74595 8.99301,-5.47352 13.30943,-8.47306 0.65181,-0.45295 2.58101,-1.00805 1.90658,-1.4266 -4.79779,-2.9775 -10.29254,-4.64766 -15.43882,-6.97148 -11.72135,5.99277 -22.91179,12.94688 -34.23961,19.6353 -6.60264,3.95287 -13.30201,7.72362 -20.18434,11.16672 -9.33663,4.8581 -9.99423,3.7481 8.12157,11.42393 0.83219,0.3526 1.70531,-0.59973 2.5527,-0.91405 1.02537,-0.38033 2.04352,-0.77986 3.06527,-1.1698 6.88738,-3.16305 13.93717,-5.90731 21.04709,-8.52021 4.06204,-1.52457 8.09972,-3.2178 12.32549,-4.25606 1.68407,-0.41377 2.88582,-0.56931 4.57605,-0.83571 -1.4108,-2.79602 3.18988,0.90932 2.44908,0.52434 -4.45476,-2.31509 -8.49787,-5.5392 -13.24768,-7.16519 -0.90867,-0.31106 -1.4379,1.27434 -2.1802,1.8838 -2.94314,2.41647 -3.50484,2.75165 -6.63769,5.0276 -6.26617,4.40862 -12.714,8.5456 -19.11454,12.75349 -4.45769,4.93043 -2.50115,2.00475 11.93444,10.09798 0.63232,0.3545 1.41442,-0.31883 2.11844,-0.49163 2.98621,-0.73295 5.93525,-1.60178 8.89459,-2.4344 5.27098,-1.7298 10.76117,-2.53812 16.20623,-3.48887 4.60406,-0.82453 9.1585,-1.85176 13.62925,-3.22747 3.11796,-1.15071 6.24676,-2.27099 9.18927,-3.82602 1.66601,-0.96562 3.51589,-1.59353 5.16478,-2.58761 0.59316,-0.3576 1.14403,-0.78107 1.71659,-1.1708 1.74687,-1.35545 2.65746,-2.35985 3.62271,-4.37357 0.84126,-2.07386 1.06124,-4.30847 1.19133,-6.52137 0.0789,-1.7605 0.0646,-3.52332 0.0576,-5.28505 0.42359,-6.7192 3.03353,-1.77248 -13.2615,-9.57841 -0.44856,-0.21487 -0.88179,0.46044 -1.3207,0.69441 -0.54734,0.29178 -1.09201,0.58855 -1.63801,0.88282 -3.72432,2.34742 -7.18224,5.08426 -10.82303,7.54849 -3.13256,2.27465 -6.59511,3.9843 -10.07352,5.64923 -2.68913,1.31556 -5.50527,2.2938 -8.12552,3.76052 4.28068,2.88035 8.26686,6.25599 12.84202,8.64104 0.64153,0.33443 1.30296,-0.62985 1.9647,-0.92223 4.26541,-1.8846 1.57695,-0.53142 6.36978,-2.80944 1.13947,-0.54159 6.36523,-3.15514 7.52569,-3.73405 2.81628,-1.45312 5.68135,-2.8072 8.50641,-4.24271 0.33246,-0.16893 1.29943,-0.30491 0.99237,-0.51652 -12.71423,-8.76181 -8.74434,-10.10314 -15.04117,-6.92273 -6.79382,3.53204 -13.74745,6.74262 -20.52354,10.31127 -7.5434,3.95521 -15.43438,7.12357 -23.44161,9.9938 -4.93666,1.93963 -10.00951,3.44204 -15.13808,4.76283 -0.61961,0.37578 -4.11298,0.37957 -4.53103,0.75257 -0.23898,0.21323 -0.7467,0.65064 -0.48212,0.83114 4.26858,2.91201 8.88087,5.28514 13.32131,7.92771 3.06017,-0.26036 6.1004,-0.66469 9.16565,-0.88282 3.5349,-0.27157 7.08772,-0.15372 10.62077,-0.41487 2.34311,-0.20178 4.68416,-0.42837 7.03502,-0.51429 1.98063,0.0101 3.95881,-0.2504 5.90142,-0.62775 1.98537,-0.26974 3.8929,-0.84394 5.80038,-1.43236 1.70419,-0.58082 3.50678,-0.50524 5.26603,-0.74464 2.53961,-0.30944 5.00901,-1.04243 7.50092,-1.60707 1.79673,-0.39005 3.62652,-0.69197 5.40419,-1.16894 0.77169,-0.20705 0.73152,-0.48877 0.6584,0.0322 -15.02092,-10.25008 -8.42834,-9.62098 -17.23578,-6.65309 -1.91185,0.77525 -3.84198,1.48065 -5.76047,2.23372 -2.1782,0.94213 -4.52971,1.33577 -6.77528,2.06463 -1.92286,0.62993 -3.95096,0.6523 -5.85054,1.34594 -1.43592,0.6249 -2.95828,0.80537 -4.50244,0.90949 -1.69973,0.41292 -3.45038,0.54237 -5.19138,0.64517 -1.88828,0.13475 -3.82163,0.27962 -5.61684,0.9172 -0.0211,-0.0295 -0.0943,-0.10741 -0.0634,-0.0885 4.4075,2.68861 8.5688,5.82092 13.20029,8.10215 0.50696,0.2497 0.95543,-0.60426 1.44328,-0.88951 2.47696,-1.44829 4.88574,-2.95444 7.29325,-4.51881 4.43353,-2.70197 8.70382,-5.65959 12.75546,-8.90627 1.32906,-0.98398 2.49335,-2.14207 3.62575,-3.33846 0,0 -12.90598,-7.87335 -12.90598,-7.87335 v 0 c -1.10475,1.13152 -2.24531,2.21955 -3.51853,3.16535 -4.0487,3.17935 -8.28306,6.10197 -12.67783,8.78408 -1.64902,1.06545 -3.39232,2.21147 -5.07695,3.22299 -0.72249,0.43381 -1.46465,0.83405 -2.19248,1.25885 -0.47497,0.27721 -1.81706,0.47105 -1.41578,0.8471 10.42477,9.76936 7.5619,11.33669 12.0127,8.76713 1.78097,-0.40206 3.61469,-0.5307 5.43243,-0.65354 1.74871,-0.15261 3.47842,-0.41518 5.21287,-0.6753 1.60693,-0.20434 3.15791,-0.59502 4.6777,-1.16108 1.93936,-0.48218 3.95267,-0.655 5.84512,-1.338 2.30159,-0.64532 4.63,-1.21698 6.8368,-2.15547 1.91913,-0.72053 3.81715,-1.49554 5.74076,-2.20447 8.62643,-3.25519 5.68409,-1.80659 -7.41768,-10.83793 -0.57951,0.42283 -1.36317,0.47509 -2.05091,0.62728 -1.43747,0.31811 -2.87464,0.63797 -4.31681,0.93429 -2.44658,0.58032 -4.88995,1.23651 -7.40174,1.46555 -1.83742,0.16771 -3.67129,0.3449 -5.42332,0.97071 -1.86505,0.56113 -3.76192,0.9855 -5.68941,1.27195 -1.92476,0.2718 -3.85014,0.48183 -5.79717,0.48098 -2.35121,0.13789 -4.69492,0.37057 -7.04479,0.52789 -3.54525,0.15356 -7.09771,0.15487 -10.63737,0.43806 -3.05823,0.25769 -6.10045,0.66447 -9.16265,0.87861 4.10095,2.6678 8.35865,5.10888 12.30286,8.00339 0.22243,0.16323 -0.50266,0.42091 -0.45351,0.69239 0.0141,0.0781 4.37054,-0.75142 4.58956,-0.83694 5.16506,-1.33006 10.23089,-2.98806 15.21856,-4.88087 8.04727,-2.92727 15.97342,-6.16211 23.55564,-10.16528 6.79245,-3.54284 13.74759,-6.75919 20.5298,-10.32082 6.1694,-3.46088 3.5939,-1.2335 -10.25015,-9.64727 -0.31437,-0.19106 -0.65421,0.33667 -0.98284,0.50202 -3.77307,1.89844 -0.71516,0.34579 -5.12115,2.54028 -1.12963,0.56264 -2.25547,1.13287 -3.38321,1.69931 -3.64669,1.81381 -7.27715,3.65196 -11.01842,5.26635 -0.92249,0.39807 -1.84114,0.80968 -2.78853,1.14419 -0.5191,0.18329 -2.01711,0.0453 -1.60091,0.40566 3.71695,3.21784 7.99353,5.72576 11.99029,8.58864 2.63436,-1.37598 5.4429,-2.34498 8.08645,-3.70104 3.53151,-1.71393 7.01231,-3.53031 10.19911,-5.84269 3.562,-2.48006 7.00535,-5.14327 10.72295,-7.3938 0.5256,-0.26341 1.03573,-0.56029 1.57681,-0.79022 0.34553,-0.14683 1.37754,-0.11453 1.07482,-0.33657 -13.82664,-10.14164 -11.55582,-12.30727 -12.54762,-6.01864 0.009,1.73726 0.0275,3.47508 -0.0202,5.21196 -0.0821,2.02459 -0.20817,4.07984 -0.90984,6.00091 -0.81376,1.75352 -1.64737,2.5454 -3.20333,3.67747 -2.14295,1.42279 -4.50639,2.42113 -6.78341,3.60728 -2.91093,1.43792 -5.96998,2.53986 -9.03631,3.59764 -4.44558,1.29448 -8.96449,2.28607 -13.52651,3.07895 -5.48707,0.94315 -10.98138,1.90216 -16.29401,3.61552 -1.12137,0.3136 -4.98063,1.40492 -6.11919,1.67531 -0.86475,0.20537 -1.72806,0.45221 -2.61364,0.5278 -0.54137,0.0462 -2.04418,-0.50768 -1.62151,-0.16625 11.42704,9.23077 8.40436,11.68934 12.60187,7.50083 6.42684,-4.20992 12.88092,-8.38087 19.1554,-12.81757 2.49735,-1.83513 4.3837,-3.14306 6.72175,-5.16166 0.83724,-0.72285 3.09224,-1.40127 2.41497,-2.27578 -6.33848,-8.18436 -7.96984,-10.4425 -14.66541,-9.60946 -5.89378,1.07124 -11.51294,3.08172 -17.0598,5.32199 -7.10879,2.62566 -14.12733,5.44994 -21.05306,8.52907 -1.01221,0.37581 -2.01669,0.77314 -3.03664,1.12742 -0.81369,0.28264 -3.03652,0.14035 -2.46272,0.78279 11.80339,13.21539 6.99615,10.9296 11.92284,8.12062 1.31947,-0.7523 3.74865,-1.97655 5.01135,-2.62453 6.89121,-3.49042 13.61336,-7.28283 20.24024,-11.25236 11.27734,-6.67828 22.43811,-13.59569 34.1506,-19.49953 -3.31035,-3.15832 -6.16118,-6.88237 -9.93104,-9.47496 -0.63445,-0.43632 -1.23051,0.92684 -1.86559,1.36225 -1.15761,0.79367 -2.33243,1.56218 -3.51117,2.32412 -5.12389,3.31211 -10.35273,6.45766 -15.57115,9.61718 -4.38295,2.49441 -7.24864,4.21481 -11.74375,6.43131 -1.51803,0.74853 -5.44101,0.61334 -4.62479,2.09606 2.12153,3.85393 7.02265,5.3155 10.31252,8.23619 0.20792,0.18458 -0.98326,0.41742 -0.80971,0.20021 0.73463,-0.91941 1.71836,-1.60849 2.57754,-2.41273 5.78153,-4.42323 11.52997,-8.88866 17.38712,-13.21198 1.42972,-1.05531 5.0733,-1.52866 4.31223,-3.13445 -5.81826,-12.27593 -6.3744,-11.75565 -11.23511,-9.58703 -10.94552,6.03635 -22.20646,11.4862 -33.34269,17.15676 -4.22937,2.06502 -10.22531,5.0476 -14.57292,6.96022 -1.81778,0.79967 -6.27975,0.40599 -5.52019,2.24089 1.70084,4.10876 6.3992,6.24034 10.07003,8.75027 1.49441,1.02179 0.88047,-9.44405 3.83393,-3.84665 10.06538,-8.93869 20.2702,-17.72751 30.91727,-25.97198 10.50559,-7.86626 21.24325,-15.45041 32.62871,-22.00546 7.36761,-3.8532 3.96829,-0.86418 -8.13522,-10.03306 -0.061,-0.0462 0.28008,-0.0735 0.22901,-0.0165 -0.47916,0.5348 -1.02792,1.00305 -1.55236,1.49352 -0.76503,0.71545 -1.53338,1.4276 -2.31581,2.12398 -1.03519,0.92135 -2.09105,1.81921 -3.13658,2.72881 -11.95628,10.17483 -24.62464,19.47989 -37.83252,27.9695 -4.36546,2.74513 -10.44576,6.69692 -15.10959,9.17839 -1.49209,0.79389 -5.49167,0.65909 -4.61365,2.10329 2.38531,3.9234 6.89231,6.21587 10.96295,8.34018 1.93356,1.00905 0.87576,-8.92908 3.97258,-5.19905 7.97197,-8.85114 16.43325,-17.24046 25.87613,-24.53933 4.62049,-3.60275 9.25775,-7.09819 14.12437,-10.36353 1.0398,-0.69767 3.88038,-1.05567 3.13843,-2.06435 -8.3794,-11.39171 -7.52997,-11.87727 -11.60778,-8.80552 -8.62141,6.08752 -17.25943,12.16238 -26.08925,17.94303 -11.65263,7.29252 -23.38273,14.63386 -36.00522,20.15284 3.76123,2.73954 7.65708,5.30322 11.28369,8.21863 0.17317,0.13921 -0.64943,0.598 -0.45933,0.48299 0.54064,-0.3271 1.0066,-0.76466 1.52415,-1.12721 3.62633,-2.54031 7.40811,-4.8487 11.16951,-7.18018 4.99607,-3.03314 5.5246,-3.4057 10.64708,-6.30516 4.6037,-2.6058 9.38129,-4.97264 13.86718,-7.78308 0.88771,-0.55616 1.72092,-1.19491 2.58138,-1.79236 0.50182,-0.59773 2.09084,-1.27701 1.50547,-1.79319 -3.87871,-3.42027 -8.57482,-5.79813 -13.05103,-8.38775 -0.47089,-0.27242 -1.08498,-0.086 -1.62866,-0.10489 -2.94034,-0.10204 -5.89671,-0.0548 -8.77687,-0.79164 -6.84974,-2.01396 -3.16404,-1.19536 7.6358,5.56497 0.6805,0.42597 -1.44013,-0.72013 -2.10585,-1.16884 -1.12844,-0.76059 -2.34725,-1.94327 -3.22517,-2.98418 -0.37019,-0.43891 -0.67583,-0.92844 -1.01374,-1.39266 0,0 -13.77387,-6.23173 -13.77387,-6.23173 z" /> | ||
| 49 | <path | ||
| 50 | style="fill:#ffffff" | ||
| 51 | id="path1033" | ||
| 52 | d="m 232.73967,343.98425 c -0.16239,-4.21792 -0.38636,-8.43294 -0.53272,-12.65164 -0.17653,-6.95973 -1.85631,-4.87751 -13.29234,-10.63359 -0.04,-0.0201 -3.53227,3.33793 -3.78645,3.58181 -1.99028,1.91133 -4.00381,3.86285 -6.2969,5.4174 -1.06135,0.71952 -1.93388,1.15314 -3.0558,1.75665 -1.95113,0.78708 -4.0813,1.77702 -6.2486,1.72206 -0.41992,-0.0106 -0.83194,-0.11686 -1.24792,-0.1753 -0.48134,-0.10515 -0.97296,-0.17113 -1.44404,-0.31546 -1.88325,-0.57698 -3.69696,-1.49191 -5.50071,-2.26758 -2.1798,-0.9374 -2.2769,-0.95062 -4.52683,-1.82148 -4.44489,-1.59935 -9.05198,-2.51641 -13.72931,-3.06968 -0.51119,-0.087 -1.04311,-0.0927 -1.53355,-0.26109 -0.28832,-0.099 -1.01299,-0.67747 -0.75025,-0.5229 4.24182,2.49551 8.37529,5.17918 12.68969,7.54702 0.17035,0.0935 0.32998,-0.20543 0.49213,-0.31253 0.47218,-0.31192 0.93292,-0.64129 1.41076,-0.94447 0.64704,-0.41054 1.30876,-0.79748 1.96314,-1.19621 5.32786,-2.88263 10.80956,-5.47437 16.22858,-8.18209 4.78718,-1.95989 9.10461,-4.78507 13.46258,-7.52548 2.62671,-1.56346 5.19283,-3.22119 7.80177,-4.81406 1.1227,-0.93327 3.56593,-1.49092 4.53824,-2.47123 0.16716,-0.16853 0.24952,-0.40389 0.37428,-0.60583 -4.23667,-2.89754 -8.32988,-6.01688 -12.71002,-8.69261 -0.29187,-0.1783 -0.64392,0.2312 -0.96946,0.33612 -2.15826,0.69562 -4.30644,1.33446 -6.42473,2.15203 -4.29152,1.63988 -8.41307,3.66717 -12.6077,5.53018 -4.82936,2.0624 -9.56932,4.30241 -14.24575,6.68873 -3.90778,2.11644 -7.73683,4.3642 -11.5033,6.72178 -1.61315,0.98308 -3.112,2.13675 -4.70891,3.14424 -0.37871,0.23893 -0.78785,0.42969 -1.15472,0.68643 -0.77674,0.54356 -1.56314,1.30623 -2.2582,1.94248 -1.3674,1.53824 -3.49987,3.52541 -3.14491,5.83895 0.0542,0.35357 0.23681,0.67509 0.35521,1.01264 4.611,2.65737 9.10124,5.53626 13.833,7.9721 0.39531,0.2035 0.88276,-0.1075 1.32296,-0.17003 2.35331,-0.33431 4.41661,-1.16882 6.57287,-2.14245 4.66944,-2.08131 9.10602,-4.67138 13.44997,-7.35827 4.57957,-2.99451 9.14835,-6.0171 13.558,-9.25944 2.82993,-2.279 5.77344,-4.40642 8.73187,-6.51307 1.93406,-1.45357 3.91458,-2.83753 5.84743,-4.29188 1.50721,-1.13725 2.80699,-2.51181 4.20715,-3.77294 0.53578,-0.55497 1.54467,-0.85008 1.93694,-1.55022 2.9445,-5.25545 -8.98814,-6.22301 -12.95355,-8.48667 -2.46378,1.36153 -4.50197,3.20247 -6.39761,5.26251 -0.39857,0.48953 -5.58042,6.12839 -4.84005,6.67322 4.2012,3.09163 8.7864,5.62424 13.1796,8.43636 3.68207,-1.12239 6.2577,-3.8927 8.89656,-6.52432 0.0606,-0.0662 1.62195,-1.787 1.8513,-1.9885 0.12384,-0.10881 0.28737,-0.16161 0.43105,-0.24242 -4.39526,-2.2892 -8.63135,-4.91417 -13.18578,-6.86759 -0.34145,-0.14645 -0.2515,0.70012 -0.40601,1.03799 -0.22397,0.48975 -0.48546,0.96145 -0.72819,1.44218 -0.86092,1.6686 -2.0156,3.14343 -3.23225,4.56183 -0.24595,0.28673 -0.47983,0.58649 -0.7558,0.84446 -0.0265,0.0247 -0.071,-0.11872 -0.0392,-0.10134 4.40101,2.40648 8.77951,4.85392 13.16926,7.28089 1.46759,-2.4309 3.48191,-4.44214 4.9583,-6.86589 1.06137,-2.32718 0.93692,-4.83266 0.0823,-7.19189 -2.80578,-4.83762 -12.6773,-7.80282 -14.85026,-8.84928 -0.31655,-0.15245 -3.53109,1.16694 -3.87595,1.30947 -4.47781,1.99153 -8.79044,4.33462 -13.22011,6.43083 -4.21998,2.0963 -8.51473,4.03792 -12.87256,5.82964 -2.77042,1.16583 -5.6843,1.88722 -8.58772,2.62102 -2.80453,0.92069 -5.70723,1.44571 -8.56832,2.15075 -2.29694,0.62103 -4.58347,1.319 -6.66594,2.49128 -0.12684,0.0518 -0.49398,0.0785 -0.38052,0.15532 4.23064,2.86275 8.3752,5.89525 12.88037,8.30297 0.43136,0.23053 0.84219,-0.49762 1.26527,-0.74304 2.97907,-1.72813 5.83126,-3.60518 8.70066,-5.51101 5.75823,-3.98884 11.70446,-7.70199 17.74697,-11.24239 2.80788,-1.4419 5.6684,-2.82242 8.61115,-3.96542 2.70305,-1.02336 5.36367,-2.15739 8.10939,-3.06346 1.86988,-0.63702 3.74282,-1.3128 5.66466,-1.77909 1.44074,-0.18382 2.82028,-0.62291 4.25689,-0.83578 0.96481,-0.11847 1.939,-0.1301 2.9096,-0.15324 0.20012,-0.005 0.40765,-0.0685 0.60035,-0.0143 0.0912,0.0256 0.11119,0.15329 0.16679,0.22994 -12.44464,-7.68644 -11.33159,-11.89752 -15.32577,-3.63822 -0.92044,2.08126 -1.91653,4.06144 -3.30388,5.86997 -1.67673,1.99068 -4.14201,2.87169 -6.44358,3.91089 -5.85047,2.07862 -6.41927,-0.30481 11.67311,7.81678 0.39119,0.1756 0.56205,-0.64848 0.86246,-0.95446 0.8056,-0.82054 1.65794,-1.59387 2.48089,-2.39702 2.13255,-1.90308 3.73829,-3.81663 4.58765,-6.56018 0.51997,-2.17666 0.50849,-4.42627 0.51403,-6.65016 -0.0365,-1.98098 0.0282,-3.96596 -0.0874,-5.94446 -0.0469,-1.31854 -0.54398,-2.4502 -1.18941,-3.56455 -11.86794,-7.43097 -10.44561,-9.61528 -19.54019,-6.64195 -2.90504,1.06197 -5.64842,2.5092 -8.47323,3.76296 -2.82972,1.2514 -5.55757,2.71314 -8.35105,4.03951 -2.73802,1.46585 -5.62337,2.6416 -8.46532,3.89092 -2.93432,1.44932 -5.95304,2.71572 -8.93136,4.07021 -2.61697,1.18126 -5.17726,2.46451 -7.68626,3.85548 -0.43106,0.29155 -1.18643,0.74935 -1.55054,1.17364 -3.52989,4.11327 7.88695,6.33505 13.16143,8.67144 0.33506,0.14842 0.66895,-0.29947 1.00343,-0.44921 1.5692,-0.85195 3.27529,-1.39841 4.82461,-2.29156 0.80789,-0.46573 1.57825,-0.99384 2.37385,-1.48028 2.34583,-1.58221 4.30424,-3.63646 6.41685,-5.49806 1.46344,-1.30423 2.65707,-2.81893 3.34364,-4.6575 0.25683,-1.25844 0.81728,-2.81572 0.29801,-4.09133 -0.10581,-0.25993 -0.31332,-0.46569 -0.46998,-0.69854 -4.55348,-2.53758 -9.0634,-5.15497 -13.66045,-7.61274 -0.17399,-0.093 -0.39472,-0.015 -0.59186,-0.007 -0.12411,0.005 -0.47422,-0.0219 -0.36984,0.0455 4.24259,2.73583 8.50906,5.4383 12.85552,8.0059 0.073,0.0431 0.4994,-0.37586 0.75985,-0.61595 1.05025,-1.01942 1.86431,-2.28026 2.42797,-3.62806 0.31654,-0.52272 0.40322,-1.30266 0.96533,-1.64806 0.0979,-0.0602 0.21352,-0.0853 0.32028,-0.12794 0.13562,-0.0966 0.27123,-0.19324 0.40684,-0.28986 5.19693,-2.57523 10.31369,3.11931 -12.05308,-8.24825 -0.31398,-0.15958 -0.57926,0.4008 -0.86889,0.60121 -2.16543,1.33756 -4.16339,2.92389 -6.13481,4.52914 -1.66623,1.5478 -3.16695,3.26179 -4.80545,4.83609 -4.54417,4.78544 2.02375,3.79484 12.61783,9.20028 0.0449,0.0229 3.73072,-1.71529 3.99793,-1.84236 2.68136,-1.48749 5.35558,-2.96967 8.08898,-4.35972 1.77149,-0.97442 3.61795,-1.83865 5.50693,-2.55921 1.83273,-0.61603 3.6719,-1.20408 5.51169,-1.79859 1.74554,-0.54089 3.55893,-0.73243 5.34083,-1.11531 1.46277,-0.3253 2.95799,-0.3803 4.44952,-0.41387 1.62889,-0.0231 3.25805,-0.0161 4.88705,-0.0128 0.7882,0.19692 0.44532,0.0265 1.04462,0.47783 0,0 -11.91856,-9.30063 -11.91856,-9.30063 v 0 c -0.87451,-0.0778 -0.42265,-0.0541 -1.35592,-0.0645 -1.64103,8e-5 -3.28289,-0.005 -4.92286,0.0623 -1.54681,0.0866 -3.08424,0.26499 -4.59518,0.61963 -1.81956,0.37014 -3.67292,0.59739 -5.42732,1.241 -1.87635,0.58373 -3.73576,1.22084 -5.57977,1.8995 -1.8899,0.82 -3.77812,1.64933 -5.57518,2.66212 -2.72347,1.37718 -5.34241,2.93712 -8.06232,4.31931 -0.6843,0.3143 -2.00098,0.94025 -2.73657,1.19988 -0.32907,0.11614 -1.28367,0.058 -1.01018,0.27479 13.7453,10.89314 8.53254,11.61904 12.61923,7.42493 1.58097,-1.56706 3.0709,-3.22812 4.73039,-4.71521 1.96252,-1.57136 3.95718,-3.10421 6.08414,-4.44895 0.30082,-0.21807 1.18637,-0.41454 0.90245,-0.65421 -7.7916,-6.57739 -8.31092,-11.32876 -13.4836,-7.94085 -1.05349,0.71525 -1.69754,1.6303 -2.14331,2.82068 -0.43797,1.15738 -1.16584,2.19887 -2.07857,3.03463 -0.0545,0.0481 -0.92528,0.80376 -0.89054,0.83075 3.96643,3.08211 8.18217,5.82945 12.29744,8.70984 0.21262,0.14882 0.55613,0.0335 0.73193,0.26553 -4.11791,-2.3947 -8.23583,-4.78939 -12.35375,-7.18409 0.11825,0.0189 1.04835,0.11235 1.20019,0.31114 0.361,0.47262 -0.13437,2.13833 -0.19015,2.6962 -0.51158,1.63225 -1.70596,2.86555 -2.95481,3.99408 -2.02917,1.82599 -3.95843,3.79249 -6.27205,5.26938 -2.2578,1.37928 -4.6055,2.54372 -7.05381,3.5521 -5.58612,1.85709 -0.0223,-0.24329 11.68112,7.64689 0.40851,0.27541 -1.63103,1.30748 0.076,0.15975 0.2753,-0.18511 0.5646,-0.3485 0.84691,-0.52274 2.4821,-1.36587 5.04373,-2.57855 7.62031,-3.75523 2.97736,-1.34282 5.97456,-2.64164 8.92032,-4.05358 2.86232,-1.24381 5.72956,-2.48833 8.49579,-3.93692 2.76914,-1.35042 5.50943,-2.76014 8.32879,-4.00587 2.76516,-1.25474 5.47188,-2.67247 8.36843,-3.60722 1.26633,-0.36215 2.56448,-0.78342 3.89756,-0.80012 0.85192,-0.0107 0.82765,0.0752 1.53593,0.42768 -4.10255,-2.46371 -8.2051,-4.92742 -12.30765,-7.39113 0.87022,0.63506 1.41465,1.66666 1.57425,2.72657 0.29538,1.91362 0.0477,3.87804 0.15627,5.8075 0.009,2.09402 0.0596,4.20901 -0.31768,6.27765 -0.62923,2.46598 -2.34965,4.1958 -4.19368,5.88001 -0.32766,0.3196 -3.65762,3.49777 -3.50955,3.62853 10.25275,9.05415 7.97742,10.62804 13.96992,8.18094 2.5228,-1.13814 5.07757,-2.32734 6.8435,-4.54726 1.43052,-1.90044 2.50357,-3.95606 3.45643,-6.13263 3.80252,-7.93496 6.00519,-2.82532 -9.84222,-12.99277 -1.31575,0.30131 -2.7269,0.0687 -4.0426,0.43813 -1.44785,0.29793 -2.89095,0.62174 -4.3327,0.94508 -1.92223,0.5696 -3.83482,1.18028 -5.7206,1.8611 -2.74705,0.97129 -5.44361,2.07764 -8.158,3.13566 -2.96416,1.26301 -5.91996,2.56526 -8.73087,4.14719 -6.05698,3.565 -12.00387,7.32161 -17.79115,11.31094 -2.0647,1.36328 -4.22082,2.82155 -6.34676,4.10232 -0.74734,0.45023 -1.50867,0.8775 -2.2773,1.29035 -0.36773,0.19752 -1.45346,0.27595 -1.12997,0.53975 11.25958,9.18223 7.85353,10.77408 12.66198,8.30613 2.05871,-0.98645 4.25118,-1.63592 6.4617,-2.18772 2.86526,-0.69453 5.75463,-1.28469 8.57389,-2.15883 2.96319,-0.74043 5.89479,-1.61773 8.71335,-2.8061 4.36341,-1.83564 8.67479,-3.78933 12.91544,-5.89422 4.38121,-2.11024 8.66513,-4.43844 13.14946,-6.32484 0.79723,-0.27088 1.6527,-0.63379 2.51458,-0.67988 0.2792,-0.0149 1.07008,0.25495 0.83172,0.10879 -18.76731,-11.50759 -15.88476,-8.71313 -10.93879,-6.55684 1.1779,1.68282 1.33452,3.92655 0.61867,5.83 -1.51283,2.47276 -3.62336,4.50648 -5.0562,7.03708 4.23053,2.78584 8.38879,5.68466 12.6916,8.35752 0.11663,0.0725 0.19532,-0.19325 0.28736,-0.29514 0.26485,-0.29315 0.5285,-0.58756 0.78305,-0.88969 1.27543,-1.51383 2.4844,-3.08757 3.40139,-4.851 0.26271,-0.51611 0.54183,-1.02418 0.78813,-1.54831 0.2028,-0.43159 0.93409,-1.0235 0.55835,-1.31712 -12.01227,-9.38683 -10.01885,-11.64406 -15.33077,-5.90598 -1.79042,1.77947 -4.35818,4.82031 -7.00038,5.20786 -0.29162,0.0428 -0.58062,-0.10179 -0.87093,-0.15268 4.02282,2.65442 7.9953,5.38677 12.06847,7.96326 0.11339,0.0717 -0.15575,-0.27006 -0.0969,-0.39064 0.21338,-0.43739 0.56456,-0.79318 0.86224,-1.17819 1.14105,-1.47578 2.35791,-2.89629 3.60821,-4.2798 1.58596,-1.68233 3.44224,-3.73251 5.76525,-4.30599 -4.2131,-2.54932 -8.56527,-4.88182 -12.6393,-7.64798 -0.21311,-0.1447 0.33388,-0.3983 0.45002,-0.62823 0.0471,-0.0933 -0.15595,0.13926 -0.23661,0.20573 -0.55561,0.45787 -1.15351,0.8601 -1.69495,1.33579 -1.35883,1.24037 -2.64174,2.57235 -4.13464,3.65708 -1.919,1.45056 -3.90613,2.80573 -5.82514,4.25672 -2.96436,2.10935 -5.88464,4.27737 -8.74246,6.52975 -4.39397,3.19928 -8.92679,6.20958 -13.49223,9.15664 -4.30697,2.6285 -8.69454,5.17349 -13.33203,7.17839 -1.61461,0.66836 -3.19119,1.33514 -4.94353,1.54626 -5.13769,0.61898 -13.74732,-7.46153 9.72713,7.05026 -0.23995,-0.0713 -0.52011,-0.0631 -0.71985,-0.21402 -1.18523,-0.89541 1.85209,-3.97341 2.42794,-4.54589 2.44199,-2.14394 5.31075,-3.71203 7.98142,-5.55112 3.74114,-2.32298 7.54254,-4.54338 11.43148,-6.61142 4.66388,-2.36268 9.40496,-4.55199 14.20446,-6.62622 4.15951,-1.87582 8.27828,-3.84601 12.55452,-5.45065 1.64953,-0.59544 3.32117,-1.23265 5.04263,-1.59795 0.36175,-0.0768 0.73109,-0.18172 1.09941,-0.14867 0.18958,0.017 0.32738,0.19427 0.49107,0.29141 -4.04564,-2.7953 -8.09128,-5.59059 -12.13692,-8.38589 -1.49052,0.86633 -3.08899,1.54686 -4.54602,2.47941 -2.60577,1.60644 -5.19029,3.24617 -7.81092,4.82826 -4.32192,2.71934 -8.65468,5.41931 -13.38153,7.40097 -5.45576,2.72376 -10.99132,5.30639 -16.30537,8.29999 -0.95554,0.60573 -2.68974,1.62849 -3.56714,2.44843 -0.384,0.35884 -1.40022,0.87251 -1.00295,1.21659 7.44833,6.45106 8.6187,8.38054 15.11935,9.26037 4.61007,0.64895 9.14931,1.68107 13.50224,3.3662 3.83153,1.54046 7.61457,3.58519 11.77229,4.0085 2.81893,-0.10125 5.47716,-0.90361 7.97168,-2.23334 3.55204,-2.01968 6.71321,-4.56746 9.56112,-7.50291 0.0581,-0.0554 2.15763,-2.09521 2.47518,-2.27343 0.0932,-0.0523 0.3483,0.24732 0.25797,0.19025 -4.10953,-2.59651 -8.21085,-5.206 -12.31627,-7.80901 0.0837,0.25546 0.21511,0.49997 0.251,0.76638 0.0637,0.47319 0.0301,0.95446 0.0451,1.4317 0.13767,4.2041 0.32794,8.40601 0.55578,12.6062 0,0 13.47024,6.86343 13.47024,6.86343 z" /> | ||
| 53 | <path | ||
| 54 | style="fill:#ffffff" | ||
| 55 | id="path1035" | ||
| 56 | d="m 185.8578,280.25304 c 2.1647,-1.03493 4.30367,-2.1219 6.44766,-3.19929 1.27197,-0.62913 2.53847,-1.12777 3.94755,-1.32128 1.43773,-0.12953 2.88474,-0.10256 4.32689,-0.10046 1.74211,0.005 3.48422,0.0109 5.22634,0.0137 1.74919,0.003 3.49839,0.003 5.24759,0.003 1.44315,9e-5 2.8863,0 4.32945,-3e-5 1.33089,-4e-5 2.66179,-5e-5 3.99269,-5e-5 1.32776,0 2.65551,0 3.98326,0 1.32931,0 2.65862,0 3.98793,0 0.17489,0.0539 0.34978,0.10779 0.52467,0.16169 0,0 -12.10961,-9.05049 -12.10961,-9.05049 v 0 c -0.21526,8.9e-4 -0.43052,0.002 -0.64578,0.003 -1.32931,0 -2.65861,0 -3.98792,0 -1.32776,0 -2.65552,0 -3.98327,10e-6 -1.33091,1e-5 -2.66181,3e-5 -3.99271,7e-5 -1.44308,3e-5 -2.88616,9e-5 -4.32924,-2.5e-4 -1.74869,-5.5e-4 -3.49737,-10e-4 -5.24605,-0.005 -1.74413,-0.004 -3.48827,-0.0127 -5.23239,-0.005 -1.50245,0.0167 -3.01946,0.0159 -4.49505,0.33689 -1.46312,0.37423 -2.82923,0.98965 -4.17862,1.66268 -2.15021,1.10101 -4.33465,2.13332 -6.47881,3.24688 0,0 12.66542,8.25477 12.66542,8.25477 z" /> | ||
| 57 | <path | ||
| 58 | style="fill:#ffffff" | ||
| 59 | id="path1037" | ||
| 60 | d="m 517.46462,337.23029 c -2.56265,4.0164 -4.88261,8.17784 -7.31927,12.26999 -1.7766,3.09546 -4.32727,5.482 -7.18992,7.54762 -2.68734,1.95262 -5.15996,4.16998 -7.5556,6.46553 -1.38721,1.40056 -2.95482,2.59485 -4.53481,3.76485 -1.22963,0.91055 -2.3367,1.76135 -3.77132,2.29409 -5.09097,-1.74318 -1.08156,-0.40133 12.37559,6.98305 0.0258,0.0142 -0.004,-0.0636 0.0139,-0.0874 0.1837,-0.24854 0.37344,-0.49383 0.58333,-0.72068 0.75786,-0.81912 1.45438,-1.37948 2.30908,-2.12616 4.70831,-3.64505 10.04801,-6.34425 15.17505,-9.33458 2.03876,-1.27739 4.2988,-2.28601 6.19949,-3.78479 0.32219,-0.25406 0.62927,-0.53694 0.87598,-0.8648 0.15744,-0.20924 0.5329,-0.57258 0.31596,-0.71924 -4.29597,-2.90429 -8.86989,-5.37458 -13.30483,-8.06188 -3.57739,1.68493 -6.85132,3.90699 -10.27248,5.87217 -2.74604,1.62213 -5.6826,2.87125 -8.61663,4.10562 -2.65401,1.21151 -5.20027,2.67454 -7.58553,4.35282 -2.14447,1.40823 -4.28086,2.81275 -6.3615,4.31081 -1.40627,0.71348 -2.30929,1.77998 -2.62065,3.32068 0.81016,7.36832 12.11254,9.3282 18.52914,9.04592 2.50936,-0.31243 5.04454,-0.61344 7.40166,-1.56914 1.97441,-0.61026 2.76446,-1.84839 3.76012,-3.5379 1.07739,-2.05927 1.9625,-4.20328 2.99711,-6.28434 1.3492,-2.74011 3.16209,-5.22591 4.86933,-7.75101 1.18101,-1.65339 2.46708,-3.2623 3.46898,-5.03427 1.7824,-6.11494 -7.84664,-6.6416 -13.30612,-9.6205 -0.2292,-0.12506 -3.29355,0.22879 -3.63397,0.27387 -2.89195,0.30971 -5.62076,1.31686 -8.29068,2.41542 -2.82065,1.13197 -5.46628,2.58099 -8.06971,4.13143 -2.53638,1.22945 -4.83578,2.84226 -7.17486,4.3939 -1.3709,0.9908 -2.65439,2.08728 -3.75772,3.36967 -1.68219,1.5319 -3.2942,3.1344 -4.9593,4.68411 -1.45269,1.52497 -2.83742,3.11478 -4.17917,4.73738 -3.63798,5.37456 3.79556,4.99992 12.33474,9.71531 0.36277,0.20032 0.74858,-0.35639 1.13043,-0.51739 2.83931,-1.19711 0.0772,0.11931 3.2847,-1.4869 2.05827,-1.08132 4.03317,-2.30736 5.99544,-3.55117 0.47837,-0.30323 2.68062,-1.74462 3.32498,-2.03614 0.35437,-0.16032 0.74954,-0.2081 1.1243,-0.31215 5.21693,1.88088 0.36376,0.16898 -12.03101,-6.67223 -0.15964,-0.0881 0.50681,0.0961 0.47276,0.27524 -0.0452,0.23791 -0.50356,0.73864 -0.59892,0.8559 -1.01692,1.01725 -2.09668,1.99757 -3.23289,2.88304 -1.13366,0.77062 -1.78521,1.99288 -2.66487,3.00144 -0.4999,0.5181 -1.82578,2.76169 -0.85978,3.33392 4.5024,2.66709 9.1647,5.0545 13.74706,7.58175 2.23462,0.21341 4.48944,-0.29226 6.57794,-1.06304 0.183,-0.12081 0.39287,-0.20847 0.549,-0.36244 0.24654,-0.24315 0.89597,-1.50801 0.63823,-1.67783 -10.79379,-7.11175 -8.78038,-7.42141 -15.10327,-7.57337 -2.15751,-0.16966 -4.25743,0.16954 -6.33775,0.69945 -1.47207,0.22532 -2.86569,0.7409 -4.32815,0.98616 -1.4266,0.0627 -2.75764,0.56693 -4.1574,0.79381 -1.34956,0.0761 -2.77182,0.34036 -4.09815,0.0146 -0.19362,-0.0476 -0.39408,-0.0731 -0.58087,-0.14283 -0.44722,-0.167 -1.67781,-0.9967 -1.05789,-0.6456 20.98616,11.88561 15.60495,9.05661 11.16471,6.32842 -1.5196,-0.99201 -1.64758,-2.44567 -1.34593,-4.11665 0.81182,-3.10827 3.25991,-5.22887 5.61134,-7.23229 3.34059,-2.85841 7.1928,-4.9601 11.03557,-7.04621 4.20603,-2.0403 8.36157,-4.17644 12.43734,-6.46651 2.83986,-1.81873 5.72411,-3.57291 8.44219,-5.57327 1.02334,-0.93747 3.59337,-2.12394 4.07012,-3.46338 0.1339,-0.37621 0.1713,-0.78006 0.25695,-1.17009 -4.58492,-2.39842 -9.04567,-5.05089 -13.75477,-7.19526 -0.3497,-0.15925 -0.7063,0.30296 -1.06207,0.44814 -2.01467,0.82213 -3.9665,1.70254 -5.91589,2.67225 -3.65467,2.01385 -7.46814,3.79404 -10.77826,6.35973 -0.21249,0.34647 -0.5386,0.64517 -0.63747,1.03939 -1.30502,5.20313 13.83595,8.79595 14.32339,9.01748 0.61437,0.27922 1.34531,-0.12016 2.0121,-0.22411 1.77354,-0.2765 3.17125,-0.64859 4.92228,-1.06644 4.5451,-1.30218 8.81106,-3.4264 12.94588,-5.69043 1.97421,-0.92375 3.81889,-2.13756 5.873,-2.87661 1.77393,-1.01584 3.70407,-1.74284 5.64601,-2.36653 0.2903,-0.0996 0.5646,-0.27948 0.8709,-0.29874 0.14174,-0.009 0.48783,0.29097 0.36421,0.22107 -4.36595,-2.4686 -8.555,-5.27784 -13.09134,-7.41726 -0.35331,-0.16663 -0.51614,0.58655 -0.77843,0.87603 -1.08393,1.19626 -1.25521,1.36145 -2.40611,2.57297 -3.24235,3.30458 -6.48143,6.60383 -9.86178,9.77025 -3.28404,3.17757 -6.24306,6.66774 -9.44409,9.92313 -2.01291,2.02617 -4.18264,3.87968 -6.23082,5.8679 -1.9223,1.70425 -3.86185,3.39422 -5.88858,4.9743 -0.96722,0.65082 -1.83726,1.617 -2.96371,2.01994 -0.16514,0.0591 -0.37401,0.12202 -0.52514,0.033 -0.1206,-0.071 -0.22959,-0.47479 -0.10781,-0.4058 4.37015,2.47548 8.64001,5.12397 12.96002,7.68596 0.53794,-0.73321 1.39731,-1.28776 1.82599,-2.1042 2.47982,-4.72287 -10.9966,-8.27065 -13.75481,-9.82443 -3.01935,-0.16368 -6.00541,0.25868 -8.9999,0.5565 -3.33136,0.25062 -6.66491,0.53234 -9.97631,0.97952 -1.49207,0.0622 -2.91511,0.57342 -4.40233,0.66511 -0.31252,0.0193 -0.63635,0.0538 -0.93899,-0.0266 -0.22732,-0.0604 -0.79953,-0.49519 -0.59419,-0.3805 4.19573,2.34367 8.37764,4.712 12.56647,7.06801 -0.12644,-0.0812 -0.29432,-0.11966 -0.37932,-0.24357 -0.73362,-1.06945 0.52294,-2.95045 0.98506,-3.83506 1.54566,-2.92927 3.75683,-5.3607 6.04032,-7.72501 3.25479,-3.46003 6.89274,-6.53596 10.49318,-9.62639 3.01741,-2.51586 6.22504,-4.77774 9.2122,-7.33076 2.43349,-2.03466 5.14772,-3.71373 7.78144,-5.47881 7.74668,-5.34821 5.26549,-2.15496 -8.93837,-11.02194 -3.67679,2.35575 -7.28617,4.81658 -10.95286,7.1879 -3.60708,2.14166 -6.78641,4.82897 -9.89312,7.62168 -1.86144,1.77073 -3.57985,3.68242 -5.41099,5.4821 -1.46318,1.5278 -2.96981,3.07905 -4.72685,4.26969 -1.60606,1.0531 -3.23293,2.05513 -4.91002,2.98816 -1.48842,0.87657 -2.93256,1.85966 -4.62833,2.26668 -2.29749,0.73424 -4.56561,1.54956 -6.89065,2.19388 -0.21725,0.033 -1.44035,0.30317 -1.78598,0.0997 -0.1635,-0.0962 -0.41263,-0.60608 -0.24764,-0.51244 4.26109,2.41814 8.34115,5.14836 12.62053,7.53399 0.13307,0.0742 0.031,-0.30774 0.0903,-0.44808 0.14456,-0.342 0.34422,-0.658 0.51633,-0.987 2.37142,-3.19148 5.06218,-6.13906 7.75268,-9.063 2.78598,-3.28539 5.95741,-6.19831 9.12416,-9.10549 2.81779,-2.65813 5.52878,-5.42524 8.32563,-8.10749 2.35912,-2.2275 4.9302,-4.20401 7.51479,-6.15787 2.21682,-1.57455 4.70767,-2.64853 7.23257,-3.61266 2.59322,-1.05976 5.31693,-1.74942 7.95483,-2.67784 6.95804,-2.23836 5.26274,-0.70784 -10.5794,-8.877 -0.38723,-0.19968 -0.77405,0.40021 -1.15813,0.6059 -0.57211,0.30639 -1.14022,0.62019 -1.71034,0.93029 -5.10523,2.95137 -10.1952,5.93597 -15.4318,8.64977 -3.75271,2.05164 -7.48656,4.12742 -11.25821,6.14133 -2.71694,1.28113 -5.35381,2.71843 -8.01141,4.11619 -1.86867,0.9152 -3.66552,1.95908 -5.47922,2.98047 -1.72455,1.10921 -3.54098,2.13062 -5.05664,3.51931 -0.90463,0.82401 -2.04789,2.27228 -3.2307,2.66682 1.2635,-5.34205 -0.16936,0.0679 12.96906,7.26428 0.17418,0.0954 0.22262,-0.32904 0.32813,-0.49729 0.11736,-0.18717 0.22653,-0.37935 0.34031,-0.56871 0.0591,-0.0985 0.27684,-0.23544 0.17847,-0.2947 -4.39251,-2.6466 -8.90136,-5.09514 -13.35205,-7.6427 -1.49531,0.85332 -2.7992,1.97088 -4.22888,2.92082 -4.9037,3.89088 -8.1625,-1.2946 11.33893,8.0153 0.21258,0.10149 0.0789,-0.46569 0.14649,-0.69135 0.0927,-0.30958 0.22227,-0.60693 0.33341,-0.91039 0.9684,-2.27791 2.17116,-4.41805 3.44801,-6.53272 1.83613,-2.66105 4.2652,-4.84603 6.6056,-7.05325 2.98662,-2.55678 6.31032,-4.67369 9.50952,-6.94733 2.50895,-1.86606 5.26592,-3.33396 7.90531,-4.98838 2.47853,-1.36936 5.0954,-2.41932 7.72677,-3.44606 3.54649,-1.27142 7.2593,-1.95785 10.88689,-2.95161 3.44504,-1.09078 6.90807,-2.11655 10.36057,-3.18415 1.57896,-0.50775 3.15215,-1.01407 4.66702,-1.69465 0.22148,-0.0995 0.40862,-0.34535 0.65075,-0.32727 0.13052,0.01 -0.0964,0.24337 -0.14462,0.36505 -4.28721,-2.811 -8.39676,-5.91369 -12.86162,-8.433 -0.33959,-0.19162 -0.65762,0.4197 -0.9957,0.61396 -2.34749,1.34885 -4.80653,2.53706 -7.25277,3.6926 -4.36193,1.75711 -8.68282,3.60481 -12.99058,5.49322 -4.10098,1.72429 -7.9938,3.8697 -11.98214,5.82283 -2.74984,1.24947 -5.53582,2.41306 -8.32154,3.58056 -2.33776,0.90325 -4.64698,1.81777 -6.82899,3.06087 -1.6298,0.95465 -2.93181,2.35203 -4.53805,3.32872 -1.22767,0.5776 -2.9154,2.1317 -4.32374,1.80128 -2.79977,-4.49808 -0.18699,-0.17007 12.40537,6.86221 0.13592,0.0759 -0.24331,-0.20851 -0.31037,-0.34901 -0.3676,-0.7701 -0.29072,-1.79017 -0.29405,-2.60894 0.27412,-1.91104 1.29442,-3.60878 2.49962,-5.08165 1.12269,-1.14938 2.65266,-1.66837 3.92767,-2.62115 1.45993,-0.82313 2.77198,-1.84586 4.11828,-2.83995 1.71524,-1.19027 3.37924,-2.44494 5.19567,-3.48489 2.614,-1.40322 5.09913,-3.01589 7.68692,-4.46278 2.75467,-1.40031 5.54523,-2.81309 8.46488,-3.83629 3.572,-1.39268 7.35073,-2.15702 11.06617,-3.06688 2.1504,-0.43081 4.24068,-1.17674 6.24799,-2.0536 1.84007,-0.61386 3.57214,-1.48886 5.38903,-2.15968 2.39709,-0.73828 4.7544,-1.60292 7.1525,-2.33873 2.13542,-0.69731 4.39264,-0.70615 6.57979,-1.09077 1.97808,-0.16946 3.86686,-0.73435 5.77371,-1.23803 2.24307,-0.87141 4.58412,-1.44007 6.8694,-2.18694 2.22625,-0.79063 3.47755,-2.45154 4.24552,-4.59789 0.49708,-1.74364 0.66891,-3.12814 -0.47933,-4.63119 -3.93217,0.9071 -9.47789,-8.04187 -15.22808,-8.02582 -0.90173,0.003 -1.78593,0.33746 -2.64717,0.56315 -2.16621,0.70967 -3.8994,2.23159 -5.77133,3.47988 -1.28133,0.95004 -2.63044,1.80516 -3.90936,2.7585 -0.35558,0.26506 -0.71563,0.52642 -1.04581,0.82251 -0.19353,0.17354 -0.73737,0.43927 -0.51972,0.58138 4.38614,2.86373 9.04269,5.29022 13.56403,7.93533 2.86923,-1.07989 5.45916,-2.74922 7.90565,-4.5716 1.30728,-1.18837 2.73972,-2.28872 3.7411,-3.76068 0.69028,-1.09515 0.57253,-2.17863 0.22624,-3.35306 0,0 -13.75609,-6.27088 -13.75609,-6.27088 v 0 c 0.43127,0.58847 0.90259,1.25446 0.5011,1.95547 -0.93702,1.30814 -2.28597,2.27716 -3.47973,3.33299 -2.35429,1.69155 -4.84173,3.25548 -7.6231,4.14233 3.94379,2.81864 7.78552,5.78584 11.83137,8.45591 0.15408,0.10169 0.25521,-0.2681 0.39804,-0.38505 1.55286,-1.27142 3.28779,-2.30435 4.90031,-3.49462 1.73096,-1.16737 3.36424,-2.58064 5.46172,-3.01518 0.62992,-0.11694 0.95322,-0.2188 1.59062,-0.18113 4.62466,0.27329 16.94781,10.99054 -10.17189,-6.59562 1.74126,0.70663 1.64617,1.19544 1.27065,2.96111 -0.59489,1.92717 -1.5473,2.7829 -3.50056,3.38484 -2.28542,0.69029 -4.56126,1.40392 -6.83314,2.13394 -1.86368,0.50835 -3.75489,0.86129 -5.67334,1.09378 -2.26937,0.26543 -4.53808,0.57399 -6.71882,1.29155 -2.40682,0.79019 -4.81647,1.57331 -7.20528,2.41681 -1.80044,0.7305 -3.58032,1.50925 -5.40406,2.1821 -2.00043,0.75722 -4.01242,1.47936 -6.12371,1.87071 -3.75896,0.93132 -7.56641,1.75112 -11.16582,3.21306 -2.93909,1.16982 -5.82135,2.51337 -8.59563,4.03386 -2.57918,1.47872 -5.10173,3.05203 -7.70477,4.49029 -1.81837,1.12195 -3.53915,2.37807 -5.2793,3.6155 -1.32898,0.99565 -2.69698,1.91207 -4.09599,2.80484 -1.43419,0.93394 -3.04899,1.61757 -4.15253,2.98549 -1.3792,1.75296 -2.51107,3.72858 -2.9716,5.9322 -0.42607,6.72358 2.58546,13.89204 13.95641,11.7794 1.70484,-0.52484 3.23516,-1.48185 4.74126,-2.41844 1.50416,-1.05667 2.85238,-2.33425 4.47183,-3.22635 2.17907,-1.14349 4.47115,-1.99876 6.74447,-2.93278 2.80281,-1.1927 5.62687,-2.33761 8.37308,-3.65834 3.94777,-1.99116 7.87216,-4.0231 11.94217,-5.76243 4.31945,-1.89631 8.68307,-3.6801 13.01855,-5.53542 1.95914,-0.9522 3.90805,-1.926 5.82546,-2.96058 0.50397,-0.27193 0.99884,-0.56038 1.49829,-0.84052 0.31277,-0.17543 1.20979,-0.29179 0.93838,-0.52618 -3.74847,-3.2371 -7.92031,-5.94879 -11.88046,-8.92318 -1.22719,1.18526 -4.01355,1.49621 -5.55291,2.18899 -3.43466,1.07096 -6.88468,2.09121 -10.32725,3.13547 -3.67765,0.9652 -7.42888,1.70351 -10.98125,3.09056 -2.69832,1.05992 -5.36095,2.21186 -7.87351,3.66898 -2.70171,1.61585 -5.40577,3.22217 -7.95915,5.07237 -3.26329,2.2977 -6.63032,4.47577 -9.62334,7.12899 -2.43197,2.30605 -4.93949,4.60371 -6.81524,7.40375 -1.324,2.1933 -2.56344,4.41585 -3.58314,6.77064 -0.28262,0.72486 -0.78535,1.96378 -0.70523,2.0225 11.11095,8.14252 9.2911,11.16909 14.84179,7.04588 1.36792,-0.96609 2.68778,-2.00505 4.16026,-2.81433 -4.13289,-2.7284 -8.2024,-5.55535 -12.39869,-8.18521 -0.0147,-0.009 -0.14428,0.25714 -0.49677,0.8256 -0.14461,0.23321 -0.65593,0.52359 -0.4415,0.6948 11.86492,9.47378 8.42352,11.94948 12.84847,8.4416 1.25222,-0.84217 2.34972,-1.89283 3.41529,-2.95596 1.52098,-1.23338 3.20569,-2.25517 4.88718,-3.25333 1.81755,-0.98999 3.586,-2.06616 5.45951,-2.95033 2.66572,-1.38772 5.30731,-2.8214 8.0166,-4.1241 3.77769,-1.99676 7.47864,-4.12937 11.25287,-6.13319 5.2221,-2.75618 10.32081,-5.73451 15.44415,-8.66863 0.5681,-0.30701 1.13636,-0.61371 1.70429,-0.92104 0.39362,-0.213 1.51892,-0.34725 1.18035,-0.63995 -10.78271,-9.32163 -7.87059,-10.07916 -14.60754,-7.84877 -2.67927,0.86748 -5.3831,1.67329 -7.99425,2.73628 -2.62213,1.04815 -5.18008,2.27437 -7.45988,3.96169 -2.62589,1.99186 -5.21853,4.03187 -7.60812,6.30749 -2.7874,2.6763 -5.48264,5.44509 -8.30843,8.07963 -3.19517,2.93828 -6.36993,5.90573 -9.19109,9.21472 -2.77132,3.02267 -5.56044,6.06177 -7.9508,9.39909 -0.21109,0.3984 -0.44372,0.7861 -0.63328,1.19518 -0.17102,0.3691 -0.76758,0.88555 -0.44676,1.13567 8.73674,6.81144 8.79946,9.68501 15.48409,8.02983 2.31854,-0.69686 4.60328,-1.49915 6.91256,-2.22598 1.69473,-0.6526 3.23281,-1.61021 4.7984,-2.5221 1.71744,-0.96571 3.37745,-2.03192 5.0069,-3.13882 1.74325,-1.34381 3.35258,-2.85474 4.85028,-4.46755 1.79202,-1.80288 3.519,-3.67001 5.3695,-5.41483 3.06276,-2.73341 6.23067,-5.31534 9.76881,-7.42471 3.56377,-2.32805 7.04548,-4.81206 10.75925,-6.89388 -13.9328,-9.40918 -8.69904,-10.77058 -16.32658,-5.58395 -2.66559,1.80994 -5.43462,3.49608 -7.86903,5.61743 -3.01558,2.52213 -6.21582,4.80304 -9.21838,7.34065 -3.63424,3.12382 -7.29004,6.24902 -10.5699,9.75083 -2.36345,2.48192 -4.64404,5.04026 -6.25701,8.09181 -0.98025,1.86575 -1.87764,3.74138 -1.49605,5.91216 4.47978,2.60314 8.89349,5.32345 13.43933,7.80943 0.21105,0.11542 1.77486,-0.0717 1.89732,-0.0878 1.46508,-0.19215 2.91168,-0.51867 4.38649,-0.64271 3.31234,-0.38091 6.63266,-0.6784 9.95549,-0.95017 2.84468,-0.29162 5.80106,-0.75883 8.61293,-0.0364 -4.01164,-2.39741 -8.13559,-4.6162 -12.03493,-7.19225 -0.57156,-0.37759 1.09357,-0.4585 0.87991,-0.0213 -0.43108,0.88221 -1.47212,1.41354 -1.93076,2.28225 11.59732,8.66545 9.22916,11.9968 16.51576,6.29329 2.04341,-1.63486 4.03926,-3.33374 5.96812,-5.10217 2.06707,-1.98767 4.24691,-3.85399 6.25376,-5.90532 3.16428,-3.26173 6.1361,-6.70905 9.40961,-9.867 3.39505,-3.19207 6.64747,-6.51931 9.90836,-9.84623 0.25916,-0.27451 3.50217,-3.69601 3.39004,-3.79676 -9.96672,-8.9553 -7.81314,-10.57163 -13.77855,-8.49854 -1.94142,0.70676 -3.84608,1.49186 -5.68861,2.42977 -2.04138,0.89469 -3.9767,2.02298 -5.96838,3.02078 -4.06108,2.16276 -8.23356,4.22156 -12.71629,5.34929 -1.62184,0.34891 -3.11936,0.73218 -4.77701,0.8605 -0.61269,0.0474 -2.33845,-0.37035 -1.84358,-0.006 3.71143,2.73254 7.973,4.64748 11.74493,7.29587 0.25756,0.18084 -0.59442,0.45068 -0.8848,0.32936 -0.20091,-0.0839 0.22928,-0.37022 0.34392,-0.55533 3.31339,-2.409 7.02441,-4.18858 10.62438,-6.12136 1.49899,-0.72828 2.99183,-1.4498 4.55192,-2.04062 0.40737,-0.15427 0.8126,-0.31832 1.23226,-0.43509 0.27664,-0.077 1.09197,0.009 0.84932,-0.1441 -4.08456,-2.58476 -8.36799,-4.841 -12.55199,-7.26149 0.23631,-0.26817 0.46018,-0.54785 0.70893,-0.80451 0.0633,-0.0653 -0.085,0.16444 -0.15015,0.22795 -0.20966,0.20434 -0.43979,0.38689 -0.66838,0.5698 -0.49416,0.39542 -1.39427,1.05108 -1.89772,1.41656 -0.35135,0.25507 -0.70466,0.50742 -1.05699,0.76112 -2.69606,1.9333 -5.52364,3.67906 -8.3446,5.4215 -4.08556,2.2643 -8.25282,4.36573 -12.4297,6.4548 -3.91946,2.15218 -7.81879,4.36688 -11.20893,7.31806 -2.56409,2.24927 -5.13013,4.66762 -6.08973,8.05732 -0.12254,0.45699 -0.29555,0.90336 -0.36763,1.37098 -0.21991,1.42675 0.1583,3.14531 0.98221,4.31256 0.6152,0.57294 0.40284,0.43897 1.13778,0.86491 4.4268,2.56565 9.66767,7.64816 15.51758,8.2066 1.45832,-0.0239 2.92651,-0.0724 4.35816,-0.37589 1.38534,-0.3112 2.76572,-0.60664 4.17151,-0.81405 1.44323,-0.36329 2.89038,-0.69179 4.34497,-1.01042 2.00379,-0.416 4.03855,-0.52536 6.07966,-0.33812 5.72181,0.63545 5.81025,4.57384 -11.08516,-7.05781 -0.29961,-0.20627 0.46661,-0.55834 0.69003,-0.84538 0.0199,-0.0256 0.0697,-0.0944 0.0376,-0.0895 -0.14156,0.0215 -0.2695,0.0969 -0.40425,0.14532 -1.96016,0.55559 -4.04613,0.95541 -6.03614,0.31552 4.10167,2.38174 8.28145,4.63379 12.30502,7.14522 0.18644,0.11637 -0.43218,0.0811 -0.64597,0.1321 -0.13909,0.0332 -0.29574,0.20363 -0.41181,0.12012 -0.14738,-0.10602 0.68335,-1.16831 0.78671,-1.28219 0.79287,-0.96803 1.45595,-2.04923 2.51794,-2.76208 1.18285,-1.00593 2.39933,-1.99452 3.41218,-3.17648 0.54333,-0.72825 1.11914,-1.48461 1.24315,-2.42177 0.72239,-5.45934 -9.27368,-9.76863 -14.60467,-8.23205 -3.72115,1.5986 -6.82861,4.35869 -10.52914,6.02585 -0.79037,0.35684 -2.17456,1.06117 -3.10237,1.21728 -0.22721,0.0382 -0.8739,-0.22497 -0.68489,-0.0932 19.74632,13.76521 9.39646,11.90022 12.61258,7.28013 1.31409,-1.56503 2.64049,-3.12515 4.08106,-4.57625 1.69193,-1.58653 3.36292,-3.19567 5.0329,-4.80455 1.06326,-1.15314 2.25451,-2.16265 3.55446,-3.04299 2.30645,-1.56012 4.65827,-3.0362 7.13059,-4.32514 2.54667,-1.50998 5.18658,-2.83633 7.93981,-3.93423 2.59206,-1.0032 5.24439,-1.8682 8.03951,-2.05215 0.75935,-0.0307 1.59431,-0.11006 2.35351,0.0289 0.2913,0.0533 1.09313,0.43888 0.84307,0.28023 -4.01992,-2.55051 -8.1716,-4.89054 -12.1736,-7.46908 -0.12926,-0.0833 0.30923,-0.13404 0.45394,-0.082 0.0848,0.0305 0.0364,0.17647 0.0545,0.26471 -0.97645,1.73866 -2.26935,3.29571 -3.40716,4.92792 -1.74567,2.58014 -3.58934,5.12616 -4.97015,7.92547 -1.01904,2.04947 -1.90463,4.1522 -2.94347,6.19086 -0.97594,1.65484 -1.31084,2.04841 -3.20104,2.64489 -2.30959,0.69141 -4.70391,1.0518 -7.11217,1.15573 -1.14872,-0.0364 -1.5685,-0.0119 -2.68256,-0.18722 -0.36652,-0.0577 -0.73474,-0.11952 -1.08936,-0.22867 -0.27354,-0.0842 -1.02827,-0.49867 -0.78483,-0.34817 12.1521,7.51284 16.98049,7.11315 11.74075,7.44753 -0.64524,-0.69029 1.54481,-1.79198 1.90769,-2.06977 2.0632,-1.5008 4.23889,-2.83145 6.34049,-4.2778 2.35593,-1.56803 4.81573,-2.99305 7.42314,-4.10393 2.96865,-1.28918 5.94312,-2.576 8.71726,-4.2586 2.65138,-1.54923 0.9872,-0.56128 3.718,-2.22274 1.63477,-0.99462 3.31862,-2.11829 5.12482,-2.79026 0.3178,-0.11823 0.66178,-0.14818 0.99267,-0.22226 -4.06444,-2.69254 -8.12888,-5.38507 -12.19331,-8.07762 -0.0247,-0.0164 0.0948,0.0277 0.0743,0.0492 -0.22712,0.23843 -0.47605,0.45804 -0.74216,0.65199 -1.93532,1.41053 -4.11286,2.46482 -6.15034,3.70855 -5.2118,3.0227 -10.63123,5.78809 -15.33673,9.58923 -1.13626,1.02541 -2.40527,2.04636 -3.20845,3.3799 -0.23462,0.38955 -0.96384,0.95945 -0.59618,1.22707 11.84803,8.6244 8.4217,9.58039 13.95895,7.68437 3.19911,-1.63845 5.94733,-4.08316 8.58378,-6.49278 2.36984,-2.23814 4.80586,-4.40541 7.4576,-6.30866 2.96125,-2.25307 5.63225,-4.7928 7.49983,-8.05914 2.42312,-4.07031 4.73001,-8.20967 7.28048,-12.20339 0,0 -13.04362,-7.64316 -13.04362,-7.64316 z" /> | ||
| 61 | <path | ||
| 62 | style="fill:#ffffff" | ||
| 63 | id="path1039" | ||
| 64 | d="m 432.49953,344.24829 c -0.74992,1.88333 -1.06676,4.9053 -2.27069,6.52441 17.7842,9.09169 10.87195,10.15007 14.63577,2.53805 1.10268,-1.69406 2.3453,-3.29642 3.58552,-4.89165 1.1227,-1.38745 2.25262,-2.77369 3.29055,-4.22698 1.32978,-2.11295 2.84636,-4.09666 4.29583,-6.1268 1.30305,-1.75714 2.50915,-3.58428 3.74208,-5.39123 1.56571,-1.86607 2.88779,-3.92042 4.5192,-5.73014 1.88665,-1.74545 3.98152,-3.25993 6.12942,-4.66512 2.9896,-2.09562 6.07519,-4.03418 8.97079,-6.26338 2.78,-1.954 5.25025,-4.28817 8.0846,-6.16612 2.11189,-1.2452 4.15986,-2.59054 6.24117,-3.88429 3.02352,-1.90978 5.76591,-4.21725 8.62645,-6.35471 1.48877,-1.16753 3.03884,-2.25756 4.59377,-3.33481 0.2987,-0.20694 0.56223,-0.49503 0.91007,-0.60017 0.10854,-0.0328 0.32024,0.31389 0.22153,0.25812 -4.34332,-2.4539 -8.64841,-4.97486 -12.97261,-7.46229 -2.62513,4.04578 -5.83026,7.70019 -8.9005,11.41297 -3.66383,4.80372 -8.12475,8.87004 -12.73052,12.74179 -5.91272,4.63036 -8.29371,2.67995 10.5926,8.76614 0.72744,0.23442 0.95585,-1.19331 1.45579,-1.7714 1.41805,-1.63974 2.66603,-2.92715 4.16048,-4.51228 2.07947,-2.31538 4.40674,-4.3784 6.66784,-6.50762 1.84986,-1.74198 3.62603,-3.51822 5.55816,-5.17079 2.44558,-1.96438 4.85201,-4.07114 7.7316,-5.37452 1.77224,-0.39347 3.46115,-1.03221 5.19071,-1.59061 2.31711,-0.81022 4.51877,-2.02167 6.60839,-3.31065 1.39458,-0.88305 2.74941,-1.91401 4.27263,-2.56811 0.19353,-0.1003 0.38287,-0.20912 0.58059,-0.3009 0.59457,-0.27597 0.58438,-0.16429 1.12628,-0.58611 0.45898,-0.35728 0.79797,-0.75022 1.30061,-1.05309 5.14148,-4.96325 -7.95067,-9.73777 -13.28583,-9.85314 -2.48876,0.35892 -4.69207,1.55828 -6.90425,2.67053 -2.3624,0.89905 -3.73647,2.37469 -4.94941,4.5448 -1.13905,2.59111 -2.308,5.17539 -3.35541,7.80576 -1.96526,6.34677 5.52532,5.98779 13.1748,9.82243 0.28349,0.14211 3.63407,-1.76581 4.01264,-1.97846 2.35819,-1.33273 4.68655,-2.71263 6.98133,-4.15154 1.37572,-0.7433 2.66223,-1.6029 3.75923,-2.71805 1.17204,-1.09559 1.77792,-2.49721 2.12557,-4.03109 0.0998,-1.18885 -0.16945,-2.73124 -1.30979,-3.41737 -13.34308,-8.02843 -8.61169,-6.96412 -14.89582,-8.20796 -2.60759,-0.54437 -5.18738,-0.18878 -7.72525,0.49502 -0.65085,0.12605 -1.04631,0.33591 -1.66437,0.0414 -0.177,-0.0843 -0.61222,-0.48509 -0.44157,-0.38856 4.20664,2.42581 8.41328,4.85163 12.61992,7.27744 -0.0556,-0.0879 -0.16461,-0.15979 -0.1668,-0.2638 -0.005,-0.23223 0.098,-0.45412 0.14694,-0.68117 0.55462,-1.72933 1.86737,-2.87785 3.2017,-4.01207 0.89484,-1.03596 2.11945,-1.6709 3.12761,-2.57143 0.86027,-0.47119 1.53234,-0.96433 1.98432,-1.83939 -10.36867,-6.55283 -10.56003,-10.30222 -16.32628,-5.43659 -1.3875,0.80231 -2.64757,1.80472 -3.95489,2.73368 -1.67535,0.85692 -3.39189,1.62876 -5.04528,2.53289 -1.98355,0.85518 -3.9245,1.76609 -5.9385,2.55617 -2.6027,1.25889 -5.19179,2.53692 -7.73292,3.91622 -3.16884,1.42333 -6.13113,3.23939 -9.20069,4.85676 -3.01773,1.63467 -5.94816,3.43762 -8.7288,5.45006 -1.8161,1.09651 -3.20033,2.68211 -4.6919,4.14933 -2.56301,2.52117 0.62871,-0.80843 -1.82713,1.78993 -1.29257,1.17689 -2.33137,2.57077 -3.45106,3.89804 -1.30688,1.49913 -2.76578,2.85213 -4.2452,4.17729 -1.67526,1.40297 -3.27521,2.85848 -4.81531,4.40468 -1.11976,1.32692 -1.82601,2.96903 -2.59642,4.51708 -0.89521,1.49446 -1.60042,3.07987 -2.33327,4.65731 -0.55628,0.99643 -1.16583,1.96813 -1.65121,3.00157 -0.53699,0.94305 -0.94412,1.93177 -1.55569,2.83258 -0.8921,1.16304 -1.39503,2.55479 -2.18277,3.77523 -0.75855,1.14369 -1.22847,2.45654 -1.66572,3.75099 -0.47382,1.2505 -0.77063,2.5524 -1.27452,3.79267 0.0176,-0.0459 0.01,-0.16167 0.0529,-0.13767 15.20424,8.50293 10.5337,11.06607 15.80147,3.6489 2.08695,-2.75768 3.99725,-5.64466 5.97647,-8.48077 1.92629,-2.76638 3.80319,-5.56446 5.65316,-8.38206 1.26769,-2.14829 2.54516,-4.29976 3.72202,-6.4994 0.99127,-1.696 1.86944,-3.43898 2.60941,-5.25851 0.6295,-1.85568 1.18095,-3.73524 1.94295,-5.54209 0.89682,-2.17472 2.06346,-4.22311 3.20854,-6.27422 1.00575,-1.47324 1.84774,-3.08799 2.93752,-4.50437 0.10494,-0.13639 1.21585,-1.33809 1.34628,-1.47981 0.9082,-1.31448 2.09267,-2.36626 3.17242,-3.52376 0.95213,-0.94011 1.9487,-1.7963 2.92104,-2.71484 1.11996,-1.04095 2.26946,-2.03134 3.34625,-3.11929 1.36036,-1.04378 2.73501,-2.1089 4.26504,-2.89286 1.62924,-1.00996 3.42299,-1.69537 5.08542,-2.63746 1.4904,-0.68881 3.08098,-1.11573 4.60632,-1.70803 1.61959,-0.626 3.03985,-1.70627 4.57116,-2.53507 1.26489,-0.46675 2.54065,-0.95411 3.84876,-1.29477 1.16488,-0.23001 2.35876,-0.2251 3.54126,-0.23621 0.64204,0.066 1.31618,-0.0895 1.95561,4.1e-4 0.38998,0.0548 0.54234,0.17649 0.85356,0.35797 -4.23079,-2.43099 -8.46158,-4.86198 -12.69237,-7.29297 -0.23596,0.40922 0.32876,-0.0608 0.27332,0.0117 -0.23121,0.30203 -1.16696,0.73465 -1.48098,0.91793 -1.89828,0.86389 -3.80655,1.66682 -5.80597,2.2709 -2.38683,0.81515 -4.90251,1.25962 -7.18253,2.35472 -1.59845,0.71242 -3.19309,1.38261 -4.74426,2.19516 -2.17518,1.09322 -4.14076,2.57229 -6.05109,4.07209 -1.04757,0.75941 -1.86421,1.78884 -2.84381,2.62126 -0.47353,0.40237 -1.00615,0.72951 -1.50758,1.09652 -2.60957,1.82293 -5.07268,3.85707 -7.44319,5.9788 -2.38247,2.05357 -4.89087,3.94947 -7.26141,6.01746 -1.45747,1.27654 -2.72386,2.74505 -3.98576,4.21007 -1.17894,1.2966 -2.3584,2.58174 -3.34479,4.03392 -0.80492,1.04949 -1.52848,2.15792 -2.2549,3.2618 -0.56841,1.04014 -0.90533,2.19537 -1.26485,3.32241 -0.20666,1.14317 -0.70444,2.17858 -1.1385,3.24297 -0.37682,1.24532 -0.9082,2.43344 -1.34007,3.65859 -0.59614,1.23685 -1.07713,2.51619 -1.55984,3.79939 -0.60561,1.64317 -1.22029,3.28304 -1.72453,4.96068 -0.49888,1.22534 -0.83747,2.50404 -1.31075,3.73744 -0.40444,1.06336 -0.97991,2.04175 -1.55375,3.01889 0,0 13.08691,7.56878 13.08691,7.56878 v 0 c 0.61569,-1.04805 1.22928,-2.09904 1.67579,-3.23392 0.49152,-1.27241 0.84263,-2.58551 1.37098,-3.84669 0.46931,-1.6472 1.08207,-3.24556 1.66603,-4.85454 0.50216,-1.29407 0.94652,-2.60845 1.58598,-3.84657 0.4,-1.21115 0.96582,-2.36112 1.30393,-3.59348 0.42896,-1.10903 1.01951,-2.16153 1.20003,-3.35429 0.28527,-0.99865 0.6179,-2.00144 1.04898,-2.94116 0.72768,-1.09373 1.44377,-2.19601 2.24241,-3.24048 0.96693,-1.3969 2.13628,-2.61995 3.25929,-3.89028 1.2158,-1.40982 2.45249,-2.8098 3.87076,-4.02235 2.38823,-2.05378 4.89907,-3.95769 7.27043,-6.03197 2.36178,-2.07152 4.79443,-4.06893 7.37306,-5.86698 1.54036,-1.15559 2.87935,-2.51821 4.3653,-3.73979 1.87881,-1.41018 3.76354,-2.83918 5.90154,-3.83977 1.53809,-0.76523 3.13304,-1.40357 4.69438,-2.11969 2.31022,-0.90327 4.75639,-1.41584 7.09887,-2.23031 2.062,-0.71132 4.09072,-1.52659 6.00735,-2.575 1.15677,-0.75487 2.31614,-1.57824 2.36727,-3.09574 -11.84239,-7.15983 -8.79952,-8.19681 -16.23653,-7.94552 -1.27385,0.0591 -2.55519,0.15442 -3.77266,0.56575 -1.36356,0.46811 -2.76869,0.85036 -4.03011,1.56676 -1.44448,0.83593 -2.83281,1.8206 -4.43538,2.33025 -1.60897,0.54133 -3.21588,1.11756 -4.72908,1.89291 -1.7058,0.87233 -3.44756,1.67232 -5.09656,2.65452 -1.58254,0.9473 -3.11251,2.01939 -4.48952,3.24809 -1.07479,1.06783 -2.23755,2.0338 -3.32634,3.0869 -0.97659,0.93843 -2.02953,1.78709 -2.95848,2.77592 -1.11653,1.14012 -2.228,2.27157 -3.18959,3.55198 -1.73615,1.91381 -3.12213,4.07803 -4.46152,6.28494 -1.17125,2.09783 -2.36412,4.19304 -3.28797,6.41396 -0.77999,1.81247 -1.36826,3.69143 -1.96005,5.57265 -0.69501,1.77895 -1.57036,3.46126 -2.52724,5.11323 -1.14995,2.16186 -2.41205,4.27023 -3.65327,6.38065 -1.84142,2.80594 -3.71367,5.58986 -5.63123,8.34468 -1.97876,2.83242 -3.8863,5.71663 -5.9713,8.47202 -5.09974,7.21931 -3.61585,3.58041 10.14902,11.86734 0.10559,0.0636 0.13753,-0.20454 0.2063,-0.30682 0.54562,-1.27233 0.85593,-2.60811 1.33519,-3.90196 0.366,-1.18393 0.79847,-2.37723 1.47685,-3.41749 0.76962,-1.22092 1.29148,-2.58159 2.16466,-3.74426 0.64823,-0.97619 1.10877,-2.02653 1.6781,-3.04528 0.45874,-1.04165 1.10992,-1.99296 1.6475,-2.99498 0.7231,-1.56766 1.42248,-3.13956 2.31322,-4.62198 0.69376,-1.40359 1.32111,-2.91229 2.35041,-4.10035 1.51226,-1.5356 3.15699,-2.90965 4.77261,-4.33535 1.51521,-1.38933 3.03002,-2.786 4.35719,-4.36096 1.07589,-1.31874 2.15268,-2.63092 3.39427,-3.80416 1.98924,-2.04024 3.98674,-4.10902 6.36889,-5.69823 2.76588,-1.9319 5.63882,-3.7109 8.62332,-5.28656 3.05605,-1.64619 6.06258,-3.38186 9.20471,-4.86291 2.50272,-1.33987 5.04539,-2.60674 7.63563,-3.76923 2.04517,-0.82102 4.03382,-1.76494 6.03055,-2.69514 1.72296,-0.88646 3.50857,-1.66371 5.15022,-2.6955 1.33179,-0.93952 2.63362,-1.91845 4.00061,-2.80598 5.75132,-4.27305 6.41889,-0.18499 -9.91136,-9.53506 0.0988,-0.15669 0.22735,-0.29822 0.29653,-0.47009 0.0309,-0.0768 -0.11478,0.11966 -0.17731,0.17392 -0.45656,0.39618 -1.06226,0.60574 -1.48578,1.04994 -1.07663,0.82836 -2.19514,1.6074 -3.14048,2.59206 -1.53745,1.34942 -2.95155,2.80683 -3.65012,4.78163 -0.0188,0.0593 -0.65643,1.94397 -0.51994,2.04078 7.75805,5.50234 9.22853,9.3143 15.73862,7.79935 2.42682,-0.47812 4.85872,-0.64526 7.24894,0.15555 5.24405,2.34611 1.12226,0.54529 -11.09364,-6.66691 -0.24934,-0.14721 0.51196,0.27175 0.75737,0.42541 0.62106,0.38887 1.13479,0.73189 1.24929,1.49492 -0.19447,1.28451 -0.67305,2.36229 -1.67066,3.23761 -1.0601,0.99375 -2.34252,1.69237 -3.58241,2.43713 -2.28455,1.40297 -4.58146,2.78316 -6.93152,4.07482 -0.6401,0.33075 -1.7557,0.97424 -2.50893,1.12733 -0.29072,0.0591 -1.13609,-0.18506 -0.88979,-0.0197 3.99496,2.68242 8.18171,5.06786 12.24564,7.64459 0.11606,0.0736 -0.36688,0.007 -0.3917,-0.1286 -0.0517,-0.28183 0.14352,-0.55483 0.21528,-0.83225 1.0086,-2.57511 2.17547,-5.08853 3.25754,-7.633 1.14069,-2.06393 2.33352,-2.87066 4.47439,-3.77341 1.99682,-0.99435 4.09811,-2.12487 6.39726,-1.94691 5.54739,1.31252 -1.16486,-0.19796 -11.70509,-7.15668 -0.22944,-0.15148 1.03266,-0.87795 0.47926,-0.42008 -0.1142,0.0945 -0.23753,0.17734 -0.3563,0.26601 -0.91035,0.69602 -1.797,1.34558 -2.88727,1.75467 -1.48835,0.81922 -2.91672,1.74573 -4.3402,2.6716 -2.04124,1.18747 -4.14532,2.33613 -6.41512,3.0194 -1.78134,0.56187 -3.56442,1.13378 -5.31489,1.77606 -2.89473,1.52569 -5.43227,3.61724 -7.94047,5.7005 -4.2817,3.73703 -8.32405,7.76259 -12.30794,11.8115 -1.41228,1.50514 -2.88298,3.03195 -4.21881,4.60891 -0.54545,0.64392 -2.16213,1.37043 -1.57814,1.97962 10.37811,10.8258 9.09373,11.67867 15.32582,6.79575 4.65474,-3.95024 9.15169,-8.10228 12.87166,-12.97238 3.18286,-3.85479 6.53093,-7.63942 9.17583,-11.89157 -4.19727,-2.86373 -8.27973,-5.90343 -12.59181,-8.59118 -0.077,-0.048 -1.09652,0.65829 -1.56463,0.98599 -1.5592,1.09152 -3.11333,2.19347 -4.61332,3.36563 -2.81974,2.1259 -5.54732,4.39017 -8.5582,6.24779 -2.08506,1.3181 -4.17789,2.62318 -6.27025,3.9296 -2.81241,1.94701 -5.33289,4.26353 -8.12848,6.23518 -2.89927,2.17261 -5.95114,4.11376 -8.92117,6.18537 -2.22251,1.51205 -4.43203,3.08505 -6.33475,4.9951 -1.64219,1.88227 -3.03883,3.95761 -4.61661,5.89356 -1.22912,1.79711 -2.42749,3.61668 -3.72627,5.3644 -1.44811,2.02933 -2.9611,4.01363 -4.29362,6.12305 -1.03598,1.43422 -2.15766,2.80499 -3.26514,4.18412 -1.28435,1.68579 -2.61731,3.34228 -3.73394,5.1467 -0.61541,1.23637 -1.10338,2.39149 -1.42745,3.7376 -1.42213,5.90724 -0.30903,0.87064 12.96295,8.86478 1.37512,-2.07493 1.70669,-4.65994 2.52212,-6.96893 0,0 -13.28274,-7.2196 -13.28274,-7.2196 z" /> | ||
| 65 | <path | ||
| 66 | style="fill:#ffffff" | ||
| 67 | id="path1041" | ||
| 68 | d="m 444.95379,346.65456 c 1.06642,-2.60177 2.26717,-5.1388 3.53885,-7.64477 0.80062,-1.72005 1.70753,-3.33379 2.78034,-4.89383 0.99874,-1.25158 1.79112,-2.64198 2.78939,-3.89223 0.97549,-1.20036 1.66709,-2.60021 2.6938,-3.75713 1.13014,-1.5117 1.88641,-3.2593 2.68308,-4.96014 0.7471,-1.60342 1.60226,-3.14777 2.40761,-4.72157 1.18872,-1.66559 2.27151,-3.4063 3.48455,-5.05396 0.96809,-1.31722 1.78836,-2.73014 2.77318,-4.03489 0.90823,-1.43352 1.91835,-2.76684 3.06487,-4.0174 1.43521,-1.23262 2.86359,-2.45013 4.14673,-3.84264 1.10736,-0.88478 2.1079,-1.91275 3.22743,-2.78438 1.26625,-0.92328 2.40104,-2.02565 3.74382,-2.83749 1.36021,-0.84114 2.47965,-1.99587 3.75402,-2.93834 1.17313,-0.82096 2.1014,-1.91914 3.19251,-2.83265 0.96511,-1.06249 1.9825,-2.0389 3.15071,-2.87458 1.46588,-1.3037 3.03026,-2.5058 4.41761,-3.8948 1.26476,-0.99496 2.67815,-1.75513 3.97292,-2.70979 1.23962,-0.91909 2.67716,-1.37512 4.09945,-1.89879 1.53746,-0.34526 2.98,-0.81959 4.436,-1.41426 0.95752,-0.25952 2.32209,-0.8001 3.33156,-0.62574 0.20945,0.0362 0.38685,0.17626 0.58027,0.26439 -4.20114,-2.33375 -8.40228,-4.6675 -12.60343,-7.00126 0,0 13.96868,5.78186 13.96868,5.78186 v 0 c -4.4863,-2.61747 -8.9726,-5.23493 -13.4589,-7.8524 -1.51335,0.0943 -2.98338,0.40961 -4.37883,1.01983 -1.4393,0.56639 -2.96156,0.82534 -4.40858,1.37347 -1.5382,0.55268 -3.00033,1.2792 -4.32591,2.24462 -1.37284,0.95626 -2.85935,1.75693 -4.09307,2.90202 -1.38146,1.30479 -2.85379,2.51476 -4.31435,3.72921 -1.15751,0.90413 -2.18874,1.92435 -3.21309,2.97405 -1.03083,0.94401 -2.01396,1.93579 -3.14828,2.76194 -1.19514,0.98762 -2.37284,1.99266 -3.68464,2.82915 -1.32344,0.92776 -2.55283,1.98338 -3.82992,2.9733 -1.12752,0.96567 -2.25289,1.94069 -3.33352,2.95626 -1.30383,1.37931 -2.79466,2.54328 -4.15785,3.86097 -1.16319,1.29631 -2.19199,2.67816 -3.13817,4.13963 -0.9771,1.31712 -1.81169,2.72753 -2.78204,4.04984 -1.24707,1.72053 -2.39712,3.51107 -3.60412,5.25971 -0.815,1.58975 -1.6765,3.15157 -2.43517,4.77007 -0.73319,1.57868 -1.42437,3.21086 -2.47717,4.60741 -0.99785,1.20674 -1.75357,2.57945 -2.72447,3.80831 -1.00142,1.28731 -1.83229,2.69059 -2.83871,3.9755 -1.1137,1.6144 -2.04714,3.29226 -2.88163,5.06874 -1.2825,2.52926 -2.50723,5.08361 -3.57702,7.71214 0,0 13.17149,7.42062 13.17149,7.42062 z" /> | ||
| 69 | <path | ||
| 70 | style="fill:#ffffff" | ||
| 71 | id="path1043" | ||
| 72 | d="m 47.723738,302.48814 c -0.235582,2.57035 -0.413918,5.14543 -0.527844,7.72396 -0.04401,1.66877 -0.637615,3.23133 -0.971978,4.84849 -0.49334,2.0254 -0.803411,4.07474 -0.962727,6.15115 -0.22051,2.38235 -0.149823,4.7319 0.269472,7.08396 0.481444,2.35269 0.997451,4.69608 1.45286,7.05476 0.457995,2.16877 0.573854,4.38236 0.790824,6.58142 0.233663,1.60072 0.456482,3.20102 0.57548,4.81477 0.08973,1.43491 0.08843,2.87328 0.09188,4.31034 -0.0031,5.94595 -0.872531,1.79721 13.666195,7.35427 0.230286,0.088 0.424137,-2.99784 0.437609,-3.20433 0.09063,-2.46503 0.06323,-4.93249 0.04974,-7.39861 -0.08329,-2.38251 0.008,-4.7492 0.31058,-7.11289 0.318125,-3.12377 1.109164,-6.1645 1.882127,-9.19858 0.603361,-2.38574 1.199296,-4.775 1.831907,-7.15334 0.5698,-2.1975 1.009163,-4.42888 1.423266,-6.66069 0.356849,-2.02356 0.443261,-4.078 0.494757,-6.12769 0.10103,-1.42529 0.383836,-2.82762 0.53435,-4.24816 -0.0029,-1.13487 0.180083,-2.1682 0.603434,-3.21105 0.321111,-0.5869 0.15321,-0.2786 0.503328,-0.92512 0,0 -13.113313,-7.52296 -13.113313,-7.52296 v 0 c -0.373806,0.69103 -0.177993,0.33257 -0.588169,1.07497 -0.466058,1.17181 -0.891486,2.30042 -0.810581,3.59475 -0.118838,1.46005 -0.383281,2.8963 -0.58493,4.34604 -0.03272,1.97659 -0.0796,3.95891 -0.384361,5.91626 -0.38509,2.19498 -0.834288,4.38156 -1.361672,6.54616 -0.65643,2.37946 -1.223845,4.78566 -1.843449,7.17462 -0.780965,3.08634 -1.636353,6.16979 -1.961447,9.34711 -0.358177,2.4239 -0.514696,4.85383 -0.409938,7.30476 0.01422,2.44019 0.03976,4.88083 -0.0099,7.32079 -0.02942,0.76513 -0.03695,1.29897 -0.112712,2.05085 -0.02471,0.24528 -0.307475,0.60077 -0.09933,0.73287 4.278007,2.71486 8.786753,5.0478 13.181546,7.56923 0.0572,0.0328 -0.153475,-0.0392 -0.168712,-0.10336 -0.04652,-0.19592 4.77e-4,-0.40274 7.16e-4,-0.60411 -7.7e-5,-1.46409 0.0069,-2.92885 -0.05054,-4.3921 -0.08299,-1.64226 -0.248525,-3.27751 -0.534015,-4.89805 -0.334753,-2.2089 -0.271582,-4.47121 -0.7496,-6.66552 -0.430658,-2.36726 -0.924448,-4.71652 -1.449337,-7.062 -0.472671,-2.26866 -0.697763,-4.4124 -0.444127,-6.73588 0.115274,-2.02246 0.364088,-4.02381 0.877321,-5.98869 0.294207,-1.70188 0.964594,-3.3091 1.075721,-5.04646 0.09428,-2.55627 0.265712,-5.10876 0.493001,-7.65667 0,0 -13.40746,-6.98527 -13.40746,-6.98527 z" /> | ||
| 73 | <path | ||
| 74 | style="fill:#ffffff" | ||
| 75 | id="path1045" | ||
| 76 | d="m 638.45033,529.45411 c 0.32043,-1.43279 0.87109,-2.79362 1.51319,-4.10875 0.49898,-1.07666 1.0692,-2.11787 1.58949,-3.18392 0.3604,-0.79469 0.72883,-1.65852 0.63073,-2.55458 -0.0213,-0.19465 -0.0803,-0.38329 -0.12051,-0.57493 -12.05402,-7.29915 -8.97396,-7.82949 -16.48178,-7.87355 -1.51954,-0.003 -3.03908,0.007 -4.55861,0.0125 -0.43502,-0.13711 -1.85991,0.26465 -2.19604,0.10033 -0.0753,-0.0368 -0.16683,-0.27616 -0.0951,-0.23283 4.27548,2.58135 8.49877,5.24815 12.74816,7.87222 -0.0323,0.11502 -0.19798,0.28158 -0.0968,0.34508 0.11127,0.0698 0.23042,-0.12621 0.34594,-0.18875 0.82367,-0.44601 1.64573,-0.88641 2.48418,-1.30455 1.04859,-0.43593 2.07827,-0.92742 3.18607,-1.19426 0.1154,-0.0333 1.46149,-0.43299 1.45532,-0.21803 -0.003,0.12184 -0.0928,0.22543 -0.13915,0.33815 -15.99901,-11.90341 -7.73872,-9.62675 -14.63806,-7.60889 -1.12229,0.32922 -2.27318,0.57267 -3.42629,0.77165 -0.8597,0.13995 -1.69864,0.34359 -2.57144,0.3832 -0.54215,-0.0101 -1.09515,0.074 -1.62901,-0.0558 -0.24961,-0.0607 -0.48864,-0.15896 -0.73526,-0.23084 -0.0894,-0.0566 -0.19053,-0.098 -0.26821,-0.16984 -0.0706,-0.0653 -0.25175,-0.28213 -0.16846,-0.23411 4.25969,2.45608 8.54644,4.87008 12.7194,7.47078 0.0386,0.0241 -0.62896,0.70858 -0.14215,0.34641 0.41198,-0.21773 0.79311,-0.50185 1.21319,-0.7044 0.241,-0.1162 0.49677,-0.19887 0.74453,-0.29986 0.64051,-0.18906 1.25438,-0.4587 1.9201,-0.54617 0.53439,-0.0211 1.07056,0.004 1.60554,0.0114 0.66172,0.20645 1.36743,0.21542 2.0551,0.24269 0.2059,0.0384 0.41918,-0.0195 0.62534,0.0116 0.0937,0.0141 0.17952,0.0618 0.27192,0.0828 0.32123,0.0728 0.65864,0.0923 0.98578,0.12092 0,0 -12.12893,-9.02458 -12.12893,-9.02458 v 0 c -0.64258,-0.0911 -1.27712,-0.13195 -1.92456,-0.15915 -0.70608,-0.0683 -1.41657,-0.216 -2.12625,-0.14532 -0.60794,0.0224 -1.21803,0.0643 -1.79888,0.26164 -0.70334,0.23112 -1.4064,0.47243 -2.0796,0.78154 -0.8134,0.39578 -1.61239,0.85829 -2.26694,1.48977 -4.40189,5.22506 7.05828,9.21158 12.81615,9.58956 0.87601,0.1034 1.76219,0.0157 2.63569,-0.0834 0.88485,-0.14403 1.76111,-0.3377 2.64498,-0.48765 1.20051,-0.2558 2.40354,-0.52668 3.55497,-0.95904 7.22662,-2.97311 -0.75811,-4.03385 -9.72884,-10.2551 -0.10392,0.0865 -0.19775,0.18687 -0.31174,0.25961 -0.14271,0.0911 -0.37549,0.11873 -0.53047,0.15979 -0.37663,0.0998 -0.74429,0.23119 -1.11935,0.33636 -1.13859,0.40564 -2.2512,0.88549 -3.336,1.41811 -0.68468,0.35565 -1.37148,0.71028 -2.0284,1.11615 -0.26605,0.16437 -0.84138,0.52704 -1.05722,0.77979 -0.20267,0.23735 -0.35044,0.51655 -0.52566,0.77483 4.25429,2.7014 8.45898,5.48255 12.76287,8.1042 0.1058,0.0645 0.23191,-0.0911 0.35356,-0.11452 0.71214,-0.1371 1.45642,-0.0262 2.17386,-0.0697 1.50973,-0.005 3.02005,-0.019 4.52929,0.0278 0.63448,0.0341 1.28759,0.049 1.90849,0.20211 0.17207,0.0424 0.34359,0.0927 0.50543,0.16496 0.13833,0.0617 0.51975,0.31137 0.38936,0.2343 -4.17013,-2.46505 -8.33904,-4.93215 -12.50856,-7.39822 0.13533,-0.0252 0.26836,-0.0779 0.406,-0.0756 0.69198,0.0116 0.0239,1.25024 -0.0802,1.48859 -0.51442,1.06625 -1.09068,2.1018 -1.58591,3.17763 -0.69439,1.40539 -1.30774,2.85802 -1.66838,4.38859 0,0 13.29807,7.19133 13.29807,7.19133 z" /> | ||
| 77 | </g> | ||
| 78 | </svg> | 6 | </svg> |
| @@ -1,48 +1,7 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 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" | 6 | </g> |
| 7 | preserveAspectRatio="xMidYMid" | ||
| 8 | id="svg8" | ||
| 9 | sodipodi:docname="koboldhorde.svg" | ||
| 10 | inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | ||
| 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 12 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 13 | xmlns="http://www.w3.org/2000/svg" | ||
| 14 | xmlns:svg="http://www.w3.org/2000/svg"> | ||
| 15 | <defs | ||
| 16 | id="defs12" /> | ||
| 17 | <sodipodi:namedview | ||
| 18 | id="namedview10" | ||
| 19 | pagecolor="#ffffff" | ||
| 20 | bordercolor="#000000" | ||
| 21 | borderopacity="0.25" | ||
| 22 | inkscape:showpageshadow="2" | ||
| 23 | inkscape:pageopacity="0.0" | ||
| 24 | inkscape:pagecheckerboard="0" | ||
| 25 | inkscape:deskcolor="#d1d1d1" | ||
| 26 | inkscape:document-units="pt" | ||
| 27 | showgrid="false" | ||
| 28 | inkscape:zoom="1.5043945" | ||
| 29 | inkscape:cx="237.63713" | ||
| 30 | inkscape:cy="285.82928" | ||
| 31 | inkscape:window-width="2560" | ||
| 32 | inkscape:window-height="1377" | ||
| 33 | inkscape:window-x="5112" | ||
| 34 | inkscape:window-y="-8" | ||
| 35 | inkscape:window-maximized="1" | ||
| 36 | inkscape:current-layer="svg8" /> | ||
| 37 | <g | ||
| 38 | transform="matrix(0.1,0,0,-0.1,-40.075165,470.78832)" | ||
| 39 | stroke="none" | ||
| 40 | id="g6"> | ||
| 41 | <path | ||
| 42 | d="m 2495,4698 c -3,-7 -6,-76 -7,-153 l -3,-140 -115,-5 c -63,-3 -114,-9 -114,-14 1,-4 34,-41 74,-82 l 72,-74 -4,-125 -3,-124 -65,-67 c -74,-75 -224,-205 -257,-223 -27,-14 -54,-3 -89,38 -22,26 -24,26 -156,26 -132,0 -135,0 -172,28 -52,40 -60,67 -41,135 9,30 14,56 11,59 -7,7 -47,-16 -77,-43 l -25,-24 -18,41 c -10,23 -39,56 -68,80 -63,49 -110,53 -160,12 -58,-49 -67,-65 -68,-118 0,-49 2,-53 63,-113 62,-61 63,-62 118,-62 h 56 l 72,-80 73,-80 h 51 c 61,0 66,-3 92,-53 11,-21 46,-69 77,-105 l 58,-67 -320,-3 -321,-2 -55,26 c -47,22 -67,42 -131,125 -79,102 -128,139 -186,139 -23,0 -27,-4 -27,-28 0,-61 91,-163 177,-198 64,-27 97,-59 167,-162 36,-55 75,-107 86,-117 24,-22 80,-108 80,-123 0,-6 -15,-35 -34,-64 -18,-29 -66,-113 -106,-185 l -72,-133 h -80 -79 l 2,80 c 1,44 0,83 -4,86 -3,3 -31,-8 -63,-24 -67,-33 -197,-82 -220,-82 -9,0 -34,-13 -57,-29 -36,-25 -55,-30 -129,-36 -77,-6 -87,-9 -92,-28 -13,-48 -15,-47 157,-47 90,0 232,-3 315,-7 l 152,-6 v -39 -38 h 281 280 l 57,47 c 31,25 80,56 109,69 87,39 78,44 -89,45 -84,1 -232,4 -328,8 l -175,6 55,28 c 30,15 63,27 72,27 16,0 58,44 58,61 0,17 92,150 113,164 12,7 57,17 100,21 l 77,7 62,-43 c 49,-35 72,-45 115,-48 31,-3 53,0 53,5 0,11 -93,102 -156,152 l -42,32 -4,90 c -2,50 -9,94 -16,101 -7,7 -12,16 -12,20 0,4 56,8 123,8 68,0 212,3 319,7 l 195,6 -56,50 c -72,65 -71,63 -71,125 0,29 -7,69 -15,89 -8,19 -15,37 -15,39 0,2 28,4 61,4 57,0 65,3 110,41 l 49,41 v 69 70 l 55,51 c 106,98 95,132 95,-292 v -371 l -45,-42 -44,-42 -2,-194 c -2,-219 -6,-209 76,-210 22,-1 50,-8 62,-16 18,-13 23,-13 30,-2 5,9 33,16 71,20 35,3 66,8 69,11 3,3 8,93 11,199 l 5,194 -47,32 -46,32 v 202 c 0,111 -3,281 -7,377 l -6,174 39,-31 c 107,-86 106,-85 111,-143 7,-89 10,-96 65,-134 43,-29 62,-36 101,-36 h 48 l -5,-32 c -3,-18 -8,-64 -10,-103 l -5,-70 -56,-50 c -30,-28 -55,-53 -55,-55 0,-3 78,-3 172,-2 95,2 237,1 315,-3 l 142,-7 -14,-29 c -9,-17 -15,-56 -15,-97 0,-85 -1,-86 -161,-228 -66,-58 -118,-111 -116,-118 9,-27 130,16 213,76 45,32 47,33 164,34 l 118,2 78,-114 c 44,-62 79,-122 79,-131 0,-16 -18,-19 -205,-24 -113,-3 -238,-7 -277,-8 -40,0 -73,-5 -73,-11 0,-5 21,-18 48,-30 26,-12 81,-43 123,-70 l 75,-50 h 316 316 l 9,38 9,37 58,3 c 53,3 58,1 51,-15 -4,-10 -11,-39 -15,-66 -5,-35 -15,-57 -40,-82 -40,-41 -33,-57 13,-30 18,11 79,35 137,55 l 105,35 9,50 c 5,28 14,53 20,57 6,4 52,8 103,8 h 91 l 11,31 c 6,18 11,33 11,34 0,2 -46,4 -103,7 -96,3 -106,5 -152,36 -27,18 -67,37 -89,43 -44,12 -112,44 -136,64 -8,7 -27,15 -42,17 -27,5 -27,5 -34,-73 l -7,-79 h -89 -89 l -57,118 c -31,64 -69,137 -84,161 -22,36 -28,57 -28,102 0,56 2,60 54,116 29,32 81,98 116,147 49,69 76,96 122,125 138,84 191,138 202,204 l 7,37 h -41 c -59,0 -88,-19 -159,-104 -65,-78 -120,-127 -181,-165 -34,-20 -46,-21 -362,-21 -192,0 -328,4 -328,9 0,5 19,24 42,42 22,18 61,64 85,101 44,68 44,68 90,68 44,0 49,3 134,86 84,82 89,86 122,80 49,-9 84,7 145,67 50,49 52,53 52,105 0,65 -10,84 -62,122 -61,45 -119,42 -165,-6 -18,-20 -43,-54 -54,-75 -10,-21 -22,-39 -26,-39 -3,0 -20,9 -37,19 -17,11 -40,21 -50,23 -17,3 -19,-3 -17,-57 1,-46 -3,-68 -19,-93 -31,-51 -68,-61 -191,-55 -131,7 -152,2 -179,-42 -12,-19 -29,-35 -38,-35 -17,0 -405,306 -429,337 -7,10 -13,45 -13,80 v 63 h 89 c 53,0 93,4 96,11 4,6 -4,22 -16,36 -32,34 -134,120 -150,125 -9,3 -13,49 -13,180 -1,97 -3,178 -6,181 -3,3 -33,8 -67,12 -49,5 -64,4 -68,-7 z" | ||
| 43 | id="path2" /> | ||
| 44 | <path | ||
| 45 | d="m 2166,2764 c -3,-9 -6,-59 -6,-112 v -97 l -49,-42 -50,-43 h -145 -146 v -110 -110 h -25 c -17,0 -43,-16 -75,-46 -53,-49 -96,-63 -164,-50 -30,6 -42,18 -89,89 -30,45 -61,97 -70,114 -16,30 -20,31 -83,35 -36,1 -68,0 -71,-5 -2,-4 25,-67 61,-140 36,-72 68,-150 72,-173 6,-38 3,-44 -33,-80 -21,-21 -62,-58 -91,-82 -29,-24 -61,-60 -71,-80 -26,-49 -106,-125 -161,-152 -56,-28 -116,-83 -141,-132 -36,-70 -25,-84 51,-64 48,12 64,25 154,118 149,155 126,148 509,148 174,0 317,-4 317,-8 0,-5 -14,-21 -31,-38 -17,-16 -53,-63 -81,-104 l -50,-75 -57,-6 c -54,-7 -58,-9 -122,-79 l -67,-73 -58,6 c -71,8 -88,0 -149,-71 -34,-41 -45,-62 -45,-88 0,-51 23,-98 64,-129 31,-24 43,-27 85,-23 73,7 121,50 145,130 5,16 8,16 48,-2 63,-29 69,-27 62,20 -6,34 -2,45 20,75 48,63 67,70 182,66 134,-3 172,4 180,35 13,52 26,48 147,-43 63,-49 167,-130 231,-182 l 116,-94 v -57 -57 l -86,-6 c -48,-3 -89,-8 -91,-11 -3,-3 26,-33 63,-68 136,-125 121,-89 123,-288 1,-96 4,-176 6,-179 3,-2 22,12 44,32 22,19 51,43 65,52 25,17 26,20 26,126 v 109 h 119 c 66,0 122,4 125,9 3,5 -33,46 -80,90 l -86,82 7,109 c 8,130 17,146 140,256 43,38 100,90 127,117 64,61 94,63 137,8 l 32,-40 145,-3 146,-3 30,-34 c 27,-30 30,-39 26,-82 -5,-48 -4,-49 21,-48 14,1 40,4 56,8 26,5 32,2 45,-21 8,-15 32,-46 53,-68 35,-36 44,-40 86,-40 38,0 57,7 90,30 45,33 61,63 61,116 0,27 -11,46 -52,91 -66,74 -63,73 -127,73 -53,0 -55,1 -132,75 l -79,75 h -79 c -51,0 -82,4 -89,13 -5,6 -15,38 -22,70 -8,38 -22,70 -41,92 -30,33 -30,34 -9,45 14,8 116,10 328,8 355,-3 350,-2 442,-101 150,-161 142,-154 194,-161 64,-10 75,1 51,55 -26,56 -86,105 -154,125 -45,13 -65,26 -101,67 -25,28 -75,83 -111,122 -37,38 -86,101 -109,140 l -42,69 32,56 c 40,70 140,277 140,291 0,10 -40,6 -112,-12 -30,-8 -42,-18 -59,-52 -28,-54 -75,-125 -107,-159 -49,-53 -158,-50 -221,7 -21,19 -49,38 -63,44 -21,8 -26,18 -31,63 -4,29 -7,78 -7,110 v 56 l -160,4 -159,3 -39,38 -39,38 -7,112 c -4,61 -8,113 -11,115 -8,9 -40,-21 -195,-186 -89,-95 -170,-177 -181,-183 -30,-15 -11,-34 -263,249 -60,67 -112,122 -116,122 -4,0 -11,-7 -14,-16 z m 388,-524 c 4,0 59,50 121,110 62,61 117,110 123,110 16,0 72,-75 72,-96 0,-18 -290,-304 -308,-304 -11,0 -326,287 -330,301 -4,10 41,75 65,95 10,9 38,-13 131,-102 64,-62 121,-114 126,-114 z m -759,-97 c 4,-21 7,-44 8,-52 2,-11 31,-14 143,-15 l 141,-1 69,-60 c 37,-33 128,-118 201,-190 l 133,-130 v -230 c 0,-218 -4,-275 -19,-275 -3,0 -35,24 -71,53 l -65,53 -12,79 c -6,44 -12,81 -12,81 -1,1 -17,14 -37,29 -25,20 -54,30 -93,34 -42,5 -55,10 -52,21 2,8 7,51 12,95 l 8,80 51,47 c 29,25 49,51 45,57 -4,7 -93,11 -268,12 -144,1 -295,4 -335,8 l -73,6 45,43 c 45,43 46,45 46,105 0,61 1,63 56,125 31,34 60,62 64,62 5,0 12,-17 15,-37 z m 1608,-34 47,-41 v -82 c 0,-69 3,-86 20,-104 12,-12 25,-22 31,-22 6,0 8,-4 5,-9 -3,-5 -71,-12 -150,-16 l -144,-7 -159,-148 c -87,-81 -187,-171 -223,-201 l -65,-55 -5,-66 c -3,-45 -11,-72 -24,-87 -36,-39 -125,-103 -136,-96 -6,4 -10,92 -10,254 v 248 l 103,99 c 108,106 215,206 285,267 l 42,37 h 149 c 158,0 161,1 161,51 0,29 20,23 73,-22 z" | ||
| 46 | id="path4" /> | ||
| 47 | </g> | ||
| 48 | </svg> | 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> | ||
| \ No newline at end of file | 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 | \ 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 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | 2 | <svg id="svg1" width="8.28mm" height="9.4349mm" version="1.1" viewBox="0 0 8.28 9.4349" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path id="path9" d="m4.14 0c-0.05 0.08-0.1 0.16-0.14 0.24-0.07 0.11-0.17 0.19-0.29 0.22-0.2 0.04-0.39 0.09-0.58 0.13 0.19 0.05 0.38 0.1 0.58 0.14 0.12 0.03 0.22 0.11 0.29 0.22 0.04 0.08 0.09 0.16 0.14 0.24 0.04-0.08 0.09-0.16 0.13-0.24 0.07-0.11 0.17-0.19 0.29-0.22 0.2-0.04 0.39-0.09 0.59-0.14-0.2-0.04-0.39-0.09-0.59-0.14-0.12-0.02-0.22-0.1-0.29-0.21-0.04-0.08-0.09-0.16-0.13-0.24zm-1.42 0.81c-0.04 0.08-0.09 0.16-0.14 0.24-0.06 0.11-0.16 0.19-0.29 0.22-0.19 0.05-0.38 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.05-0.09 0.09-0.17 0.14-0.25 0.06-0.11 0.17-0.18 0.29-0.21 0.19-0.05 0.39-0.1 0.58-0.14-0.19-0.05-0.39-0.09-0.58-0.14-0.12-0.03-0.23-0.11-0.29-0.22-0.05-0.08-0.09-0.16-0.14-0.24zm2.82 0c-0.04 0.08-0.09 0.16-0.14 0.24-0.06 0.11-0.16 0.19-0.29 0.22-0.19 0.05-0.38 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.05-0.09 0.09-0.17 0.14-0.25 0.06-0.11 0.17-0.18 0.29-0.21 0.19-0.05 0.39-0.1 0.58-0.14-0.19-0.05-0.39-0.09-0.58-0.14-0.12-0.03-0.23-0.11-0.29-0.22-0.05-0.08-0.09-0.16-0.14-0.24zm-1.4 0.83c-0.05 0.07-0.1 0.15-0.14 0.23-0.07 0.11-0.17 0.19-0.29 0.22-0.2 0.05-0.39 0.09-0.58 0.14 0.19 0.05 0.39 0.09 0.58 0.14 0.12 0.03 0.23 0.11 0.29 0.22 0.05 0.08 0.09 0.16 0.14 0.24 0.04-0.09 0.09-0.17 0.13-0.25 0.07-0.11 0.17-0.18 0.29-0.21 0.2-0.05 0.39-0.1 0.59-0.14-0.2-0.05-0.39-0.09-0.59-0.14-0.12-0.03-0.22-0.11-0.29-0.22-0.04-0.08-0.09-0.16-0.13-0.23zm-3.16 0.14-0.98 0.55v4.8c1.29 0.74 2.58 1.49 3.87 2.23 0.25 0.14 0.46 0.05 0.55 0 1.29-0.75 2.57-1.49 3.86-2.23v-4.8l-0.98-0.55c-1.11 0.86-2.96 0.79-3.16 2.4-0.2-1.61-2.04-1.55-3.16-2.4z"/></svg> |
| 3 | |||
| 4 | <svg | ||
| 5 | width="8.2832384mm" | ||
| 6 | height="9.4351206mm" | ||
| 7 | viewBox="0 0 8.2832384 9.4351206" | ||
| 8 | version="1.1" | ||
| 9 | id="svg1" | ||
| 10 | xml:space="preserve" | ||
| 11 | inkscape:version="1.3 (0e150ed, 2023-07-21)" | ||
| 12 | sodipodi:docname="scale.svg" | ||
| 13 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||
| 14 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||
| 15 | xmlns="http://www.w3.org/2000/svg" | ||
| 16 | xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview | ||
| 17 | id="namedview1" | ||
| 18 | pagecolor="#ffffff" | ||
| 19 | bordercolor="#000000" | ||
| 20 | borderopacity="0.25" | ||
| 21 | inkscape:showpageshadow="2" | ||
| 22 | inkscape:pageopacity="0.0" | ||
| 23 | inkscape:pagecheckerboard="0" | ||
| 24 | inkscape:deskcolor="#d1d1d1" | ||
| 25 | inkscape:document-units="mm" | ||
| 26 | showgrid="false" | ||
| 27 | inkscape:zoom="1.0289025" | ||
| 28 | inkscape:cx="8.7471843" | ||
| 29 | inkscape:cy="8.2612296" | ||
| 30 | inkscape:window-width="1280" | ||
| 31 | inkscape:window-height="688" | ||
| 32 | inkscape:window-x="0" | ||
| 33 | inkscape:window-y="25" | ||
| 34 | inkscape:window-maximized="1" | ||
| 35 | inkscape:current-layer="layer1" /><defs | ||
| 36 | id="defs1" /><g | ||
| 37 | inkscape:label="Layer 1" | ||
| 38 | inkscape:groupmode="layer" | ||
| 39 | id="layer1" | ||
| 40 | transform="translate(-88.746886,-143.1532)"><g | ||
| 41 | id="g1" | ||
| 42 | transform="matrix(0.26458333,0,0,0.26458333,88.077577,143.1532)"><path | ||
| 43 | d="m 14.3423,8.42148 0.0218,-0.00723 2.1725,-0.51502 c 0.4642,-0.10879 0.8631,-0.40619 1.1025,-0.81965 l 0.5041,-0.87044 0.0146,-0.02902 0.0145,0.02902 0.5041,0.87044 c 0.2394,0.41346 0.6383,0.71086 1.1025,0.81965 l 2.1725,0.51502 0.0254,0.01087 H 21.9514 L 19.7789,8.94737 C 19.3147,9.0562 18.9158,9.35359 18.6764,9.76705 l -0.5041,0.87045 -0.0145,0.029 -0.0146,-0.029 -0.5041,-0.87045 C 17.3997,9.35359 17.0008,9.0562 16.5366,8.94737 L 14.3641,8.43239 14.3423,8.42512 Z" | ||
| 44 | id="path10" /><path | ||
| 45 | d="M 14.3423,2.245 14.3641,2.23775 16.5366,1.72274 C 17.0008,1.61393 17.3997,1.31653 17.6391,0.903078 L 18.1432,0.0290146 18.1578,0 18.1723,0.0290146 18.6764,0.899453 C 18.9158,1.31291 19.3147,1.61031 19.7789,1.71911 L 21.9514,2.23412 21.9768,2.245 H 21.9514 L 19.7789,2.76726 C 19.3147,2.87607 18.9158,3.17347 18.6764,3.58692 L 18.1723,4.45738 18.1578,4.48639 18.1432,4.45738 17.6391,3.58692 C 17.3997,3.17347 17.0008,2.87607 16.5366,2.76726 L 14.3641,2.25225 Z" | ||
| 46 | id="path11" /><path | ||
| 47 | d="M 18.183,15.7998 C 17.9328,13.7832 16.754,11.9807 14.9697,10.9507 11.749,9.03938 8.96364,8.82176 6.23265,6.73999 L 2.52967,8.79638 18.183,17.8344 33.8364,8.79638 30.1334,6.74359 c -2.731,2.08181 -5.5164,2.29943 -8.737,4.21071 -1.7844,1.0301 -2.9631,2.8326 -3.2134,4.8491 z" | ||
| 48 | id="path12" /><path | ||
| 49 | d="M 18.183,35.6603 C 17.824,35.653 17.4649,35.5551 17.1458,35.3701 L 2.52967,26.9269 V 8.79646 L 17.1458,17.236 c 0.3227,0.185 0.6782,0.2829 1.0372,0.2902 0.3591,-0.0073 0.7181,-0.1052 1.0373,-0.2902 L 33.8364,8.79646 V 26.9306 l -14.6161,8.4395 c -0.3228,0.185 -0.6782,0.2829 -1.0373,0.2902 z" | ||
| 50 | id="path13" /><path | ||
| 51 | d="m 18.183,35.6603 c 0.3591,-0.0073 0.7181,-0.1052 1.0373,-0.2902 l 14.616,-8.4395 V 8.79646 L 19.2203,17.236 c -0.3228,0.185 -0.6782,0.2829 -1.0373,0.2902" | ||
| 52 | id="path14" /><path | ||
| 53 | d="M 18.183,17.5262 C 17.824,17.5189 17.4649,17.421 17.1458,17.236 L 2.52967,8.79646 V 26.9306 l 14.61613,8.4395 c 0.3227,0.185 0.6782,0.2829 1.0372,0.2902" | ||
| 54 | id="path15" /><path | ||
| 55 | d="M 18.1831,17.5262 C 17.824,17.5189 17.465,17.421 17.1458,17.236 l -1.4507,-0.8378 v 18.1341 l 1.4507,0.8378 c 0.3228,0.185 0.6782,0.2829 1.0373,0.2902 0.359,-0.0073 0.7181,-0.1052 1.0372,-0.2902 l 1.4508,-0.8378 V 16.3946 l -1.4508,0.8378 c -0.3227,0.1849 -0.6782,0.2829 -1.0372,0.2901 z" | ||
| 56 | id="path16" /><path | ||
| 57 | d="M 9,5.32055 9.02174,5.31328 11.1942,4.79826 c 0.4643,-0.10879 0.8632,-0.40618 1.1026,-0.81964 l 0.5041,-0.87044 0.0145,-0.02902 0.0145,0.02902 0.5041,0.87044 c 0.2394,0.41346 0.6384,0.71085 1.1026,0.81964 l 2.1725,0.51502 0.0253,0.01087 H 16.6091 L 14.4366,5.84644 C 13.9724,5.95523 13.5734,6.25263 13.334,6.66609 L 12.8299,7.53653 12.8154,7.56555 12.8009,7.53653 12.2968,6.66609 C 12.0574,6.25263 11.6585,5.95523 11.1942,5.84644 L 9.02174,5.33142 9,5.32415 Z" | ||
| 58 | id="path17" /><path | ||
| 59 | d="m 19.6593,5.32055 0.0218,-0.00727 2.1724,-0.51502 c 0.4642,-0.10879 0.8632,-0.40618 1.1026,-0.81964 l 0.5041,-0.87044 0.0145,-0.02902 0.0145,0.02902 0.5042,0.87044 c 0.2393,0.41346 0.6383,0.71085 1.1025,0.81964 l 2.1724,0.51502 0.0255,0.01087 H 27.2683 L 25.0959,5.84644 C 24.6317,5.95523 24.2327,6.25263 23.9934,6.66609 L 23.4892,7.53653 23.4747,7.56555 23.4602,7.53653 22.9561,6.66609 C 22.7167,6.25263 22.3177,5.95523 21.8535,5.84644 L 19.6811,5.33142 19.6593,5.32415 Z" | ||
| 60 | id="path18" /></g></g></svg> | ||
| @@ -1,88 +1,7 @@ | |||
| 1 | <?xml version="1.0" standalone="no"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | 2 | <svg id="svg1" width="1281.8pt" height="1215.8pt" preserveAspectRatio="xMidYMid" version="1.0" viewBox="0 0 1281.8 1215.8" xmlns="http://www.w3.org/2000/svg"> |
| 3 | "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | 3 | <metadata id="metadata1">Created by potrace 1.15, written by Peter Selinger 2001-2017</metadata> |
| 4 | <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | 4 | <g id="g1" transform="matrix(.1 0 0 -.1 1.9156 1216.5)"> |
| 5 | width="1280.000000pt" height="1217.000000pt" viewBox="0 0 1280.000000 1217.000000" | 5 | <path id="path1" d="m6998 11572c-96-258-333-414-533-587-370-329-494-412-1390-435-874 36-1595-534-2206-1104-62 39-142 56-199 104 0 9 262 294 464 632 74 121 192 285-66 247-417-111-1220-1151-572-305 100 137 564 539 318 683-471 46-1109-1370-1236-1102 23 505 514 977 207 1300-445 44-287-954-795-1541-1448-1651-1018-2700-654-2758 335-53 734 316 734 261 0-180-204-689-108-846 752-1155 2134-1636 3377-2057 99-35-1443-411-1876-970-121-157-587-192-679-346-256-719 2022-1610 3117-1657 812 100 1428-660 2088-1018 130-59 213-134 286 50-220 493-1110 853-956 1021 132 137 1731-15 1731 90 0 8-16 33-35 56-357 414-1665-11-1935 283 0 119 972 340 1341 597 121 97 56 186-113 194-574-4-1043-309-1478-410-184-41-447-69-725 3 83 35 240 88 530 225 448 212 563 317 748 487-10 44-16 92-41 131-101 125-621-82-1438-572-133-80-241-138-256-138-49 0-615 99-802 140-338 75-525 153-473 231 22 29 245 92 467 133 435 77 883 115 1287 305 216 102 386 272 568 422 257 211 446 400 314 650-170 319-510 265-909 653-388 377-556 379-810 585 78 10 157 17 235 26 2709 261 3344 2357 3261 1940-125-518-151-1060-65-1343 200-639 1746-555 2003-537 674 40 1167-320 1761-550 726-269 856-137 765 9-161 263-1223 582-1169 597 508 133 2142 769 1614 1010-297 136-1465-492-1435-455 91 57 965 1014 655 1059-597-178-1028-736-1477-1159-75 29-155 45-228 78 0 20 239 507 275 701 27 105 141 556-120 415-211-131-498-997-534-986-290 78-585 141-886 150-236-1-496 47-461 358 71 337 157 167 490 943 217 528-107 470 77 605 459 340 1060 1370 1115 1494 351 733 1106 1835 145 2401-1027 452-1067-31-2106 150-558 97-937 83-1207-543z"/> |
| 6 | preserveAspectRatio="xMidYMid meet"> | 6 | </g> |
| 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 | </g> | ||
| 88 | </svg> | 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> | ||
| \ No newline at end of file | 9 | \ No newline at end of file | |
| @@ -697,7 +697,7 @@ | |||
| 697 | </span> | 697 | </span> |
| 698 | </div> | 698 | </div> |
| 699 | </div> | 699 | </div> |
| 700 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai"> | 700 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,minimax,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai,workers_ai"> |
| 701 | <div class="range-block-title" data-i18n="Temperature"> | 701 | <div class="range-block-title" data-i18n="Temperature"> |
| 702 | Temperature | 702 | Temperature |
| 703 | </div> | 703 | </div> |
| @@ -710,7 +710,7 @@ | |||
| 710 | </div> | 710 | </div> |
| 711 | </div> | 711 | </div> |
| 712 | </div> | 712 | </div> |
| 713 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes"> | 713 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes,workers_ai"> |
| 714 | <div class="range-block-title" data-i18n="Frequency Penalty"> | 714 | <div class="range-block-title" data-i18n="Frequency Penalty"> |
| 715 | Frequency Penalty | 715 | Frequency Penalty |
| 716 | </div> | 716 | </div> |
| @@ -723,7 +723,7 @@ | |||
| 723 | </div> | 723 | </div> |
| 724 | </div> | 724 | </div> |
| 725 | </div> | 725 | </div> |
| 726 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes"> | 726 | <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,siliconflow,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,chutes,workers_ai"> |
| 727 | <div class="range-block-title" data-i18n="Presence Penalty"> | 727 | <div class="range-block-title" data-i18n="Presence Penalty"> |
| 728 | Presence Penalty | 728 | Presence Penalty |
| 729 | </div> | 729 | </div> |
| @@ -736,7 +736,7 @@ | |||
| 736 | </div> | 736 | </div> |
| 737 | </div> | 737 | </div> |
| 738 | </div> | 738 | </div> |
| 739 | <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt"> | 739 | <div class="range-block" data-source="claude,aimlapi,openrouter,makersuite,vertexai,cohere,perplexity,electronhub,chutes,nanogpt,workers_ai"> |
| 740 | <div class="range-block-title" data-i18n="Top K"> | 740 | <div class="range-block-title" data-i18n="Top K"> |
| 741 | Top K | 741 | Top K |
| 742 | </div> | 742 | </div> |
| @@ -749,7 +749,7 @@ | |||
| 749 | </div> | 749 | </div> |
| 750 | </div> | 750 | </div> |
| 751 | </div> | 751 | </div> |
| 752 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai"> | 752 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,siliconflow,minimax,electronhub,chutes,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai,zai,workers_ai"> |
| 753 | <div class="range-block-title" data-i18n="Top P"> | 753 | <div class="range-block-title" data-i18n="Top P"> |
| 754 | Top P | 754 | Top P |
| 755 | </div> | 755 | </div> |
| @@ -762,7 +762,7 @@ | |||
| 762 | </div> | 762 | </div> |
| 763 | </div> | 763 | </div> |
| 764 | </div> | 764 | </div> |
| 765 | <div class="range-block" data-source="openrouter,nanogpt,chutes"> | 765 | <div class="range-block" data-source="openrouter,nanogpt,chutes,workers_ai"> |
| 766 | <div class="range-block-title" data-i18n="Repetition Penalty"> | 766 | <div class="range-block-title" data-i18n="Repetition Penalty"> |
| 767 | Repetition Penalty | 767 | Repetition Penalty |
| 768 | </div> | 768 | </div> |
| @@ -986,7 +986,7 @@ | |||
| 986 | </div> | 986 | </div> |
| 987 | </div> | 987 | </div> |
| 988 | </div> | 988 | </div> |
| 989 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai"> | 989 | <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,chutes,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai,workers_ai"> |
| 990 | <div class="range-block-title justifyLeft" data-i18n="Seed"> | 990 | <div class="range-block-title justifyLeft" data-i18n="Seed"> |
| 991 | Seed | 991 | Seed |
| 992 | </div> | 992 | </div> |
| @@ -1269,8 +1269,8 @@ | |||
| 1269 | <span data-i18n="Top K">Top K</span> | 1269 | <span data-i18n="Top K">Top K</span> |
| 1270 | <div class="fa-solid fa-circle-info opacity50p" title="Top K sets a maximum amount of top tokens that can be chosen from. 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> | 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 | </small> | 1271 | </small> |
| 1272 | <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="200" step="1"> | 1272 | <input class="neo-range-slider" type="range" id="top_k_textgenerationwebui" name="volume" min="-1" max="500" step="1"> |
| 1273 | <input class="neo-range-input" type="number" min="-1" max="200" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui"> | 1273 | <input class="neo-range-input" type="number" min="-1" max="500" step="1" data-for="top_k_textgenerationwebui" id="top_k_counter_textgenerationwebui"> |
| 1274 | </div> | 1274 | </div> |
| 1275 | <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> | 1275 | <div data-tg-samplers="top_p" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> |
| 1276 | <small> | 1276 | <small> |
| @@ -1400,7 +1400,7 @@ | |||
| 1400 | <input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="max_tokens_second_textgenerationwebui" id="max_tokens_second_counter_textgenerationwebui"> | 1400 | <input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="max_tokens_second_textgenerationwebui" id="max_tokens_second_counter_textgenerationwebui"> |
| 1401 | </div> | 1401 | </div> |
| 1402 | 1402 | ||
| 1403 | <div data-tg-type="koboldcpp, llamacpp, tabby" id="adaptive_p_block" class="wide100p"> | 1403 | <div data-tg-type="koboldcpp, llamacpp, ooba, tabby" id="adaptive_p_block" class="wide100p"> |
| 1404 | <h4 class="wide100p textAlignCenter"> | 1404 | <h4 class="wide100p textAlignCenter"> |
| 1405 | <label data-i18n="Adaptive-P">Adaptive-P</label> | 1405 | <label data-i18n="Adaptive-P">Adaptive-P</label> |
| 1406 | <a href="https://github.com/MrJackSpade/adaptive-p-docs" target="_blank"> | 1406 | <a href="https://github.com/MrJackSpade/adaptive-p-docs" target="_blank"> |
| @@ -1831,6 +1831,7 @@ | |||
| 1831 | <div data-name="tfs" draggable="true"><span>Tail Free Sampling</span><small></small></div> | 1831 | <div data-name="tfs" draggable="true"><span>Tail Free Sampling</span><small></small></div> |
| 1832 | <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div> | 1832 | <div data-name="top_a" draggable="true"><span>Top A</span><small></small></div> |
| 1833 | <div data-name="min_p" draggable="true"><span>Min P</span><small></small></div> | 1833 | <div data-name="min_p" draggable="true"><span>Min P</span><small></small></div> |
| 1834 | <div data-name="adaptive_p" draggable="true"><span>Adaptive-P</span><small></small></div> | ||
| 1834 | <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div> | 1835 | <div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div> |
| 1835 | <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div> | 1836 | <div data-name="xtc" draggable="true"><span>XTC</span><small></small></div> |
| 1836 | <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div> | 1837 | <div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div> |
| @@ -1996,7 +1997,7 @@ | |||
| 1996 | </b> | 1997 | </b> |
| 1997 | </div> | 1998 | </div> |
| 1998 | </div> | 1999 | </div> |
| 1999 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai,nanogpt"> | 2000 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,siliconflow,minimax,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,chutes,azure_openai,zai,nanogpt,workers_ai"> |
| 2000 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> | 2001 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 2001 | <input id="openai_function_calling" type="checkbox" /> | 2002 | <input id="openai_function_calling" type="checkbox" /> |
| 2002 | <span data-i18n="Enable function calling">Enable function calling</span> | 2003 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -2010,8 +2011,22 @@ | |||
| 2010 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> | 2011 | <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span> |
| 2011 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong> | 2012 | <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong> |
| 2012 | </div> | 2013 | </div> |
| 2014 | <div id="tool_call_recurse_limit_block" class="range-block wide100p"> | ||
| 2015 | <div class="range-block-title"> | ||
| 2016 | <small data-i18n="Tool Call Recurse Limit">Tool Call Recurse Limit</small> | ||
| 2017 | <div class="fa-solid fa-circle-info opacity50p margin5" data-i18n="[title]Maximum number of tool call recursion steps per generation." title="Maximum number of tool call recursion steps per generation."></div> | ||
| 2018 | </div> | ||
| 2019 | <div class="range-block-range-and-counter"> | ||
| 2020 | <div class="range-block-range"> | ||
| 2021 | <input type="range" id="tool_call_recurse_limit" name="tool_call_recurse_limit" min="1" max="50" step="1"> | ||
| 2022 | </div> | ||
| 2023 | <div class="range-block-counter"> | ||
| 2024 | <input type="number" min="1" max="50" step="1" data-for="tool_call_recurse_limit" id="tool_call_recurse_limit_counter"> | ||
| 2025 | </div> | ||
| 2026 | </div> | ||
| 2027 | </div> | ||
| 2013 | </div> | 2028 | </div> |
| 2014 | <div class="range-block" data-source="openrouter"> | 2029 | <div class="range-block" data-source="openrouter,custom"> |
| 2015 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10"> | 2030 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10"> |
| 2016 | <div class="flex-container oneline-dropdown"> | 2031 | <div class="flex-container oneline-dropdown"> |
| 2017 | <label for="tool_reasoning_mode" data-i18n="Interleaved Thinking"> | 2032 | <label for="tool_reasoning_mode" data-i18n="Interleaved Thinking"> |
| @@ -2035,7 +2050,7 @@ | |||
| 2035 | </em> | 2050 | </em> |
| 2036 | </div> | 2051 | </div> |
| 2037 | </div> | 2052 | </div> |
| 2038 | <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow,chutes"> | 2053 | <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai,siliconflow,chutes,workers_ai"> |
| 2039 | <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand"> | 2054 | <label for="openai_media_inlining" class="checkbox_label flexWrap widthFreeExpand"> |
| 2040 | <input id="openai_media_inlining" type="checkbox" /> | 2055 | <input id="openai_media_inlining" type="checkbox" /> |
| 2041 | <span data-i18n="Send inline media">Send inline media</span> | 2056 | <span data-i18n="Send inline media">Send inline media</span> |
| @@ -2059,7 +2074,7 @@ | |||
| 2059 | <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong> | 2074 | <strong data-source="makersuite,vertexai" data-i18n="video_inlining_hint_4">Videos must be less than 20 MB and under 1 minute long.</strong> |
| 2060 | <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong> | 2075 | <strong data-source="makersuite,vertexai" data-i18n="audio_inlining_hint_2">Audio must be less than 20 MB.</strong> |
| 2061 | </div> | 2076 | </div> |
| 2062 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes,makersuite,vertexai"> | 2077 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai,siliconflow,chutes,makersuite,vertexai,workers_ai"> |
| 2063 | <div class="flex-container oneline-dropdown"> | 2078 | <div class="flex-container oneline-dropdown"> |
| 2064 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> | 2079 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> |
| 2065 | Inline Image Quality | 2080 | Inline Image Quality |
| @@ -2120,7 +2135,7 @@ | |||
| 2120 | </div> | 2135 | </div> |
| 2121 | </div> | 2136 | </div> |
| 2122 | </div> | 2137 | </div> |
| 2123 | <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,chutes,azure_openai,nanogpt,zai"> | 2138 | <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,chutes,azure_openai,nanogpt,zai,workers_ai"> |
| 2124 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> | 2139 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 2125 | <input id="openai_show_thoughts" type="checkbox" /> | 2140 | <input id="openai_show_thoughts" type="checkbox" /> |
| 2126 | <span data-i18n="Request model reasoning">Request model reasoning</span> | 2141 | <span data-i18n="Request model reasoning">Request model reasoning</span> |
| @@ -2129,12 +2144,12 @@ | |||
| 2129 | <span data-i18n="Allows the model to return its thinking process."> | 2144 | <span data-i18n="Allows the model to return its thinking process."> |
| 2130 | Allows the model to return its thinking process. | 2145 | Allows the model to return its thinking process. |
| 2131 | </span> | 2146 | </span> |
| 2132 | <strong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai,moonshot,openrouter"> | 2147 | <strong data-i18n="This setting affects visibility only." data-source-mode="except" data-source="zai,moonshot,openrouter,deepseek"> |
| 2133 | This setting affects visibility only. | 2148 | This setting affects visibility only. |
| 2134 | </strong> | 2149 | </strong> |
| 2135 | </div> | 2150 | </div> |
| 2136 | </div> | 2151 | </div> |
| 2137 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai,chutes,nanogpt"> | 2152 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai,chutes,nanogpt,deepseek"> |
| 2138 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> | 2153 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> |
| 2139 | <label for="openai_reasoning_effort"> | 2154 | <label for="openai_reasoning_effort"> |
| 2140 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> | 2155 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| @@ -2445,7 +2460,8 @@ | |||
| 2445 | <small data-i18n="Click Authorize below or get the key from"> | 2460 | <small data-i18n="Click Authorize below or get the key from"> |
| 2446 | Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. | 2461 | Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. |
| 2447 | <br> | 2462 | <br> |
| 2448 | <a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a> | 2463 | <a href="https://openrouter.ai/settings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a> |
| 2464 | <span class="openrouter_credits_display marginLeft5"></span> | ||
| 2449 | </div> | 2465 | </div> |
| 2450 | <div class="flex-container"> | 2466 | <div class="flex-container"> |
| 2451 | <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> | 2467 | <input id="api_key_openrouter-tg" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> |
| @@ -2894,6 +2910,7 @@ | |||
| 2894 | <option value="azure_openai">Azure OpenAI</option> | 2910 | <option value="azure_openai">Azure OpenAI</option> |
| 2895 | <option value="chutes">Chutes</option> | 2911 | <option value="chutes">Chutes</option> |
| 2896 | <option value="claude">Claude</option> | 2912 | <option value="claude">Claude</option> |
| 2913 | <option value="workers_ai">Cloudflare Workers AI</option> | ||
| 2897 | <option value="cohere">Cohere</option> | 2914 | <option value="cohere">Cohere</option> |
| 2898 | <!-- Temporarily disabled. --> | 2915 | <!-- Temporarily disabled. --> |
| 2899 | <!-- <option value="cometapi">CometAPI</option> --> | 2916 | <!-- <option value="cometapi">CometAPI</option> --> |
| @@ -2904,6 +2921,7 @@ | |||
| 2904 | <option value="makersuite">Google AI Studio</option> | 2921 | <option value="makersuite">Google AI Studio</option> |
| 2905 | <option value="vertexai">Google Vertex AI</option> | 2922 | <option value="vertexai">Google Vertex AI</option> |
| 2906 | <option value="mistralai">MistralAI</option> | 2923 | <option value="mistralai">MistralAI</option> |
| 2924 | <option value="minimax">MiniMax</option> | ||
| 2907 | <option value="moonshot">Moonshot AI</option> | 2925 | <option value="moonshot">Moonshot AI</option> |
| 2908 | <option value="nanogpt">NanoGPT</option> | 2926 | <option value="nanogpt">NanoGPT</option> |
| 2909 | <option value="openrouter">OpenRouter</option> | 2927 | <option value="openrouter">OpenRouter</option> |
| @@ -3028,8 +3046,17 @@ | |||
| 3028 | <div> | 3046 | <div> |
| 3029 | <h4 data-i18n="OpenAI Model">OpenAI Model</h4> | 3047 | <h4 data-i18n="OpenAI Model">OpenAI Model</h4> |
| 3030 | <select id="model_openai_select"> | 3048 | <select id="model_openai_select"> |
| 3049 | <optgroup label="GPT-5.5"> | ||
| 3050 | <option value="gpt-5.5">gpt-5.5</option> | ||
| 3051 | <option value="gpt-5.5-2026-04-23">gpt-5.5-2026-04-23</option> | ||
| 3052 | </optgroup> | ||
| 3031 | <optgroup label="GPT-5.4"> | 3053 | <optgroup label="GPT-5.4"> |
| 3032 | <option value="gpt-5.4">gpt-5.4</option> | 3054 | <option value="gpt-5.4">gpt-5.4</option> |
| 3055 | <option value="gpt-5.4-2026-03-05">gpt-5.4-2026-03-05</option> | ||
| 3056 | <option value="gpt-5.4-mini">gpt-5.4-mini</option> | ||
| 3057 | <option value="gpt-5.4-mini-2026-03-17">gpt-5.4-mini-2026-03-17</option> | ||
| 3058 | <option value="gpt-5.4-nano">gpt-5.4-nano</option> | ||
| 3059 | <option value="gpt-5.4-nano-2026-03-17">gpt-5.4-nano-2026-03-17</option> | ||
| 3033 | </optgroup> | 3060 | </optgroup> |
| 3034 | <optgroup label="GPT-5.3"> | 3061 | <optgroup label="GPT-5.3"> |
| 3035 | <option value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option> | 3062 | <option value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option> |
| @@ -3143,6 +3170,7 @@ | |||
| 3143 | <h4 data-i18n="Claude Model">Claude Model</h4> | 3170 | <h4 data-i18n="Claude Model">Claude Model</h4> |
| 3144 | <select id="model_claude_select"> | 3171 | <select id="model_claude_select"> |
| 3145 | <optgroup label="Versions"> | 3172 | <optgroup label="Versions"> |
| 3173 | <option value="claude-opus-4-7">claude-opus-4-7</option> | ||
| 3146 | <option value="claude-opus-4-6">claude-opus-4-6</option> | 3174 | <option value="claude-opus-4-6">claude-opus-4-6</option> |
| 3147 | <option value="claude-opus-4-5">claude-opus-4-5</option> | 3175 | <option value="claude-opus-4-5">claude-opus-4-5</option> |
| 3148 | <option value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option> | 3176 | <option value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option> |
| @@ -3176,7 +3204,8 @@ | |||
| 3176 | <small data-i18n="Click Authorize below or get the key from"> | 3204 | <small data-i18n="Click Authorize below or get the key from"> |
| 3177 | Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. | 3205 | Click "Authorize" below or get the key from </small> <a target="_blank" href="https://openrouter.ai/keys/">OpenRouter</a>. |
| 3178 | <br> | 3206 | <br> |
| 3179 | <a href="https://openrouter.ai/account" target="_blank" data-i18n="View Remaining Credits">View Remaining Credits</a> | 3207 | <a href="https://openrouter.ai/settings/credits" target="_blank" class="openrouter_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a> |
| 3208 | <span class="openrouter_credits_display marginLeft5"></span> | ||
| 3180 | </div> | 3209 | </div> |
| 3181 | <div class="flex-container"> | 3210 | <div class="flex-container"> |
| 3182 | <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> | 3211 | <input id="api_key_openrouter" name="api_key_openrouter" class="text_pole flex1 api_key_openrouter" value="" type="text" autocomplete="off"> |
| @@ -3195,36 +3224,6 @@ | |||
| 3195 | <input id="openrouter_use_fallback" type="checkbox" /> | 3224 | <input id="openrouter_use_fallback" type="checkbox" /> |
| 3196 | <span data-i18n="Allow fallback models">Allow fallback models</span> | 3225 | <span data-i18n="Allow fallback models">Allow fallback models</span> |
| 3197 | </label> | 3226 | </label> |
| 3198 | <div class="marginTopBot5"> | ||
| 3199 | <div class="inline-drawer wide100p"> | ||
| 3200 | <div class="inline-drawer-toggle inline-drawer-header"> | ||
| 3201 | <b data-i18n="OpenRouter Model Sorting">OpenRouter Model Sorting</b> | ||
| 3202 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | ||
| 3203 | </div> | ||
| 3204 | <div class="inline-drawer-content m-b-1"> | ||
| 3205 | <div class="marginTopBot5"> | ||
| 3206 | <label for="openrouter_sort_models" class="checkbox_label"> | ||
| 3207 | <select id="openrouter_sort_models"> | ||
| 3208 | <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option> | ||
| 3209 | <option data-i18n="Price" value="pricing.prompt">Price (cheapest)</option> | ||
| 3210 | <option data-i18n="Context Size" value="context_length">Context Size</option> | ||
| 3211 | </select> | ||
| 3212 | </label> | ||
| 3213 | </div> | ||
| 3214 | <div class="marginTopBot5"> | ||
| 3215 | <label for="openrouter_group_models" class="checkbox_label"> | ||
| 3216 | <input id="openrouter_group_models" type="checkbox" /> | ||
| 3217 | <span data-i18n="Group by vendors">Group by vendors</span> | ||
| 3218 | </label> | ||
| 3219 | <div class="toggle-description justifyLeft wide100p"> | ||
| 3220 | <span data-i18n="Group by vendors Description"> | ||
| 3221 | Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting. | ||
| 3222 | </span> | ||
| 3223 | </div> | ||
| 3224 | </div> | ||
| 3225 | </div> | ||
| 3226 | </div> | ||
| 3227 | </div> | ||
| 3228 | <div> | 3227 | <div> |
| 3229 | <h4> | 3228 | <h4> |
| 3230 | <span data-i18n="Model Providers">Model Providers</span> | 3229 | <span data-i18n="Model Providers">Model Providers</span> |
| @@ -3344,6 +3343,8 @@ | |||
| 3344 | <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option> | 3343 | <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option> |
| 3345 | </optgroup> | 3344 | </optgroup> |
| 3346 | <optgroup label="Gemma"> | 3345 | <optgroup label="Gemma"> |
| 3346 | <option value="gemma-4-31b-it">gemma-4-31b-it</option> | ||
| 3347 | <option value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option> | ||
| 3347 | <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option> | 3348 | <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option> |
| 3348 | <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option> | 3349 | <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option> |
| 3349 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> | 3350 | <option value="gemma-3-27b-it">gemma-3-27b-it</option> |
| @@ -3566,6 +3567,32 @@ | |||
| 3566 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | 3567 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3567 | </select> | 3568 | </select> |
| 3568 | </div> | 3569 | </div> |
| 3570 | <div id="minimax_form" data-source="minimax"> | ||
| 3571 | <h4 data-i18n="MiniMax API Key">MiniMax API Key</h4> | ||
| 3572 | <div class="flex-container"> | ||
| 3573 | <input id="api_key_minimax" name="api_key_minimax" class="text_pole flex1" value="" type="text" autocomplete="off"> | ||
| 3574 | <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_minimax"></div> | ||
| 3575 | </div> | ||
| 3576 | <div data-for="api_key_minimax" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> | ||
| 3577 | For privacy reasons, your API key will be hidden after you click 'Connect'. | ||
| 3578 | </div> | ||
| 3579 | <h4 data-i18n="MiniMax Endpoint">MiniMax Endpoint</h4> | ||
| 3580 | <select id="minimax_endpoint"> | ||
| 3581 | <option value="global" data-i18n="Global (minimax.io)">Global (minimax.io)</option> | ||
| 3582 | <option value="cn" data-i18n="China (minimaxi.com)">China (minimaxi.com)</option> | ||
| 3583 | </select> | ||
| 3584 | <h4 data-i18n="MiniMax Model">MiniMax Model</h4> | ||
| 3585 | <select id="model_minimax_select"> | ||
| 3586 | <option value="MiniMax-M2.7">MiniMax-M2.7</option> | ||
| 3587 | <option value="MiniMax-M2.7-highspeed">MiniMax-M2.7-highspeed</option> | ||
| 3588 | <option value="MiniMax-M2.5">MiniMax-M2.5</option> | ||
| 3589 | <option value="MiniMax-M2.5-highspeed">MiniMax-M2.5-highspeed</option> | ||
| 3590 | <option value="MiniMax-M2.1">MiniMax-M2.1</option> | ||
| 3591 | <option value="MiniMax-M2.1-highspeed">MiniMax-M2.1-highspeed</option> | ||
| 3592 | <option value="MiniMax-M2">MiniMax-M2</option> | ||
| 3593 | <option value="M2-her">M2-her</option> | ||
| 3594 | </select> | ||
| 3595 | </div> | ||
| 3569 | <div id="electronhub_form" data-source="electronhub"> | 3596 | <div id="electronhub_form" data-source="electronhub"> |
| 3570 | <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4> | 3597 | <h4 data-i18n="Electron Hub API Key">Electron Hub API Key</h4> |
| 3571 | <div> | 3598 | <div> |
| @@ -3584,37 +3611,6 @@ | |||
| 3584 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | 3611 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3585 | </select> | 3612 | </select> |
| 3586 | </div> | 3613 | </div> |
| 3587 | <div class="marginTopBot5"> | ||
| 3588 | <div class="inline-drawer wide100p"> | ||
| 3589 | <div class="inline-drawer-toggle inline-drawer-header"> | ||
| 3590 | <b data-i18n="Electron Hub Model Sorting">Electron Hub Model Sorting</b> | ||
| 3591 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | ||
| 3592 | </div> | ||
| 3593 | <div class="inline-drawer-content m-b-1"> | ||
| 3594 | <div class="marginTopBot5"> | ||
| 3595 | <label for="electronhub_sort_models" class="checkbox_label"> | ||
| 3596 | <select id="electronhub_sort_models"> | ||
| 3597 | <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option> | ||
| 3598 | <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option> | ||
| 3599 | <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option> | ||
| 3600 | <option data-i18n="Context Size" value="context_length">Context Size</option> | ||
| 3601 | </select> | ||
| 3602 | </label> | ||
| 3603 | </div> | ||
| 3604 | <div class="marginTopBot5"> | ||
| 3605 | <label for="electronhub_group_models" class="checkbox_label"> | ||
| 3606 | <input id="electronhub_group_models" type="checkbox" /> | ||
| 3607 | <span data-i18n="Group by vendors">Group by vendors</span> | ||
| 3608 | </label> | ||
| 3609 | <div class="toggle-description justifyLeft wide100p"> | ||
| 3610 | <span data-i18n="Group by vendors Description"> | ||
| 3611 | Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting. | ||
| 3612 | </span> | ||
| 3613 | </div> | ||
| 3614 | </div> | ||
| 3615 | </div> | ||
| 3616 | </div> | ||
| 3617 | </div> | ||
| 3618 | </div> | 3614 | </div> |
| 3619 | <div id="chutes_form" data-source="chutes"> | 3615 | <div id="chutes_form" data-source="chutes"> |
| 3620 | <h4 data-i18n="Chutes API Key">Chutes API Key</h4> | 3616 | <h4 data-i18n="Chutes API Key">Chutes API Key</h4> |
| @@ -3634,29 +3630,11 @@ | |||
| 3634 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | 3630 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3635 | </select> | 3631 | </select> |
| 3636 | </div> | 3632 | </div> |
| 3637 | <div class="marginTopBot5"> | ||
| 3638 | <div class="inline-drawer wide100p"> | ||
| 3639 | <div class="inline-drawer-toggle inline-drawer-header"> | ||
| 3640 | <b data-i18n="Chutes Model Sorting">Chutes Model Sorting</b> | ||
| 3641 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | ||
| 3642 | </div> | ||
| 3643 | <div class="inline-drawer-content m-b-1"> | ||
| 3644 | <div class="marginTopBot5"> | ||
| 3645 | <label for="chutes_sort_models" class="checkbox_label"> | ||
| 3646 | <select id="chutes_sort_models"> | ||
| 3647 | <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option> | ||
| 3648 | <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option> | ||
| 3649 | <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option> | ||
| 3650 | <option data-i18n="Context Size" value="context_length">Context Size</option> | ||
| 3651 | </select> | ||
| 3652 | </label> | ||
| 3653 | </div> | ||
| 3654 | </div> | ||
| 3655 | </div> | ||
| 3656 | </div> | ||
| 3657 | </div> | 3633 | </div> |
| 3658 | <div id="nanogpt_form" data-source="nanogpt"> | 3634 | <div id="nanogpt_form" data-source="nanogpt"> |
| 3659 | <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4> | 3635 | <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4> |
| 3636 | <a href="https://nano-gpt.com/balance" target="_blank" rel="noopener noreferrer" class="nanogpt_view_credits" data-i18n="View Remaining Credits">View Remaining Credits</a> | ||
| 3637 | <span class="nanogpt_credits_display marginLeft5"></span> | ||
| 3660 | <div class="flex-container"> | 3638 | <div class="flex-container"> |
| 3661 | <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off"> | 3639 | <input id="api_key_nanogpt" name="api_key_nanogpt" class="text_pole flex1" value="" type="text" autocomplete="off"> |
| 3662 | <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_nanogpt"></div> | 3640 | <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_nanogpt"></div> |
| @@ -3670,6 +3648,39 @@ | |||
| 3670 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | 3648 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3671 | </select> | 3649 | </select> |
| 3672 | </div> | 3650 | </div> |
| 3651 | <div> | ||
| 3652 | <h4> | ||
| 3653 | <span data-i18n="Model Providers">Model Providers</span> | ||
| 3654 | <i id="nanogpt_provider_warning" class="fa-solid fa-circle-exclamation displayNone" title="Deselect inapplicable provider(s) or select an applicable provider to avoid a 404 error."></i> | ||
| 3655 | </h4> | ||
| 3656 | <select id="nanogpt_provider"> | ||
| 3657 | <option value="" data-i18n="Auto">Auto</option> | ||
| 3658 | </select> | ||
| 3659 | <label class="checkbox_label marginTopBot5" for="nanogpt_payg_override" data-i18n="[title]Force NanoGPT pay-as-you-go billing for this request." title="Force NanoGPT pay-as-you-go billing for this request."> | ||
| 3660 | <input id="nanogpt_payg_override" type="checkbox" /> | ||
| 3661 | <span data-i18n="Use pay-as-you-go billing">Use pay-as-you-go billing</span> | ||
| 3662 | </label> | ||
| 3663 | </div> | ||
| 3664 | </div> | ||
| 3665 | <div id="workers_ai_form" data-source="workers_ai"> | ||
| 3666 | <h4><a href="https://dash.cloudflare.com/?to=/:account/ai/workers-ai/api-quick-start" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Workers AI API Key">Cloudflare Workers AI API Key</a></h4> | ||
| 3667 | <div class="flex-container"> | ||
| 3668 | <input id="api_key_workers_ai" name="api_key_workers_ai" class="text_pole flex1" value="" type="text" autocomplete="off"> | ||
| 3669 | <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_workers_ai"></div> | ||
| 3670 | </div> | ||
| 3671 | <div data-for="api_key_workers_ai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> | ||
| 3672 | For privacy reasons, your API key will be hidden after you click 'Connect'. | ||
| 3673 | </div> | ||
| 3674 | <h4><a href="https://dash.cloudflare.com/?to=/:account/workers-and-pages" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Account ID">Cloudflare Account ID</a></h4> | ||
| 3675 | <div class="flex-container"> | ||
| 3676 | <input id="workers_ai_account_id" class="text_pole flex1" value="" type="text" autocomplete="off" placeholder="e.g. 023e105f4ecef8ad9ca31a8372d0c353" data-i18n="[placeholder]e.g. 023e105f4ecef8ad9ca31a8372d0c353"> | ||
| 3677 | </div> | ||
| 3678 | <div> | ||
| 3679 | <h4 data-i18n="Workers AI Model">Workers AI Model</h4> | ||
| 3680 | <select id="model_workers_ai_select"> | ||
| 3681 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | ||
| 3682 | </select> | ||
| 3683 | </div> | ||
| 3673 | </div> | 3684 | </div> |
| 3674 | <div id="deepseek_form" data-source="deepseek"> | 3685 | <div id="deepseek_form" data-source="deepseek"> |
| 3675 | <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4> | 3686 | <h4 data-i18n="DeepSeek API Key">DeepSeek API Key</h4> |
| @@ -3683,9 +3694,7 @@ | |||
| 3683 | <div> | 3694 | <div> |
| 3684 | <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4> | 3695 | <h4 data-i18n="DeepSeek Model">DeepSeek Model</h4> |
| 3685 | <select id="model_deepseek_select"> | 3696 | <select id="model_deepseek_select"> |
| 3686 | <option value="deepseek-chat">deepseek-chat</option> | 3697 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3687 | <option value="deepseek-coder">deepseek-coder</option> | ||
| 3688 | <option value="deepseek-reasoner">deepseek-reasoner</option> | ||
| 3689 | </select> | 3698 | </select> |
| 3690 | </div> | 3699 | </div> |
| 3691 | </div> | 3700 | </div> |
| @@ -3924,6 +3933,7 @@ | |||
| 3924 | <h4 data-i18n="Z.AI Model">Z.AI Model</h4> | 3933 | <h4 data-i18n="Z.AI Model">Z.AI Model</h4> |
| 3925 | <select id="model_zai_select"> | 3934 | <select id="model_zai_select"> |
| 3926 | <option value="glm-5-turbo">glm-5-turbo</option> | 3935 | <option value="glm-5-turbo">glm-5-turbo</option> |
| 3936 | <option value="glm-5v-turbo">glm-5v-turbo</option> | ||
| 3927 | <option value="glm-5.1">glm-5.1</option> | 3937 | <option value="glm-5.1">glm-5.1</option> |
| 3928 | <option value="glm-5">glm-5</option> | 3938 | <option value="glm-5">glm-5</option> |
| 3929 | <option value="glm-4.7">glm-4.7</option> | 3939 | <option value="glm-4.7">glm-4.7</option> |
| @@ -3986,6 +3996,37 @@ | |||
| 3986 | <small data-i18n="The underlying model of your deployment. This is detected automatically when you connect.">The underlying model of your deployment. This is detected automatically when you connect.</small> | 3996 | <small data-i18n="The underlying model of your deployment. This is detected automatically when you connect.">The underlying model of your deployment. This is detected automatically when you connect.</small> |
| 3987 | </div> | 3997 | </div> |
| 3988 | </div> | 3998 | </div> |
| 3999 | <div id="model_sorting_form" data-source="openrouter,chutes,electronhub,nanogpt,aimlapi"> | ||
| 4000 | <div class="inline-drawer wide100p"> | ||
| 4001 | <div class="inline-drawer-toggle inline-drawer-header"> | ||
| 4002 | <b data-i18n="Model Sorting">Model Sorting</b> | ||
| 4003 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | ||
| 4004 | </div> | ||
| 4005 | <div class="inline-drawer-content m-b-1"> | ||
| 4006 | <div class="marginTopBot5"> | ||
| 4007 | <label for="cc_sort_models" class="checkbox_label"> | ||
| 4008 | <select id="cc_sort_models"> | ||
| 4009 | <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option> | ||
| 4010 | <option data-i18n="Prompt Price (cheapest)" value="pricing.prompt">Prompt Price (cheapest)</option> | ||
| 4011 | <option data-i18n="Completion Price (cheapest)" value="pricing.completion">Completion Price (cheapest)</option> | ||
| 4012 | <option data-i18n="Context Size" value="context_length">Context Size</option> | ||
| 4013 | </select> | ||
| 4014 | </label> | ||
| 4015 | </div> | ||
| 4016 | <div class="marginTopBot5"> | ||
| 4017 | <label for="cc_group_models" class="checkbox_label"> | ||
| 4018 | <input id="cc_group_models" type="checkbox" /> | ||
| 4019 | <span data-i18n="Group by vendors">Group by vendors</span> | ||
| 4020 | </label> | ||
| 4021 | <div class="toggle-description justifyLeft wide100p"> | ||
| 4022 | <span data-i18n="Group by vendors Description"> | ||
| 4023 | Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting. | ||
| 4024 | </span> | ||
| 4025 | </div> | ||
| 4026 | </div> | ||
| 4027 | </div> | ||
| 4028 | </div> | ||
| 4029 | </div> | ||
| 3989 | <div id="prompt_post_processing_form"> | 4030 | <div id="prompt_post_processing_form"> |
| 3990 | <h4> | 4031 | <h4> |
| 3991 | <span data-i18n="Prompt Post-Processing"> | 4032 | <span data-i18n="Prompt Post-Processing"> |
| @@ -4365,7 +4406,7 @@ | |||
| 4365 | <small data-i18n="First User Prefix">First User Prefix</small> | 4406 | <small data-i18n="First User Prefix">First User Prefix</small> |
| 4366 | <textarea id="instruct_first_input_sequence" data-macros class="text_pole textarea_compact autoSetHeight"></textarea> | 4407 | <textarea id="instruct_first_input_sequence" data-macros class="text_pole textarea_compact autoSetHeight"></textarea> |
| 4367 | </div> | 4408 | </div> |
| 4368 | <div class="flexAuto" title="Inserted before the last User's message." data-i18n="[title]instruct_last_input_sequence"> | 4409 | <div class="flexAuto" title="Inserted before the last User's message or as a last prompt line when generating an impersonation." data-i18n="[title]instruct_last_input_sequence"> |
| 4369 | <small data-i18n="Last User Prefix">Last User Prefix</small> | 4410 | <small data-i18n="Last User Prefix">Last User Prefix</small> |
| 4370 | <textarea id="instruct_last_input_sequence" data-macros class="text_pole wide100p textarea_compact autoSetHeight"></textarea> | 4411 | <textarea id="instruct_last_input_sequence" data-macros class="text_pole wide100p textarea_compact autoSetHeight"></textarea> |
| 4371 | </div> | 4412 | </div> |
| @@ -4775,7 +4816,7 @@ | |||
| 4775 | <input type="file" id="world_import_file" accept=".json,.lorebook,.png" name="avatar" hidden> | 4816 | <input type="file" id="world_import_file" accept=".json,.lorebook,.png" name="avatar" hidden> |
| 4776 | <div id="world_create_button" class="menu_button menu_button_icon"> | 4817 | <div id="world_create_button" class="menu_button menu_button_icon"> |
| 4777 | <i class="fa-solid fa-globe"></i> | 4818 | <i class="fa-solid fa-globe"></i> |
| 4778 | <span data-i18n="New">New</span> | 4819 | <span data-i18n="Create">Create</span> |
| 4779 | </div> | 4820 | </div> |
| 4780 | <small data-i18n="or">or</small> | 4821 | <small data-i18n="or">or</small> |
| 4781 | <select id="world_editor_select"> | 4822 | <select id="world_editor_select"> |
| @@ -6733,7 +6774,7 @@ | |||
| 6733 | <small class="chat_file_size select_chat_block_filename_item"></small> | 6774 | <small class="chat_file_size select_chat_block_filename_item"></small> |
| 6734 | <small class="chat_messages_num select_chat_block_filename_item"></small> | 6775 | <small class="chat_messages_num select_chat_block_filename_item"></small> |
| 6735 | </div> | 6776 | </div> |
| 6736 | <div class="flex-container gap10px"> | 6777 | <div class="select_chat_actions flex-container gap10px"> |
| 6737 | <div title="Export JSONL chat file" data-format="jsonl" class="exportRawChatButton opacity50p hoverglow fa-solid fa-file-export" data-i18n="[title]Export JSONL chat file"></div> | 6778 | <div title="Export JSONL chat file" data-format="jsonl" class="exportRawChatButton opacity50p hoverglow fa-solid fa-file-export" data-i18n="[title]Export JSONL chat file"></div> |
| 6738 | <div title="Download chat as plain text document" data-format="txt" class="exportChatButton opacity50p hoverglow fa-solid fa-file-lines" data-i18n="[title]Download chat as plain text document"></div> | 6779 | <div title="Download chat as plain text document" data-format="txt" class="exportChatButton opacity50p hoverglow fa-solid fa-file-lines" data-i18n="[title]Download chat as plain text document"></div> |
| 6739 | <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div> | 6780 | <div title="Delete chat file" file_name="" class="PastChat_cross opacity50p hoverglow fa-solid fa-skull" data-i18n="[title]Delete chat file"></div> |
| @@ -24,6 +24,7 @@ import chalk from 'chalk'; | |||
| 24 | import yaml from 'yaml'; | 24 | import yaml from 'yaml'; |
| 25 | import * as chevrotain from 'chevrotain'; | 25 | import * as chevrotain from 'chevrotain'; |
| 26 | import { gzipSync, gzip } from 'fflate'; | 26 | import { gzipSync, gzip } from 'fflate'; |
| 27 | import { sha256 } from 'js-sha256'; | ||
| 27 | 28 | ||
| 28 | /** | 29 | /** |
| 29 | * Expose the libraries to the 'window' object. | 30 | * Expose the libraries to the 'window' object. |
| @@ -105,6 +106,7 @@ export default { | |||
| 105 | chevrotain, | 106 | chevrotain, |
| 106 | gzipSync, | 107 | gzipSync, |
| 107 | gzip, | 108 | gzip, |
| 109 | sha256, | ||
| 108 | }; | 110 | }; |
| 109 | 111 | ||
| 110 | export { | 112 | export { |
| @@ -132,4 +134,5 @@ export { | |||
| 132 | chevrotain, | 134 | chevrotain, |
| 133 | gzipSync, | 135 | gzipSync, |
| 134 | gzip, | 136 | gzip, |
| 137 | sha256, | ||
| 135 | }; | 138 | }; |
| @@ -213,7 +213,7 @@ import { | |||
| 213 | tag_import_setting, | 213 | tag_import_setting, |
| 214 | applyCharacterTagsToMessageDivs, | 214 | applyCharacterTagsToMessageDivs, |
| 215 | } from './scripts/tags.js'; | 215 | } from './scripts/tags.js'; |
| 216 | import { initSecrets, readSecretState } from './scripts/secrets.js'; | 216 | import { checkOpenRouterAuth, initSecrets, readSecretState } from './scripts/secrets.js'; |
| 217 | import { markdownExclusionExt } from './scripts/showdown-exclusion.js'; | 217 | import { markdownExclusionExt } from './scripts/showdown-exclusion.js'; |
| 218 | import { markdownUnderscoreExt } from './scripts/showdown-underscore.js'; | 218 | import { markdownUnderscoreExt } from './scripts/showdown-underscore.js'; |
| 219 | import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js'; | 219 | import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js'; |
| @@ -717,6 +717,7 @@ async function firstLoadInit() { | |||
| 717 | initLoaderOverlay.appendChild(splashMessage); | 717 | initLoaderOverlay.appendChild(splashMessage); |
| 718 | 718 | ||
| 719 | const initLoaderHandle = loader.show({ | 719 | const initLoaderHandle = loader.show({ |
| 720 | slug: 'app-init', | ||
| 720 | toastMode: loader.ToastMode.NONE, | 721 | toastMode: loader.ToastMode.NONE, |
| 721 | overlayContent: initLoaderOverlay, | 722 | overlayContent: initLoaderOverlay, |
| 722 | }); | 723 | }); |
| @@ -741,12 +742,13 @@ async function firstLoadInit() { | |||
| 741 | initKoboldSettings(); | 742 | initKoboldSettings(); |
| 742 | initNovelAISettings(); | 743 | initNovelAISettings(); |
| 743 | initSystemPrompts(); | 744 | initSystemPrompts(); |
| 744 | initExtensions(); | 745 | await initExtensions(); |
| 745 | initExtensionSlashCommands(); | 746 | initExtensionSlashCommands(); |
| 746 | ToolManager.initToolSlashCommands(); | 747 | ToolManager.initToolSlashCommands(); |
| 747 | await initPresetManager(); | 748 | await initPresetManager(); |
| 748 | await initSystemMessages(); | 749 | await initSystemMessages(); |
| 749 | await getSettings(initLoaderHandle); | 750 | await getSettings(initLoaderHandle); |
| 751 | await checkOpenRouterAuth(); | ||
| 750 | initKeyboard(); | 752 | initKeyboard(); |
| 751 | initDynamicStyles(); | 753 | initDynamicStyles(); |
| 752 | initTags(); | 754 | initTags(); |
| @@ -1910,17 +1912,32 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san | |||
| 1910 | } | 1912 | } |
| 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 | * Inserts or replaces an SVG icon adjacent to the provided message's timestamp. | 1932 | * Inserts or replaces an SVG icon adjacent to the provided message's timestamp. |
| 1914 | * | 1933 | * |
| 1915 | * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced. | 1934 | * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced. |
| 1916 | * @param {ChatMessageExtra} extra - Contains the API and model details. | 1935 | * @param {ChatMessageExtra} extra - Contains the API and model details. |
| 1917 | */ | 1936 | */ |
| 1918 | function insertSVGIcon(mes, extra) { | 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 | return; | 1941 | return; |
| 1925 | } | 1942 | } |
| 1926 | 1943 | ||
| @@ -1937,16 +1954,14 @@ function insertSVGIcon(mes, extra) { | |||
| 1937 | }; | 1954 | }; |
| 1938 | }; | 1955 | }; |
| 1939 | 1956 | ||
| 1940 | const createModelImage = (className, targetSelector, insertBefore) => { | 1957 | const insertIcon = (className, targetSelector, insertBefore) => { |
| 1941 | const image = new Image(); | 1958 | const image = createModelIcon(apiName, extra?.model); |
| 1942 | image.classList.add('icon-svg', className); | 1959 | image.classList.add(className); |
| 1943 | image.src = `/img/${modelName}.svg`; | ||
| 1944 | image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`; | ||
| 1945 | insertOrReplaceSVG(image, className, targetSelector, insertBefore); | 1960 | insertOrReplaceSVG(image, className, targetSelector, insertBefore); |
| 1946 | }; | 1961 | }; |
| 1947 | 1962 | ||
| 1948 | createModelImage('timestamp-icon', '.timestamp'); | 1963 | insertIcon('timestamp-icon', '.timestamp'); |
| 1949 | createModelImage('thinking-icon', '.mes_reasoning_header_title', true); | 1964 | insertIcon('thinking-icon', '.mes_reasoning_header_title', true); |
| 1950 | } | 1965 | } |
| 1951 | 1966 | ||
| 1952 | /** | 1967 | /** |
| @@ -2907,6 +2922,11 @@ export function substituteParamsLegacy(content, _name1, _name2, _original, _grou | |||
| 2907 | export function substituteParams(content, options = {}) { | 2922 | export function substituteParams(content, options = {}) { |
| 2908 | if (!content) return ''; | 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 | // Handle legacy signature calls to substituteParams | 2930 | // Handle legacy signature calls to substituteParams |
| 2911 | // We'll simply re-route them to a temporary legacy function. In the future, we'll remove this and cleanly build the options object ourselves. | 2931 | // We'll simply re-route them to a temporary legacy function. In the future, we'll remove this and cleanly build the options object ourselves. |
| 2912 | const isOptionsObject = options && typeof options === 'object' && !Array.isArray(options); | 2932 | const isOptionsObject = options && typeof options === 'object' && !Array.isArray(options); |
| @@ -3847,9 +3867,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst | |||
| 3847 | 3867 | ||
| 3848 | // If the prompt was given as a string, convert to a message-style object assuming user role | 3868 | // If the prompt was given as a string, convert to a message-style object assuming user role |
| 3849 | if (typeof prompt === 'string') { | 3869 | if (typeof prompt === 'string') { |
| 3850 | const message = api === 'openai' | 3870 | const message = { role: 'user', content: prompt.trim() }; |
| 3851 | ? { role: 'user', content: prompt.trim() } | ||
| 3852 | : { role: 'system', content: prompt }; | ||
| 3853 | prompt = [message]; | 3871 | prompt = [message]; |
| 3854 | } else { // checks for message-style object | 3872 | } else { // checks for message-style object |
| 3855 | if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided'); | 3873 | if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided'); |
| @@ -3876,7 +3894,12 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst | |||
| 3876 | // prepend system prompt, if provided | 3894 | // prepend system prompt, if provided |
| 3877 | if (systemPrompt) { | 3895 | if (systemPrompt) { |
| 3878 | systemPrompt = substituteParams(systemPrompt); | 3896 | systemPrompt = substituteParams(systemPrompt); |
| 3879 | systemPrompt = isInstruct ? (formatInstructModeStoryString(systemPrompt) + '\n') : systemPrompt.trim(); | 3897 | systemPrompt = isInstruct ? formatInstructModeStoryString(systemPrompt) : systemPrompt.trim(); |
| 3898 | if (isInstruct && systemPrompt.length > 0 && !systemPrompt.endsWith('\n')) { | ||
| 3899 | if (power_user.instruct.wrap && !power_user.instruct.story_string_suffix) { | ||
| 3900 | systemPrompt += '\n'; | ||
| 3901 | } | ||
| 3902 | } | ||
| 3880 | prompt.unshift({ role: 'system', content: systemPrompt }); | 3903 | prompt.unshift({ role: 'system', content: systemPrompt }); |
| 3881 | } | 3904 | } |
| 3882 | 3905 | ||
| @@ -3906,7 +3929,7 @@ export function createRawPrompt(prompt, api, instructOverride, quietToLoud, syst | |||
| 3906 | * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used. | 3929 | * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used. |
| 3907 | * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response. | 3930 | * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response. |
| 3908 | * @prop {string} [prefill] An optional prefill for the prompt. | 3931 | * @prop {string} [prefill] An optional prefill for the prompt. |
| 3909 | * @prop {object} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction. | 3932 | * @prop {JsonSchema} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction. |
| 3910 | */ | 3933 | */ |
| 3911 | 3934 | ||
| 3912 | /** | 3935 | /** |
| @@ -4018,7 +4041,7 @@ export async function generateRawData({ prompt = '', api = null, instructOverrid | |||
| 4018 | } | 4041 | } |
| 4019 | 4042 | ||
| 4020 | if (jsonSchema) { | 4043 | if (jsonSchema) { |
| 4021 | return extractJsonFromData(data, { mainApi: api }); | 4044 | return extractJsonFromData(data, { mainApi: api, returnInvalidJson: jsonSchema.returnInvalid }); |
| 4022 | } | 4045 | } |
| 4023 | 4046 | ||
| 4024 | return data; | 4047 | return data; |
| @@ -4181,6 +4204,7 @@ function removeLastMessage() { | |||
| 4181 | * @property {object} value JSON schema value. | 4204 | * @property {object} value JSON schema value. |
| 4182 | * @property {string} [description] Description of the schema. | 4205 | * @property {string} [description] Description of the schema. |
| 4183 | * @property {boolean} [strict] If true, the schema will be used in strict mode, meaning that only the fields defined in the schema will be allowed. | 4206 | * @property {boolean} [strict] If true, the schema will be used in strict mode, meaning that only the fields defined in the schema will be allowed. |
| 4207 | * @property {boolean} [returnInvalid] If true, a string that can't be parsed as a JSON will be returned as is, instead of an empty object. | ||
| 4184 | * | 4208 | * |
| 4185 | * @typedef {object} GenerateOptions | 4209 | * @typedef {object} GenerateOptions |
| 4186 | * @property {boolean} [automatic_trigger] If the generation was triggered automatically (e.g. group auto mode). | 4210 | * @property {boolean} [automatic_trigger] If the generation was triggered automatically (e.g. group auto mode). |
| @@ -4267,7 +4291,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4267 | if (selected_group && !is_group_generating) { | 4291 | if (selected_group && !is_group_generating) { |
| 4268 | if (!dryRun) { | 4292 | if (!dryRun) { |
| 4269 | // Returns the promise that generateGroupWrapper returns; resolves when generation is done | 4293 | // Returns the promise that generateGroupWrapper returns; resolves when generation is done |
| 4270 | return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage }); | 4294 | return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, jsonSchema }); |
| 4271 | } | 4295 | } |
| 4272 | 4296 | ||
| 4273 | const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index])); | 4297 | const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index])); |
| @@ -4449,18 +4473,24 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4449 | for (let i = coreChat.length - 1; i >= 0; i--) { | 4473 | for (let i = coreChat.length - 1; i >= 0; i--) { |
| 4450 | const depth = coreChat.length - i - (isContinue ? 2 : 1); | 4474 | const depth = coreChat.length - i - (isContinue ? 2 : 1); |
| 4451 | const isPrefix = isContinue && i === coreChat.length - 1; | 4475 | const isPrefix = isContinue && i === coreChat.length - 1; |
| 4476 | |||
| 4477 | // In group chats, only include reasoning from the currently generating character | ||
| 4478 | const isOtherGroupMember = selected_group && coreChat[i].name !== name2; | ||
| 4479 | |||
| 4452 | coreChat[i] = { | 4480 | coreChat[i] = { |
| 4453 | ...coreChat[i], | 4481 | ...coreChat[i], |
| 4454 | mes: promptReasoning.addToMessage( | 4482 | mes: isOtherGroupMember |
| 4455 | coreChat[i].mes, | 4483 | ? coreChat[i].mes |
| 4456 | getRegexedString( | 4484 | : promptReasoning.addToMessage( |
| 4457 | String(coreChat[i].extra?.reasoning ?? ''), | 4485 | coreChat[i].mes, |
| 4458 | regex_placement.REASONING, | 4486 | getRegexedString( |
| 4459 | { isPrompt: true, depth: depth }, | 4487 | String(coreChat[i].extra?.reasoning ?? ''), |
| 4488 | regex_placement.REASONING, | ||
| 4489 | { isPrompt: true, depth: depth }, | ||
| 4490 | ), | ||
| 4491 | isPrefix, | ||
| 4492 | coreChat[i].extra?.reasoning_duration, | ||
| 4460 | ), | 4493 | ), |
| 4461 | isPrefix, | ||
| 4462 | coreChat[i].extra?.reasoning_duration, | ||
| 4463 | ), | ||
| 4464 | }; | 4494 | }; |
| 4465 | if (promptReasoning.isLimitReached()) { | 4495 | if (promptReasoning.isLimitReached()) { |
| 4466 | break; | 4496 | break; |
| @@ -4697,7 +4727,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4697 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST); | 4727 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST); |
| 4698 | } | 4728 | } |
| 4699 | 4729 | ||
| 4700 | if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct) { | 4730 | if (lastUserMessageIndex >= 0 && j === lastUserMessageIndex && isInstruct && !isImpersonate) { |
| 4701 | // Reformat with the last input sequence (if any) | 4731 | // Reformat with the last input sequence (if any) |
| 4702 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST); | 4732 | chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST); |
| 4703 | } | 4733 | } |
| @@ -5301,7 +5331,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 5301 | streamingProcessor.firstMessageText = ''; | 5331 | streamingProcessor.firstMessageText = ''; |
| 5302 | } | 5332 | } |
| 5303 | 5333 | ||
| 5304 | streamingProcessor.generator = await sendStreamingRequest(type, generate_data); | 5334 | streamingProcessor.generator = await sendStreamingRequest(type, generate_data, { jsonSchema }); |
| 5305 | 5335 | ||
| 5306 | hideSwipeButtons(); | 5336 | hideSwipeButtons(); |
| 5307 | let getMessage = await streamingProcessor.generate(); | 5337 | let getMessage = await streamingProcessor.generate(); |
| @@ -5390,7 +5420,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 5390 | 5420 | ||
| 5391 | if (jsonSchema) { | 5421 | if (jsonSchema) { |
| 5392 | unblockGeneration(type); | 5422 | unblockGeneration(type); |
| 5393 | return extractJsonFromData(data); | 5423 | return extractJsonFromData(data, { returnInvalidJson: jsonSchema.returnInvalid ?? false }); |
| 5394 | } | 5424 | } |
| 5395 | 5425 | ||
| 5396 | //const getData = await response.json(); | 5426 | //const getData = await response.json(); |
| @@ -5823,11 +5853,11 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul | |||
| 5823 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt); | 5853 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt); |
| 5824 | } else { | 5854 | } else { |
| 5825 | chat.push(message); | 5855 | chat.push(message); |
| 5856 | await saveChatConditional(); | ||
| 5826 | const chat_id = (chat.length - 1); | 5857 | const chat_id = (chat.length - 1); |
| 5827 | await eventSource.emit(event_types.MESSAGE_SENT, chat_id); | 5858 | await eventSource.emit(event_types.MESSAGE_SENT, chat_id); |
| 5828 | addOneMessage(message); | 5859 | addOneMessage(message); |
| 5829 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); | 5860 | await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); |
| 5830 | await saveChatConditional(); | ||
| 5831 | } | 5861 | } |
| 5832 | 5862 | ||
| 5833 | return message; | 5863 | return message; |
| @@ -6003,7 +6033,7 @@ function setInContextMessages(msgInContextCount, type) { | |||
| 6003 | 6033 | ||
| 6004 | if (lastMessageBlock.length === 0) { | 6034 | if (lastMessageBlock.length === 0) { |
| 6005 | const firstMessageId = getFirstDisplayedMessageId(); | 6035 | const firstMessageId = getFirstDisplayedMessageId(); |
| 6006 | chatElement.find(`.mes[mesid="${firstMessageId}"`).addClass('lastInContext'); | 6036 | chatElement.find(`.mes[mesid="${firstMessageId}"]`).addClass('lastInContext'); |
| 6007 | } | 6037 | } |
| 6008 | 6038 | ||
| 6009 | // Update last id to chat. No metadata save on purpose, gets hopefully saved via another call | 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 | * Extracts JSON from the response data. | 6244 | * Extracts JSON from the response data. |
| 6215 | * @param {object} data Response data | 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 | * @returns {string} Extracted JSON string from the response data | 6250 | * @returns {string} Extracted JSON string from the response data |
| 6217 | */ | 6251 | */ |
| 6218 | export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null } = {}) { | 6252 | export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null, returnInvalidJson = false } = {}) { |
| 6219 | mainApi = mainApi ?? main_api; | 6253 | mainApi = mainApi ?? main_api; |
| 6220 | chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source; | 6254 | chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source; |
| 6221 | 6255 | ||
| @@ -6238,6 +6272,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource | |||
| 6238 | break; | 6272 | break; |
| 6239 | case chat_completion_sources.PERPLEXITY: | 6273 | case chat_completion_sources.PERPLEXITY: |
| 6240 | result = tryParse(removeReasoningFromString(text)); | 6274 | result = tryParse(removeReasoningFromString(text)); |
| 6275 | if (!result && returnInvalidJson) { | ||
| 6276 | return text; | ||
| 6277 | } | ||
| 6241 | break; | 6278 | break; |
| 6242 | case chat_completion_sources.VERTEXAI: | 6279 | case chat_completion_sources.VERTEXAI: |
| 6243 | case chat_completion_sources.MAKERSUITE: | 6280 | case chat_completion_sources.MAKERSUITE: |
| @@ -6258,6 +6295,9 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource | |||
| 6258 | case chat_completion_sources.ZAI: | 6295 | case chat_completion_sources.ZAI: |
| 6259 | default: | 6296 | default: |
| 6260 | result = tryParse(text); | 6297 | result = tryParse(text); |
| 6298 | if (!result && returnInvalidJson) { | ||
| 6299 | return text; | ||
| 6300 | } | ||
| 6261 | break; | 6301 | break; |
| 6262 | } | 6302 | } |
| 6263 | } break; | 6303 | } break; |
| @@ -7924,6 +7964,15 @@ export async function getSettings(initLoaderHandle = null) { | |||
| 7924 | const isVersionChanged = settings.currentVersion !== currentVersion; | 7964 | const isVersionChanged = settings.currentVersion !== currentVersion; |
| 7925 | await loadExtensionSettings(settings, isVersionChanged, enableAutoUpdate); | 7965 | await loadExtensionSettings(settings, isVersionChanged, enableAutoUpdate); |
| 7926 | await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED); | 7966 | await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED); |
| 7967 | } else { | ||
| 7968 | Object.assign(extension_settings, (settings.extension_settings ?? {})); | ||
| 7969 | $('#third_party_extension_button').addClass('disabled'); | ||
| 7970 | $('#extensions_details').addClass('disabled'); | ||
| 7971 | $('#extensions_connect').addClass('disabled'); | ||
| 7972 | $('#extensions_notify_updates').attr('disabled', 'disabled'); | ||
| 7973 | $('#extensions_autoconnect').attr('disabled', 'disabled'); | ||
| 7974 | $('#extensions_url').attr('disabled', 'disabled'); | ||
| 7975 | $('#extensions_api_key').attr('disabled', 'disabled'); | ||
| 7927 | } | 7976 | } |
| 7928 | 7977 | ||
| 7929 | firstRun = !!settings.firstRun; | 7978 | firstRun = !!settings.firstRun; |
| @@ -10565,6 +10614,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile | |||
| 10565 | } | 10614 | } |
| 10566 | 10615 | ||
| 10567 | const loaderHandle = showLoader ? loader.show({ | 10616 | const loaderHandle = showLoader ? loader.show({ |
| 10617 | slug: 'chat-rename', | ||
| 10568 | title: t`Rename Chat`, | 10618 | title: t`Rename Chat`, |
| 10569 | message: t`Renaming chat…`, | 10619 | message: t`Renaming chat…`, |
| 10570 | toastMode: loader.ToastMode.STATIC, | 10620 | toastMode: loader.ToastMode.STATIC, |
| @@ -10602,6 +10652,9 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile | |||
| 10602 | if (currentChatId) { | 10652 | if (currentChatId) { |
| 10603 | await reloadCurrentChat(); | 10653 | await reloadCurrentChat(); |
| 10604 | } | 10654 | } |
| 10655 | |||
| 10656 | const eventData = { avatarId: body.avatar_url, groupId, oldFileName: body.original_file, newFileName: body.renamed_file }; | ||
| 10657 | await eventSource.emit(event_types.CHAT_RENAMED, eventData); | ||
| 10605 | } catch { | 10658 | } catch { |
| 10606 | await delay(500); | 10659 | await delay(500); |
| 10607 | await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT); | 10660 | await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT); |
| @@ -11166,6 +11219,7 @@ jQuery(async function () { | |||
| 11166 | $('#select_chat_cross').trigger('click'); | 11219 | $('#select_chat_cross').trigger('click'); |
| 11167 | 11220 | ||
| 11168 | const loaderHandle = loader.show({ | 11221 | const loaderHandle = loader.show({ |
| 11222 | slug: 'chat-delete', | ||
| 11169 | title: t`Delete Chat`, | 11223 | title: t`Delete Chat`, |
| 11170 | message: t`Deleting chat…`, | 11224 | message: t`Deleting chat…`, |
| 11171 | toastMode: loader.ToastMode.STATIC, | 11225 | toastMode: loader.ToastMode.STATIC, |
| @@ -12460,7 +12514,9 @@ jQuery(async function () { | |||
| 12460 | $('#avatar-and-name-block').slideToggle(); | 12514 | $('#avatar-and-name-block').slideToggle(); |
| 12461 | }); | 12515 | }); |
| 12462 | 12516 | ||
| 12463 | $(document).on('mouseup touchend', '#show_more_messages', async function () { | 12517 | $(document).on('click', '#show_more_messages', async function (event) { |
| 12518 | event.stopPropagation(); | ||
| 12519 | event.preventDefault(); | ||
| 12464 | await showMoreMessages(); | 12520 | await showMoreMessages(); |
| 12465 | }); | 12521 | }); |
| 12466 | 12522 | ||
| @@ -848,6 +848,7 @@ class BulkEditOverlay { | |||
| 848 | const deleteChats = checkbox.prop('checked') ?? false; | 848 | const deleteChats = checkbox.prop('checked') ?? false; |
| 849 | 849 | ||
| 850 | const loaderHandle = loader.show({ | 850 | const loaderHandle = loader.show({ |
| 851 | slug: 'bulk-delete', | ||
| 851 | title: t`Bulk Delete`, | 852 | title: t`Bulk Delete`, |
| 852 | message: t`Deleting ${characterIds.length} character(s)…`, | 853 | message: t`Deleting ${characterIds.length} character(s)…`, |
| 853 | toastMode: loader.ToastMode.STATIC, | 854 | toastMode: loader.ToastMode.STATIC, |
| @@ -407,6 +407,8 @@ function RA_autoconnect(PrevApi) { | |||
| 407 | || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) | 407 | || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) |
| 408 | || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI) | 408 | || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI) |
| 409 | || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) | 409 | || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) |
| 410 | || (secret_state[SECRET_KEYS.WORKERS_AI] && oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI) | ||
| 411 | || (secret_state[SECRET_KEYS.MINIMAX] && oai_settings.chat_completion_source == chat_completion_sources.MINIMAX) | ||
| 410 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) | 412 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 411 | || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) | 413 | || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) |
| 412 | ) { | 414 | ) { |
| @@ -16,6 +16,7 @@ const buttonSelectors = [ | |||
| 16 | '.jg-menu .jg-button', | 16 | '.jg-menu .jg-button', |
| 17 | '.bg_example .mobile-only-menu-toggle', | 17 | '.bg_example .mobile-only-menu-toggle', |
| 18 | '.paginationjs-pages li a', | 18 | '.paginationjs-pages li a', |
| 19 | '#show_more_messages', | ||
| 19 | ].join(', '); | 20 | ].join(', '); |
| 20 | 21 | ||
| 21 | const listSelectors = [ | 22 | const listSelectors = [ |
| @@ -1,4 +1,4 @@ | |||
| 1 | import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoader } from './action-loader.js'; | 1 | import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, loader } from './action-loader.js'; |
| 2 | import { t } from './i18n.js'; | 2 | import { t } from './i18n.js'; |
| 3 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 3 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 4 | import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; | 4 | import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; |
| @@ -7,6 +7,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 7 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 7 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 8 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 8 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 9 | import { isFalseBoolean } from './utils.js'; | 9 | import { isFalseBoolean } from './utils.js'; |
| 10 | import { DOMPurify } from '../lib.js'; | ||
| 10 | 11 | ||
| 11 | /** | 12 | /** |
| 12 | * Registers slash commands for the action loader module. | 13 | * Registers slash commands for the action loader module. |
| @@ -116,6 +117,12 @@ export function registerActionLoaderSlashCommands() { | |||
| 116 | typeList: [ARGUMENT_TYPE.STRING], | 117 | typeList: [ARGUMENT_TYPE.STRING], |
| 117 | }), | 118 | }), |
| 118 | SlashCommandNamedArgument.fromProps({ | 119 | SlashCommandNamedArgument.fromProps({ |
| 120 | name: 'slug', | ||
| 121 | description: 'Unique slug for the loader (to identify it easily via code or CSS)', | ||
| 122 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 123 | defaultValue: 'slash-wrap', | ||
| 124 | }), | ||
| 125 | SlashCommandNamedArgument.fromProps({ | ||
| 119 | name: 'stopTooltip', | 126 | name: 'stopTooltip', |
| 120 | description: 'Tooltip text for the stop button (only used when toast=stoppable)', | 127 | description: 'Tooltip text for the stop button (only used when toast=stoppable)', |
| 121 | typeList: [ARGUMENT_TYPE.STRING], | 128 | typeList: [ARGUMENT_TYPE.STRING], |
| @@ -148,7 +155,8 @@ export function registerActionLoaderSlashCommands() { | |||
| 148 | const title = args.title ? String(args.title) : ''; | 155 | const title = args.title ? String(args.title) : ''; |
| 149 | const stopTooltip = String(args.stopTooltip ?? t`Stop`); | 156 | const stopTooltip = String(args.stopTooltip ?? t`Stop`); |
| 150 | 157 | ||
| 151 | const loader = showActionLoader({ | 158 | const actionLoader = loader.show({ |
| 159 | slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap', | ||
| 152 | blocking, | 160 | blocking, |
| 153 | toastMode, | 161 | toastMode, |
| 154 | message, | 162 | message, |
| @@ -162,7 +170,7 @@ export function registerActionLoaderSlashCommands() { | |||
| 162 | const result = await closureCopy.execute(); | 170 | const result = await closureCopy.execute(); |
| 163 | return result.pipe; | 171 | return result.pipe; |
| 164 | } finally { | 172 | } finally { |
| 165 | await loader.hide(); | 173 | await actionLoader.hide(); |
| 166 | } | 174 | } |
| 167 | }, | 175 | }, |
| 168 | })); | 176 | })); |
| @@ -232,6 +240,12 @@ export function registerActionLoaderSlashCommands() { | |||
| 232 | typeList: [ARGUMENT_TYPE.STRING], | 240 | typeList: [ARGUMENT_TYPE.STRING], |
| 233 | }), | 241 | }), |
| 234 | SlashCommandNamedArgument.fromProps({ | 242 | SlashCommandNamedArgument.fromProps({ |
| 243 | name: 'slug', | ||
| 244 | description: 'Unique slug for the loader (to identify it easily via code or CSS)', | ||
| 245 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 246 | defaultValue: 'slash-show', | ||
| 247 | }), | ||
| 248 | SlashCommandNamedArgument.fromProps({ | ||
| 235 | name: 'stopTooltip', | 249 | name: 'stopTooltip', |
| 236 | description: 'Tooltip text for the stop button (only used when toast=stoppable)', | 250 | description: 'Tooltip text for the stop button (only used when toast=stoppable)', |
| 237 | typeList: [ARGUMENT_TYPE.STRING], | 251 | typeList: [ARGUMENT_TYPE.STRING], |
| @@ -258,11 +272,12 @@ export function registerActionLoaderSlashCommands() { | |||
| 258 | const title = args.title ? String(args.title) : ''; | 272 | const title = args.title ? String(args.title) : ''; |
| 259 | const stopTooltip = String(args.stopTooltip ?? t`Stop`); | 273 | const stopTooltip = String(args.stopTooltip ?? t`Stop`); |
| 260 | 274 | ||
| 261 | const handle = showActionLoader({ | 275 | const handle = loader.show({ |
| 276 | slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show', | ||
| 262 | blocking, | 277 | blocking, |
| 263 | toastMode, | 278 | toastMode, |
| 264 | message, | 279 | message: DOMPurify.sanitize(message), |
| 265 | title, | 280 | title: DOMPurify.sanitize(title), |
| 266 | stopTooltip, | 281 | stopTooltip, |
| 267 | onStop: createClosureHandler(args.onStop), | 282 | onStop: createClosureHandler(args.onStop), |
| 268 | onHide: createClosureHandler(args.onHide, { argName: 'onHide' }), | 283 | onHide: createClosureHandler(args.onHide, { argName: 'onHide' }), |
| @@ -307,7 +322,7 @@ export function registerActionLoaderSlashCommands() { | |||
| 307 | } | 322 | } |
| 308 | 323 | ||
| 309 | // No handle provided - hide all active loaders | 324 | // No handle provided - hide all active loaders |
| 310 | const result = await hideActionLoader(); | 325 | const result = await loader.hide(); |
| 311 | return result ? 'true' : 'false'; | 326 | return result ? 'true' : 'false'; |
| 312 | }, | 327 | }, |
| 313 | })); | 328 | })); |
| @@ -33,6 +33,7 @@ export const ActionLoaderToastMode = { | |||
| 33 | * @typedef {object} ActionLoaderOptions | 33 | * @typedef {object} ActionLoaderOptions |
| 34 | * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders. | 34 | * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders. |
| 35 | * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode | 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 | * @property {string} [message='Generating...'] - The message to display in the toast | 37 | * @property {string} [message='Generating...'] - The message to display in the toast |
| 37 | * @property {string} [title] - Optional title for the toast notification | 38 | * @property {string} [title] - Optional title for the toast notification |
| 38 | * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button | 39 | * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button |
| @@ -73,8 +74,20 @@ function hasBlockingLoaders() { | |||
| 73 | * Manages its own toast, stop handler, and lifecycle. | 74 | * Manages its own toast, stop handler, and lifecycle. |
| 74 | */ | 75 | */ |
| 75 | export class ActionLoaderHandle { | 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 | /** @type {string} Unique identifier for this handle */ | 86 | /** @type {string} Unique identifier for this handle */ |
| 77 | id; | 87 | #id; |
| 88 | |||
| 89 | /** @type {string|null} Unique slug for the loader */ | ||
| 90 | #slug = null; | ||
| 78 | 91 | ||
| 79 | /** @type {JQuery<HTMLElement>|null} The toast element for this loader */ | 92 | /** @type {JQuery<HTMLElement>|null} The toast element for this loader */ |
| 80 | #toast = null; | 93 | #toast = null; |
| @@ -96,9 +109,11 @@ export class ActionLoaderHandle { | |||
| 96 | * @param {object} options - Configuration options | 109 | * @param {object} options - Configuration options |
| 97 | * @param {boolean} [options.blocking=true] - Whether to show blocking overlay | 110 | * @param {boolean} [options.blocking=true] - Whether to show blocking overlay |
| 98 | * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode | 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 | * @param {string} [options.message='Generating...'] - Message to display in the toast | 113 | * @param {string} [options.message='Generating...'] - Message to display in the toast |
| 100 | * @param {string} [options.title] - Title for the toast notification | 114 | * @param {string} [options.title] - Title for the toast notification |
| 101 | * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button | 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 | * @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner) | 117 | * @param {HTMLElement|string|null} [options.overlayContent] - Custom content for the overlay (replaces default spinner) |
| 103 | * @param {(() => void)|null} [options.onStop] - Custom stop handler | 118 | * @param {(() => void)|null} [options.onStop] - Custom stop handler |
| 104 | * @param {(() => void)|null} [options.onHide] - Custom hide handler | 119 | * @param {(() => void)|null} [options.onHide] - Custom hide handler |
| @@ -106,14 +121,22 @@ export class ActionLoaderHandle { | |||
| 106 | constructor({ | 121 | constructor({ |
| 107 | blocking = true, | 122 | blocking = true, |
| 108 | toastMode = ActionLoaderToastMode.STOPPABLE, | 123 | toastMode = ActionLoaderToastMode.STOPPABLE, |
| 124 | slug = null, | ||
| 109 | message = t`Generating...`, | 125 | message = t`Generating...`, |
| 110 | title = '', | 126 | title = '', |
| 111 | stopTooltip = t`Stop`, | 127 | stopTooltip = t`Stop`, |
| 112 | overlayContent = null, | 128 | overlayContent = null, |
| 113 | onStop = null, | 129 | onStop = null, |
| 114 | onHide = null, | 130 | onHide = null, |
| 131 | predisposed = false, | ||
| 115 | } = {}) { | 132 | } = {}) { |
| 116 | this.id = generateLoaderId(); | 133 | if (predisposed) { |
| 134 | this.#disposed = true; | ||
| 135 | return; | ||
| 136 | } | ||
| 137 | |||
| 138 | this.#id = generateLoaderId(); | ||
| 139 | this.#slug = slug; | ||
| 117 | this.#blocking = blocking; | 140 | this.#blocking = blocking; |
| 118 | this.#onStop = onStop; | 141 | this.#onStop = onStop; |
| 119 | this.#onHide = onHide; | 142 | this.#onHide = onHide; |
| @@ -148,6 +171,12 @@ export class ActionLoaderHandle { | |||
| 148 | const toastContent = document.createElement('div'); | 171 | const toastContent = document.createElement('div'); |
| 149 | toastContent.className = 'action-loader-toast'; | 172 | toastContent.className = 'action-loader-toast'; |
| 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 | const messageSpan = document.createElement('span'); | 180 | const messageSpan = document.createElement('span'); |
| 152 | messageSpan.className = 'action-loader-message'; | 181 | messageSpan.className = 'action-loader-message'; |
| 153 | messageSpan.textContent = message; | 182 | messageSpan.textContent = message; |
| @@ -202,6 +231,22 @@ export class ActionLoaderHandle { | |||
| 202 | } | 231 | } |
| 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 | * Whether this handle is still active (not disposed). | 250 | * Whether this handle is still active (not disposed). |
| 206 | * @returns {boolean} | 251 | * @returns {boolean} |
| 207 | */ | 252 | */ |
| @@ -700,6 +700,7 @@ export function initBookmarks() { | |||
| 700 | } | 700 | } |
| 701 | 701 | ||
| 702 | const loaderHandle = loader.show({ | 702 | const loaderHandle = loader.show({ |
| 703 | slug: 'chat-load', | ||
| 703 | title: t`Chat History`, | 704 | title: t`Chat History`, |
| 704 | message: t`Loading chat…`, | 705 | message: t`Loading chat…`, |
| 705 | toastMode: loader.ToastMode.STATIC, | 706 | toastMode: loader.ToastMode.STATIC, |
| @@ -72,6 +72,14 @@ function applyBrowserFixes() { | |||
| 72 | 72 | ||
| 73 | if (isMobile()) { | 73 | if (isMobile()) { |
| 74 | const fixFunkyPositioning = () => { | 74 | const fixFunkyPositioning = () => { |
| 75 | if (isFirefox()) { | ||
| 76 | const active = document.activeElement; | ||
| 77 | if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) { | ||
| 78 | // The positioning hack below breaks GBoard candidate replacement | ||
| 79 | // in Firefox Mobile on Android. | ||
| 80 | return; | ||
| 81 | } | ||
| 82 | } | ||
| 75 | console.debug('[Mobile] Device viewport change detected.'); | 83 | console.debug('[Mobile] Device viewport change detected.'); |
| 76 | document.documentElement.style.position = 'fixed'; | 84 | document.documentElement.style.position = 'fixed'; |
| 77 | requestAnimationFrame(() => document.documentElement.style.position = ''); | 85 | requestAnimationFrame(() => document.documentElement.style.position = ''); |
| @@ -2288,11 +2288,14 @@ export function initChatUtilities() { | |||
| 2288 | await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true }); | 2288 | await callGenericPopup(wrapper, POPUP_TYPE.TEXT, '', { wide: true, large: true }); |
| 2289 | }); | 2289 | }); |
| 2290 | 2290 | ||
| 2291 | $(document).on('click', 'body .mes .mes_text', function () { | 2291 | $(document).on('click', 'body .mes .mes_text, body .mes .mes_reasoning', function (event) { |
| 2292 | if (!power_user.click_to_edit) return; | 2292 | if (!power_user.click_to_edit) return; |
| 2293 | if (window.getSelection().toString()) return; | 2293 | if (window.getSelection().toString()) return; |
| 2294 | if ($('.edit_textarea').length) return; | 2294 | if ($('.edit_textarea').length) return; |
| 2295 | $(this).closest('.mes').find('.mes_edit').trigger('click'); | 2295 | $(this).closest('.mes').find('.mes_edit').trigger('click'); |
| 2296 | if ($(event.target).closest('.mes_reasoning').length) { | ||
| 2297 | $('.reasoning_edit_textarea').trigger('focus'); | ||
| 2298 | } | ||
| 2296 | }); | 2299 | }); |
| 2297 | 2300 | ||
| 2298 | $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog); | 2301 | $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog); |
| @@ -51,6 +51,7 @@ import EventSourceStream from './sse-stream.js'; | |||
| 51 | * @property {string} [reverse_proxy] - Optional reverse proxy URL | 51 | * @property {string} [reverse_proxy] - Optional reverse proxy URL |
| 52 | * @property {string} [proxy_password] - Optional proxy password | 52 | * @property {string} [proxy_password] - Optional proxy password |
| 53 | * @property {string} [custom_prompt_post_processing] - Optional custom prompt post-processing | 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 | /** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */ | 57 | /** @typedef {Record<string, any> & ChatCompletionPayloadBase} ChatCompletionPayload */ |
| @@ -505,7 +506,7 @@ export class ChatCompletionService { | |||
| 505 | return async function* streamData() { | 506 | return async function* streamData() { |
| 506 | let text = ''; | 507 | let text = ''; |
| 507 | const swipes = []; | 508 | const swipes = []; |
| 508 | const state = { reasoning: '', image: '' }; | 509 | const state = { reasoning: '', images: [], signature: '', toolSignatures: {} }; |
| 509 | while (true) { | 510 | while (true) { |
| 510 | const { done, value } = await reader.read(); | 511 | const { done, value } = await reader.read(); |
| 511 | if (done) return; | 512 | if (done) return; |
| @@ -591,7 +592,7 @@ export class ChatCompletionService { | |||
| 591 | } | 592 | } |
| 592 | 593 | ||
| 593 | // Ensure api-url is properly applied for all sources that accept it | 594 | // Ensure api-url is properly applied for all sources that accept it |
| 594 | ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint'].forEach(field => { | 595 | ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint', 'minimax_endpoint'].forEach(field => { |
| 595 | // The order is: connection profile => CC preset => CC settings | 596 | // The order is: connection profile => CC preset => CC settings |
| 596 | overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field]; | 597 | overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field]; |
| 597 | }); | 598 | }); |
| @@ -126,6 +126,12 @@ function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { | |||
| 126 | // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside, | 126 | // If something like :focus-within or a more specific selector like `.blah:has(:focus-visible)` for elements inside, |
| 127 | // it should be manually defined in CSS. | 127 | // it should be manually defined in CSS. |
| 128 | const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible'); | 128 | const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible'); |
| 129 | |||
| 130 | // Skip pseudo-elements (::before, ::after, ::-webkit-scrollbar, etc.) | ||
| 131 | // as they cannot have :focus-visible appended (invalid CSS syntax) | ||
| 132 | if (focusSelector.includes('::')) { | ||
| 133 | return; | ||
| 134 | } | ||
| 129 | let focusRule = `${focusSelector} { ${rule.style.cssText} }`; | 135 | let focusRule = `${focusSelector} { ${rule.style.cssText} }`; |
| 130 | 136 | ||
| 131 | // Wrap the generated rule into the same @media/@supports/@container chain (if any) | 137 | // Wrap the generated rule into the same @media/@supports/@container chain (if any) |
| @@ -50,6 +50,7 @@ export const event_types = { | |||
| 50 | FORCE_SET_BACKGROUND: 'force_set_background', | 50 | FORCE_SET_BACKGROUND: 'force_set_background', |
| 51 | CHAT_DELETED: 'chat_deleted', | 51 | CHAT_DELETED: 'chat_deleted', |
| 52 | CHAT_CREATED: 'chat_created', | 52 | CHAT_CREATED: 'chat_created', |
| 53 | CHAT_RENAMED: 'chat_renamed', | ||
| 53 | GROUP_CHAT_DELETED: 'group_chat_deleted', | 54 | GROUP_CHAT_DELETED: 'group_chat_deleted', |
| 54 | GROUP_CHAT_CREATED: 'group_chat_created', | 55 | GROUP_CHAT_CREATED: 'group_chat_created', |
| 55 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', | 56 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', |
| @@ -97,9 +98,16 @@ export const event_types = { | |||
| 97 | WORLDINFO_SCAN_DONE: 'worldinfo_scan_done', | 98 | WORLDINFO_SCAN_DONE: 'worldinfo_scan_done', |
| 98 | MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted', | 99 | MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted', |
| 99 | PERSONA_CHANGED: 'persona_changed', | 100 | PERSONA_CHANGED: 'persona_changed', |
| 101 | PERSONA_CREATED: 'persona_created', | ||
| 102 | PERSONA_UPDATED: 'persona_updated', | ||
| 103 | PERSONA_RENAMED: 'persona_renamed', | ||
| 104 | PERSONA_DELETED: 'persona_deleted', | ||
| 100 | TTS_JOB_STARTED: 'tts_job_started', | 105 | TTS_JOB_STARTED: 'tts_job_started', |
| 101 | TTS_AUDIO_READY: 'tts_audio_ready', | 106 | TTS_AUDIO_READY: 'tts_audio_ready', |
| 102 | TTS_JOB_COMPLETE: 'tts_job_complete', | 107 | TTS_JOB_COMPLETE: 'tts_job_complete', |
| 108 | ITEMIZED_PROMPTS_LOADED: 'itemized_prompts_loaded', | ||
| 109 | ITEMIZED_PROMPTS_SAVED: 'itemized_prompts_saved', | ||
| 110 | ITEMIZED_PROMPTS_DELETED: 'itemized_prompts_deleted', | ||
| 103 | }; | 111 | }; |
| 104 | 112 | ||
| 105 | export const eventSource = new EventEmitter([event_types.APP_READY, event_types.APP_INITIALIZED]); | 113 | export const eventSource = new EventEmitter([event_types.APP_READY, event_types.APP_INITIALIZED]); |
| @@ -1,9 +1,9 @@ | |||
| 1 | import { DOMPurify, Popper } from '../lib.js'; | 1 | import { Popper } from '../lib.js'; |
| 2 | 2 | ||
| 3 | import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js'; | 3 | import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration, CLIENT_VERSION } from '../script.js'; |
| 4 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 4 | import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js'; |
| 5 | import { renderTemplate, renderTemplateAsync } from './templates.js'; | 5 | import { renderTemplate, renderTemplateAsync } from './templates.js'; |
| 6 | import { delay, equalsIgnoreCaseAndAccents, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js'; | 6 | import { delay, deleteValueByPath, equalsIgnoreCaseAndAccents, escapeHtml, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js'; |
| 7 | import { getContext } from './st-context.js'; | 7 | import { getContext } from './st-context.js'; |
| 8 | import { isAdmin } from './user.js'; | 8 | import { isAdmin } from './user.js'; |
| 9 | import { addLocaleData, getCurrentLocale, t } from './i18n.js'; | 9 | import { addLocaleData, getCurrentLocale, t } from './i18n.js'; |
| @@ -61,6 +61,19 @@ let manifests = {}; | |||
| 61 | */ | 61 | */ |
| 62 | const defaultUrl = 'http://localhost:5100'; | 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 | let requiresReload = false; | 77 | let requiresReload = false; |
| 65 | let stateChanged = false; | 78 | let stateChanged = false; |
| 66 | let saveMetadataTimeout = null; | 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 | * Discovers extensions from the API. | 295 | * Discovers extensions from the API. |
| 271 | * @returns {Promise<{name: string, type: string}[]>} | 296 | * @returns {Promise<{name: string, type: string}[]>} |
| 272 | */ | 297 | */ |
| @@ -343,7 +368,7 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) { | |||
| 343 | } | 368 | } |
| 344 | 369 | ||
| 345 | toggleContainer | 370 | toggleContainer |
| 346 | .find(`.extension_block[data-name="${name.replace('third-party', '')}"] .extension_toggle input`) | 371 | .find(`.extension_block[data-name="${getNameSelector(name)}"] .extension_toggle input`) |
| 347 | .prop('checked', enable) | 372 | .prop('checked', enable) |
| 348 | .toggleClass('toggle_enable', !enable) | 373 | .toggleClass('toggle_enable', !enable) |
| 349 | .toggleClass('toggle_disable', enable) | 374 | .toggleClass('toggle_disable', enable) |
| @@ -355,11 +380,27 @@ function onToggleAllExtensions(extensionsToToggle, toggleContainer) { | |||
| 355 | } | 380 | } |
| 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 | * Calls a manifest hook for an extension. | 399 | * Calls a manifest hook for an extension. |
| 359 | * Hooks are optional function names exported from the extension's JS entry point module. | 400 | * Hooks are optional function names exported from the extension's JS entry point module. |
| 360 | * The hook function can optionally return a Promise that will be awaited. | 401 | * The hook function can optionally return a Promise that will be awaited. |
| 361 | * @param {string} name Extension name | 402 | * @param {string} name Extension name |
| 362 | * @param {'install' | 'update' | 'delete' | 'enable' | 'disable' | 'activate'} hookName The hook to call | 403 | * @param {'install' | 'update' | 'delete' | 'clean' | 'enable' | 'disable' | 'activate'} hookName The hook to call |
| 363 | * @returns {Promise<void>} | 404 | * @returns {Promise<void>} |
| 364 | */ | 405 | */ |
| 365 | async function callExtensionHook(name, hookName) { | 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 | * Loads manifest.json files for extensions. | 533 | * Loads manifest.json files for extensions. |
| 478 | * @param {string[]} names Array of extension names | 534 | * @param {string[]} names Array of extension names |
| 479 | * @returns {Promise<Record<string, object>>} Object with extension names as keys and their manifests as values | 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 | * Generates HTML string for displaying an extension in the UI. | 880 | * Generates an element for displaying an extension in the UI. |
| 825 | * | 881 | * |
| 826 | * @param {string} name - The name of the extension. | 882 | * @param {string} name - The name of the extension. |
| 827 | * @param {object} manifest - The manifest of the extension. | 883 | * @param {object} manifest - The manifest of the extension. |
| @@ -829,95 +885,180 @@ function addExtensionLocale(name, manifest) { | |||
| 829 | * @param {boolean} isDisabled - Whether the extension is disabled or not. | 885 | * @param {boolean} isDisabled - Whether the extension is disabled or not. |
| 830 | * @param {boolean} isExternal - Whether the extension is external or not. | 886 | * @param {boolean} isExternal - Whether the extension is external or not. |
| 831 | * @param {string} checkboxClass - The class for the checkbox HTML element. | 887 | * @param {string} checkboxClass - The class for the checkbox HTML element. |
| 832 | * @return {string} - The HTML string that represents the extension. | 888 | * @return {HTMLElement} - The element that represents the extension. |
| 833 | */ | 889 | */ |
| 834 | function generateExtensionHtml(name, manifest, isActive, isDisabled, isExternal, checkboxClass) { | 890 | function generateExtensionElement(name, manifest, isActive, isDisabled, isExternal, checkboxClass) { |
| 835 | function getExtensionIcon() { | 891 | function getExtensionIcon() { |
| 836 | const type = getExtensionType(name); | 892 | const type = getExtensionType(name); |
| 893 | const icon = document.createElement('i'); | ||
| 894 | icon.classList.add('fa-sm', 'fa-fw', 'fa-solid'); | ||
| 837 | switch (type) { | 895 | switch (type) { |
| 838 | case 'global': | 896 | case 'global': |
| 839 | return '<i class="fa-sm fa-fw fa-solid fa-server" data-i18n="[title]ext_type_global" title="This is a global extension, available for all users."></i>'; | 897 | icon.classList.add('fa-server'); |
| 898 | icon.title = t`This is a global extension, available for all users.`; | ||
| 899 | break; | ||
| 840 | case 'local': | 900 | case 'local': |
| 841 | return '<i class="fa-sm fa-fw fa-solid fa-user" data-i18n="[title]ext_type_local" title="This is a local extension, available only for you."></i>'; | 901 | icon.classList.add('fa-user'); |
| 902 | icon.title = t`This is a local extension, available only for you.`; | ||
| 903 | break; | ||
| 842 | case 'system': | 904 | case 'system': |
| 843 | return '<i class="fa-sm fa-fw fa-solid fa-cog" data-i18n="[title]ext_type_system" title="This is a built-in extension. It cannot be deleted and updates with the app."></i>'; | 905 | icon.classList.add('fa-cog'); |
| 906 | icon.title = t`This is a built-in extension. It cannot be deleted and updates with the app.`; | ||
| 907 | break; | ||
| 844 | default: | 908 | default: |
| 845 | return '<i class="fa-sm fa-fw fa-solid fa-question" title="Unknown extension type."></i>'; | 909 | icon.classList.add('fa-question'); |
| 910 | icon.title = t`Unknown extension type.`; | ||
| 911 | break; | ||
| 846 | } | 912 | } |
| 913 | return icon; | ||
| 847 | } | 914 | } |
| 848 | 915 | ||
| 849 | const isUserAdmin = isAdmin(); | 916 | const isUserAdmin = isAdmin(); |
| 850 | const extensionIcon = getExtensionIcon(); | ||
| 851 | const displayName = manifest.display_name; | 917 | const displayName = manifest.display_name; |
| 852 | const displayVersion = manifest.version || ''; | 918 | const displayVersion = manifest.version || ''; |
| 853 | const externalId = name.replace('third-party', ''); | 919 | const externalId = name.replace('third-party', ''); |
| 854 | let originHtml = ''; | 920 | |
| 855 | if (isExternal) { | 921 | // Root block |
| 856 | originHtml = '<a>'; | 922 | const block = document.createElement('div'); |
| 923 | block.classList.add('extension_block'); | ||
| 924 | block.dataset.name = externalId; | ||
| 925 | |||
| 926 | // Toggle | ||
| 927 | const toggleDiv = document.createElement('div'); | ||
| 928 | toggleDiv.classList.add('extension_toggle'); | ||
| 929 | const toggle = document.createElement('input'); | ||
| 930 | toggle.type = 'checkbox'; | ||
| 931 | toggle.dataset.name = name; | ||
| 932 | if (isActive || isDisabled) { | ||
| 933 | toggle.title = t`Click to toggle`; | ||
| 934 | toggle.classList.add(isActive ? 'toggle_disable' : 'toggle_enable'); | ||
| 935 | if (checkboxClass) toggle.classList.add(checkboxClass); | ||
| 936 | toggle.checked = isActive; | ||
| 937 | } else { | ||
| 938 | toggle.title = t`Cannot enable extension`; | ||
| 939 | toggle.classList.add('extension_missing'); | ||
| 940 | if (checkboxClass) toggle.classList.add(checkboxClass); | ||
| 941 | toggle.disabled = true; | ||
| 857 | } | 942 | } |
| 943 | toggleDiv.appendChild(toggle); | ||
| 944 | block.appendChild(toggleDiv); | ||
| 945 | |||
| 946 | // Icon | ||
| 947 | const iconDiv = document.createElement('div'); | ||
| 948 | iconDiv.classList.add('extension_icon'); | ||
| 949 | iconDiv.appendChild(getExtensionIcon()); | ||
| 950 | block.appendChild(iconDiv); | ||
| 951 | |||
| 952 | // Text block | ||
| 953 | const textBlock = document.createElement('div'); | ||
| 954 | textBlock.classList.add('flexGrow', 'extension_text_block'); | ||
| 955 | |||
| 956 | const statusSpan = document.createElement('span'); | ||
| 957 | statusSpan.className = isActive ? 'extension_enabled' : isDisabled ? 'extension_disabled' : 'extension_missing'; | ||
| 958 | |||
| 959 | const nameSpan = document.createElement('span'); | ||
| 960 | nameSpan.classList.add('extension_name'); | ||
| 961 | nameSpan.textContent = displayName; | ||
| 962 | |||
| 963 | const authorSpan = document.createElement('span'); | ||
| 964 | authorSpan.classList.add('extension_author'); | ||
| 858 | 965 | ||
| 859 | let toggleElement = isActive || isDisabled ? | 966 | const versionSpan = document.createElement('span'); |
| 860 | '<input type="checkbox" title="' + t`Click to toggle` + `" data-name="${name}" class="${isActive ? 'toggle_disable' : 'toggle_enable'} ${checkboxClass}" ${isActive ? 'checked' : ''}>` : | 967 | versionSpan.classList.add('extension_version'); |
| 861 | `<input type="checkbox" title="Cannot enable extension" data-name="${name}" class="extension_missing ${checkboxClass}" disabled>`; | 968 | versionSpan.textContent = displayVersion; |
| 862 | 969 | ||
| 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>` : ''; | 970 | statusSpan.append(nameSpan, authorSpan, versionSpan); |
| 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>` : ''; | ||
| 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>` : ''; | ||
| 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>` : ''; | ||
| 867 | let modulesInfo = ''; | ||
| 868 | 971 | ||
| 869 | if (isActive && Array.isArray(manifest.optional)) { | 972 | if (isActive && Array.isArray(manifest.optional)) { |
| 870 | const optional = new Set(manifest.optional); | 973 | const optional = new Set(manifest.optional); |
| 871 | modules.forEach(x => optional.delete(x)); | 974 | modules.forEach(x => optional.delete(x)); |
| 872 | if (optional.size > 0) { | 975 | if (optional.size > 0) { |
| 873 | const optionalString = DOMPurify.sanitize([...optional].join(', ')); | 976 | const modulesDiv = document.createElement('div'); |
| 874 | modulesInfo = '<div class="extension_modules">' + t`Optional modules:` + ` <span class="optional">${optionalString}</span></div>`; | 977 | modulesDiv.classList.add('extension_modules'); |
| 978 | const optionalSpan = document.createElement('span'); | ||
| 979 | optionalSpan.classList.add('optional'); | ||
| 980 | optionalSpan.textContent = [...optional].join(', '); | ||
| 981 | modulesDiv.append(t`Optional modules:`, ' ', optionalSpan); | ||
| 982 | statusSpan.appendChild(modulesDiv); | ||
| 875 | } | 983 | } |
| 876 | } else if (!isDisabled) { // Neither active nor disabled | 984 | } else if (!isDisabled) { |
| 985 | // Neither active nor disabled | ||
| 877 | const requirements = new Set(manifest.requires); | 986 | const requirements = new Set(manifest.requires); |
| 878 | modules.forEach(x => requirements.delete(x)); | 987 | modules.forEach(x => requirements.delete(x)); |
| 879 | if (requirements.size > 0) { | 988 | if (requirements.size > 0) { |
| 880 | const requirementsString = DOMPurify.sanitize([...requirements].join(', ')); | 989 | const modulesDiv = document.createElement('div'); |
| 881 | modulesInfo = `<div class="extension_modules">Missing modules: <span class="failure">${requirementsString}</span></div>`; | 990 | modulesDiv.classList.add('extension_modules'); |
| 991 | const failureSpan = document.createElement('span'); | ||
| 992 | failureSpan.classList.add('failure'); | ||
| 993 | failureSpan.textContent = [...requirements].join(', '); | ||
| 994 | modulesDiv.append(t`Missing modules:`, ' ', failureSpan); | ||
| 995 | statusSpan.appendChild(modulesDiv); | ||
| 882 | } | 996 | } |
| 883 | } | 997 | } |
| 884 | 998 | ||
| 885 | // if external, wrap the name in a link to the repo | 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'); | ||
| 886 | 1013 | ||
| 887 | let extensionHtml = ` | 1014 | /** |
| 888 | <div class="extension_block" data-name="${externalId}"> | 1015 | * Helper function to create an action button for an extension. |
| 889 | <div class="extension_toggle"> | 1016 | * @param {string} cls Class name |
| 890 | ${toggleElement} | 1017 | * @param {string} dataName Name of the extension |
| 891 | </div> | 1018 | * @param {string} title Title of the button |
| 892 | <div class="extension_icon"> | 1019 | * @param {string} iconClasses Classes for the icon |
| 893 | ${extensionIcon} | 1020 | * @returns {HTMLButtonElement} The created button element |
| 894 | </div> | 1021 | */ |
| 895 | <div class="flexGrow extension_text_block"> | 1022 | function makeActionButton(cls, dataName, title, iconClasses) { |
| 896 | ${originHtml} | 1023 | const btn = document.createElement('button'); |
| 897 | <span class="${isActive ? 'extension_enabled' : isDisabled ? 'extension_disabled' : 'extension_missing'}"> | 1024 | btn.classList.add(cls, 'menu_button'); |
| 898 | <span class="extension_name">${DOMPurify.sanitize(displayName)}</span> | 1025 | btn.dataset.name = dataName; |
| 899 | <span class="extension_version">${DOMPurify.sanitize(displayVersion)}</span> | 1026 | btn.title = title; |
| 900 | ${modulesInfo} | 1027 | const icon = document.createElement('i'); |
| 901 | </span> | 1028 | icon.classList.add(...iconClasses.split(' ')); |
| 902 | ${isExternal ? '</a>' : ''} | 1029 | btn.appendChild(icon); |
| 903 | </div> | 1030 | return btn; |
| 904 | 1031 | } | |
| 905 | <div class="extension_actions flex-container alignItemsCenter"> | 1032 | |
| 906 | ${updateButton} | 1033 | if (isExternal) { |
| 907 | ${branchButton} | 1034 | const updateBtn = makeActionButton('btn_update', externalId, t`Update available`, 'fa-solid fa-download fa-fw'); |
| 908 | ${moveButton} | 1035 | updateBtn.classList.add('displayNone'); |
| 909 | ${deleteButton} | 1036 | actionsDiv.appendChild(updateBtn); |
| 910 | </div> | 1037 | } |
| 911 | </div>`; | 1038 | |
| 912 | 1039 | if (isExternal && hasExtensionHook(externalId, 'clean')) { | |
| 913 | return extensionHtml; | 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); | ||
| 1053 | |||
| 1054 | return block; | ||
| 914 | } | 1055 | } |
| 915 | 1056 | ||
| 916 | /** | 1057 | /** |
| 917 | * Gets extension data and generates the corresponding HTML for displaying the extension. | 1058 | * Gets extension data and generates the corresponding element for displaying the extension. |
| 918 | * | 1059 | * |
| 919 | * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest. | 1060 | * @param {Array} extension - An array where the first element is the extension name and the second element is the extension manifest. |
| 920 | * @return {object} - An object with 'isExternal' indicating whether the extension is external, and 'extensionHtml' for the extension's HTML string. | 1061 | * @return {{isExternal: boolean, extensionElement: HTMLElement}} - An object with 'isExternal' indicating whether the extension is external, and 'extensionElement' for the extension's HTML element. |
| 921 | */ | 1062 | */ |
| 922 | function getExtensionData(extension) { | 1063 | function getExtensionData(extension) { |
| 923 | const name = extension[0]; | 1064 | const name = extension[0]; |
| @@ -927,33 +1068,43 @@ function getExtensionData(extension) { | |||
| 927 | const isExternal = name.startsWith('third-party'); | 1068 | const isExternal = name.startsWith('third-party'); |
| 928 | 1069 | ||
| 929 | const checkboxClass = isDisabled ? 'checkbox_disabled' : ''; | 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 | * Gets the module information to be displayed. | 1078 | * Gets the module information to be displayed. |
| 939 | * | 1079 | * |
| 940 | * @return {string} - The HTML string for the module information. | 1080 | * @return {HTMLElement} - The element containing the module information. |
| 941 | */ | 1081 | */ |
| 942 | function getModuleInformation() { | 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 | * Generates HTML for the extension load errors. | 1102 | * Generates HTMLElement for the extension load errors. |
| 952 | * @returns {string} HTML string containing the errors that occurred while loading extensions. | 1103 | * @returns {HTMLElement} - The element containing the extension load errors. |
| 953 | */ | 1104 | */ |
| 954 | function getExtensionLoadErrorsHtml() { | 1105 | function getExtensionLoadErrors() { |
| 955 | if (extensionLoadErrors.size === 0) { | 1106 | if (extensionLoadErrors.size === 0) { |
| 956 | return ''; | 1107 | return document.createElement('div'); |
| 957 | } | 1108 | } |
| 958 | 1109 | ||
| 959 | const container = document.createElement('div'); | 1110 | const container = document.createElement('div'); |
| @@ -965,7 +1116,7 @@ function getExtensionLoadErrorsHtml() { | |||
| 965 | container.appendChild(errorElement); | 1116 | container.appendChild(errorElement); |
| 966 | } | 1117 | } |
| 967 | 1118 | ||
| 968 | return container.outerHTML; | 1119 | return container; |
| 969 | } | 1120 | } |
| 970 | 1121 | ||
| 971 | /** | 1122 | /** |
| @@ -982,22 +1133,35 @@ async function showExtensionsDetails() { | |||
| 982 | initialScrollTop = oldPopup.content.scrollTop; | 1133 | initialScrollTop = oldPopup.content.scrollTop; |
| 983 | await oldPopup.completeCancelled(); | 1134 | await oldPopup.completeCancelled(); |
| 984 | } | 1135 | } |
| 985 | const htmlErrors = getExtensionLoadErrorsHtml(); | 1136 | const errors = getExtensionLoadErrors(); |
| 986 | const htmlDefault = $('<div class="marginBot10"><h3>' + t`Built-in Extensions:` + '</h3></div>'); | 1137 | |
| 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 | const sortOrderKey = 'extensions_sortByName'; | 1166 | const sortOrderKey = 'extensions_sortByName'; |
| 1003 | const sortByName = accountStorage.getItem(sortOrderKey) === 'true'; | 1167 | const sortByName = accountStorage.getItem(sortOrderKey) === 'true'; |
| @@ -1006,16 +1170,16 @@ async function showExtensionsDetails() { | |||
| 1006 | let extensionsToToggle = []; | 1170 | let extensionsToToggle = []; |
| 1007 | 1171 | ||
| 1008 | extensions.forEach(value => { | 1172 | extensions.forEach(value => { |
| 1009 | const { isExternal, extensionHtml } = value; | 1173 | const { isExternal, extensionElement } = value; |
| 1010 | const container = isExternal ? htmlExternal : htmlDefault; | 1174 | const container = isExternal ? externalContainer : defaultContainer; |
| 1011 | container.append(extensionHtml); | 1175 | container.appendChild(extensionElement); |
| 1012 | }); | 1176 | }); |
| 1013 | 1177 | ||
| 1014 | const html = $('<div></div>') | 1178 | const extensionsMenu = $('<div></div>') |
| 1015 | .addClass('extensions_info') | 1179 | .addClass('extensions_info') |
| 1016 | .append(htmlErrors) | 1180 | .append(errors) |
| 1017 | .append(htmlDefault) | 1181 | .append(defaultContainer) |
| 1018 | .append(htmlExternal) | 1182 | .append(externalContainer) |
| 1019 | .append(getModuleInformation()); | 1183 | .append(getModuleInformation()); |
| 1020 | 1184 | ||
| 1021 | { | 1185 | { |
| @@ -1041,23 +1205,24 @@ async function showExtensionsDetails() { | |||
| 1041 | const toggleAllExtensionsButton = document.createElement('div'); | 1205 | const toggleAllExtensionsButton = document.createElement('div'); |
| 1042 | toggleAllExtensionsButton.classList.add('menu_button', 'menu_button_icon'); | 1206 | toggleAllExtensionsButton.classList.add('menu_button', 'menu_button_icon'); |
| 1043 | toggleAllExtensionsButton.title = t`Bulk toggle third-party extensions.`; | 1207 | toggleAllExtensionsButton.title = t`Bulk toggle third-party extensions.`; |
| 1044 | toggleAllExtensionsButton.innerHTML = ` | 1208 | const toggleAllLabel = document.createElement('span'); |
| 1045 | <span>${t`Toggle extensions`}</span> | 1209 | toggleAllLabel.textContent = t`Toggle extensions`; |
| 1046 | <div class="fa-solid fa-circle-info opacity50p"></div> | 1210 | const toggleAllIcon = document.createElement('div'); |
| 1047 | `; | 1211 | toggleAllIcon.classList.add('fa-solid', 'fa-circle-info', 'opacity50p'); |
| 1212 | toggleAllExtensionsButton.append(toggleAllLabel, toggleAllIcon); | ||
| 1048 | 1213 | ||
| 1049 | const restoreBulkToggledExtensionsButton = document.createElement('div'); | 1214 | const restoreBulkToggledExtensionsButton = document.createElement('div'); |
| 1050 | restoreBulkToggledExtensionsButton.classList.add('menu_button', 'menu_button_icon', 'fa-solid', 'fa-arrow-right-rotate', 'displayNone'); | 1215 | restoreBulkToggledExtensionsButton.classList.add('menu_button', 'menu_button_icon', 'fa-solid', 'fa-arrow-right-rotate', 'displayNone'); |
| 1051 | restoreBulkToggledExtensionsButton.title = t`Restore toggled extensions.\n\nIt does not restore extensions toggled individually.`; | 1216 | restoreBulkToggledExtensionsButton.title = t`Restore toggled extensions.\n\nIt does not restore extensions toggled individually.`; |
| 1052 | 1217 | ||
| 1053 | toggleAllExtensionsButton.addEventListener('click', () => { | 1218 | toggleAllExtensionsButton.addEventListener('click', () => { |
| 1054 | extensionsToToggle = onToggleAllExtensions(extensionsToToggle, htmlExternal); | 1219 | extensionsToToggle = onToggleAllExtensions(extensionsToToggle, $(externalContainer)); |
| 1055 | 1220 | ||
| 1056 | for (const extension of extensionsToToggle) { | 1221 | for (const extension of extensionsToToggle) { |
| 1057 | const { name } = extension; | 1222 | const { name } = extension; |
| 1058 | 1223 | ||
| 1059 | htmlExternal | 1224 | $(externalContainer) |
| 1060 | .find(`.extension_block[data-name="${name.replace('third-party', '')}"] .extension_toggle input`) | 1225 | .find(`.extension_block[data-name="${getNameSelector(name)}"] .extension_toggle input`) |
| 1061 | .off('click') | 1226 | .off('click') |
| 1062 | .one('click', () => { | 1227 | .one('click', () => { |
| 1063 | extensionsToToggle = extensionsToToggle.filter(ext => ext.name !== name); | 1228 | extensionsToToggle = extensionsToToggle.filter(ext => ext.name !== name); |
| @@ -1074,8 +1239,8 @@ async function showExtensionsDetails() { | |||
| 1074 | const { name } = extension; | 1239 | const { name } = extension; |
| 1075 | const isDisabled = extension_settings.disabledExtensions.includes(name); | 1240 | const isDisabled = extension_settings.disabledExtensions.includes(name); |
| 1076 | 1241 | ||
| 1077 | htmlExternal | 1242 | $(externalContainer) |
| 1078 | .find(`.extension_block[data-name="${name.replace('third-party', '')}"] .extension_toggle input`) | 1243 | .find(`.extension_block[data-name="${getNameSelector(name)}"] .extension_toggle input`) |
| 1079 | .prop('checked', !isDisabled) | 1244 | .prop('checked', !isDisabled) |
| 1080 | .toggleClass('toggle_enable', isDisabled) | 1245 | .toggleClass('toggle_enable', isDisabled) |
| 1081 | .toggleClass('toggle_disable', !isDisabled) | 1246 | .toggleClass('toggle_disable', !isDisabled) |
| @@ -1099,13 +1264,13 @@ async function showExtensionsDetails() { | |||
| 1099 | }); | 1264 | }); |
| 1100 | 1265 | ||
| 1101 | toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton); | 1266 | toolbar.append(updateAllButton, updateEnabledOnlyButton, flexExpander, sortOrderButton); |
| 1102 | htmlExternal.find('.third_party_toolbar').append(restoreBulkToggledExtensionsButton, toggleAllExtensionsButton); | 1267 | thirdPartyToolbar.append(restoreBulkToggledExtensionsButton, toggleAllExtensionsButton); |
| 1103 | html.prepend(toolbar); | 1268 | extensionsMenu.prepend(toolbar); |
| 1104 | } | 1269 | } |
| 1105 | 1270 | ||
| 1106 | let waitingForSave = false; | 1271 | let waitingForSave = false; |
| 1107 | 1272 | ||
| 1108 | const popup = new Popup(html, POPUP_TYPE.TEXT, '', { | 1273 | const popup = new Popup(extensionsMenu, POPUP_TYPE.TEXT, '', { |
| 1109 | okButton: t`Close`, | 1274 | okButton: t`Close`, |
| 1110 | wide: true, | 1275 | wide: true, |
| 1111 | large: true, | 1276 | large: true, |
| @@ -1147,7 +1312,7 @@ async function showExtensionsDetails() { | |||
| 1147 | }); | 1312 | }); |
| 1148 | popupPromise = popup.show(); | 1313 | popupPromise = popup.show(); |
| 1149 | popup.content.scrollTop = initialScrollTop; | 1314 | popup.content.scrollTop = initialScrollTop; |
| 1150 | checkForUpdatesManual(sortFn, abortController.signal).finally(() => htmlLoading.remove()); | 1315 | checkForUpdatesManual(sortFn, abortController.signal).finally(() => loadingEl.remove()); |
| 1151 | } catch (error) { | 1316 | } catch (error) { |
| 1152 | toastr.error(t`Error loading extensions. See browser console for details.`); | 1317 | toastr.error(t`Error loading extensions. See browser console for details.`); |
| 1153 | console.error(error); | 1318 | console.error(error); |
| @@ -1234,6 +1399,7 @@ async function updateExtension(extensionName, quiet, timeout = null) { | |||
| 1234 | * This function makes a POST request to '/api/extensions/delete' with the extension's name. | 1399 | * This function makes a POST request to '/api/extensions/delete' with the extension's name. |
| 1235 | * If the extension is deleted, it displays a success message. | 1400 | * If the extension is deleted, it displays a success message. |
| 1236 | * Creates a popup for the user to confirm before delete. | 1401 | * Creates a popup for the user to confirm before delete. |
| 1402 | * If the extension has a 'clean' hook, an optional checkbox to also run the cleanup is shown. | ||
| 1237 | */ | 1403 | */ |
| 1238 | async function onDeleteClick() { | 1404 | async function onDeleteClick() { |
| 1239 | const extensionName = $(this).data('name'); | 1405 | const extensionName = $(this).data('name'); |
| @@ -1244,11 +1410,48 @@ async function onDeleteClick() { | |||
| 1244 | return; | 1410 | return; |
| 1245 | } | 1411 | } |
| 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 | if (confirmation === POPUP_RESULT.AFFIRMATIVE) { | 1420 | if (confirmation === POPUP_RESULT.AFFIRMATIVE) { |
| 1250 | await deleteExtension(extensionName); | 1421 | const shouldClean = hasCleanHook && Boolean(popup.inputResults?.get('extension_delete_cleanup')); |
| 1422 | await deleteExtension(extensionName, shouldClean); | ||
| 1423 | } | ||
| 1424 | } | ||
| 1425 | |||
| 1426 | /** | ||
| 1427 | * Handles the click event for the clean button of an extension. | ||
| 1428 | * Runs the extension's 'clean' hook after user confirmation, then reloads the page. | ||
| 1429 | */ | ||
| 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 | async function onBranchClick() { | 1457 | async function onBranchClick() { |
| @@ -1303,8 +1506,8 @@ async function onMoveClick() { | |||
| 1303 | 1506 | ||
| 1304 | const confirmationHeader = t`Move extension`; | 1507 | const confirmationHeader = t`Move extension`; |
| 1305 | const confirmationText = source == 'global' | 1508 | const confirmationText = source == 'global' |
| 1306 | ? t`Are you sure you want to move ${extensionName} to your local extensions? This will make it available only for you.` | 1509 | ? t`Are you sure you want to move ${escapeHtml(extensionName)} to your local extensions? This will make it available only for you.` |
| 1307 | : t`Are you sure you want to move ${extensionName} to the global extensions? This will make it available for all users.`; | 1510 | : t`Are you sure you want to move ${escapeHtml(extensionName)} to the global extensions? This will make it available for all users.`; |
| 1308 | 1511 | ||
| 1309 | const confirmation = await Popup.show.confirm(confirmationHeader, confirmationText); | 1512 | const confirmation = await Popup.show.confirm(confirmationHeader, confirmationText); |
| 1310 | 1513 | ||
| @@ -1353,9 +1556,16 @@ async function moveExtension(extensionName, source, destination) { | |||
| 1353 | /** | 1556 | /** |
| 1354 | * Deletes an extension via the API. | 1557 | * Deletes an extension via the API. |
| 1355 | * @param {string} extensionName Extension name to delete | 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 | export async function deleteExtension(extensionName) { | 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 | try { | 1570 | try { |
| 1361 | await fetch('/api/extensions/delete', { | 1571 | await fetch('/api/extensions/delete', { |
| @@ -1370,6 +1580,9 @@ export async function deleteExtension(extensionName) { | |||
| 1370 | console.error('Error:', error); | 1580 | console.error('Error:', error); |
| 1371 | } | 1581 | } |
| 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 | toastr.success(t`Extension ${extensionName} deleted`); | 1586 | toastr.success(t`Extension ${extensionName} deleted`); |
| 1374 | delay(1000).then(() => location.reload()); | 1587 | delay(1000).then(() => location.reload()); |
| 1375 | } | 1588 | } |
| @@ -1398,6 +1611,9 @@ async function getExtensionVersion(extensionName, abortSignal) { | |||
| 1398 | const data = await response.json(); | 1611 | const data = await response.json(); |
| 1399 | return data; | 1612 | return data; |
| 1400 | } catch (error) { | 1613 | } catch (error) { |
| 1614 | if (error instanceof Error && error.name === 'AbortError') { | ||
| 1615 | return; | ||
| 1616 | } | ||
| 1401 | console.error('Error:', error); | 1617 | console.error('Error:', error); |
| 1402 | } | 1618 | } |
| 1403 | } | 1619 | } |
| @@ -1476,9 +1692,53 @@ async function switchExtensionBranch(extensionName, isGlobal, branch) { | |||
| 1476 | * Installs a third-party extension via the API. | 1692 | * Installs a third-party extension via the API. |
| 1477 | * @param {string} url Extension repository URL | 1693 | * @param {string} url Extension repository URL |
| 1478 | * @param {boolean} global Is the extension global? | 1694 | * @param {boolean} global Is the extension global? |
| 1479 | * @returns {Promise<void>} | 1695 | * @param {string} [branch] Optional branch to install, if not provided the default branch will be used |
| 1696 | * @returns {Promise<boolean>} True if the extension was installed successfully, false otherwise | ||
| 1480 | */ | 1697 | */ |
| 1481 | export async function installExtension(url, global, branch = '') { | 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 | console.debug('Extension installation started', url); | 1742 | console.debug('Extension installation started', url); |
| 1483 | 1743 | ||
| 1484 | toastr.info(t`Please wait...`, t`Installing extension`); | 1744 | toastr.info(t`Please wait...`, t`Installing extension`); |
| @@ -1497,11 +1757,11 @@ export async function installExtension(url, global, branch = '') { | |||
| 1497 | const text = await request.text(); | 1757 | const text = await request.text(); |
| 1498 | toastr.warning(text || request.statusText, t`Extension installation failed`, { timeOut: 5000 }); | 1758 | toastr.warning(text || request.statusText, t`Extension installation failed`, { timeOut: 5000 }); |
| 1499 | console.error('Extension installation failed', request.status, request.statusText, text); | 1759 | console.error('Extension installation failed', request.status, request.statusText, text); |
| 1500 | return; | 1760 | return false; |
| 1501 | } | 1761 | } |
| 1502 | 1762 | ||
| 1503 | const response = await request.json(); | 1763 | const response = await request.json(); |
| 1504 | toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`); | 1764 | toastr.success(t`Extension '${response.display_name}' has been installed successfully!`, t`Extension installation successful`); |
| 1505 | console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`); | 1765 | console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`); |
| 1506 | await loadExtensionSettings({}, false, false); | 1766 | await loadExtensionSettings({}, false, false); |
| 1507 | await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response); | 1767 | await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED, response); |
| @@ -1510,6 +1770,8 @@ export async function installExtension(url, global, branch = '') { | |||
| 1510 | const extensionName = `third-party/${response.folderName}`; | 1770 | const extensionName = `third-party/${response.folderName}`; |
| 1511 | await callExtensionHook(extensionName, 'install'); | 1771 | await callExtensionHook(extensionName, 'install'); |
| 1512 | } | 1772 | } |
| 1773 | |||
| 1774 | return true; | ||
| 1513 | } | 1775 | } |
| 1514 | 1776 | ||
| 1515 | /** | 1777 | /** |
| @@ -1589,7 +1851,11 @@ async function checkForUpdatesManual(sortFn, abortSignal) { | |||
| 1589 | const promise = enqueueVersionCheck(async () => { | 1851 | const promise = enqueueVersionCheck(async () => { |
| 1590 | try { | 1852 | try { |
| 1591 | const data = await getExtensionVersion(externalId, abortSignal); | 1853 | const data = await getExtensionVersion(externalId, abortSignal); |
| 1592 | const extensionBlock = document.querySelector(`.extension_block[data-name="${externalId}"]`); | 1854 | if (!data) { |
| 1855 | return; | ||
| 1856 | } | ||
| 1857 | const selector = getNameSelector(externalId, { prefix: '' }); | ||
| 1858 | const extensionBlock = document.querySelector(`.extension_block[data-name="${selector}"]`); | ||
| 1593 | if (extensionBlock && data) { | 1859 | if (extensionBlock && data) { |
| 1594 | if (data.isUpToDate === false) { | 1860 | if (data.isUpToDate === false) { |
| 1595 | const buttonElement = extensionBlock.querySelector('.btn_update'); | 1861 | const buttonElement = extensionBlock.querySelector('.btn_update'); |
| @@ -1620,6 +1886,18 @@ async function checkForUpdatesManual(sortFn, abortSignal) { | |||
| 1620 | } | 1886 | } |
| 1621 | } | 1887 | } |
| 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 | const versionElement = extensionBlock.querySelector('.extension_version'); | 1901 | const versionElement = extensionBlock.querySelector('.extension_version'); |
| 1624 | if (versionElement) { | 1902 | if (versionElement) { |
| 1625 | versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`; | 1903 | versionElement.textContent += ` (${branch}-${commitHash.substring(0, 7)})`; |
| @@ -1672,6 +1950,9 @@ async function checkForExtensionUpdates(force) { | |||
| 1672 | const promise = enqueueVersionCheck(async () => { | 1950 | const promise = enqueueVersionCheck(async () => { |
| 1673 | try { | 1951 | try { |
| 1674 | const data = await getExtensionVersion(id.replace('third-party', '')); | 1952 | const data = await getExtensionVersion(id.replace('third-party', '')); |
| 1953 | if (!data) { | ||
| 1954 | return; | ||
| 1955 | } | ||
| 1675 | if (!data.isUpToDate) { | 1956 | if (!data.isUpToDate) { |
| 1676 | updatesAvailable.push(manifest.display_name); | 1957 | updatesAvailable.push(manifest.display_name); |
| 1677 | } | 1958 | } |
| @@ -1759,6 +2040,18 @@ export async function runGenerationInterceptors(chat, contextSize, type) { | |||
| 1759 | } | 2040 | } |
| 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 | * Writes a field to the character's data extensions object. | 2055 | * Writes a field to the character's data extensions object. |
| 1763 | * @param {number|string} characterId Index in the character array | 2056 | * @param {number|string} characterId Index in the character array |
| 1764 | * @param {string} key Field name | 2057 | * @param {string} key Field name |
| @@ -1772,13 +2065,23 @@ export async function writeExtensionField(characterId, key, value) { | |||
| 1772 | console.warn('Character not found', characterId); | 2065 | console.warn('Character not found', characterId); |
| 1773 | return; | 2066 | return; |
| 1774 | } | 2067 | } |
| 1775 | const path = `data.extensions.${key}`; | 2068 | const extensionPath = `data.extensions.${key}`; |
| 1776 | setValueByPath(character, path, value); | 2069 | const isUnset = value === UNSET_VALUE; |
| 2070 | |||
| 2071 | if (isUnset) { | ||
| 2072 | deleteValueByPath(character, extensionPath); | ||
| 2073 | } else { | ||
| 2074 | setValueByPath(character, extensionPath, value); | ||
| 2075 | } | ||
| 1777 | 2076 | ||
| 1778 | // Process JSON data | 2077 | // Process JSON data |
| 1779 | if (character.json_data) { | 2078 | if (character.json_data) { |
| 1780 | const jsonData = JSON.parse(character.json_data); | 2079 | const jsonData = JSON.parse(character.json_data); |
| 1781 | setValueByPath(jsonData, path, value); | 2080 | if (isUnset) { |
| 2081 | deleteValueByPath(jsonData, extensionPath); | ||
| 2082 | } else { | ||
| 2083 | setValueByPath(jsonData, extensionPath, value); | ||
| 2084 | } | ||
| 1782 | character.json_data = JSON.stringify(jsonData); | 2085 | character.json_data = JSON.stringify(jsonData); |
| 1783 | 2086 | ||
| 1784 | // Make sure the data doesn't get lost when saving the current character | 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 | * Prompts the user to enter the Git URL of the extension to import. | 2215 | * Prompts the user to enter the Git URL of the extension to import. |
| 1812 | * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension. | 2216 | * After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension. |
| 1813 | * If the extension is imported successfully, a success message is displayed. | 2217 | * If the extension is imported successfully, a success message is displayed. |
| @@ -1853,6 +2257,39 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') { | |||
| 1853 | await installExtension(url, global, branchName); | 2257 | await installExtension(url, global, branchName); |
| 1854 | } | 2258 | } |
| 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 | export async function initExtensions() { | 2293 | export async function initExtensions() { |
| 1857 | await addExtensionsButtonAndMenu(); | 2294 | await addExtensionsButtonAndMenu(); |
| 1858 | $('#extensionsMenuButton').css('display', 'flex'); | 2295 | $('#extensionsMenuButton').css('display', 'flex'); |
| @@ -1865,6 +2302,7 @@ export async function initExtensions() { | |||
| 1865 | $(document).on('click', '.extensions_info .extension_block .toggle_enable', onEnableExtensionClick); | 2302 | $(document).on('click', '.extensions_info .extension_block .toggle_enable', onEnableExtensionClick); |
| 1866 | $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick); | 2303 | $(document).on('click', '.extensions_info .extension_block .btn_update', onUpdateClick); |
| 1867 | $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick); | 2304 | $(document).on('click', '.extensions_info .extension_block .btn_delete', onDeleteClick); |
| 2305 | $(document).on('click', '.extensions_info .extension_block .btn_clean', onCleanClick); | ||
| 1868 | $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick); | 2306 | $(document).on('click', '.extensions_info .extension_block .btn_move', onMoveClick); |
| 1869 | $(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick); | 2307 | $(document).on('click', '.extensions_info .extension_block .btn_branch', onBranchClick); |
| 1870 | 2308 | ||
| @@ -5,12 +5,12 @@ TODO: | |||
| 5 | 5 | ||
| 6 | import { DOMPurify } from '../../../lib.js'; | 6 | import { DOMPurify } from '../../../lib.js'; |
| 7 | import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js'; | 7 | import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js'; |
| 8 | import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; | 8 | import { deleteExtension, EMPTY_AUTHOR, extensionNames, getAuthorFromUrl, getContext, installExtension, renderExtensionTemplateAsync, isOfficialExtension } from '../../extensions.js'; |
| 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; | 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; |
| 10 | import { executeSlashCommandsWithOptions } from '../../slash-commands.js'; | ||
| 11 | import { accountStorage } from '../../util/AccountStorage.js'; | 10 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; | 11 | import { escapeHtml, flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 13 | import { t, translate } from '../../i18n.js'; | 12 | import { t, translate } from '../../i18n.js'; |
| 13 | import { SlashCommandParser } from '/scripts/slash-commands/SlashCommandParser.js'; | ||
| 14 | export { MODULE_NAME }; | 14 | export { MODULE_NAME }; |
| 15 | 15 | ||
| 16 | const MODULE_NAME = 'assets'; | 16 | const MODULE_NAME = 'assets'; |
| @@ -60,219 +60,262 @@ const KNOWN_TYPES = { | |||
| 60 | 'blip': t`Blip sounds`, | 60 | 'blip': t`Blip sounds`, |
| 61 | }; | 61 | }; |
| 62 | 62 | ||
| 63 | const EMPTY_AUTHOR = { | 63 | /** |
| 64 | name: '', | 64 | * Creates the download/delete button element for a single asset, with all interaction handlers attached. |
| 65 | url: '', | 65 | * @param {object} asset The asset data object, containing at least id, name, description and url fields |
| 66 | }; | 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 | ||
| 69 | */ | ||
| 70 | function createAssetButton(asset, assetType, index) { | ||
| 71 | const elemId = `assets_install_${assetType}_${index}`; | ||
| 72 | const element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' }); | ||
| 73 | const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>'); | ||
| 74 | element.append(label); | ||
| 75 | |||
| 76 | console.debug(DEBUG_PREFIX, 'Checking asset', asset.id, asset.url); | ||
| 77 | |||
| 78 | const assetInstall = async function () { | ||
| 79 | element.off('click'); | ||
| 80 | label.removeClass('fa-download'); | ||
| 81 | this.classList.add('asset-download-button-loading'); | ||
| 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 | } | ||
| 91 | label.addClass('fa-check'); | ||
| 92 | this.classList.remove('asset-download-button-loading'); | ||
| 93 | element.on('click', assetDelete); | ||
| 94 | element.on('mouseenter', function () { | ||
| 95 | label.removeClass('fa-check'); | ||
| 96 | label.addClass('fa-trash'); | ||
| 97 | label.addClass('redOverlayGlow'); | ||
| 98 | }).on('mouseleave', function () { | ||
| 99 | label.addClass('fa-check'); | ||
| 100 | label.removeClass('fa-trash'); | ||
| 101 | label.removeClass('redOverlayGlow'); | ||
| 102 | }); | ||
| 103 | }; | ||
| 104 | |||
| 105 | const assetDelete = async function () { | ||
| 106 | if (assetType === 'character') { | ||
| 107 | toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported'); | ||
| 108 | await SlashCommandParser.commands.go.callback(null, asset.id); | ||
| 109 | return; | ||
| 110 | } | ||
| 111 | element.off('click'); | ||
| 112 | await deleteAsset(assetType, asset.id); | ||
| 113 | label.removeClass('fa-check'); | ||
| 114 | label.removeClass('redOverlayGlow'); | ||
| 115 | label.removeClass('fa-trash'); | ||
| 116 | label.addClass('fa-download'); | ||
| 117 | element.off('mouseenter').off('mouseleave'); | ||
| 118 | element.on('click', assetInstall); | ||
| 119 | }; | ||
| 120 | |||
| 121 | if (isAssetInstalled(assetType, asset.id)) { | ||
| 122 | console.debug(DEBUG_PREFIX, 'installed, checked'); | ||
| 123 | label.toggleClass('fa-download'); | ||
| 124 | label.toggleClass('fa-check'); | ||
| 125 | element.on('click', assetDelete); | ||
| 126 | element.on('mouseenter', function () { | ||
| 127 | label.removeClass('fa-check'); | ||
| 128 | label.addClass('fa-trash'); | ||
| 129 | label.addClass('redOverlayGlow'); | ||
| 130 | }).on('mouseleave', function () { | ||
| 131 | label.addClass('fa-check'); | ||
| 132 | label.removeClass('fa-trash'); | ||
| 133 | label.removeClass('redOverlayGlow'); | ||
| 134 | }); | ||
| 135 | } else { | ||
| 136 | console.debug(DEBUG_PREFIX, 'not installed, unchecked'); | ||
| 137 | element.prop('checked', false); | ||
| 138 | element.on('click', assetInstall); | ||
| 139 | } | ||
| 140 | |||
| 141 | return element; | ||
| 142 | } | ||
| 67 | 143 | ||
| 68 | /** | 144 | /** |
| 69 | * Extracts the repository author from a given URL. | 145 | * Creates the full visual block element for a single asset. |
| 70 | * @param {string} url - The URL of the repository. | 146 | * @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. | 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 | ||
| 72 | */ | 150 | */ |
| 73 | function getAuthorFromUrl(url) { | 151 | function createAssetBlock(asset, assetType, element) { |
| 74 | const result = structuredClone(EMPTY_AUTHOR); | 152 | console.debug(DEBUG_PREFIX, 'Created element for ', asset.id); |
| 153 | |||
| 154 | const displayName = DOMPurify.sanitize(asset.name || asset.id); | ||
| 155 | const description = DOMPurify.sanitize(asset.description || ''); | ||
| 156 | const url = isValidUrl(asset.url) ? asset.url : ''; | ||
| 157 | const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`; | ||
| 158 | const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; | ||
| 159 | const toolTag = assetType === 'extension' && asset.tool; | ||
| 160 | const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR; | ||
| 161 | |||
| 162 | const nameSpan = $('<span>', { class: 'asset-name flex-container alignitemscenter' }) | ||
| 163 | .append($('<b>').text(displayName)) | ||
| 164 | .append($('<a>', { class: 'asset_preview', href: url, target: '_blank', title: title }) | ||
| 165 | .append($('<i>', { class: `fa-solid fa-sm ${previewIcon}` }))); | ||
| 166 | |||
| 167 | if (toolTag) { | ||
| 168 | const tagSpan = $('<span>', { class: 'tag', title: t`Adds a function tool` }) | ||
| 169 | .append($('<i>', { class: 'fa-solid fa-sm fa-wrench' })) | ||
| 170 | .append(document.createTextNode(` ${t`Tool`}`)); | ||
| 171 | nameSpan.append(tagSpan); | ||
| 172 | } | ||
| 75 | 173 | ||
| 76 | try { | 174 | nameSpan.append($('<span>', { class: 'expander' })); |
| 77 | const parsedUrl = new URL(url); | 175 | |
| 78 | const pathSegments = parsedUrl.pathname.split('/').filter(s => s.length > 0); | 176 | if (author.name) { |
| 177 | nameSpan.append($('<a>', { href: author.url, target: '_blank', class: 'asset-author-info' }) | ||
| 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); | ||
| 187 | |||
| 188 | assetBlock.find('.tag').on('click', function (e) { | ||
| 189 | const a = document.createElement('a'); | ||
| 190 | a.href = 'https://docs.sillytavern.app/for-contributors/function-calling/'; | ||
| 191 | a.target = '_blank'; | ||
| 192 | a.click(); | ||
| 193 | }); | ||
| 79 | 194 | ||
| 80 | // TODO: Handle non-GitHub URLs if needed | 195 | if (assetType === 'character') { |
| 81 | if (parsedUrl.host === 'github.com' && pathSegments.length >= 2) { | 196 | if (asset.highlight) { |
| 82 | result.name = pathSegments[0]; | 197 | nameSpan.append($('<i>', { class: 'fa-solid fa-sm fa-trophy' })); |
| 83 | result.url = `${parsedUrl.protocol}//${parsedUrl.hostname}/${result.name}`; | ||
| 84 | } | 198 | } |
| 85 | } catch (error) { | 199 | nameSpan.prepend($('<div>', { class: 'avatar' }).append($('<img>', { src: asset.url, alt: displayName }))); |
| 86 | console.debug(DEBUG_PREFIX, 'Error parsing URL:', error); | ||
| 87 | } | 200 | } |
| 88 | 201 | ||
| 89 | return result; | 202 | assetBlock.addClass('asset-block'); |
| 203 | return assetBlock; | ||
| 90 | } | 204 | } |
| 91 | 205 | ||
| 92 | async function downloadAssetsList(url) { | 206 | /** |
| 93 | updateCurrentAssets().then(async function () { | 207 | * Builds and appends the menu section for a single asset type. |
| 94 | fetch(url, { cache: 'no-cache' }) | 208 | * @param {string} assetType Asset type, e.g. 'extension', 'character', 'ambient', 'bgm', 'blip' |
| 95 | .then(response => response.json()) | 209 | * @returns {Promise<void>} |
| 96 | .then(async function (json) { | 210 | */ |
| 97 | availableAssets = {}; | 211 | async function buildAssetTypeSection(assetType) { |
| 98 | $('#assets_menu').empty(); | 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(); | ||
| 99 | 215 | ||
| 100 | console.debug(DEBUG_PREFIX, 'Received assets dictionary', json); | 216 | if (assetType == 'extension') { |
| 217 | assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation')); | ||
| 218 | } | ||
| 101 | 219 | ||
| 102 | for (const i of json) { | 220 | for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a.name.localeCompare(b.name))) { |
| 103 | //console.log(DEBUG_PREFIX,i) | 221 | const i = availableAssets[assetType].indexOf(asset); |
| 104 | if (availableAssets[i.type] === undefined) | 222 | const element = createAssetButton(asset, assetType, i); |
| 105 | availableAssets[i.type] = []; | 223 | const assetBlock = createAssetBlock(asset, assetType, element); |
| 106 | 224 | ||
| 107 | availableAssets[i.type].push(i); | 225 | if (assetType === 'extension') { |
| 108 | } | 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 { | ||
| 231 | assetTypeMenu.append(assetBlock); | ||
| 232 | } | ||
| 233 | } | ||
| 109 | 234 | ||
| 110 | console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets); | 235 | assetTypeMenu.appendTo('#assets_menu'); |
| 111 | // First extensions, then everything else | 236 | assetTypeMenu.on('click', 'a.asset_preview', previewAsset); |
| 112 | const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0); | 237 | } |
| 113 | 238 | ||
| 114 | $('#assets_type_select').empty(); | 239 | /** |
| 115 | $('#assets_search').val(''); | 240 | * Parses the fetched assets JSON and renders the full assets menu. |
| 116 | $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); | 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(); | ||
| 117 | 246 | ||
| 118 | for (const type of assetTypes) { | 247 | console.debug(DEBUG_PREFIX, 'Received assets dictionary', json); |
| 119 | const text = translate(KNOWN_TYPES[type] || type); | ||
| 120 | const option = $('<option />', { value: type, text: text }); | ||
| 121 | $('#assets_type_select').append(option); | ||
| 122 | } | ||
| 123 | 248 | ||
| 124 | if (assetTypes.includes('extension')) { | 249 | for (const i of json) { |
| 125 | $('#assets_type_select').val('extension'); | 250 | if (availableAssets[i.type] === undefined) |
| 126 | } | 251 | availableAssets[i.type] = []; |
| 252 | availableAssets[i.type].push(i); | ||
| 253 | } | ||
| 127 | 254 | ||
| 128 | $('#assets_type_select').off('change').on('change', filterAssets); | 255 | console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets); |
| 129 | $('#assets_search').off('input').on('input', filterAssets); | 256 | // First extensions, then everything else |
| 130 | 257 | const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0); | |
| 131 | for (const assetType of assetTypes) { | ||
| 132 | let assetTypeMenu = $('<div />', { id: 'assets_audio_ambient_div', class: 'assets-list-div' }); | ||
| 133 | assetTypeMenu.attr('data-type', assetType); | ||
| 134 | assetTypeMenu.append(`<h3>${KNOWN_TYPES[assetType] || assetType}</h3>`).hide(); | ||
| 135 | |||
| 136 | if (assetType == 'extension') { | ||
| 137 | assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation')); | ||
| 138 | } | ||
| 139 | |||
| 140 | for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a.name.localeCompare(b.name))) { | ||
| 141 | const i = availableAssets[assetType].indexOf(asset); | ||
| 142 | const elemId = `assets_install_${assetType}_${i}`; | ||
| 143 | let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' }); | ||
| 144 | const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>'); | ||
| 145 | element.append(label); | ||
| 146 | |||
| 147 | //if (DEBUG_TONY_SAMA_FORK_MODE) | ||
| 148 | // asset["url"] = asset["url"].replace("https://github.com/SillyTavern/","https://github.com/Tony-sama/"); // DBG | ||
| 149 | |||
| 150 | console.debug(DEBUG_PREFIX, 'Checking asset', asset.id, asset.url); | ||
| 151 | |||
| 152 | const assetInstall = async function () { | ||
| 153 | element.off('click'); | ||
| 154 | label.removeClass('fa-download'); | ||
| 155 | this.classList.add('asset-download-button-loading'); | ||
| 156 | await installAsset(asset.url, assetType, asset.id); | ||
| 157 | label.addClass('fa-check'); | ||
| 158 | this.classList.remove('asset-download-button-loading'); | ||
| 159 | element.on('click', assetDelete); | ||
| 160 | element.on('mouseenter', function () { | ||
| 161 | label.removeClass('fa-check'); | ||
| 162 | label.addClass('fa-trash'); | ||
| 163 | label.addClass('redOverlayGlow'); | ||
| 164 | }).on('mouseleave', function () { | ||
| 165 | label.addClass('fa-check'); | ||
| 166 | label.removeClass('fa-trash'); | ||
| 167 | label.removeClass('redOverlayGlow'); | ||
| 168 | }); | ||
| 169 | }; | ||
| 170 | |||
| 171 | const assetDelete = async function () { | ||
| 172 | if (assetType === 'character') { | ||
| 173 | toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported'); | ||
| 174 | await executeSlashCommandsWithOptions(`/go ${asset.id}`); | ||
| 175 | return; | ||
| 176 | } | ||
| 177 | element.off('click'); | ||
| 178 | await deleteAsset(assetType, asset.id); | ||
| 179 | label.removeClass('fa-check'); | ||
| 180 | label.removeClass('redOverlayGlow'); | ||
| 181 | label.removeClass('fa-trash'); | ||
| 182 | label.addClass('fa-download'); | ||
| 183 | element.off('mouseenter').off('mouseleave'); | ||
| 184 | element.on('click', assetInstall); | ||
| 185 | }; | ||
| 186 | |||
| 187 | if (isAssetInstalled(assetType, asset.id)) { | ||
| 188 | console.debug(DEBUG_PREFIX, 'installed, checked'); | ||
| 189 | label.toggleClass('fa-download'); | ||
| 190 | label.toggleClass('fa-check'); | ||
| 191 | element.on('click', assetDelete); | ||
| 192 | element.on('mouseenter', function () { | ||
| 193 | label.removeClass('fa-check'); | ||
| 194 | label.addClass('fa-trash'); | ||
| 195 | label.addClass('redOverlayGlow'); | ||
| 196 | }).on('mouseleave', function () { | ||
| 197 | label.addClass('fa-check'); | ||
| 198 | label.removeClass('fa-trash'); | ||
| 199 | label.removeClass('redOverlayGlow'); | ||
| 200 | }); | ||
| 201 | } else { | ||
| 202 | console.debug(DEBUG_PREFIX, 'not installed, unchecked'); | ||
| 203 | element.prop('checked', false); | ||
| 204 | element.on('click', assetInstall); | ||
| 205 | } | ||
| 206 | |||
| 207 | console.debug(DEBUG_PREFIX, 'Created element for ', asset.id); | ||
| 208 | |||
| 209 | const displayName = DOMPurify.sanitize(asset.name || asset.id); | ||
| 210 | const description = DOMPurify.sanitize(asset.description || ''); | ||
| 211 | const url = isValidUrl(asset.url) ? asset.url : ''; | ||
| 212 | const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`; | ||
| 213 | const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; | ||
| 214 | const toolTag = assetType === 'extension' && asset.tool; | ||
| 215 | const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR; | ||
| 216 | |||
| 217 | const assetBlock = $('<i></i>') | ||
| 218 | .append(element) | ||
| 219 | .append(`<div class="flex-container flexFlowColumn flexNoGap wide100p overflowHidden"> | ||
| 220 | <span class="asset-name flex-container alignitemscenter"> | ||
| 221 | <b>${displayName}</b> | ||
| 222 | <a class="asset_preview" href="${url}" target="_blank" title="${title}"> | ||
| 223 | <i class="fa-solid fa-sm ${previewIcon}"></i> | ||
| 224 | </a>` + | ||
| 225 | (toolTag ? '<span class="tag" title="' + t`Adds a function tool` + '"><i class="fa-solid fa-sm fa-wrench"></i> ' + | ||
| 226 | t`Tool` + '</span>' : '') + | ||
| 227 | '<span class="expander"></span>' + | ||
| 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>` : '') + | ||
| 229 | `</span> | ||
| 230 | <small class="asset-description"> | ||
| 231 | ${description} | ||
| 232 | </small> | ||
| 233 | </div>`); | ||
| 234 | |||
| 235 | assetBlock.find('.tag').on('click', function (e) { | ||
| 236 | const a = document.createElement('a'); | ||
| 237 | a.href = 'https://docs.sillytavern.app/for-contributors/function-calling/'; | ||
| 238 | a.target = '_blank'; | ||
| 239 | a.click(); | ||
| 240 | }); | ||
| 241 | |||
| 242 | if (assetType === 'character') { | ||
| 243 | if (asset.highlight) { | ||
| 244 | assetBlock.find('.asset-name').append('<i class="fa-solid fa-sm fa-trophy"></i>'); | ||
| 245 | } | ||
| 246 | assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset.url}" alt="${displayName}"></div>`); | ||
| 247 | } | ||
| 248 | |||
| 249 | assetBlock.addClass('asset-block'); | ||
| 250 | |||
| 251 | assetTypeMenu.append(assetBlock); | ||
| 252 | } | ||
| 253 | assetTypeMenu.appendTo('#assets_menu'); | ||
| 254 | assetTypeMenu.on('click', 'a.asset_preview', previewAsset); | ||
| 255 | } | ||
| 256 | 258 | ||
| 257 | filterAssets(); | 259 | $('#assets_type_select').empty(); |
| 258 | $('#assets_filters').show(); | 260 | $('#assets_search').val(''); |
| 259 | $('#assets_menu').show(); | 261 | $('#assets_type_select').append($('<option />', { value: '', text: t`All` })); |
| 260 | }) | 262 | |
| 261 | .catch((error) => { | 263 | for (const type of assetTypes) { |
| 262 | // Info hint if the user maybe... likely accidently was trying to install an extension and we wanna help guide them? uwu :3 | 264 | const text = translate(KNOWN_TYPES[type] || type); |
| 263 | const installButton = $('#third_party_extension_button'); | 265 | const option = $('<option />', { value: type, text: text }); |
| 264 | flashHighlight(installButton, 10_000); | 266 | $('#assets_type_select').append(option); |
| 265 | toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 }); | 267 | } |
| 266 | 268 | ||
| 267 | // Error logged after, to appear on top | 269 | if (assetTypes.includes('extension')) { |
| 268 | console.error(error); | 270 | $('#assets_type_select').val('extension'); |
| 269 | toastr.error('Problem with assets URL', DEBUG_PREFIX + 'Cannot get assets list'); | 271 | } |
| 270 | $('#assets-connect-button').addClass('fa-plug-circle-exclamation'); | 272 | |
| 271 | $('#assets-connect-button').addClass('redOverlayGlow'); | 273 | $('#assets_type_select').off('change').on('change', filterAssets); |
| 272 | }); | 274 | $('#assets_search').off('input').on('input', filterAssets); |
| 273 | }); | 275 | |
| 276 | for (const assetType of assetTypes) { | ||
| 277 | await buildAssetTypeSection(assetType); | ||
| 278 | } | ||
| 279 | |||
| 280 | filterAssets(); | ||
| 281 | $('#assets_filters').show(); | ||
| 282 | $('#assets_menu').show(); | ||
| 274 | } | 283 | } |
| 275 | 284 | ||
| 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 | ||
| 303 | const installButton = $('#third_party_extension_button'); | ||
| 304 | flashHighlight(installButton, 10_000); | ||
| 305 | toastr.info('Click the flashing button at the top right corner of the menu.', 'Trying to install a custom extension?', { timeOut: 10_000 }); | ||
| 306 | |||
| 307 | // Error logged after, to appear on top | ||
| 308 | console.error(error); | ||
| 309 | toastr.error('Problem with assets URL', 'Cannot get assets list'); | ||
| 310 | $('#assets-connect-button').addClass('fa-plug-circle-exclamation'); | ||
| 311 | $('#assets-connect-button').addClass('redOverlayGlow'); | ||
| 312 | } | ||
| 313 | } | ||
| 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 | function previewAsset(e) { | 319 | function previewAsset(e) { |
| 277 | const href = $(this).attr('href'); | 320 | const href = $(this).attr('href'); |
| 278 | const audioExtensions = ['.mp3', '.ogg', '.wav']; | 321 | const audioExtensions = ['.mp3', '.ogg', '.wav']; |
| @@ -295,6 +338,15 @@ function previewAsset(e) { | |||
| 295 | } | 338 | } |
| 296 | } | 339 | } |
| 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 | function isAssetInstalled(assetType, filename) { | 350 | function isAssetInstalled(assetType, filename) { |
| 299 | let assetList = currentAssets[assetType]; | 351 | let assetList = currentAssets[assetType]; |
| 300 | 352 | ||
| @@ -316,15 +368,22 @@ function isAssetInstalled(assetType, filename) { | |||
| 316 | return false; | 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 | async function installAsset(url, assetType, filename) { | 378 | async function installAsset(url, assetType, filename) { |
| 320 | console.debug(DEBUG_PREFIX, 'Downloading ', url); | 379 | console.debug(DEBUG_PREFIX, 'Downloading ', url); |
| 321 | const category = assetType; | 380 | const category = assetType; |
| 322 | try { | 381 | try { |
| 323 | if (category === 'extension') { | 382 | if (category === 'extension') { |
| 324 | console.debug(DEBUG_PREFIX, 'Installing extension ', url); | 383 | console.debug(DEBUG_PREFIX, 'Installing extension ', url); |
| 325 | await installExtension(url, false); | 384 | const result = await installExtension(url, false); |
| 326 | console.debug(DEBUG_PREFIX, 'Extension installed.'); | 385 | console.debug(DEBUG_PREFIX, 'Extension installed.'); |
| 327 | return; | 386 | return result; |
| 328 | } | 387 | } |
| 329 | 388 | ||
| 330 | const body = { url, category, filename }; | 389 | const body = { url, category, filename }; |
| @@ -340,16 +399,25 @@ async function installAsset(url, assetType, filename) { | |||
| 340 | console.debug(DEBUG_PREFIX, 'Importing character ', filename); | 399 | console.debug(DEBUG_PREFIX, 'Importing character ', filename); |
| 341 | const blob = await result.blob(); | 400 | const blob = await result.blob(); |
| 342 | const file = new File([blob], filename, { type: blob.type }); | 401 | const file = new File([blob], filename, { type: blob.type }); |
| 343 | await processDroppedFiles([file]); | 402 | const fileNameMap = new Map([[file, filename]]); |
| 403 | await processDroppedFiles([file], fileNameMap); | ||
| 344 | console.debug(DEBUG_PREFIX, 'Character downloaded.'); | 404 | console.debug(DEBUG_PREFIX, 'Character downloaded.'); |
| 345 | } | 405 | } |
| 406 | return true; | ||
| 346 | } | 407 | } |
| 408 | return false; | ||
| 347 | } catch (err) { | 409 | } catch (err) { |
| 348 | console.log(err); | 410 | console.log(err); |
| 349 | return []; | 411 | return false; |
| 350 | } | 412 | } |
| 351 | } | 413 | } |
| 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 | async function deleteAsset(assetType, filename) { | 421 | async function deleteAsset(assetType, filename) { |
| 354 | console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename); | 422 | console.debug(DEBUG_PREFIX, 'Deleting ', assetType, filename); |
| 355 | const category = assetType; | 423 | const category = assetType; |
| @@ -358,6 +426,7 @@ async function deleteAsset(assetType, filename) { | |||
| 358 | console.debug(DEBUG_PREFIX, 'Deleting extension ', filename); | 426 | console.debug(DEBUG_PREFIX, 'Deleting extension ', filename); |
| 359 | await deleteExtension(filename); | 427 | await deleteExtension(filename); |
| 360 | console.debug(DEBUG_PREFIX, 'Extension deleted.'); | 428 | console.debug(DEBUG_PREFIX, 'Extension deleted.'); |
| 429 | return true; | ||
| 361 | } | 430 | } |
| 362 | 431 | ||
| 363 | const body = { category, filename }; | 432 | const body = { category, filename }; |
| @@ -369,19 +438,37 @@ async function deleteAsset(assetType, filename) { | |||
| 369 | }); | 438 | }); |
| 370 | if (result.ok) { | 439 | if (result.ok) { |
| 371 | console.debug(DEBUG_PREFIX, 'Deletion success.'); | 440 | console.debug(DEBUG_PREFIX, 'Deletion success.'); |
| 441 | return true; | ||
| 372 | } | 442 | } |
| 443 | return false; | ||
| 373 | } catch (err) { | 444 | } catch (err) { |
| 374 | console.log(err); | 445 | console.log(err); |
| 375 | return []; | 446 | return false; |
| 376 | } | 447 | } |
| 377 | } | 448 | } |
| 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 | async function openCharacterBrowser(forceDefault) { | 455 | async function openCharacterBrowser(forceDefault) { |
| 380 | const url = forceDefault ? ASSETS_JSON_URL : String($('#assets-json-url-field').val()); | 456 | const url = forceDefault ? ASSETS_JSON_URL : String($('#assets-json-url-field').val()); |
| 457 | if (!isValidUrl(url)) { | ||
| 458 | toastr.error('Please enter a valid URL'); | ||
| 459 | return; | ||
| 460 | } | ||
| 381 | const fetchResult = await fetch(url, { cache: 'no-cache' }); | 461 | const fetchResult = await fetch(url, { cache: 'no-cache' }); |
| 462 | if (!fetchResult.ok) { | ||
| 463 | toastr.error('Cannot download the assets list.'); | ||
| 464 | return; | ||
| 465 | } | ||
| 382 | const json = await fetchResult.json(); | 466 | const json = await fetchResult.json(); |
| 383 | const characters = json.filter(x => x.type === 'character'); | 467 | if (!Array.isArray(json)) { |
| 384 | 468 | toastr.error('Assets list is not an array'); | |
| 469 | return; | ||
| 470 | } | ||
| 471 | const characters = json.filter(x => x && x.type === 'character'); | ||
| 385 | if (!characters.length) { | 472 | if (!characters.length) { |
| 386 | toastr.error('No characters found in the assets list', 'Character browser'); | 473 | toastr.error('No characters found in the assets list', 'Character browser'); |
| 387 | return; | 474 | return; |
| @@ -398,12 +485,19 @@ async function openCharacterBrowser(forceDefault) { | |||
| 398 | 485 | ||
| 399 | downloadButton.toggle(!isInstalled).on('click', async () => { | 486 | downloadButton.toggle(!isInstalled).on('click', async () => { |
| 400 | downloadButton.toggleClass('fa-download fa-spinner fa-spin'); | 487 | downloadButton.toggleClass('fa-download fa-spinner fa-spin'); |
| 401 | await installAsset(character.url, 'character', character.id); | 488 | const result = await installAsset(character.url, 'character', character.id); |
| 402 | downloadButton.hide(); | 489 | if (result) { |
| 403 | checkMark.show(); | 490 | downloadButton.hide(); |
| 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 | listElement.append(characterElement); | 502 | listElement.append(characterElement); |
| 409 | } | 503 | } |
| @@ -435,7 +529,7 @@ async function updateCurrentAssets() { | |||
| 435 | //#############################// | 529 | //#############################// |
| 436 | 530 | ||
| 437 | // This function is called when the extension is loaded | 531 | // This function is called when the extension is loaded |
| 438 | jQuery(async () => { | 532 | export async function init() { |
| 439 | // This is an example of loading HTML from a file | 533 | // This is an example of loading HTML from a file |
| 440 | const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {}); | 534 | const windowTemplate = await renderExtensionTemplateAsync(MODULE_NAME, 'window', {}); |
| 441 | const windowHtml = $(windowTemplate); | 535 | const windowHtml = $(windowTemplate); |
| @@ -457,11 +551,16 @@ jQuery(async () => { | |||
| 457 | 551 | ||
| 458 | const connectButton = windowHtml.find('#assets-connect-button'); | 552 | const connectButton = windowHtml.find('#assets-connect-button'); |
| 459 | connectButton.on('click', async function () { | 553 | connectButton.on('click', async function () { |
| 460 | const url = DOMPurify.sanitize(String(assetsJsonUrl.val())); | 554 | const urlString = String(assetsJsonUrl.val()).trim(); |
| 461 | const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; | 555 | if (!isValidUrl(urlString)) { |
| 556 | toastr.error('Please enter a valid URL'); | ||
| 557 | return; | ||
| 558 | } | ||
| 559 | const url = new URL(urlString); | ||
| 560 | const rememberKey = `Assets_SkipConfirm_${getStringHash(url.href)}`; | ||
| 462 | const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; | 561 | const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; |
| 463 | 562 | ||
| 464 | const confirmation = skipConfirm || await Popup.show.confirm(t`Loading Asset List`, '<span>' + t`Are you sure you want to connect to the following url?` + `</span><var>${url}</var>`, { | 563 | const confirmation = skipConfirm || await Popup.show.confirm(t`Loading Asset List`, '<span>' + t`Are you sure you want to connect to the following url?` + `</span><var>${escapeHtml(url.href)}</var>`, { |
| 465 | customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], | 564 | customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], |
| 466 | onClose: popup => { | 565 | onClose: popup => { |
| 467 | if (popup.result) { | 566 | if (popup.result) { |
| @@ -480,7 +579,7 @@ jQuery(async () => { | |||
| 480 | connectButton.addClass('fa-plug-circle-check'); | 579 | connectButton.addClass('fa-plug-circle-check'); |
| 481 | } catch (error) { | 580 | } catch (error) { |
| 482 | console.error('Error:', error); | 581 | console.error('Error:', error); |
| 483 | toastr.error(`Cannot get assets list from ${url}`); | 582 | toastr.error(`Cannot get assets list from ${url.href}`); |
| 484 | connectButton.removeClass('fa-plug-circle-check'); | 583 | connectButton.removeClass('fa-plug-circle-check'); |
| 485 | connectButton.addClass('fa-plug-circle-exclamation'); | 584 | connectButton.addClass('fa-plug-circle-exclamation'); |
| 486 | connectButton.removeClass('redOverlayGlow'); | 585 | connectButton.removeClass('redOverlayGlow'); |
| @@ -496,4 +595,4 @@ jQuery(async () => { | |||
| 496 | eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => { | 595 | eventSource.on(event_types.OPEN_CHARACTER_LIBRARY, async (forceDefault) => { |
| 497 | openCharacterBrowser(forceDefault); | 596 | openCharacterBrowser(forceDefault); |
| 498 | }); | 597 | }); |
| 499 | }); | 598 | } |
| @@ -1,4 +1,22 @@ | |||
| 1 | <div class="assets-list-git"> | 1 | <div class="assets-list-git"> |
| 2 | <span data-i18n="extension_install_1">To download extensions from this page, you need to have </span><a href="https://git-scm.com/downloads" target="_blank">Git</a><span data-i18n="extension_install_2"> installed.</span><br> | 2 | <span data-i18n="extension_install_1">To download extensions from this page, you need to have </span><a href="https://git-scm.com/downloads" target="_blank">Git</a><span data-i18n="extension_install_2"> installed.</span><br> |
| 3 | <span data-i18n="extension_install_3">Click the </span><i class="fa-solid fa-sm fa-arrow-up-right-from-square"></i><span data-i18n="extension_install_4"> icon to visit the Extension's repo for tips on how to use it.</span> | 3 | <span data-i18n="extension_install_3">Click the </span><i class="fa-solid fa-sm fa-arrow-up-right-from-square"></i><span data-i18n="extension_install_4"> icon to visit the Extension's repo for tips on how to use it.</span> |
| 4 | </div> | ||
| 4 | \ No newline at end of file | \ No newline at end of file | |
| 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 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "Keij#6799", | 8 | "author": "Keij#6799", |
| 9 | "version": "0.1.0", | 9 | "version": "0.1.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -27,15 +27,20 @@ | |||
| 27 | margin-bottom: 0.25em; | 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 | .assets-list-div h3 { | 35 | .assets-list-div h3 { |
| 31 | text-transform: capitalize; | 36 | text-transform: capitalize; |
| 32 | } | 37 | } |
| 33 | 38 | ||
| 34 | .assets-list-div i a { | 39 | .assets-list-div .asset-block a { |
| 35 | color: inherit; | 40 | color: inherit; |
| 36 | } | 41 | } |
| 37 | 42 | ||
| 38 | .assets-list-div>i { | 43 | .assets-list-div .asset-block { |
| 39 | display: flex; | 44 | display: flex; |
| 40 | flex-direction: row; | 45 | flex-direction: row; |
| 41 | align-items: center; | 46 | align-items: center; |
| @@ -46,7 +51,7 @@ | |||
| 46 | border-bottom: 1px solid var(--SmartThemeBorderColor); | 51 | border-bottom: 1px solid var(--SmartThemeBorderColor); |
| 47 | } | 52 | } |
| 48 | 53 | ||
| 49 | .assets-list-div i span:first-of-type { | 54 | .assets-list-div .asset-block span:first-of-type { |
| 50 | font-weight: bold; | 55 | font-weight: bold; |
| 51 | } | 56 | } |
| 52 | 57 | ||
| @@ -198,3 +203,11 @@ | |||
| 198 | .asset-name>b { | 203 | .asset-name>b { |
| 199 | font-weight: 600; | 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 | jQuery(async () => { | 246 | export async function init() { |
| 247 | eventSource.on(event_types.APP_READY, cleanUpAttachments); | 247 | eventSource.on(event_types.APP_READY, cleanUpAttachments); |
| 248 | eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments); | 248 | eventSource.on(event_types.CHARACTER_DELETED, cleanUpCharacterAttachments); |
| 249 | eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename); | 249 | eventSource.on(event_types.CHARACTER_RENAMED, handleCharacterRename); |
| @@ -407,4 +407,4 @@ jQuery(async () => { | |||
| 407 | }), | 407 | }), |
| 408 | ], | 408 | ], |
| 409 | })); | 409 | })); |
| 410 | }); | 410 | } |
| @@ -7,5 +7,8 @@ | |||
| 7 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "Cohee1207", | 8 | "author": "Cohee1207", |
| 9 | "version": "1.0.0", | 9 | "version": "1.0.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -1,8 +1,9 @@ | |||
| 1 | import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; | 1 | import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; | 3 | import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParams } from '../../../script.js'; |
| 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; | 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 6 | import { oai_settings } from '../../openai.js'; | ||
| 6 | import { getMultimodalCaption } from '../shared.js'; | 7 | import { getMultimodalCaption } from '../shared.js'; |
| 7 | import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js'; | 8 | import { textgen_types, textgenerationwebui_settings } from '../../textgen-settings.js'; |
| 8 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 9 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| @@ -99,7 +100,7 @@ async function wrapCaptionTemplate(caption) { | |||
| 99 | template += ' {{caption}}'; | 100 | template += ' {{caption}}'; |
| 100 | } | 101 | } |
| 101 | 102 | ||
| 102 | let messageText = substituteParamsExtended(template, { caption: caption }); | 103 | let messageText = substituteParams(template, { dynamicMacros: { caption: caption } }); |
| 103 | 104 | ||
| 104 | if (extension_settings.caption.refine_mode) { | 105 | if (extension_settings.caption.refine_mode) { |
| 105 | messageText = await Popup.show.input( | 106 | messageText = await Popup.show.input( |
| @@ -321,6 +322,8 @@ async function captionMultimodal(base64Img, externalPrompt) { | |||
| 321 | prompt = String(customPrompt).trim(); | 322 | prompt = String(customPrompt).trim(); |
| 322 | } | 323 | } |
| 323 | 324 | ||
| 325 | prompt = substituteParams(prompt); | ||
| 326 | |||
| 324 | const caption = await getMultimodalCaption(base64Img, prompt); | 327 | const caption = await getMultimodalCaption(base64Img, prompt); |
| 325 | return { caption }; | 328 | return { caption }; |
| 326 | } | 329 | } |
| @@ -454,7 +457,7 @@ function isVideoCaptioningAvailable() { | |||
| 454 | return ['google', 'vertexai', 'zai'].includes(extension_settings.caption.multimodal_api); | 457 | return ['google', 'vertexai', 'zai'].includes(extension_settings.caption.multimodal_api); |
| 455 | } | 458 | } |
| 456 | 459 | ||
| 457 | jQuery(async function () { | 460 | export async function init() { |
| 458 | function addSendPictureButton() { | 461 | function addSendPictureButton() { |
| 459 | const sendButton = $(` | 462 | const sendButton = $(` |
| 460 | <div id="send_picture" class="list-group-item flex-container flexGap5"> | 463 | <div id="send_picture" class="list-group-item flex-container flexGap5"> |
| @@ -504,6 +507,7 @@ jQuery(async function () { | |||
| 504 | 'chutes': SECRET_KEYS.CHUTES, | 507 | 'chutes': SECRET_KEYS.CHUTES, |
| 505 | 'electronhub': SECRET_KEYS.ELECTRONHUB, | 508 | 'electronhub': SECRET_KEYS.ELECTRONHUB, |
| 506 | 'pollinations': SECRET_KEYS.POLLINATIONS, | 509 | 'pollinations': SECRET_KEYS.POLLINATIONS, |
| 510 | 'workers_ai': SECRET_KEYS.WORKERS_AI, | ||
| 507 | }; | 511 | }; |
| 508 | 512 | ||
| 509 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { | 513 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -580,7 +584,7 @@ jQuery(async function () { | |||
| 580 | } | 584 | } |
| 581 | 585 | ||
| 582 | async function addRemoteEndpointModels() { | 586 | async function addRemoteEndpointModels() { |
| 583 | async function processEndpoint(api, url) { | 587 | async function processEndpoint(api, url, additionalParams = {}) { |
| 584 | const dropdown = document.getElementById('caption_multimodal_model'); | 588 | const dropdown = document.getElementById('caption_multimodal_model'); |
| 585 | if (!(dropdown instanceof HTMLSelectElement)) { | 589 | if (!(dropdown instanceof HTMLSelectElement)) { |
| 586 | return; | 590 | return; |
| @@ -591,7 +595,8 @@ jQuery(async function () { | |||
| 591 | const options = Array.from(dropdown.options); | 595 | const options = Array.from(dropdown.options); |
| 592 | const response = await fetch(url, { | 596 | const response = await fetch(url, { |
| 593 | method: 'POST', | 597 | method: 'POST', |
| 594 | headers: getRequestHeaders({ omitContentType: true }), | 598 | headers: getRequestHeaders(), |
| 599 | body: JSON.stringify(additionalParams), | ||
| 595 | }); | 600 | }); |
| 596 | if (!response.ok) { | 601 | if (!response.ok) { |
| 597 | return; | 602 | return; |
| @@ -620,6 +625,7 @@ jQuery(async function () { | |||
| 620 | await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral'); | 625 | await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral'); |
| 621 | await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai'); | 626 | await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai'); |
| 622 | await processEndpoint('moonshot', '/api/backends/chat-completions/multimodal-models/moonshot'); | 627 | await processEndpoint('moonshot', '/api/backends/chat-completions/multimodal-models/moonshot'); |
| 628 | await processEndpoint('workers_ai', '/api/backends/chat-completions/multimodal-models/workers_ai', { workers_ai_account_id: oai_settings.workers_ai_account_id }); | ||
| 623 | } | 629 | } |
| 624 | 630 | ||
| 625 | await addSettings(); | 631 | await addSettings(); |
| @@ -804,4 +810,4 @@ jQuery(async function () { | |||
| 804 | })); | 810 | })); |
| 805 | 811 | ||
| 806 | document.body.classList.add('caption'); | 812 | document.body.classList.add('caption'); |
| 807 | }); | 813 | } |
| @@ -9,5 +9,8 @@ | |||
| 9 | "css": "style.css", | 9 | "css": "style.css", |
| 10 | "author": "Cohee#1207", | 10 | "author": "Cohee#1207", |
| 11 | "version": "1.0.0", | 11 | "version": "1.0.0", |
| 12 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 12 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 13 | "hooks": { | ||
| 14 | "activate": "init" | ||
| 15 | } | ||
| 13 | } | 16 | } |
| @@ -20,6 +20,7 @@ | |||
| 20 | <option value="aimlapi">AI/ML API</option> | 20 | <option value="aimlapi">AI/ML API</option> |
| 21 | <option value="chutes">Chutes</option> | 21 | <option value="chutes">Chutes</option> |
| 22 | <option value="anthropic">Claude</option> | 22 | <option value="anthropic">Claude</option> |
| 23 | <option value="workers_ai">Cloudflare Workers AI</option> | ||
| 23 | <option value="cohere">Cohere</option> | 24 | <option value="cohere">Cohere</option> |
| 24 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> | 25 | <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option> |
| 25 | <option value="electronhub">Electron Hub</option> | 26 | <option value="electronhub">Electron Hub</option> |
| @@ -53,7 +54,14 @@ | |||
| 53 | <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option> | 54 | <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option> |
| 54 | <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option> | 55 | <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option> |
| 55 | <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option> | 56 | <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option> |
| 57 | <option data-type="openai" value="gpt-5.5">gpt-5.5</option> | ||
| 58 | <option data-type="openai" value="gpt-5.5-2026-04-23">gpt-5.5-2026-04-23</option> | ||
| 56 | <option data-type="openai" value="gpt-5.4">gpt-5.4</option> | 59 | <option data-type="openai" value="gpt-5.4">gpt-5.4</option> |
| 60 | <option data-type="openai" value="gpt-5.4-2026-03-05">gpt-5.4-2026-03-05</option> | ||
| 61 | <option data-type="openai" value="gpt-5.4-mini">gpt-5.4-mini</option> | ||
| 62 | <option data-type="openai" value="gpt-5.4-mini-2026-03-17">gpt-5.4-mini-2026-03-17</option> | ||
| 63 | <option data-type="openai" value="gpt-5.4-nano">gpt-5.4-nano</option> | ||
| 64 | <option data-type="openai" value="gpt-5.4-nano-2026-03-17">gpt-5.4-nano-2026-03-17</option> | ||
| 57 | <option data-type="openai" value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option> | 65 | <option data-type="openai" value="gpt-5.3-chat-latest">gpt-5.3-chat-latest</option> |
| 58 | <option data-type="openai" value="gpt-5.2">gpt-5.2</option> | 66 | <option data-type="openai" value="gpt-5.2">gpt-5.2</option> |
| 59 | <option data-type="openai" value="gpt-5.2-2025-12-11">gpt-5.2-2025-12-11</option> | 67 | <option data-type="openai" value="gpt-5.2-2025-12-11">gpt-5.2-2025-12-11</option> |
| @@ -88,6 +96,7 @@ | |||
| 88 | <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option> | 96 | <option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option> |
| 89 | <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option> | 97 | <option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option> |
| 90 | <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> | 98 | <option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option> |
| 99 | <option data-type="anthropic" value="claude-opus-4-7">claude-opus-4-7</option> | ||
| 91 | <option data-type="anthropic" value="claude-opus-4-6">claude-opus-4-6</option> | 100 | <option data-type="anthropic" value="claude-opus-4-6">claude-opus-4-6</option> |
| 92 | <option data-type="anthropic" value="claude-opus-4-5">claude-opus-4-5</option> | 101 | <option data-type="anthropic" value="claude-opus-4-5">claude-opus-4-5</option> |
| 93 | <option data-type="anthropic" value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option> | 102 | <option data-type="anthropic" value="claude-opus-4-5-20251101">claude-opus-4-5-20251101</option> |
| @@ -144,6 +153,11 @@ | |||
| 144 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> | 153 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> |
| 145 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> | 154 | <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option> |
| 146 | <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option> | 155 | <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option> |
| 156 | <option data-type="google" value="gemma-4-31b-it">gemma-4-31b-it</option> | ||
| 157 | <option data-type="google" value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option> | ||
| 158 | <option data-type="google" value="gemma-3-27b-it">gemma-3-27b-it</option> | ||
| 159 | <option data-type="google" value="gemma-3-12b-it">gemma-3-12b-it</option> | ||
| 160 | <option data-type="google" value="gemma-3-4b-it">gemma-3-4b-it</option> | ||
| 147 | <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option> | 161 | <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option> |
| 148 | <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option> | 162 | <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option> |
| 149 | <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option> | 163 | <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option> |
| @@ -174,6 +188,7 @@ | |||
| 174 | <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option> | 188 | <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option> |
| 175 | <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option> | 189 | <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option> |
| 176 | <option data-type="ollama" value="llama4">llama4</option> | 190 | <option data-type="ollama" value="llama4">llama4</option> |
| 191 | <option data-type="zai" value="glm-5v-turbo">glm-5v-turbo</option> | ||
| 177 | <option data-type="zai" value="glm-4.6v">glm-4.6v</option> | 192 | <option data-type="zai" value="glm-4.6v">glm-4.6v</option> |
| 178 | <option data-type="zai" value="glm-4.6v-flashx">glm-4.6v-flashx</option> | 193 | <option data-type="zai" value="glm-4.6v-flashx">glm-4.6v-flashx</option> |
| 179 | <option data-type="zai" value="glm-4.6v-flash">glm-4.6v-flash</option> | 194 | <option data-type="zai" value="glm-4.6v-flash">glm-4.6v-flash</option> |
| @@ -1,7 +1,7 @@ | |||
| 1 | import { DOMPurify, Fuse } from '../../../lib.js'; | 1 | import { DOMPurify, Fuse } from '../../../lib.js'; |
| 2 | 2 | ||
| 3 | import { event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js'; | 3 | import { activateSendButtons, deactivateSendButtons, event_types, eventSource, main_api, online_status, saveSettingsDebounced } from '../../../script.js'; |
| 4 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; | 4 | import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; | 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; |
| 6 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 6 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 7 | import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js'; | 7 | import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js'; |
| @@ -9,11 +9,16 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from ' | |||
| 9 | import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 9 | import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 10 | import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js'; | 10 | import { SlashCommandDebugController } from '../../slash-commands/SlashCommandDebugController.js'; |
| 11 | import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; | 11 | import { enumTypes, SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 12 | import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js'; | ||
| 12 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 13 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 13 | import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js'; | 14 | import { SlashCommandScope } from '../../slash-commands/SlashCommandScope.js'; |
| 14 | import { collapseSpaces, getUniqueName, isFalseBoolean, uuidv4, waitUntilCondition } from '../../utils.js'; | 15 | import { collapseSpaces, getUniqueName, isFalseBoolean, isTrueBoolean, uuidv4, waitUntilCondition } from '../../utils.js'; |
| 15 | import { t } from '../../i18n.js'; | 16 | import { t } from '../../i18n.js'; |
| 16 | import { getSecretLabelById } from '../../secrets.js'; | 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 | const MODULE_NAME = 'connection-manager'; | 23 | const MODULE_NAME = 'connection-manager'; |
| 19 | const NONE = '<None>'; | 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 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); | 699 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); |
| 479 | 700 | ||
| 480 | for (const key of Object.keys(DEFAULT_SETTINGS)) { | 701 | for (const key of Object.keys(DEFAULT_SETTINGS)) { |
| @@ -824,4 +1045,114 @@ async function renderDetailsContent(detailsContent) { | |||
| 824 | return JSON.stringify(profile); | 1045 | return JSON.stringify(profile); |
| 825 | }, | 1046 | }, |
| 826 | })); | 1047 | })); |
| 827 | })(); | 1048 | |
| 1049 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1050 | name: 'profile-genstream', | ||
| 1051 | callback: generateStreamCallback, | ||
| 1052 | returns: t`generated text`, | ||
| 1053 | namedArgumentList: [ | ||
| 1054 | new SlashCommandNamedArgument( | ||
| 1055 | 'lock', t`lock user input during generation`, [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), | ||
| 1056 | ), | ||
| 1057 | SlashCommandNamedArgument.fromProps({ | ||
| 1058 | name: 'profile', | ||
| 1059 | description: t`connection profile ID to use for generation`, | ||
| 1060 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1061 | enumProvider: commonEnumProviders.connectionProfiles(), | ||
| 1062 | }), | ||
| 1063 | SlashCommandNamedArgument.fromProps({ | ||
| 1064 | name: 'reasoning', | ||
| 1065 | description: t`include formatted reasoning in the output`, | ||
| 1066 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 1067 | defaultValue: 'false', | ||
| 1068 | enumProvider: commonEnumProviders.boolean('trueFalse'), | ||
| 1069 | }), | ||
| 1070 | SlashCommandNamedArgument.fromProps({ | ||
| 1071 | name: 'system', | ||
| 1072 | description: t`system prompt at the start`, | ||
| 1073 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1074 | }), | ||
| 1075 | SlashCommandNamedArgument.fromProps({ | ||
| 1076 | name: 'length', | ||
| 1077 | description: t`API response length in tokens`, | ||
| 1078 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 1079 | defaultValue: '2048', | ||
| 1080 | }), | ||
| 1081 | SlashCommandNamedArgument.fromProps({ | ||
| 1082 | name: 'generating', | ||
| 1083 | description: t`label/title for the generation display`, | ||
| 1084 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1085 | defaultValue: 'Generating...', | ||
| 1086 | }), | ||
| 1087 | SlashCommandNamedArgument.fromProps({ | ||
| 1088 | name: 'completed', | ||
| 1089 | description: t`updated label/title for when generation completes`, | ||
| 1090 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1091 | defaultValue: 'Generated', | ||
| 1092 | }), | ||
| 1093 | SlashCommandNamedArgument.fromProps({ | ||
| 1094 | name: 'delay', | ||
| 1095 | description: t`auto-hide delay in ms after generation completes. Use "infinite" or negative to keep until manually closed`, | ||
| 1096 | typeList: [ARGUMENT_TYPE.NUMBER], | ||
| 1097 | defaultValue: '3000', | ||
| 1098 | enumList: [ | ||
| 1099 | new SlashCommandEnumValue('infinite', 'Keep the streaming display open until manually closed', 'command', '♾️'), | ||
| 1100 | new SlashCommandEnumValue('any delay in seconds', null, 'number', '⌚', () => true, input => input), | ||
| 1101 | ], | ||
| 1102 | }), | ||
| 1103 | SlashCommandNamedArgument.fromProps({ | ||
| 1104 | name: 'stop', | ||
| 1105 | description: t`show a stop button on the streaming display that aborts generation when clicked`, | ||
| 1106 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 1107 | defaultValue: 'true', | ||
| 1108 | enumProvider: commonEnumProviders.boolean('trueFalse'), | ||
| 1109 | }), | ||
| 1110 | SlashCommandNamedArgument.fromProps({ | ||
| 1111 | name: 'onStop', | ||
| 1112 | description: t`closure to execute when the stop button is clicked (in addition to aborting the request)`, | ||
| 1113 | typeList: [ARGUMENT_TYPE.CLOSURE], | ||
| 1114 | }), | ||
| 1115 | SlashCommandNamedArgument.fromProps({ | ||
| 1116 | name: 'onComplete', | ||
| 1117 | description: t`closure to execute after generation completes successfully`, | ||
| 1118 | typeList: [ARGUMENT_TYPE.CLOSURE], | ||
| 1119 | }), | ||
| 1120 | ], | ||
| 1121 | unnamedArgumentList: [ | ||
| 1122 | SlashCommandArgument.fromProps({ | ||
| 1123 | description: 'prompt', | ||
| 1124 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1125 | isRequired: true, | ||
| 1126 | }), | ||
| 1127 | ], | ||
| 1128 | helpString: ` | ||
| 1129 | <div> | ||
| 1130 | ${t`Generates text using Connection Manager with streaming display. Shows live generation progress including reasoning (thinking) and content.`} | ||
| 1131 | </div> | ||
| 1132 | <div> | ||
| 1133 | ${t`Requires Connection Manager extension. Uses the currently selected profile or the specified profile= argument.`} | ||
| 1134 | </div> | ||
| 1135 | <div> | ||
| 1136 | ${t`Use reasoning=true to include formatted reasoning in the output (using the defined reasoning template). This can be parsed later with /reasoning-parse.`} | ||
| 1137 | </div> | ||
| 1138 | <div> | ||
| 1139 | ${t`Use delay to control auto-hide behavior: number (ms), "infinite", or negative to keep the display open until manually closed. The display shows a green LED when complete.`} | ||
| 1140 | </div> | ||
| 1141 | <div> | ||
| 1142 | ${t`A stop button is shown by default (stop=true). Click it to abort generation and return whatever was streamed so far. Use stop=false to hide the stop button.`} | ||
| 1143 | </div> | ||
| 1144 | <div> | ||
| 1145 | ${t`Use onStop and onComplete closures for custom behavior when generation is stopped or completes.`} | ||
| 1146 | </div> | ||
| 1147 | <div> | ||
| 1148 | ${t`Example: <pre><code>/profile-genstream profile=my-profile-id reasoning=true Summarize the following text</code></pre>`} | ||
| 1149 | </div> | ||
| 1150 | <div> | ||
| 1151 | ${t`Example with infinite display: <pre><code>/profile-genstream delay=infinite Tell me a story</code></pre>`} | ||
| 1152 | </div> | ||
| 1153 | <div> | ||
| 1154 | ${t`Example with custom stop handler: <pre><code>/profile-genstream onStop={: /echo "Generation stopped!" :} Tell me a story</code></pre>`} | ||
| 1155 | </div> | ||
| 1156 | `, | ||
| 1157 | })); | ||
| 1158 | } | ||
| @@ -7,5 +7,8 @@ | |||
| 7 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "Cohee1207", | 8 | "author": "Cohee1207", |
| 9 | "version": "1.0.0", | 9 | "version": "1.0.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, | |||
| 4 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; | 4 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; |
| 5 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; | 5 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 6 | import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js'; | 6 | import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js'; |
| 7 | import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js'; | 7 | import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean, includesIgnoreCaseAndAccents } from '../../utils.js'; |
| 8 | import { hideMutedSprites, selected_group } from '../../group-chats.js'; | 8 | import { hideMutedSprites, selected_group } from '../../group-chats.js'; |
| 9 | import { isJsonSchemaSupported } from '../../textgen-settings.js'; | 9 | import { isJsonSchemaSupported } from '../../textgen-settings.js'; |
| 10 | import { debounce_timeout } from '../../constants.js'; | 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 | * Returns the sprite folder name (including override) for a character. | 816 | * Returns the sprite folder name (including override) for a character. |
| 791 | * @param {object} char Character object | 817 | * @param {object} char Character object |
| 792 | * @param {string} char.avatar Avatar filename with extension | 818 | * @param {string} char.avatar Avatar filename with extension |
| @@ -2140,7 +2166,7 @@ function migrateSettings() { | |||
| 2140 | } | 2166 | } |
| 2141 | } | 2167 | } |
| 2142 | 2168 | ||
| 2143 | (async function () { | 2169 | export async function init() { |
| 2144 | function addExpressionImage() { | 2170 | function addExpressionImage() { |
| 2145 | const html = ` | 2171 | const html = ` |
| 2146 | <div id="expression-wrapper"> | 2172 | <div id="expression-wrapper"> |
| @@ -2317,6 +2343,42 @@ function migrateSettings() { | |||
| 2317 | returns: 'The currently set expression label after setting it.', | 2343 | returns: 'The currently set expression label after setting it.', |
| 2318 | })); | 2344 | })); |
| 2319 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2345 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 2346 | name: 'expression-fallback', | ||
| 2347 | callback: setFallBackExpressionSlashCommand, | ||
| 2348 | unnamedArgumentList: [ | ||
| 2349 | SlashCommandArgument.fromProps({ | ||
| 2350 | description: 'expression label to set', | ||
| 2351 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 2352 | isRequired: false, | ||
| 2353 | enumProvider: () => [ | ||
| 2354 | new SlashCommandEnumValue('#none', 'Sets the fallback expression to no image'), | ||
| 2355 | new SlashCommandEnumValue('#emoji', 'Sets the fallback expression to emojis'), | ||
| 2356 | ...localEnumProviders.expressions(), | ||
| 2357 | ], | ||
| 2358 | }), | ||
| 2359 | ], | ||
| 2360 | helpString: ` | ||
| 2361 | <div> | ||
| 2362 | Gets the currently selected expression fallback for all characters.<br /> | ||
| 2363 | If a valid expression label is sent, it will be set as the new fallback. | ||
| 2364 | </div> | ||
| 2365 | <div> | ||
| 2366 | <strong>Example:</strong> | ||
| 2367 | <ul> | ||
| 2368 | <li> | ||
| 2369 | <pre><code>/expression-fallback | /echo</code></pre> | ||
| 2370 | <small>Returns the currently selected fallback.</small> | ||
| 2371 | </li> | ||
| 2372 | <li> | ||
| 2373 | <pre><code>/expression-fallback admiration</code></pre> | ||
| 2374 | <small>Sets a new expression as fallback.</small> | ||
| 2375 | </li> | ||
| 2376 | </ul> | ||
| 2377 | </div> | ||
| 2378 | `, | ||
| 2379 | returns: 'The currently set expression label after setting it.', | ||
| 2380 | })); | ||
| 2381 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2320 | name: 'expression-folder-override', | 2382 | name: 'expression-folder-override', |
| 2321 | aliases: ['spriteoverride', 'costume'], | 2383 | aliases: ['spriteoverride', 'costume'], |
| 2322 | callback: setSpriteFolderCommand, | 2384 | callback: setSpriteFolderCommand, |
| @@ -2511,4 +2573,4 @@ function migrateSettings() { | |||
| 2511 | </div> | 2573 | </div> |
| 2512 | `, | 2574 | `, |
| 2513 | })); | 2575 | })); |
| 2514 | })(); | 2576 | } |
| @@ -9,5 +9,8 @@ | |||
| 9 | "css": "style.css", | 9 | "css": "style.css", |
| 10 | "author": "Cohee#1207", | 10 | "author": "Cohee#1207", |
| 11 | "version": "1.0.0", | 11 | "version": "1.0.0", |
| 12 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 12 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 13 | "hooks": { | ||
| 14 | "activate": "init" | ||
| 15 | } | ||
| 13 | } | 16 | } |
| @@ -818,7 +818,7 @@ function addGalleryWandButton() { | |||
| 818 | } | 818 | } |
| 819 | 819 | ||
| 820 | // On extension load, ensure the settings are initialized | 820 | // On extension load, ensure the settings are initialized |
| 821 | (function () { | 821 | export async function init() { |
| 822 | initSettings(); | 822 | initSettings(); |
| 823 | eventSource.on(event_types.CHARACTER_RENAMED, (oldAvatar, newAvatar) => { | 823 | eventSource.on(event_types.CHARACTER_RENAMED, (oldAvatar, newAvatar) => { |
| 824 | const context = SillyTavern.getContext(); | 824 | const context = SillyTavern.getContext(); |
| @@ -850,4 +850,4 @@ function addGalleryWandButton() { | |||
| 850 | }), | 850 | }), |
| 851 | ); | 851 | ); |
| 852 | addGalleryWandButton(); | 852 | addGalleryWandButton(); |
| 853 | })(); | 853 | } |
| @@ -8,5 +8,8 @@ | |||
| 8 | "css": "style.css", | 8 | "css": "style.css", |
| 9 | "author": "City-Unit", | 9 | "author": "City-Unit", |
| 10 | "version": "1.5.0", | 10 | "version": "1.5.0", |
| 11 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 11 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 12 | "hooks": { | ||
| 13 | "activate": "init" | ||
| 14 | } | ||
| 12 | } | 15 | } |
| @@ -1063,7 +1063,7 @@ function setupListeners() { | |||
| 1063 | }); | 1063 | }); |
| 1064 | } | 1064 | } |
| 1065 | 1065 | ||
| 1066 | jQuery(async function () { | 1066 | export async function init() { |
| 1067 | async function addExtensionControls() { | 1067 | async function addExtensionControls() { |
| 1068 | const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings }); | 1068 | const settingsHtml = await renderExtensionTemplateAsync('memory', 'settings', { defaultSettings }); |
| 1069 | $('#summarize_container').append(settingsHtml); | 1069 | $('#summarize_container').append(settingsHtml); |
| @@ -1128,4 +1128,4 @@ jQuery(async function () { | |||
| 1128 | () => summaryMacroHandler(), | 1128 | () => summaryMacroHandler(), |
| 1129 | 'Returns the latest memory/summary from the current chat.'); | 1129 | 'Returns the latest memory/summary from the current chat.'); |
| 1130 | } | 1130 | } |
| 1131 | }); | 1131 | } |
| @@ -9,5 +9,8 @@ | |||
| 9 | "css": "style.css", | 9 | "css": "style.css", |
| 10 | "author": "Cohee#1207", | 10 | "author": "Cohee#1207", |
| 11 | "version": "1.0.0", | 11 | "version": "1.0.0", |
| 12 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 12 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 13 | "hooks": { | ||
| 14 | "activate": "init" | ||
| 15 | } | ||
| 13 | } | 16 | } |
| @@ -169,7 +169,7 @@ const handleCharChange = () => { | |||
| 169 | settings.charConfig = charConfig; | 169 | settings.charConfig = charConfig; |
| 170 | }; | 170 | }; |
| 171 | 171 | ||
| 172 | const init = async () => { | 172 | export async function init() { |
| 173 | await loadSets(); | 173 | await loadSets(); |
| 174 | await loadSettings(); | 174 | await loadSettings(); |
| 175 | log('settings: ', settings); | 175 | log('settings: ', settings); |
| @@ -214,7 +214,8 @@ const init = async () => { | |||
| 214 | eventSource.on(event_types.APP_READY, async () => await finalizeInit()); | 214 | eventSource.on(event_types.APP_READY, async () => await finalizeInit()); |
| 215 | 215 | ||
| 216 | globalThis.quickReplyApi = quickReplyApi; | 216 | globalThis.quickReplyApi = quickReplyApi; |
| 217 | }; | 217 | } |
| 218 | |||
| 218 | const finalizeInit = async () => { | 219 | const finalizeInit = async () => { |
| 219 | debug('executing startup'); | 220 | debug('executing startup'); |
| 220 | await autoExec.handleStartup(); | 221 | await autoExec.handleStartup(); |
| @@ -229,7 +230,7 @@ const finalizeInit = async () => { | |||
| 229 | isReady = true; | 230 | isReady = true; |
| 230 | debug('READY'); | 231 | debug('READY'); |
| 231 | }; | 232 | }; |
| 232 | await init(); | 233 | |
| 233 | 234 | ||
| 234 | const purgeCharacterQuickReplySets = ({ character }) => { | 235 | const purgeCharacterQuickReplySets = ({ character }) => { |
| 235 | // Remove the character's Quick Reply Sets from the settings. | 236 | // Remove the character's Quick Reply Sets from the settings. |
| @@ -7,5 +7,8 @@ | |||
| 7 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "RossAscends#1779", | 8 | "author": "RossAscends#1779", |
| 9 | "version": "2.0.0", | 9 | "version": "2.0.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -1639,7 +1639,7 @@ async function checkCharEmbeddedRegexScripts() { | |||
| 1639 | function notifyReloadCurrentChat(presetName) { | 1639 | function notifyReloadCurrentChat(presetName) { |
| 1640 | toastr.info( | 1640 | toastr.info( |
| 1641 | t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>', | 1641 | t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>', |
| 1642 | t`Preset '${presetName}' contains enabled regex scripts`, | 1642 | t`Preset '${escapeHtml(presetName)}' contains enabled regex scripts`, |
| 1643 | { | 1643 | { |
| 1644 | timeOut: 5000, | 1644 | timeOut: 5000, |
| 1645 | escapeHtml: false, | 1645 | escapeHtml: false, |
| @@ -1709,7 +1709,7 @@ function onPresetRenamed({ apiId, oldName, newName }) { | |||
| 1709 | 1709 | ||
| 1710 | // Workaround for loading in sequence with other extensions | 1710 | // Workaround for loading in sequence with other extensions |
| 1711 | // NOTE: Always puts extension at the top of the list, but this is fine since it's static | 1711 | // NOTE: Always puts extension at the top of the list, but this is fine since it's static |
| 1712 | jQuery(async () => { | 1712 | export async function init() { |
| 1713 | if (!Array.isArray(extension_settings.regex)) { | 1713 | if (!Array.isArray(extension_settings.regex)) { |
| 1714 | extension_settings.regex = []; | 1714 | extension_settings.regex = []; |
| 1715 | } | 1715 | } |
| @@ -2069,6 +2069,37 @@ jQuery(async () => { | |||
| 2069 | helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.', | 2069 | helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.', |
| 2070 | })); | 2070 | })); |
| 2071 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2071 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 2072 | name: 'regex-state', | ||
| 2073 | /** @param {object} _ @param {string} name */ | ||
| 2074 | callback: (_, name) => { | ||
| 2075 | if (!name) { | ||
| 2076 | toastr.warning('No regex script name provided.'); | ||
| 2077 | return ''; | ||
| 2078 | } | ||
| 2079 | |||
| 2080 | const scripts = getRegexScripts(); | ||
| 2081 | const script = scripts.find(s => equalsIgnoreCaseAndAccents(s.scriptName, name)); | ||
| 2082 | |||
| 2083 | if (!script) { | ||
| 2084 | toastr.warning(`Regex script "${name}" not found.`); | ||
| 2085 | return ''; | ||
| 2086 | } | ||
| 2087 | |||
| 2088 | return script.disabled ? 'false' : 'true'; | ||
| 2089 | }, | ||
| 2090 | returns: 'true (for enabled) or false (for disabled)', | ||
| 2091 | unnamedArgumentList: [ | ||
| 2092 | SlashCommandArgument.fromProps({ | ||
| 2093 | description: 'script name', | ||
| 2094 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 2095 | isRequired: true, | ||
| 2096 | enumProvider: localEnumProviders.regexScripts, | ||
| 2097 | }), | ||
| 2098 | ], | ||
| 2099 | helpString: 'Returns the current state of a regex script.', | ||
| 2100 | })); | ||
| 2101 | |||
| 2102 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2072 | name: 'regex-toggle', | 2103 | name: 'regex-toggle', |
| 2073 | callback: toggleRegexCallback, | 2104 | callback: toggleRegexCallback, |
| 2074 | returns: 'The name of the script that was toggled', | 2105 | returns: 'The name of the script that was toggled', |
| @@ -2123,4 +2154,4 @@ jQuery(async () => { | |||
| 2123 | 2154 | ||
| 2124 | presetManager.setupEventListeners(); | 2155 | presetManager.setupEventListeners(); |
| 2125 | presetManager.registerSlashCommands(); | 2156 | presetManager.registerSlashCommands(); |
| 2126 | }); | 2157 | } |
| @@ -7,5 +7,8 @@ | |||
| 7 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "kingbri", | 8 | "author": "kingbri", |
| 9 | "version": "1.0.0", | 9 | "version": "1.0.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -1,4 +1,4 @@ | |||
| 1 | import { CONNECT_API_MAP, getRequestHeaders } from '../../script.js'; | 1 | import { CONNECT_API_MAP, createModelIcon, getRequestHeaders } from '../../script.js'; |
| 2 | import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js'; | 2 | import { extension_settings, openThirdPartyExtensionMenu } from '../extensions.js'; |
| 3 | import { t } from '../i18n.js'; | 3 | import { t } from '../i18n.js'; |
| 4 | import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js'; | 4 | import { oai_settings, proxies, ZAI_ENDPOINT } from '../openai.js'; |
| @@ -126,6 +126,10 @@ export async function getMultimodalCaption(base64Img, prompt) { | |||
| 126 | requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON; | 126 | requestBody.zai_endpoint = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON; |
| 127 | } | 127 | } |
| 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 | function getEndpointUrl() { | 133 | function getEndpointUrl() { |
| 130 | switch (extension_settings.caption.multimodal_api) { | 134 | switch (extension_settings.caption.multimodal_api) { |
| 131 | case 'google': | 135 | case 'google': |
| @@ -283,6 +287,10 @@ function throwIfInvalidModel(useReverseProxy) { | |||
| 283 | if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) { | 287 | if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) { |
| 284 | throw new Error('Pollinations API key is not set.'); | 288 | throw new Error('Pollinations API key is not set.'); |
| 285 | } | 289 | } |
| 290 | |||
| 291 | if (multimodalApi === 'workers_ai' && (!secret_state[SECRET_KEYS.WORKERS_AI] || !oai_settings.workers_ai_account_id)) { | ||
| 292 | throw new Error('Workers AI API key or account ID is not set.'); | ||
| 293 | } | ||
| 286 | } | 294 | } |
| 287 | 295 | ||
| 288 | /** | 296 | /** |
| @@ -435,10 +443,12 @@ export class ConnectionManagerRequestService { | |||
| 435 | max_tokens: maxTokens, | 443 | max_tokens: maxTokens, |
| 436 | model: profile.model, | 444 | model: profile.model, |
| 437 | chat_completion_source: selectedApiMap.source, | 445 | chat_completion_source: selectedApiMap.source, |
| 446 | secret_id: profile['secret-id'], | ||
| 438 | custom_url: profile['api-url'], | 447 | custom_url: profile['api-url'], |
| 439 | vertexai_region: profile['api-url'], | 448 | vertexai_region: profile['api-url'], |
| 440 | zai_endpoint: profile['api-url'], | 449 | zai_endpoint: profile['api-url'], |
| 441 | siliconflow_endpoint: profile['api-url'], | 450 | siliconflow_endpoint: profile['api-url'], |
| 451 | minimax_endpoint: profile['api-url'], | ||
| 442 | reverse_proxy: proxyPreset?.url, | 452 | reverse_proxy: proxyPreset?.url, |
| 443 | proxy_password: proxyPreset?.password, | 453 | proxy_password: proxyPreset?.password, |
| 444 | custom_prompt_post_processing: profile['prompt-post-processing'], | 454 | custom_prompt_post_processing: profile['prompt-post-processing'], |
| @@ -459,6 +469,7 @@ export class ConnectionManagerRequestService { | |||
| 459 | model: profile.model, | 469 | model: profile.model, |
| 460 | api_type: selectedApiMap.type, | 470 | api_type: selectedApiMap.type, |
| 461 | api_server: profile['api-url'], | 471 | api_server: profile['api-url'], |
| 472 | secret_id: profile['secret-id'], | ||
| 462 | ...overridePayload, | 473 | ...overridePayload, |
| 463 | }, { | 474 | }, { |
| 464 | instructName: includeInstruct ? profile.instruct : undefined, | 475 | instructName: includeInstruct ? profile.instruct : undefined, |
| @@ -534,6 +545,29 @@ export class ConnectionManagerRequestService { | |||
| 534 | } | 545 | } |
| 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 | * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile] | 571 | * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile] |
| 538 | * @returns {boolean} | 572 | * @returns {boolean} |
| 539 | */ | 573 | */ |
| @@ -2,7 +2,3 @@ | |||
| 2 | <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div> | 2 | <div class="fa-solid fa-paintbrush extensionsMenuExtensionButton" title="Trigger Stable Diffusion" data-i18n="[title]Trigger Stable Diffusion"></div> |
| 3 | <span data-i18n="Generate Image">Generate Image</span> | 3 | <span data-i18n="Generate Image">Generate Image</span> |
| 4 | </div> | 4 | </div> |
| 5 | <div id="sd_stop_gen" class="list-group-item flex-container flexGap5"> | ||
| 6 | <div class="fa-solid fa-circle-stop extensionsMenuExtensionButton" title="Abort current image generation task" data-i18n="[title]Abort current image generation task"></div> | ||
| 7 | <span data-i18n="Stop Image Generation">Stop Image Generation</span> | ||
| 8 | </div> | ||
| @@ -62,18 +62,13 @@ import { t, translate } from '../../i18n.js'; | |||
| 62 | import { oai_settings } from '../../openai.js'; | 62 | import { oai_settings } from '../../openai.js'; |
| 63 | import { power_user } from '/scripts/power-user.js'; | 63 | import { power_user } from '/scripts/power-user.js'; |
| 64 | import { MacrosParser } from '/scripts/macros.js'; | 64 | import { MacrosParser } from '/scripts/macros.js'; |
| 65 | import { ActionLoaderHandle, loader } from '/scripts/action-loader.js'; | ||
| 65 | 66 | ||
| 66 | export { MODULE_NAME }; | 67 | export { MODULE_NAME }; |
| 67 | 68 | ||
| 68 | const MODULE_NAME = 'sd'; | 69 | const MODULE_NAME = 'sd'; |
| 69 | // This is a 1x1 transparent PNG | 70 | // This is a 1x1 transparent PNG |
| 70 | const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; | 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 | const sources = { | 73 | const sources = { |
| 79 | extras: 'extras', | 74 | extras: 'extras', |
| @@ -99,6 +94,7 @@ const sources = { | |||
| 99 | google: 'google', | 94 | google: 'google', |
| 100 | zai: 'zai', | 95 | zai: 'zai', |
| 101 | openrouter: 'openrouter', | 96 | openrouter: 'openrouter', |
| 97 | workersai: 'workersai', | ||
| 102 | }; | 98 | }; |
| 103 | const comfyTypes = { | 99 | const comfyTypes = { |
| 104 | standard: 'standard', | 100 | standard: 'standard', |
| @@ -1747,6 +1743,9 @@ async function loadSamplers() { | |||
| 1747 | case sources.openrouter: | 1743 | case sources.openrouter: |
| 1748 | samplers = ['N/A']; | 1744 | samplers = ['N/A']; |
| 1749 | break; | 1745 | break; |
| 1746 | case sources.workersai: | ||
| 1747 | samplers = ['N/A']; | ||
| 1748 | break; | ||
| 1750 | } | 1749 | } |
| 1751 | 1750 | ||
| 1752 | for (const sampler of samplers) { | 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 | async function loadSdcppSamplers() { | 1845 | async function loadSdcppSamplers() { |
| 1819 | // The sdcpp server does not provide an API for samplers, so we return the known list. | 1846 | // The sdcpp server does not provide an API for samplers, so we return the known list. |
| 1820 | return ['euler', 'euler_a', 'heun', 'dpm2', 'dpm++2s_a', 'dpm++2m', 'dpm++2mv2', 'ipndm', 'ipndm_v', 'lcm', 'ddim_trailing', 'tcd']; | 1847 | return ['euler', 'euler_a', 'heun', 'dpm2', 'dpm++2s_a', 'dpm++2m', 'dpm++2mv2', 'ipndm', 'ipndm_v', 'lcm', 'ddim_trailing', 'tcd']; |
| @@ -1910,7 +1937,7 @@ async function loadModels() { | |||
| 1910 | models = await loadAutoModels(); | 1937 | models = await loadAutoModels(); |
| 1911 | break; | 1938 | break; |
| 1912 | case sources.sdcpp: | 1939 | case sources.sdcpp: |
| 1913 | models = [{ value: '', text: 'N/A' }]; | 1940 | models = await loadSdcppModels(); |
| 1914 | break; | 1941 | break; |
| 1915 | case sources.drawthings: | 1942 | case sources.drawthings: |
| 1916 | models = await loadDrawthingsModels(); | 1943 | models = await loadDrawthingsModels(); |
| @@ -1969,6 +1996,9 @@ async function loadModels() { | |||
| 1969 | case sources.openrouter: | 1996 | case sources.openrouter: |
| 1970 | models = await loadOpenRouterModels(); | 1997 | models = await loadOpenRouterModels(); |
| 1971 | break; | 1998 | break; |
| 1999 | case sources.workersai: | ||
| 2000 | models = await loadWorkersAIImageModels(); | ||
| 2001 | break; | ||
| 1972 | } | 2002 | } |
| 1973 | 2003 | ||
| 1974 | if (extension_settings.sd.source === sources.electronhub) { | 2004 | if (extension_settings.sd.source === sources.electronhub) { |
| @@ -2096,6 +2126,33 @@ async function loadXAIModels() { | |||
| 2096 | ]; | 2126 | ]; |
| 2097 | } | 2127 | } |
| 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 | async function loadPollinationsModels() { | 2156 | async function loadPollinationsModels() { |
| 2100 | $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]); | 2157 | $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]); |
| 2101 | 2158 | ||
| @@ -2325,6 +2382,8 @@ async function loadDrawthingsModels() { | |||
| 2325 | 2382 | ||
| 2326 | async function loadOpenAiModels() { | 2383 | async function loadOpenAiModels() { |
| 2327 | return [ | 2384 | return [ |
| 2385 | { value: 'gpt-image-2', text: 'gpt-image-2' }, | ||
| 2386 | { value: 'gpt-image-2-2026-04-21', text: 'gpt-image-2-2026-04-21' }, | ||
| 2328 | { value: 'gpt-image-1.5', text: 'gpt-image-1.5' }, | 2387 | { value: 'gpt-image-1.5', text: 'gpt-image-1.5' }, |
| 2329 | { value: 'gpt-image-1-mini', text: 'gpt-image-1-mini' }, | 2388 | { value: 'gpt-image-1-mini', text: 'gpt-image-1-mini' }, |
| 2330 | { value: 'gpt-image-1', text: 'gpt-image-1' }, | 2389 | { value: 'gpt-image-1', text: 'gpt-image-1' }, |
| @@ -2581,6 +2640,9 @@ async function loadSchedulers() { | |||
| 2581 | case sources.openrouter: | 2640 | case sources.openrouter: |
| 2582 | schedulers = ['N/A']; | 2641 | schedulers = ['N/A']; |
| 2583 | break; | 2642 | break; |
| 2643 | case sources.workersai: | ||
| 2644 | schedulers = ['N/A']; | ||
| 2645 | break; | ||
| 2584 | } | 2646 | } |
| 2585 | 2647 | ||
| 2586 | for (const scheduler of schedulers) { | 2648 | for (const scheduler of schedulers) { |
| @@ -2701,6 +2763,9 @@ async function loadVaes() { | |||
| 2701 | case sources.openrouter: | 2763 | case sources.openrouter: |
| 2702 | vaes = ['N/A']; | 2764 | vaes = ['N/A']; |
| 2703 | break; | 2765 | break; |
| 2766 | case sources.workersai: | ||
| 2767 | vaes = ['N/A']; | ||
| 2768 | break; | ||
| 2704 | } | 2769 | } |
| 2705 | 2770 | ||
| 2706 | for (const vae of vaes) { | 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 | * Generates an image based on the given trigger word. | 2981 | * Generates an image based on the given trigger word. |
| 2973 | * @param {string} initiator The initiator of the image generation | 2982 | * @param {string} initiator The initiator of the image generation |
| 2974 | * @param {Record<string, object>} args Command arguments | 2983 | * @param {Record<string, object>} args Command arguments |
| @@ -3028,12 +3037,13 @@ async function generatePicture(initiator, args, trigger, message, callback) { | |||
| 3028 | 3037 | ||
| 3029 | const dimensions = setTypeSpecificDimensions(generationType); | 3038 | const dimensions = setTypeSpecificDimensions(generationType); |
| 3030 | const abortController = new AbortController(); | 3039 | const abortController = new AbortController(); |
| 3031 | const stopButton = document.getElementById('sd_stop_gen'); | ||
| 3032 | let negativePromptPrefix = args?.negative || ''; | 3040 | let negativePromptPrefix = args?.negative || ''; |
| 3033 | let imagePath = ''; | 3041 | let imagePath = ''; |
| 3034 | 3042 | ||
| 3035 | const stopListener = () => abortController.abort('Aborted by user'); | 3043 | const stopListener = () => abortController.abort('Aborted by user'); |
| 3036 | 3044 | ||
| 3045 | let loaderHandle = ActionLoaderHandle.EMPTY; | ||
| 3046 | |||
| 3037 | try { | 3047 | try { |
| 3038 | const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); }; | 3048 | const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); }; |
| 3039 | 3049 | ||
| @@ -3046,16 +3056,19 @@ async function generatePicture(initiator, args, trigger, message, callback) { | |||
| 3046 | await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData); | 3056 | await eventSource.emit(event_types.SD_PROMPT_PROCESSING, eventData); |
| 3047 | prompt = eventData.prompt; // Allow extensions to modify the prompt | 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 | if (typeof args?._abortController?.addEventListener === 'function') { | 3059 | if (typeof args?._abortController?.addEventListener === 'function') { |
| 3056 | args._abortController.addEventListener('abort', stopListener); | 3060 | args._abortController.addEventListener('abort', stopListener); |
| 3057 | } | 3061 | } |
| 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 | // generate the image | 3072 | // generate the image |
| 3060 | imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal); | 3073 | imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal); |
| 3061 | } catch (err) { | 3074 | } catch (err) { |
| @@ -3074,10 +3087,8 @@ async function generatePicture(initiator, args, trigger, message, callback) { | |||
| 3074 | toastr.error(errorText, 'Image Generation'); | 3087 | toastr.error(errorText, 'Image Generation'); |
| 3075 | throw new Error(errorText); | 3088 | throw new Error(errorText); |
| 3076 | } finally { | 3089 | } finally { |
| 3077 | $(stopButton).hide(); | ||
| 3078 | restoreOriginalDimensions(dimensions); | 3090 | restoreOriginalDimensions(dimensions); |
| 3079 | eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener); | 3091 | await loaderHandle.hide(); |
| 3080 | endGenerationTracking(); | ||
| 3081 | } | 3092 | } |
| 3082 | 3093 | ||
| 3083 | return imagePath; | 3094 | return imagePath; |
| @@ -3404,6 +3415,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 3404 | case sources.openrouter: | 3415 | case sources.openrouter: |
| 3405 | result = await generateOpenRouterImage(prefixedPrompt, signal); | 3416 | result = await generateOpenRouterImage(prefixedPrompt, signal); |
| 3406 | break; | 3417 | break; |
| 3418 | case sources.workersai: | ||
| 3419 | result = await generateWorkersAIImage(prefixedPrompt, negativePrompt, signal); | ||
| 3420 | break; | ||
| 3407 | } | 3421 | } |
| 3408 | 3422 | ||
| 3409 | if (!result.data) { | 3423 | if (!result.data) { |
| @@ -3858,6 +3872,7 @@ async function generateAutoImage(prompt, negativePrompt, signal) { | |||
| 3858 | async function generateSdcppImage(prompt, negativePrompt, signal) { | 3872 | async function generateSdcppImage(prompt, negativePrompt, signal) { |
| 3859 | const payload = { | 3873 | const payload = { |
| 3860 | url: extension_settings.sd.sdcpp_url, | 3874 | url: extension_settings.sd.sdcpp_url, |
| 3875 | model: extension_settings.sd.model || undefined, | ||
| 3861 | prompt: prompt, | 3876 | prompt: prompt, |
| 3862 | negative_prompt: negativePrompt, | 3877 | negative_prompt: negativePrompt, |
| 3863 | steps: extension_settings.sd.steps, | 3878 | steps: extension_settings.sd.steps, |
| @@ -4062,7 +4077,7 @@ async function generateOpenAiImage(prompt, signal) { | |||
| 4062 | 4077 | ||
| 4063 | const isDalle2 = /dall-e-2/.test(extension_settings.sd.model); | 4078 | const isDalle2 = /dall-e-2/.test(extension_settings.sd.model); |
| 4064 | const isDalle3 = /dall-e-3/.test(extension_settings.sd.model); | 4079 | const isDalle3 = /dall-e-3/.test(extension_settings.sd.model); |
| 4065 | const isGptImg = /gpt-image-(1|latest)/.test(extension_settings.sd.model); | 4080 | const isGptImg = /gpt-image-(1|2|latest)/.test(extension_settings.sd.model); |
| 4066 | const isSora2 = /sora-2/.test(extension_settings.sd.model); | 4081 | const isSora2 = /sora-2/.test(extension_settings.sd.model); |
| 4067 | 4082 | ||
| 4068 | if (isDalle2 && prompt.length > dalle2PromptLimit) { | 4083 | if (isDalle2 && prompt.length > dalle2PromptLimit) { |
| @@ -4719,6 +4734,33 @@ async function generateOpenRouterImage(prompt, signal) { | |||
| 4719 | throw new Error(text); | 4734 | throw new Error(text); |
| 4720 | } | 4735 | } |
| 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 | async function onComfyOpenWorkflowEditorClick() { | 4764 | async function onComfyOpenWorkflowEditorClick() { |
| 4723 | let workflow = await (await fetch('/api/sd/comfy/workflow', { | 4765 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| 4724 | method: 'POST', | 4766 | method: 'POST', |
| @@ -5029,10 +5071,6 @@ async function addSDGenButtons() { | |||
| 5029 | generatePicture(initiators.wand, {}, param); | 5071 | generatePicture(initiators.wand, {}, param); |
| 5030 | } | 5072 | } |
| 5031 | }); | 5073 | }); |
| 5032 | |||
| 5033 | const stopGenButton = $('#sd_stop_gen'); | ||
| 5034 | stopGenButton.hide(); | ||
| 5035 | stopGenButton.on('click', () => eventSource.emit(CUSTOM_STOP_EVENT)); | ||
| 5036 | } | 5074 | } |
| 5037 | 5075 | ||
| 5038 | function isValidState() { | 5076 | function isValidState() { |
| @@ -5091,6 +5129,8 @@ function isValidState() { | |||
| 5091 | return secret_state[SECRET_KEYS.ZAI]; | 5129 | return secret_state[SECRET_KEYS.ZAI]; |
| 5092 | case sources.openrouter: | 5130 | case sources.openrouter: |
| 5093 | return secret_state[SECRET_KEYS.OPENROUTER]; | 5131 | return secret_state[SECRET_KEYS.OPENROUTER]; |
| 5132 | case sources.workersai: | ||
| 5133 | return !!oai_settings.workers_ai_account_id && secret_state[SECRET_KEYS.WORKERS_AI]; | ||
| 5094 | default: | 5134 | default: |
| 5095 | return false; | 5135 | return false; |
| 5096 | } | 5136 | } |
| @@ -5115,10 +5155,6 @@ async function sdMessageButton($icon, { animate } = {}) { | |||
| 5115 | $icon.toggleClass(classes.idle, !isBusy); | 5155 | $icon.toggleClass(classes.idle, !isBusy); |
| 5116 | $icon.toggleClass(classes.busy, isBusy); | 5156 | $icon.toggleClass(classes.busy, isBusy); |
| 5117 | $media.toggleClass(classes.animation, isBusy); | 5157 | $media.toggleClass(classes.animation, isBusy); |
| 5118 | |||
| 5119 | // Update generation counter toast | ||
| 5120 | const trackingFunction = isBusy ? startGenerationTracking : endGenerationTracking; | ||
| 5121 | trackingFunction(); | ||
| 5122 | } | 5158 | } |
| 5123 | 5159 | ||
| 5124 | let $media = jQuery(); | 5160 | let $media = jQuery(); |
| @@ -5233,7 +5269,6 @@ async function writePromptFields(characterId) { | |||
| 5233 | * @returns {Promise<MediaAttachment|null>} - A promise that resolves to the newly generated media attachment, or null if generation failed or was aborted. | 5269 | * @returns {Promise<MediaAttachment|null>} - A promise that resolves to the newly generated media attachment, or null if generation failed or was aborted. |
| 5234 | */ | 5270 | */ |
| 5235 | async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, abortController = new AbortController()) { | 5271 | async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, abortController = new AbortController()) { |
| 5236 | const stopButton = document.getElementById('sd_stop_gen'); | ||
| 5237 | const stopListener = () => abortController.abort('Aborted by user'); | 5272 | const stopListener = () => abortController.abort('Aborted by user'); |
| 5238 | const generationType = mediaAttachment.generation_type ?? message?.extra?.generationType ?? generationMode.FREE; | 5273 | const generationType = mediaAttachment.generation_type ?? message?.extra?.generationType ?? generationMode.FREE; |
| 5239 | let dimensions = { width: extension_settings.sd.width, height: extension_settings.sd.height }; | 5274 | let dimensions = { width: extension_settings.sd.width, height: extension_settings.sd.height }; |
| @@ -5247,9 +5282,9 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, | |||
| 5247 | source: MEDIA_SOURCE.GENERATED, | 5282 | source: MEDIA_SOURCE.GENERATED, |
| 5248 | }; | 5283 | }; |
| 5249 | 5284 | ||
| 5285 | let loaderHandle = ActionLoaderHandle.EMPTY; | ||
| 5286 | |||
| 5250 | try { | 5287 | try { |
| 5251 | $(stopButton).show(); | ||
| 5252 | eventSource.once(CUSTOM_STOP_EVENT, stopListener); | ||
| 5253 | const callback = (_a, _b, _c, _d, _e, _f, format) => { result.type = isVideo(format) ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.IMAGE; }; | 5288 | const callback = (_a, _b, _c, _d, _e, _f, format) => { result.type = isVideo(format) ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.IMAGE; }; |
| 5254 | const savedPrompt = mediaAttachment.title ?? message.extra.title ?? ''; | 5289 | const savedPrompt = mediaAttachment.title ?? message.extra.title ?? ''; |
| 5255 | const savedNegative = mediaAttachment.negative ?? message.extra.negative ?? ''; | 5290 | const savedNegative = mediaAttachment.negative ?? message.extra.negative ?? ''; |
| @@ -5265,6 +5300,15 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, | |||
| 5265 | ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString() | 5300 | ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString() |
| 5266 | : context.characters[context.characterId]?.name; | 5301 | : context.characters[context.characterId]?.name; |
| 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 | onStart(); | 5312 | onStart(); |
| 5269 | result.url = await sendGenerationRequest(generationType, prompt, refineArgs.negative, characterName, callback, initiators.swipe, abortController.signal); | 5313 | result.url = await sendGenerationRequest(generationType, prompt, refineArgs.negative, characterName, callback, initiators.swipe, abortController.signal); |
| 5270 | result.generation_type = generationType; | 5314 | result.generation_type = generationType; |
| @@ -5276,11 +5320,10 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, | |||
| 5276 | } | 5320 | } |
| 5277 | } finally { | 5321 | } finally { |
| 5278 | onComplete(); | 5322 | onComplete(); |
| 5279 | $(stopButton).hide(); | ||
| 5280 | eventSource.removeListener(CUSTOM_STOP_EVENT, stopListener); | ||
| 5281 | restoreOriginalDimensions(dimensions); | 5323 | restoreOriginalDimensions(dimensions); |
| 5282 | extension_settings.sd.seed = extension_settings.sd.original_seed; | 5324 | extension_settings.sd.seed = extension_settings.sd.original_seed; |
| 5283 | delete extension_settings.sd.original_seed; | 5325 | delete extension_settings.sd.original_seed; |
| 5326 | await loaderHandle.hide(); | ||
| 5284 | } | 5327 | } |
| 5285 | 5328 | ||
| 5286 | if (!result.url) { | 5329 | if (!result.url) { |
| @@ -5441,7 +5484,7 @@ function registerFunctionTool() { | |||
| 5441 | }); | 5484 | }); |
| 5442 | } | 5485 | } |
| 5443 | 5486 | ||
| 5444 | jQuery(async () => { | 5487 | export async function init() { |
| 5445 | await addSDGenButtons(); | 5488 | await addSDGenButtons(); |
| 5446 | 5489 | ||
| 5447 | const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null)); | 5490 | const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null)); |
| @@ -5850,6 +5893,9 @@ jQuery(async () => { | |||
| 5850 | extension_settings.sd.google_duration = Number($(this).val()); | 5893 | extension_settings.sd.google_duration = Number($(this).val()); |
| 5851 | saveSettingsDebounced(); | 5894 | saveSettingsDebounced(); |
| 5852 | }); | 5895 | }); |
| 5896 | $('#sd_models_refresh').on('click', async () => { | ||
| 5897 | await loadModels(); | ||
| 5898 | }); | ||
| 5853 | $('#sd_electronhub_quality').on('change', function () { | 5899 | $('#sd_electronhub_quality').on('change', function () { |
| 5854 | extension_settings.sd.electronhub_quality = String($(this).val()); | 5900 | extension_settings.sd.electronhub_quality = String($(this).val()); |
| 5855 | saveSettingsDebounced(); | 5901 | saveSettingsDebounced(); |
| @@ -5893,6 +5939,7 @@ jQuery(async () => { | |||
| 5893 | [sources.aimlapi]: SECRET_KEYS.AIMLAPI, | 5939 | [sources.aimlapi]: SECRET_KEYS.AIMLAPI, |
| 5894 | [sources.comfy]: SECRET_KEYS.COMFY_RUNPOD, | 5940 | [sources.comfy]: SECRET_KEYS.COMFY_RUNPOD, |
| 5895 | [sources.pollinations]: SECRET_KEYS.POLLINATIONS, | 5941 | [sources.pollinations]: SECRET_KEYS.POLLINATIONS, |
| 5942 | [sources.workersai]: SECRET_KEYS.WORKERS_AI, | ||
| 5896 | }; | 5943 | }; |
| 5897 | const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key); | 5944 | const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key); |
| 5898 | if (!shouldReloadOptions) { | 5945 | if (!shouldReloadOptions) { |
| @@ -5948,4 +5995,4 @@ jQuery(async () => { | |||
| 5948 | t`Character's negative Image Generation prompt prefix`, | 5995 | t`Character's negative Image Generation prompt prefix`, |
| 5949 | ); | 5996 | ); |
| 5950 | } | 5997 | } |
| 5951 | }); | 5998 | } |
| @@ -10,5 +10,8 @@ | |||
| 10 | "css": "style.css", | 10 | "css": "style.css", |
| 11 | "author": "Cohee#1207", | 11 | "author": "Cohee#1207", |
| 12 | "version": "1.0.0", | 12 | "version": "1.0.0", |
| 13 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 13 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 14 | "hooks": { | ||
| 15 | "activate": "init" | ||
| 16 | } | ||
| 14 | } | 17 | } |
| @@ -40,10 +40,11 @@ | |||
| 40 | <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span> | 40 | <span data-i18n="sd_minimal_prompt_processing_txt">Minimal response prompt processing</span> |
| 41 | </label> | 41 | </label> |
| 42 | <label for="sd_source" data-i18n="Source">Source</label> | 42 | <label for="sd_source" data-i18n="Source">Source</label> |
| 43 | <select id="sd_source"> | 43 | <select id="sd_source" class="text_pole"> |
| 44 | <option value="aimlapi">AI/ML API</option> | 44 | <option value="aimlapi">AI/ML API</option> |
| 45 | <option value="bfl">BFL (Black Forest Labs)</option> | 45 | <option value="bfl">BFL (Black Forest Labs)</option> |
| 46 | <option value="chutes">Chutes</option> | 46 | <option value="chutes">Chutes</option> |
| 47 | <option value="workersai">Cloudflare Workers AI</option> | ||
| 47 | <option value="comfy">ComfyUI</option> | 48 | <option value="comfy">ComfyUI</option> |
| 48 | <option value="drawthings">DrawThings HTTP API</option> | 49 | <option value="drawthings">DrawThings HTTP API</option> |
| 49 | <option value="electronhub">Electron Hub</option> | 50 | <option value="electronhub">Electron Hub</option> |
| @@ -123,7 +124,7 @@ | |||
| 123 | <div class="flex-container" id="sd_electronhub_quality_row"> | 124 | <div class="flex-container" id="sd_electronhub_quality_row"> |
| 124 | <div class="flex1"> | 125 | <div class="flex1"> |
| 125 | <label for="sd_electronhub_quality" data-i18n="Image Quality">Image Quality</label> | 126 | <label for="sd_electronhub_quality" data-i18n="Image Quality">Image Quality</label> |
| 126 | <select id="sd_electronhub_quality"></select> | 127 | <select id="sd_electronhub_quality" class="text_pole"></select> |
| 127 | </div> | 128 | </div> |
| 128 | </div> | 129 | </div> |
| 129 | </div> | 130 | </div> |
| @@ -191,14 +192,14 @@ | |||
| 191 | <div class="flex-container"> | 192 | <div class="flex-container"> |
| 192 | <div data-sd-model="dall-e-3" class="flex1"> | 193 | <div data-sd-model="dall-e-3" class="flex1"> |
| 193 | <label for="sd_openai_style" data-i18n="Image Style">Image Style</label> | 194 | <label for="sd_openai_style" data-i18n="Image Style">Image Style</label> |
| 194 | <select id="sd_openai_style"> | 195 | <select id="sd_openai_style" class="text_pole"> |
| 195 | <option value="vivid">Vivid</option> | 196 | <option value="vivid">Vivid</option> |
| 196 | <option value="natural">Natural</option> | 197 | <option value="natural">Natural</option> |
| 197 | </select> | 198 | </select> |
| 198 | </div> | 199 | </div> |
| 199 | <div data-sd-model="gpt-image" class="flex1"> | 200 | <div data-sd-model="gpt-image" class="flex1"> |
| 200 | <label for="sd_openai_quality_gpt" data-i18n="Image Quality">Image Quality</label> | 201 | <label for="sd_openai_quality_gpt" data-i18n="Image Quality">Image Quality</label> |
| 201 | <select id="sd_openai_quality_gpt"> | 202 | <select id="sd_openai_quality_gpt" class="text_pole"> |
| 202 | <option value="auto" data-i18n="Auto">Auto</option> | 203 | <option value="auto" data-i18n="Auto">Auto</option> |
| 203 | <option value="low" data-i18n="Low">Low</option> | 204 | <option value="low" data-i18n="Low">Low</option> |
| 204 | <option value="medium" data-i18n="Medium">Medium</option> | 205 | <option value="medium" data-i18n="Medium">Medium</option> |
| @@ -207,7 +208,7 @@ | |||
| 207 | </div> | 208 | </div> |
| 208 | <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1"> | 209 | <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1"> |
| 209 | <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label> | 210 | <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label> |
| 210 | <select id="sd_openai_quality"> | 211 | <select id="sd_openai_quality" class="text_pole"> |
| 211 | <option value="standard" data-i18n="Standard">Standard</option> | 212 | <option value="standard" data-i18n="Standard">Standard</option> |
| 212 | <option value="hd" data-i18n="HD">HD</option> | 213 | <option value="hd" data-i18n="HD">HD</option> |
| 213 | </select> | 214 | </select> |
| @@ -216,7 +217,7 @@ | |||
| 216 | <div data-sd-model="sora-2,sora-2-pro" class="flex-container"> | 217 | <div data-sd-model="sora-2,sora-2-pro" class="flex-container"> |
| 217 | <div class="flex1"> | 218 | <div class="flex1"> |
| 218 | <label for="sd_openai_duration" data-i18n="Duration">Duration</label> | 219 | <label for="sd_openai_duration" data-i18n="Duration">Duration</label> |
| 219 | <select id="sd_openai_duration"> | 220 | <select id="sd_openai_duration" class="text_pole"> |
| 220 | <option value="4" data-i18n="Short (4 seconds)">Short (4 seconds)</option> | 221 | <option value="4" data-i18n="Short (4 seconds)">Short (4 seconds)</option> |
| 221 | <option value="8" data-i18n="Medium (8 seconds)">Medium (8 seconds)</option> | 222 | <option value="8" data-i18n="Medium (8 seconds)">Medium (8 seconds)</option> |
| 222 | <option value="12" data-i18n="Long (16 seconds)">Long (12 seconds)</option> | 223 | <option value="12" data-i18n="Long (16 seconds)">Long (12 seconds)</option> |
| @@ -226,7 +227,7 @@ | |||
| 226 | </div> | 227 | </div> |
| 227 | <div data-sd-source="comfy"> | 228 | <div data-sd-source="comfy"> |
| 228 | <label for="sd_comfy_type">Server Type</label> | 229 | <label for="sd_comfy_type">Server Type</label> |
| 229 | <select id="sd_comfy_type"> | 230 | <select id="sd_comfy_type" class="text_pole"> |
| 230 | <option value="standard">Standard Server</option> | 231 | <option value="standard">Standard Server</option> |
| 231 | <option value="runpod_serverless">RunPod Serverless Endpoint</option> | 232 | <option value="runpod_serverless">RunPod Serverless Endpoint</option> |
| 232 | </select> | 233 | </select> |
| @@ -318,7 +319,7 @@ | |||
| 318 | <div class="flex-container"> | 319 | <div class="flex-container"> |
| 319 | <div class="flex1"> | 320 | <div class="flex1"> |
| 320 | <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label> | 321 | <label for="sd_stability_style_preset" data-i18n="Style Preset">Style Preset</label> |
| 321 | <select id="sd_stability_style_preset"> | 322 | <select id="sd_stability_style_preset" class="text_pole"> |
| 322 | <option value="anime">Anime</option> | 323 | <option value="anime">Anime</option> |
| 323 | <option value="3d-model">3D Model</option> | 324 | <option value="3d-model">3D Model</option> |
| 324 | <option value="analog-film">Analog Film</option> | 325 | <option value="analog-film">Analog Film</option> |
| @@ -375,6 +376,20 @@ | |||
| 375 | </div> | 376 | </div> |
| 376 | </div> | 377 | </div> |
| 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 | <div data-sd-source="google"> | 393 | <div data-sd-source="google"> |
| 379 | <div class="flex-container"> | 394 | <div class="flex-container"> |
| 380 | <div class="flex1"> | 395 | <div class="flex1"> |
| @@ -394,7 +409,7 @@ | |||
| 394 | </label> | 409 | </label> |
| 395 | <div class="flex1"> | 410 | <div class="flex1"> |
| 396 | <label for="sd_google_duration" data-i18n="Duration (Veo)">Duration (Veo)</label> | 411 | <label for="sd_google_duration" data-i18n="Duration (Veo)">Duration (Veo)</label> |
| 397 | <select id="sd_google_duration"> | 412 | <select id="sd_google_duration" class="text_pole"> |
| 398 | <option value="4">Short (4 seconds)</option> | 413 | <option value="4">Short (4 seconds)</option> |
| 399 | <option value="6">Medium (6 seconds)</option> | 414 | <option value="6">Medium (6 seconds)</option> |
| 400 | <option value="8">Long (8 seconds)</option> | 415 | <option value="8">Long (8 seconds)</option> |
| @@ -405,37 +420,42 @@ | |||
| 405 | 420 | ||
| 406 | <div class="flex-container"> | 421 | <div class="flex-container"> |
| 407 | <div class="flex1"> | 422 | <div class="flex1"> |
| 408 | <label for="sd_model" data-i18n="Model">Model</label> | 423 | <label for="sd_model" class="flex-container justifySpaceBetween"> |
| 409 | <select id="sd_model"></select> | 424 | <span data-i18n="Model">Model</span> |
| 425 | <div id="sd_models_refresh" class="right_menu_button margin0 padding0" title="Refresh model list" data-i18n="[title]Refresh model list"> | ||
| 426 | <i class="fa-solid fa-sync"></i> | ||
| 427 | </div> | ||
| 428 | </label> | ||
| 429 | <select id="sd_model" class="text_pole"></select> | ||
| 410 | </div> | 430 | </div> |
| 411 | 431 | ||
| 412 | <div class="flex1" data-sd-source="comfy,auto"> | 432 | <div class="flex1" data-sd-source="comfy,auto"> |
| 413 | <label for="sd_vae">VAE</label> | 433 | <label for="sd_vae">VAE</label> |
| 414 | <select id="sd_vae"></select> | 434 | <select id="sd_vae" class="text_pole"></select> |
| 415 | </div> | 435 | </div> |
| 416 | </div> | 436 | </div> |
| 417 | 437 | ||
| 418 | <div class="flex-container"> | 438 | <div class="flex-container"> |
| 419 | <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp"> | 439 | <div class="flex1" data-sd-source="extras,horde,auto,drawthings,novel,vlad,comfy,sdcpp"> |
| 420 | <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label> | 440 | <label for="sd_sampler" data-i18n="Sampling method">Sampling method</label> |
| 421 | <select id="sd_sampler"></select> | 441 | <select id="sd_sampler" class="text_pole"></select> |
| 422 | </div> | 442 | </div> |
| 423 | 443 | ||
| 424 | <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp"> | 444 | <div class="flex1" data-sd-source="comfy,auto,novel,sdcpp"> |
| 425 | <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label> | 445 | <label for="sd_scheduler" data-i18n="Scheduler">Scheduler</label> |
| 426 | <select id="sd_scheduler"></select> | 446 | <select id="sd_scheduler" class="text_pole"></select> |
| 427 | </div> | 447 | </div> |
| 428 | </div> | 448 | </div> |
| 429 | 449 | ||
| 430 | <div class="flex-container"> | 450 | <div class="flex-container"> |
| 431 | <div class="flex1"> | 451 | <div class="flex1"> |
| 432 | <label for="sd_resolution" data-i18n="Resolution">Resolution</label> | 452 | <label for="sd_resolution" data-i18n="Resolution">Resolution</label> |
| 433 | <select id="sd_resolution"><!-- Populated in JS --></select> | 453 | <select id="sd_resolution" class="text_pole"><!-- Populated in JS --></select> |
| 434 | </div> | 454 | </div> |
| 435 | 455 | ||
| 436 | <div class="flex1" data-sd-source="auto,vlad,drawthings"> | 456 | <div class="flex1" data-sd-source="auto,vlad,drawthings"> |
| 437 | <label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label> | 457 | <label for="sd_hr_upscaler" data-i18n="Upscaler">Upscaler</label> |
| 438 | <select id="sd_hr_upscaler"></select> | 458 | <select id="sd_hr_upscaler" class="text_pole"></select> |
| 439 | </div> | 459 | </div> |
| 440 | </div> | 460 | </div> |
| 441 | 461 | ||
| @@ -1,7 +1,3 @@ | |||
| 1 | .sd_settings label:not(.checkbox_label) { | ||
| 2 | display: block; | ||
| 3 | } | ||
| 4 | |||
| 5 | #sd_dropdown { | 1 | #sd_dropdown { |
| 6 | z-index: 30000; | 2 | z-index: 30000; |
| 7 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); | 3 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); |
| @@ -101,7 +101,7 @@ async function doCount() { | |||
| 101 | return count; | 101 | return count; |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | jQuery(() => { | 104 | export function init() { |
| 105 | const buttonHtml = ` | 105 | const buttonHtml = ` |
| 106 | <div id="token_counter" class="list-group-item flex-container flexGap5"> | 106 | <div id="token_counter" class="list-group-item flex-container flexGap5"> |
| 107 | <div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>` + | 107 | <div class="fa-solid fa-1 extensionsMenuExtensionButton" /></div>` + |
| @@ -115,4 +115,4 @@ jQuery(() => { | |||
| 115 | returns: 'number of tokens', | 115 | returns: 'number of tokens', |
| 116 | helpString: 'Counts the number of tokens in the current chat.', | 116 | helpString: 'Counts the number of tokens in the current chat.', |
| 117 | })); | 117 | })); |
| 118 | }); | 118 | } |
| @@ -7,5 +7,8 @@ | |||
| 7 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "Cohee#1207", | 8 | "author": "Cohee#1207", |
| 9 | "version": "1.0.0", | 9 | "version": "1.0.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -707,7 +707,7 @@ const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayTe | |||
| 707 | 707 | ||
| 708 | globalThis.translate = translate; | 708 | globalThis.translate = translate; |
| 709 | 709 | ||
| 710 | jQuery(async () => { | 710 | export async function init() { |
| 711 | const html = await renderExtensionTemplateAsync('translate', 'index'); | 711 | const html = await renderExtensionTemplateAsync('translate', 'index'); |
| 712 | const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons'); | 712 | const buttonHtml = await renderExtensionTemplateAsync('translate', 'buttons'); |
| 713 | 713 | ||
| @@ -801,4 +801,4 @@ jQuery(async () => { | |||
| 801 | }, | 801 | }, |
| 802 | returns: ARGUMENT_TYPE.STRING, | 802 | returns: ARGUMENT_TYPE.STRING, |
| 803 | })); | 803 | })); |
| 804 | }); | 804 | } |
| @@ -7,5 +7,8 @@ | |||
| 7 | "css": "style.css", | 7 | "css": "style.css", |
| 8 | "author": "Cohee#1207", | 8 | "author": "Cohee#1207", |
| 9 | "version": "1.0.0", | 9 | "version": "1.0.0", |
| 10 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 10 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 11 | "hooks": { | ||
| 12 | "activate": "init" | ||
| 13 | } | ||
| 11 | } | 14 | } |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js'; | 1 | import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js'; |
| 2 | import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js'; | 3 | import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js'; |
| 4 | import { accountStorage } from '../../util/AccountStorage.js'; | ||
| 4 | import { EdgeTtsProvider } from './edge.js'; | 5 | import { EdgeTtsProvider } from './edge.js'; |
| 5 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; | 6 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; |
| 6 | import { SileroTtsProvider } from './silerotts.js'; | 7 | import { SileroTtsProvider } from './silerotts.js'; |
| @@ -165,7 +166,7 @@ async function onNarrateOneMessage() { | |||
| 165 | } | 166 | } |
| 166 | 167 | ||
| 167 | resetTtsPlayback(); | 168 | resetTtsPlayback(); |
| 168 | processAndQueueTtsMessage(message, Number(id)); | 169 | processAndQueueTtsMessage(message, Number(id), { manual: true }); |
| 169 | moduleWorker(); | 170 | moduleWorker(); |
| 170 | } | 171 | } |
| 171 | 172 | ||
| @@ -186,13 +187,20 @@ async function onNarrateText(args, text) { | |||
| 186 | ? voiceMap[DEFAULT_VOICE_MARKER] | 187 | ? voiceMap[DEFAULT_VOICE_MARKER] |
| 187 | : voiceMap[name]; | 188 | : voiceMap[name]; |
| 188 | 189 | ||
| 189 | if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) { | 190 | if (voiceMapEntry === DISABLED_VOICE_MARKER) { |
| 191 | toastr.info(`TTS voice for ${name} is disabled.`); | ||
| 192 | await initVoiceMap(false); | ||
| 193 | return; | ||
| 194 | } | ||
| 195 | |||
| 196 | if (!voiceMapEntry) { | ||
| 190 | toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`); | 197 | toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`); |
| 198 | await initVoiceMap(false); | ||
| 191 | return; | 199 | return; |
| 192 | } | 200 | } |
| 193 | 201 | ||
| 194 | resetTtsPlayback(); | 202 | resetTtsPlayback(); |
| 195 | processAndQueueTtsMessage({ mes: text, name: name }); | 203 | processAndQueueTtsMessage({ mes: text, name: name }, null, { manual: true }); |
| 196 | await moduleWorker(); | 204 | await moduleWorker(); |
| 197 | 205 | ||
| 198 | // Return back to the chat voices | 206 | // Return back to the chat voices |
| @@ -245,7 +253,7 @@ function isTtsProcessing() { | |||
| 245 | } | 253 | } |
| 246 | 254 | ||
| 247 | /** | 255 | /** |
| 248 | * @typedef {ChatMessage & { id?: number }} TtsMessage | 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 | * (if enabled) and adds each part to the TTS job queue. | 261 | * (if enabled) and adds each part to the TTS job queue. |
| 254 | * @param {ChatMessage} message - The message object to be processed. | 262 | * @param {ChatMessage} message - The message object to be processed. |
| 255 | * @param {number|null} [messageId=null] - The chat message index to associate with TTS events. | 263 | * @param {number|null} [messageId=null] - The chat message index to associate with TTS events. |
| 264 | * @param {object} [options={}] - Additional options for processing. | ||
| 265 | * @param {boolean} [options.manual=false] - Whether this TTS job was manually triggered (e.g., from the UI) rather than automatically from a new chat message. | ||
| 256 | * @returns {void} | 266 | * @returns {void} |
| 257 | */ | 267 | */ |
| 258 | function processAndQueueTtsMessage(message, messageId = null) { | 268 | function processAndQueueTtsMessage(message, messageId = null, { manual = false } = {}) { |
| 259 | /** @type {TtsMessage} */ | 269 | /** @type {TtsMessage} */ |
| 260 | const clone = structuredClone(message); | 270 | const clone = structuredClone(message); |
| 261 | clone.id = messageId ?? null; | 271 | clone.id = messageId ?? null; |
| 272 | clone.manual = manual ?? false; | ||
| 262 | 273 | ||
| 263 | if (!extension_settings.tts.narrate_by_paragraphs) { | 274 | if (!extension_settings.tts.narrate_by_paragraphs) { |
| 264 | ttsJobQueue.push(clone); | 275 | ttsJobQueue.push(clone); |
| @@ -408,9 +419,10 @@ function onAudioControlClicked() { | |||
| 408 | // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful | 419 | // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful |
| 409 | if (!audioElement.paused || isTtsProcessing()) { | 420 | if (!audioElement.paused || isTtsProcessing()) { |
| 410 | resetTtsPlayback(); | 421 | resetTtsPlayback(); |
| 411 | } else { | 422 | } else if (context?.chat?.length > 0) { |
| 412 | // Default play behavior if not processing or playing is to play the last message. | 423 | // Default play behavior if not processing or playing is to play the last message. |
| 413 | processAndQueueTtsMessage(context.chat[context.chat.length - 1]); | 424 | const id = context.chat.length - 1; |
| 425 | processAndQueueTtsMessage(context.chat[id], id, { manual: true }); | ||
| 414 | } | 426 | } |
| 415 | updateUiAudioPlayState(); | 427 | updateUiAudioPlayState(); |
| 416 | } | 428 | } |
| @@ -481,8 +493,10 @@ async function processAudioJobQueue() { | |||
| 481 | // TTS Control // | 493 | // TTS Control // |
| 482 | //################// | 494 | //################// |
| 483 | 495 | ||
| 496 | /** @type {TtsMessage[]} */ | ||
| 484 | const ttsJobQueue = []; | 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 | function completeTtsJob() { | 501 | function completeTtsJob() { |
| 488 | console.info(`Current TTS job for ${currentTtsJob?.name} completed.`); | 502 | console.info(`Current TTS job for ${currentTtsJob?.name} completed.`); |
| @@ -621,7 +635,18 @@ async function processTtsQueue() { | |||
| 621 | 635 | ||
| 622 | const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey]; | 636 | const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey]; |
| 623 | 637 | ||
| 624 | if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) { | 638 | if (voiceMapEntry === DISABLED_VOICE_MARKER) { |
| 639 | const storageKey = `tts_disabled_warned_${char}`; | ||
| 640 | if (!accountStorage.getItem(storageKey) || currentTtsJob.manual) { | ||
| 641 | accountStorage.setItem(storageKey, 'true'); | ||
| 642 | toastr.info(`TTS voice for ${char} is disabled.`); | ||
| 643 | } | ||
| 644 | currentTtsJob = null; | ||
| 645 | setTimeout(() => wrapper.update(), 0); | ||
| 646 | return; | ||
| 647 | } | ||
| 648 | |||
| 649 | if (!voiceMapEntry) { | ||
| 625 | throw `${char} not in voicemap. Configure character in extension settings voice map`; | 650 | throw `${char} not in voicemap. Configure character in extension settings voice map`; |
| 626 | } | 651 | } |
| 627 | 652 | ||
| @@ -649,7 +674,6 @@ async function processTtsQueue() { | |||
| 649 | text = substituteParams(text); | 674 | text = substituteParams(text); |
| 650 | 675 | ||
| 651 | if (extension_settings.tts.skip_codeblocks) { | 676 | if (extension_settings.tts.skip_codeblocks) { |
| 652 | text = text.replace(/^\s{4}.*$/gm, '').trim(); | ||
| 653 | text = text.replace(/```.*?```/gs, '').trim(); | 677 | text = text.replace(/```.*?```/gs, '').trim(); |
| 654 | text = text.replace(/~~~.*?~~~/gs, '').trim(); | 678 | text = text.replace(/~~~.*?~~~/gs, '').trim(); |
| 655 | } | 679 | } |
| @@ -724,6 +748,7 @@ async function processTtsQueue() { | |||
| 724 | mes: currentTtsJob.mes, | 748 | mes: currentTtsJob.mes, |
| 725 | extra: currentTtsJob.extra, | 749 | extra: currentTtsJob.extra, |
| 726 | id: currentTtsJob.id, | 750 | id: currentTtsJob.id, |
| 751 | manual: currentTtsJob.manual, | ||
| 727 | }; | 752 | }; |
| 728 | ttsJobQueue.unshift(segmentJob); | 753 | ttsJobQueue.unshift(segmentJob); |
| 729 | } | 754 | } |
| @@ -824,7 +849,7 @@ async function playFullConversation() { | |||
| 824 | 849 | ||
| 825 | context.chat.forEach((msg, i) => { | 850 | context.chat.forEach((msg, i) => { |
| 826 | if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') { | 851 | if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') { |
| 827 | processAndQueueTtsMessage(msg, i); | 852 | processAndQueueTtsMessage(msg, i, { manual: false }); |
| 828 | } | 853 | } |
| 829 | }); | 854 | }); |
| 830 | 855 | ||
| @@ -1164,7 +1189,7 @@ async function onMessageEvent(messageId, lastCharIndex) { | |||
| 1164 | message.id = messageId; | 1189 | message.id = messageId; |
| 1165 | ttsJobQueue.push(message); | 1190 | ttsJobQueue.push(message); |
| 1166 | } else { | 1191 | } else { |
| 1167 | processAndQueueTtsMessage(message, messageId); | 1192 | processAndQueueTtsMessage(message, messageId, { manual: false }); |
| 1168 | } | 1193 | } |
| 1169 | } | 1194 | } |
| 1170 | 1195 | ||
| @@ -1506,7 +1531,7 @@ async function initVoiceMapInternal(unrestricted) { | |||
| 1506 | updateVoiceMap(); | 1531 | updateVoiceMap(); |
| 1507 | } | 1532 | } |
| 1508 | 1533 | ||
| 1509 | jQuery(async function () { | 1534 | export async function init() { |
| 1510 | async function addExtensionControls() { | 1535 | async function addExtensionControls() { |
| 1511 | const settingsHtml = $(await renderExtensionTemplateAsync('tts', 'settings')); | 1536 | const settingsHtml = $(await renderExtensionTemplateAsync('tts', 'settings')); |
| 1512 | $('#tts_container').append(settingsHtml); | 1537 | $('#tts_container').append(settingsHtml); |
| @@ -1594,4 +1619,4 @@ jQuery(async function () { | |||
| 1594 | })); | 1619 | })); |
| 1595 | 1620 | ||
| 1596 | document.body.appendChild(audioElement); | 1621 | document.body.appendChild(audioElement); |
| 1597 | }); | 1622 | } |
| @@ -11,5 +11,8 @@ | |||
| 11 | "css": "style.css", | 11 | "css": "style.css", |
| 12 | "author": "Ouoertheo#7264", | 12 | "author": "Ouoertheo#7264", |
| 13 | "version": "1.0.0", | 13 | "version": "1.0.0", |
| 14 | "homePage": "None" | 14 | "homePage": "None", |
| 15 | "hooks": { | ||
| 16 | "activate": "init" | ||
| 17 | } | ||
| 15 | } | 18 | } |
| @@ -44,6 +44,7 @@ import { oai_settings } from '../../openai.js'; | |||
| 44 | * @property {string} text - The hashed message text | 44 | * @property {string} text - The hashed message text |
| 45 | * @property {number} hash - The hash used as the vector key | 45 | * @property {number} hash - The hash used as the vector key |
| 46 | * @property {number} index - The index of the message in the chat | 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 | const MODULE_NAME = 'vectors'; | 50 | const MODULE_NAME = 'vectors'; |
| @@ -77,10 +78,13 @@ const settings = { | |||
| 77 | summarize_sent: false, | 78 | summarize_sent: false, |
| 78 | summary_source: 'main', | 79 | summary_source: 'main', |
| 79 | summary_prompt: 'Ignore previous instructions. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.', | 80 | summary_prompt: 'Ignore previous instructions. Summarize the most important parts of the message. Limit yourself to 250 words or less. Your response should include nothing but the summary.', |
| 81 | summary_retries: 2, | ||
| 82 | summary_threshold: 200, | ||
| 80 | force_chunk_delimiter: '', | 83 | force_chunk_delimiter: '', |
| 81 | 84 | ||
| 82 | // For chats | 85 | // For chats |
| 83 | enabled_chats: false, | 86 | enabled_chats: false, |
| 87 | keep_hidden: false, | ||
| 84 | template: 'Past events:\n{{text}}', | 88 | template: 'Past events:\n{{text}}', |
| 85 | depth: 2, | 89 | depth: 2, |
| 86 | position: extension_prompt_types.IN_PROMPT, | 90 | position: extension_prompt_types.IN_PROMPT, |
| @@ -117,10 +121,77 @@ const settings = { | |||
| 117 | 121 | ||
| 118 | const moduleWorker = new ModuleWorkerWrapper(synchronizeChat); | 122 | const moduleWorker = new ModuleWorkerWrapper(synchronizeChat); |
| 119 | const webllmProvider = new WebLlmVectorProvider(); | 123 | const webllmProvider = new WebLlmVectorProvider(); |
| 124 | /** | ||
| 125 | * Cache for storing summaries of messages by their hash. | ||
| 126 | * @type {Map<number, string>} | ||
| 127 | */ | ||
| 120 | const cachedSummaries = new Map(); | 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 | const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp']; | 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 | * Gets the Collection ID for a file embedded in the chat. | 195 | * Gets the Collection ID for a file embedded in the chat. |
| 125 | * @param {string} fileUrl URL of the file | 196 | * @param {string} fileUrl URL of the file |
| 126 | * @returns {string} Collection ID | 197 | * @returns {string} Collection ID |
| @@ -145,10 +216,12 @@ async function onVectorizeAllClick() { | |||
| 145 | // Clear all cached summaries to ensure that new ones are created | 216 | // Clear all cached summaries to ensure that new ones are created |
| 146 | // upon request of a full vectorise | 217 | // upon request of a full vectorise |
| 147 | cachedSummaries.clear(); | 218 | cachedSummaries.clear(); |
| 219 | skippedHashes.clear(); | ||
| 148 | 220 | ||
| 149 | const batchSize = getBatchSize(); | 221 | const batchSize = getBatchSize(); |
| 150 | const elapsedLog = []; | 222 | const elapsedLog = []; |
| 151 | let finished = false; | 223 | let finished = false; |
| 224 | let initialPending = null; // total items pending at the start of this run — set on first sync return | ||
| 152 | $('#vectorize_progress').show(); | 225 | $('#vectorize_progress').show(); |
| 153 | $('#vectorize_progress_percent').text('0'); | 226 | $('#vectorize_progress_percent').text('0'); |
| 154 | $('#vectorize_progress_eta').text('...'); | 227 | $('#vectorize_progress_eta').text('...'); |
| @@ -162,16 +235,27 @@ async function onVectorizeAllClick() { | |||
| 162 | const startTime = Date.now(); | 235 | const startTime = Date.now(); |
| 163 | const remaining = await synchronizeChat(batchSize); | 236 | const remaining = await synchronizeChat(batchSize); |
| 164 | const elapsed = Date.now() - startTime; | 237 | const elapsed = Date.now() - startTime; |
| 238 | |||
| 239 | if (remaining === null) { | ||
| 240 | // synchronizeChat already surfaced a toast; bail out of the loop. | ||
| 241 | throw new Error('Vectorization aborted'); | ||
| 242 | } | ||
| 243 | |||
| 165 | elapsedLog.push(elapsed); | 244 | elapsedLog.push(elapsed); |
| 166 | finished = remaining <= 0; | 245 | finished = remaining <= 0; |
| 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 | const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times | 255 | const lastElapsed = elapsedLog.slice(-5); // last 5 elapsed times |
| 172 | const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item | 256 | const averageElapsed = lastElapsed.reduce((a, b) => a + b, 0) / lastElapsed.length; // average time needed to process one item |
| 173 | const pace = averageElapsed / batchSize; // time needed to process one item | 257 | const pace = averageElapsed / batchSize; // time needed to process one item |
| 174 | const remainingTime = Math.round(pace * remaining / 1000); | 258 | const remainingTime = Math.round(pace * pending / 1000); |
| 175 | 259 | ||
| 176 | $('#vectorize_progress_percent').text(processedPercent); | 260 | $('#vectorize_progress_percent').text(processedPercent); |
| 177 | $('#vectorize_progress_eta').text(remainingTime); | 261 | $('#vectorize_progress_eta').text(remainingTime); |
| @@ -180,6 +264,9 @@ async function onVectorizeAllClick() { | |||
| 180 | throw new Error('Chat changed'); | 264 | throw new Error('Chat changed'); |
| 181 | } | 265 | } |
| 182 | } | 266 | } |
| 267 | if (skippedHashes.size > 0) { | ||
| 268 | toastr.warning(`${skippedHashes.size} message(s) skipped due to errors. Click Vectorize All again to retry.`, 'Vectorization partial'); | ||
| 269 | } | ||
| 183 | } catch (error) { | 270 | } catch (error) { |
| 184 | console.error('Vectors: Failed to vectorize all', error); | 271 | console.error('Vectors: Failed to vectorize all', error); |
| 185 | } finally { | 272 | } finally { |
| @@ -250,7 +337,7 @@ async function summarizeExtra(element) { | |||
| 250 | 337 | ||
| 251 | if (apiResult.ok) { | 338 | if (apiResult.ok) { |
| 252 | const data = await apiResult.json(); | 339 | const data = await apiResult.json(); |
| 253 | element.text = data.summary; | 340 | element.text = removeReasoningFromString(data.summary); |
| 254 | } | 341 | } |
| 255 | } catch (error) { | 342 | } catch (error) { |
| 256 | console.log(error); | 343 | console.log(error); |
| @@ -282,45 +369,70 @@ async function summarizeWebLLM(element) { | |||
| 282 | } | 369 | } |
| 283 | 370 | ||
| 284 | const messages = [{ role: 'system', content: settings.summary_prompt }, { role: 'user', content: element.text }]; | 371 | const messages = [{ role: 'system', content: settings.summary_prompt }, { role: 'user', content: element.text }]; |
| 285 | element.text = await generateWebLlmChatPrompt(messages); | 372 | element.text = removeReasoningFromString(await generateWebLlmChatPrompt(messages)); |
| 286 | 373 | ||
| 287 | return true; | 374 | return true; |
| 288 | } | 375 | } |
| 289 | 376 | ||
| 290 | /** | 377 | /** |
| 291 | * Summarizes messages using the chosen method. | 378 | * Runs one summarization attempt for a single element via the chosen endpoint. |
| 292 | * @param {HashedMessage[]} hashedMessages Array of hashed messages | 379 | * @param {HashedMessage} element |
| 380 | * @param {string} endpoint | ||
| 381 | * @returns {Promise<boolean>} Whether the attempt succeeded. | ||
| 382 | */ | ||
| 383 | async function summarizeOne(element, endpoint) { | ||
| 384 | switch (endpoint) { | ||
| 385 | case 'main': | ||
| 386 | return await summarizeMain(element); | ||
| 387 | case 'extras': | ||
| 388 | return await summarizeExtra(element); | ||
| 389 | case 'webllm': | ||
| 390 | return await summarizeWebLLM(element); | ||
| 391 | default: | ||
| 392 | throw new Error(`Unsupported summary endpoint: ${endpoint}`, { cause: 'summary_endpoint_invalid' }); | ||
| 393 | } | ||
| 394 | } | ||
| 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) | ||
| 293 | * @param {string} endpoint Type of endpoint to use | 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 | ||
| 294 | * @returns {Promise<HashedMessage[]>} Summarized messages | 404 | * @returns {Promise<HashedMessage[]>} Summarized messages |
| 295 | */ | 405 | */ |
| 296 | async function summarize(hashedMessages, endpoint = 'main') { | 406 | async function summarize(hashedMessages, endpoint = 'main', { skipOnFailure = false } = {}) { |
| 407 | const maxAttempts = Math.max(1, Number(settings.summary_retries) || 1); | ||
| 297 | for (const element of hashedMessages) { | 408 | for (const element of hashedMessages) { |
| 298 | const cachedSummary = cachedSummaries.get(element.hash); | 409 | const cachedSummary = cachedSummaries.get(element.hash); |
| 299 | if (!cachedSummary) { | 410 | if (cachedSummary) { |
| 300 | let success = true; | 411 | element.text = cachedSummary; |
| 301 | switch (endpoint) { | 412 | continue; |
| 302 | case 'main': | 413 | } |
| 303 | success = await summarizeMain(element); | 414 | |
| 304 | break; | 415 | let success = false; |
| 305 | case 'extras': | 416 | for (let attempt = 1; attempt <= maxAttempts; attempt++) { |
| 306 | success = await summarizeExtra(element); | 417 | try { |
| 307 | break; | 418 | success = await summarizeOne(element, endpoint); |
| 308 | case 'webllm': | 419 | if (success) break; |
| 309 | success = await summarizeWebLLM(element); | 420 | } catch (error) { |
| 310 | break; | 421 | if (FATAL_CAUSES.has(error?.cause)) throw error; |
| 311 | default: | 422 | console.warn(`Vectors: summary attempt ${attempt}/${maxAttempts} threw for hash ${element.hash}`, error); |
| 312 | console.error('Unsupported endpoint', endpoint); | ||
| 313 | success = false; | ||
| 314 | break; | ||
| 315 | } | 423 | } |
| 316 | if (success) { | 424 | console.warn(`Vectors: summary attempt ${attempt}/${maxAttempts} failed for hash ${element.hash}`); |
| 317 | cachedSummaries.set(element.hash, element.text); | 425 | } |
| 318 | } else { | 426 | if (!success) { |
| 319 | break; | 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; | ||
| 320 | } | 431 | } |
| 321 | } else { | 432 | |
| 322 | element.text = cachedSummary; | 433 | throw new Error(`Summarization failed after ${maxAttempts} attempt(s)`, { cause: 'summary_failed' }); |
| 323 | } | 434 | } |
| 435 | cachedSummaries.set(element.hash, element.text); | ||
| 324 | } | 436 | } |
| 325 | return hashedMessages; | 437 | return hashedMessages; |
| 326 | } | 438 | } |
| @@ -347,21 +459,43 @@ async function synchronizeChat(batchSize = 5) { | |||
| 347 | return -1; | 459 | return -1; |
| 348 | } | 460 | } |
| 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 | const hashesInCollection = await getSavedHashes(chatId); | 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 | const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x)); | 469 | const deletedHashes = hashesInCollection.filter(x => !hashedMessages.some(y => y.hash === x)); |
| 355 | 470 | ||
| 471 | let batch = newVectorItems.slice(0, batchSize); | ||
| 472 | |||
| 356 | if (settings.summarize) { | 473 | if (settings.summarize) { |
| 357 | newVectorItems = await summarize(newVectorItems, settings.summary_source); | 474 | const minLength = Math.max(0, Number(settings.summary_threshold) || 0); |
| 475 | const toSummarize = minLength > 0 ? batch.filter(x => x.text.length >= minLength) : batch; | ||
| 476 | if (toSummarize.length > 0) { | ||
| 477 | await summarize(toSummarize, settings.summary_source, { skipOnFailure: true }); | ||
| 478 | const failed = toSummarize.filter(x => x.summaryFailed); | ||
| 479 | if (failed.length > 0) { | ||
| 480 | for (const item of failed) skippedHashes.add(item.hash); | ||
| 481 | batch = batch.filter(x => !x.summaryFailed); | ||
| 482 | } | ||
| 483 | } | ||
| 358 | } | 484 | } |
| 359 | 485 | ||
| 360 | if (newVectorItems.length > 0) { | 486 | if (batch.length > 0) { |
| 361 | const chunkedBatch = splitByChunks(newVectorItems.slice(0, batchSize)); | 487 | const chunkedBatch = splitByChunks(batch); |
| 362 | 488 | ||
| 363 | console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batchSize}...`); | 489 | console.log(`Vectors: Found ${newVectorItems.length} new items. Processing ${batch.length}...`); |
| 364 | await insertVectorItems(chatId, chunkedBatch); | 490 | try { |
| 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 | if (deletedHashes.length > 0) { | 501 | if (deletedHashes.length > 0) { |
| @@ -388,6 +522,12 @@ async function synchronizeChat(batchSize = 5) { | |||
| 388 | return 'Extras API must provide an "embeddings" module.'; | 522 | return 'Extras API must provide an "embeddings" module.'; |
| 389 | case 'webllm_not_supported': | 523 | case 'webllm_not_supported': |
| 390 | return 'WebLLM extension is not installed or the model is not set.'; | 524 | return 'WebLLM extension is not installed or the model is not set.'; |
| 525 | case 'account_id_missing': | ||
| 526 | return 'Workers AI account ID is required. Save it in the "API Connections" panel.'; | ||
| 527 | case 'summary_endpoint_invalid': | ||
| 528 | return 'Summarization endpoint is not supported.'; | ||
| 529 | case 'summary_failed': | ||
| 530 | return 'Summarization failed after the configured number of retries.'; | ||
| 391 | default: | 531 | default: |
| 392 | return 'Check server console for more details'; | 532 | return 'Check server console for more details'; |
| 393 | } | 533 | } |
| @@ -397,7 +537,7 @@ async function synchronizeChat(batchSize = 5) { | |||
| 397 | 537 | ||
| 398 | const message = getErrorMessage(error.cause); | 538 | const message = getErrorMessage(error.cause); |
| 399 | toastr.error(message, 'Vectorization failed', { preventDuplicates: true }); | 539 | toastr.error(message, 'Vectorization failed', { preventDuplicates: true }); |
| 400 | return -1; | 540 | return null; |
| 401 | } finally { | 541 | } finally { |
| 402 | syncBlocked = false; | 542 | syncBlocked = false; |
| 403 | } | 543 | } |
| @@ -771,7 +911,11 @@ async function getQueryText(chat, initiator) { | |||
| 771 | .slice(0, settings.query); | 911 | .slice(0, settings.query); |
| 772 | 912 | ||
| 773 | if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) { | 913 | if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) { |
| 774 | hashedMessages = await summarize(hashedMessages, settings.summary_source); | 914 | const minLength = Math.max(0, Number(settings.summary_threshold) || 0); |
| 915 | const toSummarize = minLength > 0 ? hashedMessages.filter(x => x.text.length >= minLength) : hashedMessages; | ||
| 916 | if (toSummarize.length > 0) { | ||
| 917 | await summarize(toSummarize, settings.summary_source, { skipOnFailure: true }); | ||
| 918 | } | ||
| 775 | } | 919 | } |
| 776 | 920 | ||
| 777 | const queryText = hashedMessages.map(x => x.text).join('\n'); | 921 | const queryText = hashedMessages.map(x => x.text).join('\n'); |
| @@ -842,6 +986,10 @@ function getVectorsRequestBody(args = {}) { | |||
| 842 | body.model = extension_settings.vectors.siliconflow_model; | 986 | body.model = extension_settings.vectors.siliconflow_model; |
| 843 | body.siliconflow_endpoint = oai_settings.siliconflow_endpoint; | 987 | body.siliconflow_endpoint = oai_settings.siliconflow_endpoint; |
| 844 | break; | 988 | break; |
| 989 | case 'workers_ai': | ||
| 990 | body.model = extension_settings.vectors.workers_ai_model || '@cf/baai/bge-m3'; | ||
| 991 | body.workers_ai_account_id = oai_settings.workers_ai_account_id; | ||
| 992 | break; | ||
| 845 | default: | 993 | default: |
| 846 | break; | 994 | break; |
| 847 | } | 995 | } |
| @@ -935,6 +1083,7 @@ function throwIfSourceInvalid() { | |||
| 935 | settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] || | 1083 | settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] || |
| 936 | settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] || | 1084 | settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] || |
| 937 | settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE] || | 1085 | settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE] || |
| 1086 | settings.source === 'workers_ai' && !secret_state[SECRET_KEYS.WORKERS_AI] || | ||
| 938 | settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) { | 1087 | settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) { |
| 939 | throw new Error('Vectors: API key missing', { cause: 'api_key_missing' }); | 1088 | throw new Error('Vectors: API key missing', { cause: 'api_key_missing' }); |
| 940 | } | 1089 | } |
| @@ -963,6 +1112,10 @@ function throwIfSourceInvalid() { | |||
| 963 | if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) { | 1112 | if (settings.source === 'webllm' && (!isWebLlmSupported() || !settings.webllm_model)) { |
| 964 | throw new Error('Vectors: WebLLM is not supported', { cause: 'webllm_not_supported' }); | 1113 | throw new Error('Vectors: WebLLM is not supported', { cause: 'webllm_not_supported' }); |
| 965 | } | 1114 | } |
| 1115 | |||
| 1116 | if (settings.source === 'workers_ai' && !oai_settings.workers_ai_account_id) { | ||
| 1117 | throw new Error('Vectors: Workers AI account ID missing', { cause: 'account_id_missing' }); | ||
| 1118 | } | ||
| 966 | } | 1119 | } |
| 967 | 1120 | ||
| 968 | /** | 1121 | /** |
| @@ -972,11 +1125,12 @@ function throwIfSourceInvalid() { | |||
| 972 | * @returns {Promise<void>} | 1125 | * @returns {Promise<void>} |
| 973 | */ | 1126 | */ |
| 974 | async function deleteVectorItems(collectionId, hashes) { | 1127 | async function deleteVectorItems(collectionId, hashes) { |
| 1128 | const args = await getAdditionalArgs([]); | ||
| 975 | const response = await fetch('/api/vector/delete', { | 1129 | const response = await fetch('/api/vector/delete', { |
| 976 | method: 'POST', | 1130 | method: 'POST', |
| 977 | headers: getRequestHeaders(), | 1131 | headers: getRequestHeaders(), |
| 978 | body: JSON.stringify({ | 1132 | body: JSON.stringify({ |
| 979 | ...getVectorsRequestBody(), | 1133 | ...getVectorsRequestBody(args), |
| 980 | collectionId: collectionId, | 1134 | collectionId: collectionId, |
| 981 | hashes: hashes, | 1135 | hashes: hashes, |
| 982 | source: settings.source, | 1136 | source: settings.source, |
| @@ -1154,211 +1308,77 @@ function toggleSettings() { | |||
| 1154 | $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp'); | 1308 | $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp'); |
| 1155 | $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai'); | 1309 | $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai'); |
| 1156 | $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow'); | 1310 | $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow'); |
| 1311 | $('#workers_ai_vectorsModel').toggle(settings.source === 'workers_ai'); | ||
| 1157 | $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source)); | 1312 | $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source)); |
| 1158 | switch (settings.source) { | 1313 | if (settings.source === 'webllm') { |
| 1159 | case 'webllm': | 1314 | loadWebLlmModels(); |
| 1160 | loadWebLlmModels(); | 1315 | } else if (settings.source in remoteEmbeddingEndpoints) { |
| 1161 | break; | 1316 | loadRemoteEmbeddingModels(settings.source); |
| 1162 | case 'electronhub': | ||
| 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 | } | 1317 | } |
| 1178 | } | 1318 | } |
| 1179 | 1319 | ||
| 1180 | async function loadChutesModels() { | 1320 | /** |
| 1181 | try { | 1321 | * Loads models from a remote embedding endpoint and populates the corresponding select element. |
| 1182 | const response = await fetch('/api/openai/chutes/models/embedding', { | 1322 | * @param {string} source - The source key matching a remoteEmbeddingEndpoints entry |
| 1183 | method: 'POST', | 1323 | */ |
| 1184 | headers: getRequestHeaders({ omitContentType: true }), | 1324 | async function loadRemoteEmbeddingModels(source) { |
| 1185 | }); | 1325 | const config = remoteEmbeddingEndpoints[source]; |
| 1186 | if (!response.ok) { | 1326 | if (!config) { |
| 1187 | throw new Error(`HTTP ${response.status}`); | 1327 | return; |
| 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 | } | 1328 | } |
| 1197 | } | ||
| 1198 | 1329 | ||
| 1199 | function populateChutesModelSelect(models) { | 1330 | const { url, settingsKey, selectId, getBody, filter } = config; |
| 1200 | const select = $('#vectors_chutes_model'); | 1331 | const valueProperty = config.valueProperty || 'id'; |
| 1201 | select.empty(); | 1332 | const textProperty = config.textProperty; |
| 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 | 1333 | ||
| 1214 | async function loadNanoGPTModels() { | 1334 | /** |
| 1215 | try { | 1335 | * Populates the select element with the given models. |
| 1216 | const response = await fetch('/api/openai/nanogpt/models/embedding', { | 1336 | * @param {any[]} models - Array of model objects |
| 1217 | method: 'POST', | 1337 | */ |
| 1218 | headers: getRequestHeaders({ omitContentType: true }), | 1338 | function populateSelect(models) { |
| 1219 | }); | 1339 | const select = $(`#${selectId}`); |
| 1220 | if (!response.ok) { | 1340 | select.empty(); |
| 1221 | throw new Error(`HTTP ${response.status}`); | 1341 | for (const m of models) { |
| 1342 | const option = document.createElement('option'); | ||
| 1343 | option.value = m[valueProperty]; | ||
| 1344 | option.text = textProperty ? (m[textProperty] || m[valueProperty]) : m[valueProperty]; | ||
| 1345 | select.append(option); | ||
| 1222 | } | 1346 | } |
| 1223 | /** @type {Array<any>} */ | 1347 | if (!settings[settingsKey] && models.length) { |
| 1224 | const data = await response.json(); | 1348 | settings[settingsKey] = models[0][valueProperty]; |
| 1225 | const models = Array.isArray(data) ? data : []; | 1349 | Object.assign(extension_settings.vectors, settings); |
| 1226 | populateNanoGPTModelSelect(models); | 1350 | saveSettingsDebounced(); |
| 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 | } | 1351 | } |
| 1257 | /** @type {Array<any>} */ | 1352 | select.val(settings[settingsKey]); |
| 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 | } | ||
| 1266 | } | ||
| 1267 | |||
| 1268 | /** | ||
| 1269 | * Populates the Electron Hub model select element. | ||
| 1270 | * @param {{ id: string, name: string }[]} models Electron Hub models | ||
| 1271 | */ | ||
| 1272 | function populateElectronHubModelSelect(models) { | ||
| 1273 | const select = $('#vectors_electronhub_model'); | ||
| 1274 | select.empty(); | ||
| 1275 | for (const m of models) { | ||
| 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 | } | 1353 | } |
| 1284 | $('#vectors_electronhub_model').val(settings.electronhub_model); | ||
| 1285 | } | ||
| 1286 | 1354 | ||
| 1287 | async function loadOpenRouterModels() { | ||
| 1288 | try { | 1355 | try { |
| 1289 | const response = await fetch('/api/openrouter/models/embedding', { | 1356 | const body = typeof getBody === 'function' ? getBody() : {}; |
| 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 | |||
| 1306 | /** | ||
| 1307 | * Populates the OpenRouter model select element. | ||
| 1308 | * @param {{ id: string, name: string }[]} models OpenRouter models | ||
| 1309 | */ | ||
| 1310 | function populateOpenRouterModelSelect(models) { | ||
| 1311 | const select = $('#vectors_openrouter_model'); | ||
| 1312 | select.empty(); | ||
| 1313 | for (const m of models) { | ||
| 1314 | const option = document.createElement('option'); | ||
| 1315 | option.value = m.id; | ||
| 1316 | option.text = m.name || m.id; | ||
| 1317 | select.append(option); | ||
| 1318 | } | ||
| 1319 | if (!settings.openrouter_model && models.length) { | ||
| 1320 | settings.openrouter_model = models[0].id; | ||
| 1321 | } | ||
| 1322 | $('#vectors_openrouter_model').val(settings.openrouter_model); | ||
| 1323 | } | ||
| 1324 | 1357 | ||
| 1325 | async function loadSiliconFlowModels() { | 1358 | /** @type {RequestInit} */ |
| 1326 | try { | 1359 | const fetchOptions = { |
| 1327 | const response = await fetch('/api/openai/siliconflow/models/embedding', { | ||
| 1328 | method: 'POST', | 1360 | method: 'POST', |
| 1329 | headers: getRequestHeaders(), | 1361 | headers: getRequestHeaders(), |
| 1330 | body: JSON.stringify({ | 1362 | body: JSON.stringify(body || {}), |
| 1331 | siliconflow_endpoint: oai_settings.siliconflow_endpoint, | 1363 | }; |
| 1332 | }), | ||
| 1333 | }); | ||
| 1334 | 1364 | ||
| 1365 | const response = await fetch(url, fetchOptions); | ||
| 1335 | if (!response.ok) { | 1366 | if (!response.ok) { |
| 1336 | throw new Error(`HTTP ${response.status}`); | 1367 | throw new Error(`HTTP ${response.status}`); |
| 1337 | } | 1368 | } |
| 1338 | 1369 | ||
| 1339 | /** @type {Array<any>} */ | 1370 | /** @type {Array<any>} */ |
| 1340 | const data = await response.json(); | 1371 | const data = await response.json(); |
| 1341 | const models = Array.isArray(data) ? data : []; | 1372 | let models = Array.isArray(data) ? data : []; |
| 1342 | populateSiliconFlowModelSelect(models); | 1373 | if (filter) { |
| 1343 | } catch (err) { | 1374 | models = filter(models); |
| 1344 | console.warn('SiliconFlow models fetch failed', err); | 1375 | } |
| 1345 | populateSiliconFlowModelSelect([]); | ||
| 1346 | } | ||
| 1347 | } | ||
| 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 | { timeOut: 10000, escapeHtml: false }, | 1516 | { timeOut: 10000, escapeHtml: false }, |
| 1497 | ); | 1517 | ); |
| 1498 | 1518 | ||
| 1519 | $('#chat .mes.vectorized').removeClass('vectorized'); | ||
| 1499 | const chat = getContext().chat; | 1520 | const chat = getContext().chat; |
| 1500 | for (const message of chat) { | 1521 | for (const message of chat) { |
| 1501 | if (hashesInCollection.includes(getStringHash(substituteParams(message.mes)))) { | 1522 | if (hashesInCollection.includes(getStringHash(substituteParams(message.mes)))) { |
| 1502 | const messageElement = $(`.mes[mesid="${chat.indexOf(message)}"]`); | 1523 | const messageElement = $(`#chat .mes[mesid="${chat.indexOf(message)}"]`); |
| 1503 | messageElement.addClass('vectorized'); | 1524 | messageElement.addClass('vectorized'); |
| 1504 | } | 1525 | } |
| 1505 | } | 1526 | } |
| @@ -1704,7 +1725,7 @@ async function activateWorldInfo(chat) { | |||
| 1704 | await eventSource.emit(event_types.WORLDINFO_FORCE_ACTIVATE, activatedEntries); | 1725 | await eventSource.emit(event_types.WORLDINFO_FORCE_ACTIVATE, activatedEntries); |
| 1705 | } | 1726 | } |
| 1706 | 1727 | ||
| 1707 | jQuery(async () => { | 1728 | export async function init() { |
| 1708 | if (!extension_settings.vectors) { | 1729 | if (!extension_settings.vectors) { |
| 1709 | extension_settings.vectors = settings; | 1730 | extension_settings.vectors = settings; |
| 1710 | } | 1731 | } |
| @@ -1726,6 +1747,11 @@ jQuery(async () => { | |||
| 1726 | saveSettingsDebounced(); | 1747 | saveSettingsDebounced(); |
| 1727 | toggleSettings(); | 1748 | toggleSettings(); |
| 1728 | }); | 1749 | }); |
| 1750 | $('#vectors_keep_hidden').prop('checked', settings.keep_hidden).on('input', () => { | ||
| 1751 | settings.keep_hidden = !!$('#vectors_keep_hidden').prop('checked'); | ||
| 1752 | Object.assign(extension_settings.vectors, settings); | ||
| 1753 | saveSettingsDebounced(); | ||
| 1754 | }); | ||
| 1729 | $('#vectors_enabled_files').prop('checked', settings.enabled_files).on('input', () => { | 1755 | $('#vectors_enabled_files').prop('checked', settings.enabled_files).on('input', () => { |
| 1730 | settings.enabled_files = $('#vectors_enabled_files').prop('checked'); | 1756 | settings.enabled_files = $('#vectors_enabled_files').prop('checked'); |
| 1731 | Object.assign(extension_settings.vectors, settings); | 1757 | Object.assign(extension_settings.vectors, settings); |
| @@ -1778,6 +1804,11 @@ jQuery(async () => { | |||
| 1778 | Object.assign(extension_settings.vectors, settings); | 1804 | Object.assign(extension_settings.vectors, settings); |
| 1779 | saveSettingsDebounced(); | 1805 | saveSettingsDebounced(); |
| 1780 | }); | 1806 | }); |
| 1807 | $('#vectors_workers_ai_model').val(settings.workers_ai_model).on('change', () => { | ||
| 1808 | settings.workers_ai_model = String($('#vectors_workers_ai_model').val()); | ||
| 1809 | Object.assign(extension_settings.vectors, settings); | ||
| 1810 | saveSettingsDebounced(); | ||
| 1811 | }); | ||
| 1781 | $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => { | 1812 | $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => { |
| 1782 | settings.openrouter_model = String($('#vectors_openrouter_model').val()); | 1813 | settings.openrouter_model = String($('#vectors_openrouter_model').val()); |
| 1783 | Object.assign(extension_settings.vectors, settings); | 1814 | Object.assign(extension_settings.vectors, settings); |
| @@ -1888,6 +1919,20 @@ jQuery(async () => { | |||
| 1888 | saveSettingsDebounced(); | 1919 | saveSettingsDebounced(); |
| 1889 | }); | 1920 | }); |
| 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 | $('#vectors_message_chunk_size').val(settings.message_chunk_size).on('input', () => { | 1936 | $('#vectors_message_chunk_size').val(settings.message_chunk_size).on('input', () => { |
| 1892 | settings.message_chunk_size = Number($('#vectors_message_chunk_size').val()); | 1937 | settings.message_chunk_size = Number($('#vectors_message_chunk_size').val()); |
| 1893 | Object.assign(extension_settings.vectors, settings); | 1938 | Object.assign(extension_settings.vectors, settings); |
| @@ -2310,4 +2355,4 @@ jQuery(async () => { | |||
| 2310 | } | 2355 | } |
| 2311 | await purgeAllVectorIndexes(); | 2356 | await purgeAllVectorIndexes(); |
| 2312 | }); | 2357 | }); |
| 2313 | }); | 2358 | } |
| @@ -10,5 +10,8 @@ | |||
| 10 | "css": "style.css", | 10 | "css": "style.css", |
| 11 | "author": "Cohee#1207", | 11 | "author": "Cohee#1207", |
| 12 | "version": "1.0.0", | 12 | "version": "1.0.0", |
| 13 | "homePage": "https://github.com/SillyTavern/SillyTavern" | 13 | "homePage": "https://github.com/SillyTavern/SillyTavern", |
| 14 | "hooks": { | ||
| 15 | "activate": "init" | ||
| 16 | } | ||
| 14 | } | 17 | } |
| @@ -11,6 +11,7 @@ | |||
| 11 | </label> | 11 | </label> |
| 12 | <select id="vectors_source" class="text_pole"> | 12 | <select id="vectors_source" class="text_pole"> |
| 13 | <option value="chutes">Chutes</option> | 13 | <option value="chutes">Chutes</option> |
| 14 | <option value="workers_ai">Cloudflare Workers AI</option> | ||
| 14 | <option value="cohere">Cohere</option> | 15 | <option value="cohere">Cohere</option> |
| 15 | <option value="electronhub">Electron Hub</option> | 16 | <option value="electronhub">Electron Hub</option> |
| 16 | <option value="extras">Extras (deprecated)</option> | 17 | <option value="extras">Extras (deprecated)</option> |
| @@ -205,6 +206,16 @@ | |||
| 205 | </i> | 206 | </i> |
| 206 | </div> | 207 | </div> |
| 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 | <div class="flex-container marginTopBot5"> | 219 | <div class="flex-container marginTopBot5"> |
| 209 | <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance."> | 220 | <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance."> |
| 210 | <label for="vectors_query"> | 221 | <label for="vectors_query"> |
| @@ -408,6 +419,10 @@ | |||
| 408 | <hr> | 419 | <hr> |
| 409 | 420 | ||
| 410 | <div id="vectors_chats_settings"> | 421 | <div id="vectors_chats_settings"> |
| 422 | <label class="checkbox_label expander" for="vectors_keep_hidden" title="Keep hidden (system) messages in the vector index and include them in query results."> | ||
| 423 | <input id="vectors_keep_hidden" type="checkbox" class="checkbox"> | ||
| 424 | <span data-i18n="Include hidden messages">Include hidden messages</span> | ||
| 425 | </label> | ||
| 411 | <div id="vectors_advanced_settings"> | 426 | <div id="vectors_advanced_settings"> |
| 412 | <label for="vectors_template" data-i18n="Injection Template"> | 427 | <label for="vectors_template" data-i18n="Injection Template"> |
| 413 | Injection Template | 428 | Injection Template |
| @@ -478,6 +493,16 @@ | |||
| 478 | <label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label> | 493 | <label for="vectors_summary_prompt" title="Summary Prompt:">Summary Prompt:</label> |
| 479 | <small data-i18n="Only used when Main API or WebLLM Extension is selected.">Only used when Main API or WebLLM Extension is selected.</small> | 494 | <small data-i18n="Only used when Main API or WebLLM Extension is selected.">Only used when Main API or WebLLM Extension is selected.</small> |
| 480 | <textarea id="vectors_summary_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation."></textarea> | 495 | <textarea id="vectors_summary_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation."></textarea> |
| 496 | |||
| 497 | <label for="vectors_summary_retries" title="Number of attempts per message before aborting vectorization."> | ||
| 498 | <span data-i18n="Summarization retries per message">Summarization retries per message</span> | ||
| 499 | </label> | ||
| 500 | <input id="vectors_summary_retries" type="number" class="text_pole widthUnset" min="1" max="10" step="1" /> | ||
| 501 | |||
| 502 | <label for="vectors_summary_threshold" title="Messages shorter than this (in characters) are embedded as-is without summarization. Set to 0 to always summarize."> | ||
| 503 | <span data-i18n="Summarization min length (chars)">Summarization min length (chars)</span> | ||
| 504 | </label> | ||
| 505 | <input id="vectors_summary_threshold" type="number" class="text_pole widthUnset" min="0" step="1" /> | ||
| 481 | </div> | 506 | </div> |
| 482 | </div> | 507 | </div> |
| 483 | <small data-i18n="Old messages are vectorized gradually as you chat. To process all previous messages, click the button below."> | 508 | <small data-i18n="Old messages are vectorized gradually as you chat. To process all previous messages, click the button below."> |
| @@ -289,6 +289,16 @@ export class FilterHelper { | |||
| 289 | return this.filterDataByState(data, state, isFolder); | 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 | filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) { | 302 | filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) { |
| 293 | if (isFilterState(state, FILTER_STATES.SELECTED)) { | 303 | if (isFilterState(state, FILTER_STATES.SELECTED)) { |
| 294 | return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag')); | 304 | return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag')); |
| @@ -414,7 +424,7 @@ export class FilterHelper { | |||
| 414 | typeScores.set(uid, score); | 424 | typeScores.set(uid, score); |
| 415 | } | 425 | } |
| 416 | this.scoreCache.set(type, typeScores); | 426 | this.scoreCache.set(type, typeScores); |
| 417 | console.debug('search scores chached', type, typeScores); | 427 | console.debug('search scores cached', type, typeScores); |
| 418 | } | 428 | } |
| 419 | 429 | ||
| 420 | /** | 430 | /** |
| @@ -139,7 +139,8 @@ async function getLocaleData(language) { | |||
| 139 | function findLang(language) { | 139 | function findLang(language) { |
| 140 | const supportedLang = langs.find(x => x.lang === language); | 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 | console.warn(`Unsupported language: ${language}`); | 144 | console.warn(`Unsupported language: ${language}`); |
| 144 | } | 145 | } |
| 145 | return supportedLang; | 146 | return supportedLang; |
| @@ -597,7 +597,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, | |||
| 597 | function getSequence() { | 597 | function getSequence() { |
| 598 | // User impersonation prompt | 598 | // User impersonation prompt |
| 599 | if (isImpersonate) { | 599 | if (isImpersonate) { |
| 600 | return instruct.input_sequence; | 600 | return instruct.last_input_sequence || instruct.input_sequence; |
| 601 | } | 601 | } |
| 602 | 602 | ||
| 603 | // Neutral / system / quiet prompt | 603 | // Neutral / system / quiet prompt |
| @@ -31,6 +31,8 @@ export async function loadItemizedPrompts(chatId) { | |||
| 31 | if (!itemizedPrompts) { | 31 | if (!itemizedPrompts) { |
| 32 | itemizedPrompts = []; | 32 | itemizedPrompts = []; |
| 33 | } | 33 | } |
| 34 | |||
| 35 | await eventSource.emit(event_types.ITEMIZED_PROMPTS_LOADED, { chatId: chatId }); | ||
| 34 | } catch { | 36 | } catch { |
| 35 | console.log('Error loading itemized prompts for chat', chatId); | 37 | console.log('Error loading itemized prompts for chat', chatId); |
| 36 | itemizedPrompts = []; | 38 | itemizedPrompts = []; |
| @@ -48,6 +50,7 @@ export async function saveItemizedPrompts(chatId) { | |||
| 48 | } | 50 | } |
| 49 | 51 | ||
| 50 | await promptStorage.setItem(chatId, itemizedPrompts); | 52 | await promptStorage.setItem(chatId, itemizedPrompts); |
| 53 | await eventSource.emit(event_types.ITEMIZED_PROMPTS_SAVED, { chatId: chatId }); | ||
| 51 | } catch { | 54 | } catch { |
| 52 | console.log('Error saving itemized prompts for chat', chatId); | 55 | console.log('Error saving itemized prompts for chat', chatId); |
| 53 | } | 56 | } |
| @@ -84,6 +87,7 @@ export async function deleteItemizedPrompts(chatId) { | |||
| 84 | } | 87 | } |
| 85 | 88 | ||
| 86 | await promptStorage.removeItem(chatId); | 89 | await promptStorage.removeItem(chatId); |
| 90 | await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { chatId: chatId, all: false }); | ||
| 87 | } catch { | 91 | } catch { |
| 88 | console.log('Error deleting itemized prompts for chat', chatId); | 92 | console.log('Error deleting itemized prompts for chat', chatId); |
| 89 | } | 93 | } |
| @@ -96,6 +100,7 @@ export async function clearItemizedPrompts() { | |||
| 96 | try { | 100 | try { |
| 97 | await promptStorage.clear(); | 101 | await promptStorage.clear(); |
| 98 | itemizedPrompts = []; | 102 | itemizedPrompts = []; |
| 103 | await eventSource.emit(event_types.ITEMIZED_PROMPTS_DELETED, { all: true }); | ||
| 99 | } catch { | 104 | } catch { |
| 100 | console.log('Error clearing itemized prompts'); | 105 | console.log('Error clearing itemized prompts'); |
| 101 | } | 106 | } |
| @@ -202,6 +202,7 @@ export function getKoboldGenerationData(finalPrompt, settings, maxLength, maxCon | |||
| 202 | mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined, | 202 | mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined, |
| 203 | use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined, | 203 | use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined, |
| 204 | grammar: (kai_flags.can_use_grammar || isHorde) ? substituteParams(kai_settings.grammar) : undefined, | 204 | grammar: (kai_flags.can_use_grammar || isHorde) ? substituteParams(kai_settings.grammar) : undefined, |
| 205 | grammar_retain_state: (kai_flags.can_use_grammar && !!isContinue) ? true : undefined, | ||
| 205 | sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined, | 206 | sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined, |
| 206 | api_server: kai_settings.api_server, | 207 | api_server: kai_settings.api_server, |
| 207 | }; | 208 | }; |
| @@ -28,6 +28,7 @@ export function showLoader() { | |||
| 28 | 28 | ||
| 29 | // Create a blocking loader with no toast (matches old behavior) | 29 | // Create a blocking loader with no toast (matches old behavior) |
| 30 | legacyLoaderHandle = loader.show({ | 30 | legacyLoaderHandle = loader.show({ |
| 31 | slug: 'legacy-loader', | ||
| 31 | blocking: true, | 32 | blocking: true, |
| 32 | toastMode: loader.ToastMode.NONE, | 33 | toastMode: loader.ToastMode.NONE, |
| 33 | }); | 34 | }); |
| @@ -58,10 +58,11 @@ export class MacrosParser { | |||
| 58 | * | 58 | * |
| 59 | * @param {string} method | 59 | * @param {string} method |
| 60 | * @param {string} replacement | 60 | * @param {string} replacement |
| 61 | * @param {IArguments} [methodArgs=null] | ||
| 61 | * @returns {void} | 62 | * @returns {void} |
| 62 | */ | 63 | */ |
| 63 | static #logDeprecated(method, replacement) { | 64 | static #logDeprecated(method, replacement, methodArgs = null) { |
| 64 | console.warn(`[DEPRECATED] MacrosParser.${method} is deprecated and will be removed in a future version. Use ${replacement} instead.`); | 65 | console.warn(`[DEPRECATED] MacrosParser.${method} is deprecated and will be removed in a future version. Use ${replacement} instead. Arguments:`, (methodArgs ?? 'none')); |
| 65 | } | 66 | } |
| 66 | 67 | ||
| 67 | /** | 68 | /** |
| @@ -155,7 +156,7 @@ export class MacrosParser { | |||
| 155 | * @returns {string|MacroFunction|undefined} The macro value | 156 | * @returns {string|MacroFunction|undefined} The macro value |
| 156 | */ | 157 | */ |
| 157 | static get(key) { | 158 | static get(key) { |
| 158 | MacrosParser.#logDeprecated('get', 'macros.registry.getMacro (from scripts/macros/macro-system.js)'); | 159 | MacrosParser.#logDeprecated('get', 'macros.registry.getMacro (from scripts/macros/macro-system.js)', arguments); |
| 159 | return MacrosParser.#macros.get(key); | 160 | return MacrosParser.#macros.get(key); |
| 160 | } | 161 | } |
| 161 | 162 | ||
| @@ -165,7 +166,7 @@ export class MacrosParser { | |||
| 165 | * @returns {boolean} True if the macro is registered, false otherwise | 166 | * @returns {boolean} True if the macro is registered, false otherwise |
| 166 | */ | 167 | */ |
| 167 | static has(key) { | 168 | static has(key) { |
| 168 | MacrosParser.#logDeprecated('has', 'macros.registry.hasMacro (from scripts/macros/macro-system.js)'); | 169 | MacrosParser.#logDeprecated('has', 'macros.registry.hasMacro (from scripts/macros/macro-system.js)', arguments); |
| 169 | if (power_user.experimental_macro_engine) { | 170 | if (power_user.experimental_macro_engine) { |
| 170 | return macroSystem.registry.hasMacro(key); | 171 | return macroSystem.registry.hasMacro(key); |
| 171 | } | 172 | } |
| @@ -180,7 +181,7 @@ export class MacrosParser { | |||
| 180 | * @param {string} [description] Optional description of the macro | 181 | * @param {string} [description] Optional description of the macro |
| 181 | */ | 182 | */ |
| 182 | static registerMacro(key, value, description = '') { | 183 | static registerMacro(key, value, description = '') { |
| 183 | MacrosParser.#logDeprecated('registerMacro', 'macros.registry.registerMacro (from scripts/macros/macro-system.js) or substituteParams({ dynamicMacros })'); | 184 | MacrosParser.#logDeprecated('registerMacro', 'macros.registry.registerMacro (from scripts/macros/macro-system.js) or substituteParams({ dynamicMacros })', arguments); |
| 184 | if (typeof key !== 'string') { | 185 | if (typeof key !== 'string') { |
| 185 | throw new Error('Macro key must be a string'); | 186 | throw new Error('Macro key must be a string'); |
| 186 | } | 187 | } |
| @@ -223,7 +224,7 @@ export class MacrosParser { | |||
| 223 | * @param {string} key Macro name (key) | 224 | * @param {string} key Macro name (key) |
| 224 | */ | 225 | */ |
| 225 | static unregisterMacro(key) { | 226 | static unregisterMacro(key) { |
| 226 | MacrosParser.#logDeprecated('unregisterMacro', 'macros.registry.unregisterMacro (from scripts/macros/macro-system.js)'); | 227 | MacrosParser.#logDeprecated('unregisterMacro', 'macros.registry.unregisterMacro (from scripts/macros/macro-system.js)', arguments); |
| 227 | if (typeof key !== 'string') { | 228 | if (typeof key !== 'string') { |
| 228 | throw new Error('Macro key must be a string'); | 229 | throw new Error('Macro key must be a string'); |
| 229 | } | 230 | } |
| @@ -47,6 +47,7 @@ import { SECRET_KEYS, secret_state, writeSecret } from './secrets.js'; | |||
| 47 | 47 | ||
| 48 | import { getEventSourceStream } from './sse-stream.js'; | 48 | import { getEventSourceStream } from './sse-stream.js'; |
| 49 | import { | 49 | import { |
| 50 | clamp, | ||
| 50 | createThumbnail, | 51 | createThumbnail, |
| 51 | delay, | 52 | delay, |
| 52 | download, | 53 | download, |
| @@ -79,7 +80,7 @@ import { t } from './i18n.js'; | |||
| 79 | import { ToolManager } from './tool-calling.js'; | 80 | import { ToolManager } from './tool-calling.js'; |
| 80 | import { accountStorage } from './util/AccountStorage.js'; | 81 | import { accountStorage } from './util/AccountStorage.js'; |
| 81 | import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js'; | 82 | import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js'; |
| 82 | import { syncOpenRouterProvidersForModel, updateOpenRouterProvidersWarning } from './textgen-models.js'; | 83 | import { syncNanoGptProvidersForModel, syncOpenRouterProvidersForModel, updateNanoGptProvidersWarning, updateOpenRouterProvidersWarning } from './textgen-models.js'; |
| 83 | 84 | ||
| 84 | export { | 85 | export { |
| 85 | openai_messages_count, | 86 | openai_messages_count, |
| @@ -196,6 +197,8 @@ export const chat_completion_sources = { | |||
| 196 | AZURE_OPENAI: 'azure_openai', | 197 | AZURE_OPENAI: 'azure_openai', |
| 197 | ZAI: 'zai', | 198 | ZAI: 'zai', |
| 198 | SILICONFLOW: 'siliconflow', | 199 | SILICONFLOW: 'siliconflow', |
| 200 | WORKERS_AI: 'workers_ai', | ||
| 201 | MINIMAX: 'minimax', | ||
| 199 | }; | 202 | }; |
| 200 | 203 | ||
| 201 | const character_names_behavior = { | 204 | const character_names_behavior = { |
| @@ -256,6 +259,7 @@ export const tool_reasoning_modes = { | |||
| 256 | // Providers that support interleaved reasoning forwarding in tool-call chains. | 259 | // Providers that support interleaved reasoning forwarding in tool-call chains. |
| 257 | const interleaved_reasoning_providers = [ | 260 | const interleaved_reasoning_providers = [ |
| 258 | chat_completion_sources.OPENROUTER, | 261 | chat_completion_sources.OPENROUTER, |
| 262 | chat_completion_sources.CUSTOM, | ||
| 259 | ]; | 263 | ]; |
| 260 | 264 | ||
| 261 | export const ZAI_ENDPOINT = { | 265 | export const ZAI_ENDPOINT = { |
| @@ -268,6 +272,11 @@ export const SILICONFLOW_ENDPOINT = { | |||
| 268 | CN: 'cn', | 272 | CN: 'cn', |
| 269 | }; | 273 | }; |
| 270 | 274 | ||
| 275 | export const MINIMAX_ENDPOINT = { | ||
| 276 | GLOBAL: 'global', | ||
| 277 | CN: 'cn', | ||
| 278 | }; | ||
| 279 | |||
| 271 | const sensitiveFields = [ | 280 | const sensitiveFields = [ |
| 272 | 'reverse_proxy', | 281 | 'reverse_proxy', |
| 273 | 'proxy_password', | 282 | 'proxy_password', |
| @@ -279,6 +288,7 @@ const sensitiveFields = [ | |||
| 279 | 'vertexai_express_project_id', | 288 | 'vertexai_express_project_id', |
| 280 | 'azure_base_url', | 289 | 'azure_base_url', |
| 281 | 'azure_deployment_name', | 290 | 'azure_deployment_name', |
| 291 | 'workers_ai_account_id', | ||
| 282 | ]; | 292 | ]; |
| 283 | 293 | ||
| 284 | /** | 294 | /** |
| @@ -296,12 +306,12 @@ export const settingsToUpdate = { | |||
| 296 | min_p: ['#min_p_openai', 'min_p_openai', false, false], | 306 | min_p: ['#min_p_openai', 'min_p_openai', false, false], |
| 297 | repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false], | 307 | repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false], |
| 298 | max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false], | 308 | max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false], |
| 309 | group_models: ['#cc_group_models', 'group_models', true, true], | ||
| 310 | sort_models: ['#cc_sort_models', 'sort_models', false, true], | ||
| 299 | openai_model: ['#model_openai_select', 'openai_model', false, true], | 311 | openai_model: ['#model_openai_select', 'openai_model', false, true], |
| 300 | claude_model: ['#model_claude_select', 'claude_model', false, true], | 312 | claude_model: ['#model_claude_select', 'claude_model', false, true], |
| 301 | openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true], | 313 | openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true], |
| 302 | openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true], | 314 | openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true], |
| 303 | openrouter_group_models: ['#openrouter_group_models', 'openrouter_group_models', false, true], | ||
| 304 | openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false, true], | ||
| 305 | openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true], | 315 | openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true], |
| 306 | openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true], | 316 | openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true], |
| 307 | openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true], | 317 | openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true], |
| @@ -313,13 +323,14 @@ export const settingsToUpdate = { | |||
| 313 | perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true], | 323 | perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true], |
| 314 | groq_model: ['#model_groq_select', 'groq_model', false, true], | 324 | groq_model: ['#model_groq_select', 'groq_model', false, true], |
| 315 | chutes_model: ['#model_chutes_select', 'chutes_model', false, true], | 325 | chutes_model: ['#model_chutes_select', 'chutes_model', false, true], |
| 316 | chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true], | ||
| 317 | siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true], | 326 | siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true], |
| 318 | siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true], | 327 | siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true], |
| 328 | minimax_model: ['#model_minimax_select', 'minimax_model', false, true], | ||
| 329 | minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true], | ||
| 319 | electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true], | 330 | electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true], |
| 320 | electronhub_sort_models: ['#electronhub_sort_models', 'electronhub_sort_models', false, true], | ||
| 321 | electronhub_group_models: ['#electronhub_group_models', 'electronhub_group_models', false, true], | ||
| 322 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true], | 331 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true], |
| 332 | nanogpt_provider: ['#nanogpt_provider', 'nanogpt_provider', false, true], | ||
| 333 | nanogpt_payg_override: ['#nanogpt_payg_override', 'nanogpt_payg_override', true, true], | ||
| 323 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true], | 334 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true], |
| 324 | aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true], | 335 | aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true], |
| 325 | xai_model: ['#model_xai_select', 'xai_model', false, true], | 336 | xai_model: ['#model_xai_select', 'xai_model', false, true], |
| @@ -337,6 +348,8 @@ export const settingsToUpdate = { | |||
| 337 | vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], | 348 | vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true], |
| 338 | zai_model: ['#model_zai_select', 'zai_model', false, true], | 349 | zai_model: ['#model_zai_select', 'zai_model', false, true], |
| 339 | zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true], | 350 | zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true], |
| 351 | workers_ai_model: ['#model_workers_ai_select', 'workers_ai_model', false, true], | ||
| 352 | workers_ai_account_id: ['#workers_ai_account_id', 'workers_ai_account_id', false, true], | ||
| 340 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], | 353 | openai_max_context: ['#openai_max_context', 'openai_max_context', false, false], |
| 341 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], | 354 | openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false], |
| 342 | names_behavior: ['#names_behavior', 'names_behavior', false, false], | 355 | names_behavior: ['#names_behavior', 'names_behavior', false, false], |
| @@ -369,6 +382,7 @@ export const settingsToUpdate = { | |||
| 369 | continue_prefill: ['#continue_prefill', 'continue_prefill', true, false], | 382 | continue_prefill: ['#continue_prefill', 'continue_prefill', true, false], |
| 370 | continue_postfix: ['#continue_postfix', 'continue_postfix', false, false], | 383 | continue_postfix: ['#continue_postfix', 'continue_postfix', false, false], |
| 371 | function_calling: ['#openai_function_calling', 'function_calling', true, false], | 384 | function_calling: ['#openai_function_calling', 'function_calling', true, false], |
| 385 | tool_call_recurse_limit: ['#tool_call_recurse_limit', 'tool_call_recurse_limit', false, false], | ||
| 372 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false], | 386 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true, false], |
| 373 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false], | 387 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false, false], |
| 374 | verbosity: ['#openai_verbosity', 'verbosity', false, false], | 388 | verbosity: ['#openai_verbosity', 'verbosity', false, false], |
| @@ -413,6 +427,8 @@ const default_settings = { | |||
| 413 | group_nudge_prompt: default_group_nudge_prompt, | 427 | group_nudge_prompt: default_group_nudge_prompt, |
| 414 | scenario_format: default_scenario_format, | 428 | scenario_format: default_scenario_format, |
| 415 | personality_format: default_personality_format, | 429 | personality_format: default_personality_format, |
| 430 | sort_models: 'alphabetically', | ||
| 431 | group_models: false, | ||
| 416 | openai_model: 'gpt-4-turbo', | 432 | openai_model: 'gpt-4-turbo', |
| 417 | claude_model: 'claude-sonnet-4-5', | 433 | claude_model: 'claude-sonnet-4-5', |
| 418 | google_model: 'gemini-2.5-pro', | 434 | google_model: 'gemini-2.5-pro', |
| @@ -423,14 +439,15 @@ const default_settings = { | |||
| 423 | perplexity_model: 'sonar-pro', | 439 | perplexity_model: 'sonar-pro', |
| 424 | groq_model: 'llama-3.3-70b-versatile', | 440 | groq_model: 'llama-3.3-70b-versatile', |
| 425 | chutes_model: 'deepseek-ai/DeepSeek-V3-0324', | 441 | chutes_model: 'deepseek-ai/DeepSeek-V3-0324', |
| 426 | chutes_sort_models: 'alphabetically', | ||
| 427 | siliconflow_model: 'deepseek-ai/DeepSeek-V3', | 442 | siliconflow_model: 'deepseek-ai/DeepSeek-V3', |
| 428 | siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL, | 443 | siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL, |
| 444 | minimax_model: 'MiniMax-M2.7', | ||
| 445 | minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL, | ||
| 429 | electronhub_model: 'gpt-4o-mini', | 446 | electronhub_model: 'gpt-4o-mini', |
| 430 | electronhub_sort_models: 'alphabetically', | ||
| 431 | electronhub_group_models: false, | ||
| 432 | nanogpt_model: 'gpt-4o-mini', | 447 | nanogpt_model: 'gpt-4o-mini', |
| 433 | deepseek_model: 'deepseek-chat', | 448 | nanogpt_provider: '', |
| 449 | nanogpt_payg_override: false, | ||
| 450 | deepseek_model: 'deepseek-v4-flash', | ||
| 434 | aimlapi_model: 'chatgpt-4o-latest', | 451 | aimlapi_model: 'chatgpt-4o-latest', |
| 435 | xai_model: 'grok-3-beta', | 452 | xai_model: 'grok-3-beta', |
| 436 | pollinations_model: 'openai', | 453 | pollinations_model: 'openai', |
| @@ -439,6 +456,8 @@ const default_settings = { | |||
| 439 | fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct', | 456 | fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct', |
| 440 | zai_model: 'glm-4.6', | 457 | zai_model: 'glm-4.6', |
| 441 | zai_endpoint: ZAI_ENDPOINT.COMMON, | 458 | zai_endpoint: ZAI_ENDPOINT.COMMON, |
| 459 | workers_ai_model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast', | ||
| 460 | workers_ai_account_id: '', | ||
| 442 | azure_base_url: '', | 461 | azure_base_url: '', |
| 443 | azure_deployment_name: '', | 462 | azure_deployment_name: '', |
| 444 | azure_api_version: '2024-02-15-preview', | 463 | azure_api_version: '2024-02-15-preview', |
| @@ -450,8 +469,6 @@ const default_settings = { | |||
| 450 | custom_include_headers: '', | 469 | custom_include_headers: '', |
| 451 | openrouter_model: openrouter_website_model, | 470 | openrouter_model: openrouter_website_model, |
| 452 | openrouter_use_fallback: false, | 471 | openrouter_use_fallback: false, |
| 453 | openrouter_group_models: false, | ||
| 454 | openrouter_sort_models: 'alphabetically', | ||
| 455 | openrouter_providers: [], | 472 | openrouter_providers: [], |
| 456 | openrouter_quantizations: [], | 473 | openrouter_quantizations: [], |
| 457 | openrouter_allow_fallbacks: true, | 474 | openrouter_allow_fallbacks: true, |
| @@ -474,6 +491,7 @@ const default_settings = { | |||
| 474 | bypass_status_check: false, | 491 | bypass_status_check: false, |
| 475 | continue_prefill: false, | 492 | continue_prefill: false, |
| 476 | function_calling: false, | 493 | function_calling: false, |
| 494 | tool_call_recurse_limit: 5, | ||
| 477 | names_behavior: character_names_behavior.DEFAULT, | 495 | names_behavior: character_names_behavior.DEFAULT, |
| 478 | continue_postfix: continue_postfix_types.SPACE, | 496 | continue_postfix: continue_postfix_types.SPACE, |
| 479 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 497 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| @@ -597,8 +615,10 @@ function setOpenAIMessages(chat) { | |||
| 597 | const originApi = chat[j]?.extra?.api; | 615 | const originApi = chat[j]?.extra?.api; |
| 598 | const originModel = chat[j]?.extra?.model; | 616 | const originModel = chat[j]?.extra?.model; |
| 599 | const isSameModel = originApi === currentApi && originModel === currentModel; | 617 | const isSameModel = originApi === currentApi && originModel === currentModel; |
| 600 | const signature = isSameModel ? chat[j]?.extra?.reasoning_signature : null; | 618 | // In group chats, only include reasoning from the currently generating character |
| 601 | const reasoning = isSameModel ? String(chat[j]?.extra?.reasoning ?? '') : ''; | 619 | const isOtherGroupMember = selected_group && chat[j].name !== name2; |
| 620 | const signature = isSameModel && !isOtherGroupMember ? chat[j]?.extra?.reasoning_signature : null; | ||
| 621 | const reasoning = isSameModel && !isOtherGroupMember ? String(chat[j]?.extra?.reasoning ?? '') : ''; | ||
| 602 | 622 | ||
| 603 | // Remove reasoning metadata from invocations if the API/model don't match | 623 | // Remove reasoning metadata from invocations if the API/model don't match |
| 604 | if (Array.isArray(invocations) && invocations.length > 0) { | 624 | if (Array.isArray(invocations) && invocations.length > 0) { |
| @@ -1013,8 +1033,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 1013 | const clone = structuredClone(invocation); | 1033 | const clone = structuredClone(invocation); |
| 1014 | if (!reasoningIsEligible) { | 1034 | if (!reasoningIsEligible) { |
| 1015 | delete clone.reasoning; | 1035 | delete clone.reasoning; |
| 1016 | } else if (previousAssistantReasoning) { | 1036 | } else if (previousAssistantReasoning && !clone.reasoning) { |
| 1017 | // Prefer currently editable assistant-text reasoning based on forwarding mode over invocation snapshot. | 1037 | // Fall back to adjacent assistant-text reasoning only when the invocation has none of its own. |
| 1018 | clone.reasoning = previousAssistantReasoning; | 1038 | clone.reasoning = previousAssistantReasoning; |
| 1019 | } | 1039 | } |
| 1020 | return clone; | 1040 | return clone; |
| @@ -1703,6 +1723,8 @@ export function getChatCompletionModel(settings = null) { | |||
| 1703 | return settings.groq_model; | 1723 | return settings.groq_model; |
| 1704 | case chat_completion_sources.SILICONFLOW: | 1724 | case chat_completion_sources.SILICONFLOW: |
| 1705 | return settings.siliconflow_model; | 1725 | return settings.siliconflow_model; |
| 1726 | case chat_completion_sources.MINIMAX: | ||
| 1727 | return settings.minimax_model; | ||
| 1706 | case chat_completion_sources.ELECTRONHUB: | 1728 | case chat_completion_sources.ELECTRONHUB: |
| 1707 | return settings.electronhub_model; | 1729 | return settings.electronhub_model; |
| 1708 | case chat_completion_sources.CHUTES: | 1730 | case chat_completion_sources.CHUTES: |
| @@ -1727,6 +1749,8 @@ export function getChatCompletionModel(settings = null) { | |||
| 1727 | return settings.azure_openai_model; | 1749 | return settings.azure_openai_model; |
| 1728 | case chat_completion_sources.ZAI: | 1750 | case chat_completion_sources.ZAI: |
| 1729 | return settings.zai_model; | 1751 | return settings.zai_model; |
| 1752 | case chat_completion_sources.WORKERS_AI: | ||
| 1753 | return settings.workers_ai_model; | ||
| 1730 | default: | 1754 | default: |
| 1731 | console.error(`Unknown chat completion source: ${source}`); | 1755 | console.error(`Unknown chat completion source: ${source}`); |
| 1732 | return ''; | 1756 | return ''; |
| @@ -1871,6 +1895,33 @@ function getChutesModelTemplate(option) { | |||
| 1871 | `)); | 1895 | `)); |
| 1872 | } | 1896 | } |
| 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 | function getNanoGptModelTemplate(option) { | 1925 | function getNanoGptModelTemplate(option) { |
| 1875 | const model = model_list.find(x => x.id === option?.element?.value); | 1926 | const model = model_list.find(x => x.id === option?.element?.value); |
| 1876 | 1927 | ||
| @@ -1880,10 +1931,9 @@ function getNanoGptModelTemplate(option) { | |||
| 1880 | 1931 | ||
| 1881 | const inputPrice = model.pricing?.prompt; | 1932 | const inputPrice = model.pricing?.prompt; |
| 1882 | const outputPrice = model.pricing?.completion; | 1933 | const outputPrice = model.pricing?.completion; |
| 1883 | |||
| 1884 | let price = 'Unknown'; | 1934 | let price = 'Unknown'; |
| 1935 | |||
| 1885 | if (inputPrice !== undefined && outputPrice !== undefined) { | 1936 | if (inputPrice !== undefined && outputPrice !== undefined) { |
| 1886 | // Check if both prices are 0 (free model) | ||
| 1887 | if (inputPrice === 0 && outputPrice === 0) { | 1937 | if (inputPrice === 0 && outputPrice === 0) { |
| 1888 | price = 'Free'; | 1938 | price = 'Free'; |
| 1889 | } else { | 1939 | } else { |
| @@ -1891,40 +1941,61 @@ function getNanoGptModelTemplate(option) { | |||
| 1891 | } | 1941 | } |
| 1892 | } | 1942 | } |
| 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 | const contextLength = model.context_length || 'Unknown'; | 1975 | const contextLength = model.context_length || 'Unknown'; |
| 1976 | const modelName = model.name || model.id; | ||
| 1895 | 1977 | ||
| 1896 | return $((` | 1978 | return $((` |
| 1897 | <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}"> | 1979 | <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}"> |
| 1898 | <strong>${DOMPurify.sanitize(model.id)}</strong> | ${contextLength} ctx | <small>${price}</small> | 1980 | <strong>${DOMPurify.sanitize(modelName)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities} |
| 1899 | </div> | 1981 | </div> |
| 1900 | `)); | 1982 | `)); |
| 1901 | } | 1983 | } |
| 1902 | 1984 | ||
| 1903 | function calculateChutesCost() { | 1985 | function getAimlapiModelTemplate(option) { |
| 1904 | if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) { | 1986 | const model = model_list.find(x => x.id === option?.element?.value); |
| 1905 | return; | ||
| 1906 | } | ||
| 1907 | |||
| 1908 | let cost = 'Unknown'; | ||
| 1909 | const model = model_list.find(x => x.id === oai_settings.chutes_model); | ||
| 1910 | |||
| 1911 | if (model?.pricing) { | ||
| 1912 | const outputPrice = model.pricing?.output; | ||
| 1913 | const inputPrice = model.pricing?.input; | ||
| 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 | function saveModelList(data) { | 2001 | function saveModelList(data) { |
| @@ -1932,14 +2003,22 @@ function saveModelList(data) { | |||
| 1932 | model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id)); | 2003 | model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id)); |
| 1933 | 2004 | ||
| 1934 | if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) { | 2005 | if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) { |
| 1935 | model_list = openRouterSortBy(model_list, oai_settings.openrouter_sort_models); | 2006 | model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.OPENROUTER); |
| 1936 | |||
| 1937 | $('#model_openrouter_select').empty(); | 2007 | $('#model_openrouter_select').empty(); |
| 2008 | $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` })); | ||
| 1938 | 2009 | ||
| 1939 | if (true === oai_settings.openrouter_group_models) { | 2010 | if (oai_settings.group_models) { |
| 1940 | appendOpenRouterOptions(openRouterGroupByVendor(model_list), oai_settings.openrouter_group_models); | 2011 | groupModelsByVendor(model_list, chat_completion_sources.OPENROUTER).forEach((models, vendor) => { |
| 2012 | const optgroup = $('<optgroup>').attr('label', vendor); | ||
| 2013 | models.forEach((model) => { | ||
| 2014 | optgroup.append($('<option>', { value: model.id, text: model.name })); | ||
| 2015 | }); | ||
| 2016 | $('#model_openrouter_select').append(optgroup); | ||
| 2017 | }); | ||
| 1941 | } else { | 2018 | } else { |
| 1942 | appendOpenRouterOptions(model_list); | 2019 | model_list.forEach((model) => { |
| 2020 | $('#model_openrouter_select').append($('<option>', { value: model.id, text: model.name })); | ||
| 2021 | }); | ||
| 1943 | } | 2022 | } |
| 1944 | 2023 | ||
| 1945 | $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change'); | 2024 | $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change'); |
| @@ -1979,13 +2058,26 @@ function saveModelList(data) { | |||
| 1979 | } | 2058 | } |
| 1980 | 2059 | ||
| 1981 | if (oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI) { | 2060 | if (oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI) { |
| 2061 | model_list = model_list.filter(m => m.type === 'chat-completion'); | ||
| 2062 | model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.AIMLAPI); | ||
| 1982 | $('#model_aimlapi_select').empty(); | 2063 | $('#model_aimlapi_select').empty(); |
| 1983 | const chatModels = model_list.filter(m => m.type === 'chat-completion'); | ||
| 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 | if (!oai_settings.aimlapi_model && chatModels.length > 0) { | 2079 | if (!oai_settings.aimlapi_model && model_list.length > 0) { |
| 1988 | oai_settings.aimlapi_model = chatModels[0].id; | 2080 | oai_settings.aimlapi_model = model_list[0].id; |
| 1989 | } | 2081 | } |
| 1990 | 2082 | ||
| 1991 | $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change'); | 2083 | $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change'); |
| @@ -2008,13 +2100,22 @@ function saveModelList(data) { | |||
| 2008 | 2100 | ||
| 2009 | if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { | 2101 | if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { |
| 2010 | model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions')); | 2102 | model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions')); |
| 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 | $('#model_electronhub_select').empty(); | 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 | const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model); | 2120 | const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model); |
| 2020 | if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) { | 2121 | if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) { |
| @@ -2026,15 +2127,21 @@ function saveModelList(data) { | |||
| 2026 | 2127 | ||
| 2027 | if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) { | 2128 | if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) { |
| 2028 | model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine')); | 2129 | model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine')); |
| 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 | $('#model_chutes_select').empty(); | 2131 | $('#model_chutes_select').empty(); |
| 2033 | 2132 | ||
| 2034 | for (const model of model_list) { | 2133 | if (oai_settings.group_models) { |
| 2035 | const option = $('<option>').val(model.id).text(model.id); | 2134 | groupModelsByVendor(model_list, chat_completion_sources.CHUTES).forEach((models, vendor) => { |
| 2036 | option.attr('data-model', JSON.stringify(model)); | 2135 | const optgroup = $('<optgroup>').attr('label', vendor); |
| 2037 | $('#model_chutes_select').append(option); | 2136 | models.forEach((model) => { |
| 2137 | optgroup.append($('<option>', { value: model.id, text: model.id })); | ||
| 2138 | }); | ||
| 2139 | $('#model_chutes_select').append(optgroup); | ||
| 2140 | }); | ||
| 2141 | } else { | ||
| 2142 | model_list.forEach((model) => { | ||
| 2143 | $('#model_chutes_select').append($('<option>', { value: model.id, text: model.id })); | ||
| 2144 | }); | ||
| 2038 | } | 2145 | } |
| 2039 | 2146 | ||
| 2040 | const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model); | 2147 | const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model); |
| @@ -2046,14 +2153,22 @@ function saveModelList(data) { | |||
| 2046 | } | 2153 | } |
| 2047 | 2154 | ||
| 2048 | if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) { | 2155 | if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) { |
| 2156 | model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.NANOGPT); | ||
| 2049 | $('#model_nanogpt_select').empty(); | 2157 | $('#model_nanogpt_select').empty(); |
| 2050 | model_list.forEach((model) => { | 2158 | |
| 2051 | $('#model_nanogpt_select').append( | 2159 | if (oai_settings.group_models) { |
| 2052 | $('<option>', { | 2160 | groupModelsByVendor(model_list, chat_completion_sources.NANOGPT).forEach((models, vendor) => { |
| 2053 | value: model.id, | 2161 | const optgroup = $('<optgroup>').attr('label', vendor); |
| 2054 | text: model.id, | 2162 | models.forEach((model) => { |
| 2055 | })); | 2163 | optgroup.append($('<option>', { value: model.id, text: model.name || model.id })); |
| 2056 | }); | 2164 | }); |
| 2165 | $('#model_nanogpt_select').append(optgroup); | ||
| 2166 | }); | ||
| 2167 | } else { | ||
| 2168 | model_list.forEach((model) => { | ||
| 2169 | $('#model_nanogpt_select').append($('<option>', { value: model.id, text: model.name || model.id })); | ||
| 2170 | }); | ||
| 2171 | } | ||
| 2057 | 2172 | ||
| 2058 | const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model); | 2173 | const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model); |
| 2059 | if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) { | 2174 | if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) { |
| @@ -2066,11 +2181,7 @@ function saveModelList(data) { | |||
| 2066 | if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { | 2181 | if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { |
| 2067 | $('#model_deepseek_select').empty(); | 2182 | $('#model_deepseek_select').empty(); |
| 2068 | model_list.forEach((model) => { | 2183 | model_list.forEach((model) => { |
| 2069 | $('#model_deepseek_select').append( | 2184 | $('#model_deepseek_select').append($('<option>', { value: model.id, text: model.id })); |
| 2070 | $('<option>', { | ||
| 2071 | value: model.id, | ||
| 2072 | text: model.id, | ||
| 2073 | })); | ||
| 2074 | }); | 2185 | }); |
| 2075 | 2186 | ||
| 2076 | const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model); | 2187 | const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model); |
| @@ -2084,11 +2195,7 @@ function saveModelList(data) { | |||
| 2084 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) { | 2195 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) { |
| 2085 | $('#model_pollinations_select').empty(); | 2196 | $('#model_pollinations_select').empty(); |
| 2086 | model_list.forEach((model) => { | 2197 | model_list.forEach((model) => { |
| 2087 | $('#model_pollinations_select').append( | 2198 | $('#model_pollinations_select').append($('<option>', { value: model.id, text: model.id })); |
| 2088 | $('<option>', { | ||
| 2089 | value: model.id, | ||
| 2090 | text: model.id, | ||
| 2091 | })); | ||
| 2092 | }); | 2199 | }); |
| 2093 | 2200 | ||
| 2094 | const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model); | 2201 | const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model); |
| @@ -2193,6 +2300,24 @@ function saveModelList(data) { | |||
| 2193 | $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change'); | 2300 | $('#model_fireworks_select').val(oai_settings.fireworks_model).trigger('change'); |
| 2194 | } | 2301 | } |
| 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 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { | 2321 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { |
| 2197 | $('#model_cometapi_select').empty(); | 2322 | $('#model_cometapi_select').empty(); |
| 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 | 2397 | return data.sort((a, b) => { | |
| 2273 | if (groupModels) { | 2398 | if (property === 'context_length') { |
| 2274 | model_list.forEach((models, vendor) => { | 2399 | return (b.context_length || 0) - (a.context_length || 0); |
| 2275 | const optgroup = $(`<optgroup label="${vendor}">`); | 2400 | } else if (property === 'pricing.input' || property === 'pricing.prompt') { |
| 2276 | 2401 | return parseFloat(a.pricing?.prompt || 0) - parseFloat(b.pricing?.prompt || 0); | |
| 2277 | models.forEach((model) => { | 2402 | } else if (property === 'pricing.output' || property === 'pricing.completion') { |
| 2278 | appendOption(model, optgroup); | 2403 | return parseFloat(a.pricing?.completion || 0) - parseFloat(b.pricing?.completion || 0); |
| 2404 | } else { | ||
| 2405 | return a?.name && b?.name ? a.name.localeCompare(b.name) : 0; | ||
| 2406 | } | ||
| 2279 | }); | 2407 | }); |
| 2280 | 2408 | case chat_completion_sources.CHUTES: | |
| 2281 | $('#model_openrouter_select').append(optgroup); | 2409 | return data.sort((a, b) => { |
| 2282 | }); | 2410 | if (property === 'context_length') { |
| 2283 | } else { | 2411 | return (b.context_length || 0) - (a.context_length || 0); |
| 2284 | model_list.forEach((model) => { | 2412 | } else if (property === 'pricing.input' || property === 'pricing.prompt') { |
| 2285 | appendOption(model); | 2413 | return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0); |
| 2286 | }); | 2414 | } else if (property === 'pricing.output' || property === 'pricing.completion') { |
| 2287 | } | 2415 | return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0); |
| 2288 | } | 2416 | } else { |
| 2289 | 2417 | return a?.id && b?.id ? a.id.localeCompare(b.id) : 0; | |
| 2290 | const openRouterSortBy = (data, property = 'alphabetically') => { | 2418 | } |
| 2291 | return data.sort((a, b) => { | ||
| 2292 | if (property === 'context_length') { | ||
| 2293 | return b.context_length - a.context_length; | ||
| 2294 | } else if (property === 'pricing.prompt') { | ||
| 2295 | return parseFloat(a.pricing.prompt) - parseFloat(b.pricing.prompt); | ||
| 2296 | } else { | ||
| 2297 | // Alphabetically | ||
| 2298 | return a?.name && b?.name && a.name.localeCompare(b.name); | ||
| 2299 | } | ||
| 2300 | }); | ||
| 2301 | }; | ||
| 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 | return data.sort((a, b) => { | ||
| 2319 | if (property === 'context_length') { | ||
| 2320 | return b.context_length - a.context_length; | ||
| 2321 | } else if (property === 'pricing.input') { | ||
| 2322 | const aPrice = parseFloat(a.pricing?.input || 0); | ||
| 2323 | const bPrice = parseFloat(b.pricing?.input || 0); | ||
| 2324 | return aPrice - bPrice; | ||
| 2325 | } else if (property === 'pricing.output') { | ||
| 2326 | const aPrice = parseFloat(a.pricing?.output || 0); | ||
| 2327 | const bPrice = parseFloat(b.pricing?.output || 0); | ||
| 2328 | return aPrice - bPrice; | ||
| 2329 | } else { | ||
| 2330 | return a?.id && b?.id && a.id.localeCompare(b.id); | ||
| 2331 | } | ||
| 2332 | }); | ||
| 2333 | } | ||
| 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 | }); | 2419 | }); |
| 2351 | 2420 | case chat_completion_sources.ELECTRONHUB: | |
| 2352 | $('#model_electronhub_select').append(optgroup); | 2421 | return data.sort((a, b) => { |
| 2353 | }); | 2422 | if (property === 'context_length') { |
| 2354 | } else { | 2423 | return (b.tokens || 0) - (a.tokens || 0); |
| 2355 | model_list.forEach((model) => { | 2424 | } else if (property === 'pricing.input' || property === 'pricing.prompt') { |
| 2356 | appendOption(model); | 2425 | return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0); |
| 2357 | }); | 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; | ||
| 2430 | } | ||
| 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); | ||
| 2440 | } else { | ||
| 2441 | return a?.name && b?.name ? a.name.localeCompare(b.name) : 0; | ||
| 2442 | } | ||
| 2443 | }); | ||
| 2444 | case chat_completion_sources.AIMLAPI: | ||
| 2445 | return data.sort((a, b) => { | ||
| 2446 | if (property === 'context_length') { | ||
| 2447 | return (b.info?.contextLength || 0) - (a.info?.contextLength || 0); | ||
| 2448 | } else { | ||
| 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; | ||
| 2451 | } | ||
| 2452 | }); | ||
| 2453 | default: | ||
| 2454 | return data; | ||
| 2358 | } | 2455 | } |
| 2359 | } | 2456 | } |
| 2360 | 2457 | ||
| 2361 | function electronHubSortBy(data, property = 'alphabetically') { | 2458 | /** |
| 2362 | return data.sort((a, b) => { | 2459 | * Groups models by vendor for the given source. If not supported, returns a map with a single entry containing all models. |
| 2363 | if (property === 'context_length') { | 2460 | * @param {object[]} array Array of model objects |
| 2364 | return b.tokens - a.tokens; | 2461 | * @param {string} source Chat Completion source (e.g., 'openrouter') |
| 2365 | } else if (property === 'pricing.input') { | 2462 | * @returns {Map<string, object[]>} Map of vendor to array of models |
| 2366 | return parseFloat(a.pricing.input) - parseFloat(b.pricing.input); | 2463 | */ |
| 2367 | } else if (property === 'pricing.output') { | 2464 | function groupModelsByVendor(array, source) { |
| 2368 | return parseFloat(a.pricing.output) - parseFloat(b.pricing.output); | 2465 | switch (source) { |
| 2369 | } else { | 2466 | case chat_completion_sources.OPENROUTER: |
| 2370 | return a?.name && b?.name && a.name.localeCompare(b.name); | 2467 | return array.reduce((acc, curr) => { |
| 2371 | } | 2468 | const vendor = curr.id.split('/')[0]; |
| 2372 | }); | 2469 | if (!acc.has(vendor)) { |
| 2373 | } | 2470 | acc.set(vendor, []); |
| 2374 | 2471 | } | |
| 2375 | function electronHubGroupByVendor(array) { | 2472 | acc.get(vendor).push(curr); |
| 2376 | return array.reduce((acc, curr) => { | 2473 | return acc; |
| 2377 | const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other'; | 2474 | }, new Map()); |
| 2378 | 2475 | case chat_completion_sources.ELECTRONHUB: | |
| 2379 | if (!acc.has(vendor)) { | 2476 | return array.reduce((acc, curr) => { |
| 2380 | acc.set(vendor, []); | 2477 | const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other'; |
| 2381 | } | 2478 | if (!acc.has(vendor)) { |
| 2382 | 2479 | acc.set(vendor, []); | |
| 2383 | acc.get(vendor).push(curr); | 2480 | } |
| 2384 | 2481 | acc.get(vendor).push(curr); | |
| 2385 | return acc; | 2482 | return acc; |
| 2386 | }, new Map()); | 2483 | }, new Map()); |
| 2387 | } | 2484 | case chat_completion_sources.NANOGPT: |
| 2388 | 2485 | return array.reduce((acc, curr) => { | |
| 2389 | function aimlapiGroupByVendor(array) { | 2486 | const vendorPart = /\//.test(curr.id) ? curr.id.split('/')[0] : curr.id.split('-')[0]; |
| 2390 | return array.reduce((acc, curr) => { | 2487 | const vendor = String(vendorPart?.trim()?.toLowerCase() || 'Other'); |
| 2391 | const vendor = curr.info.developer; | 2488 | if (!acc.has(vendor)) { |
| 2392 | 2489 | acc.set(vendor, []); | |
| 2393 | if (!acc.has(vendor)) { | 2490 | } |
| 2394 | acc.set(vendor, []); | 2491 | acc.get(vendor).push(curr); |
| 2395 | } | 2492 | return acc; |
| 2396 | 2493 | }, new Map()); | |
| 2397 | acc.get(vendor).push(curr); | 2494 | case chat_completion_sources.CHUTES: |
| 2398 | 2495 | return array.reduce((acc, curr) => { | |
| 2399 | return acc; | 2496 | const vendor = curr.id.split('/')[0]; |
| 2400 | }, new Map()); | 2497 | if (!acc.has(vendor)) { |
| 2401 | } | 2498 | acc.set(vendor, []); |
| 2402 | 2499 | } | |
| 2403 | function appendAimlapiOptions(model_list) { | 2500 | acc.get(vendor).push(curr); |
| 2404 | const appendOption = (model, parent = null) => { | 2501 | return acc; |
| 2405 | (parent || $('#model_aimlapi_select')).append( | 2502 | }, new Map()); |
| 2406 | $('<option>', { | 2503 | case chat_completion_sources.AIMLAPI: |
| 2407 | value: model.id, | 2504 | return array.reduce((acc, curr) => { |
| 2408 | text: model.info?.name || model.name || model.id, | 2505 | const vendor = curr.info?.developer || 'Other'; |
| 2409 | })); | 2506 | if (!acc.has(vendor)) { |
| 2410 | }; | 2507 | acc.set(vendor, []); |
| 2411 | 2508 | } | |
| 2412 | model_list.forEach((models, vendor) => { | 2509 | acc.get(vendor).push(curr); |
| 2413 | const optgroup = $(`<optgroup label="${vendor}">`); | 2510 | return acc; |
| 2414 | 2511 | }, new Map()); | |
| 2415 | models.forEach((model) => { | 2512 | default: |
| 2416 | appendOption(model, optgroup); | 2513 | return new Map([['', array]]); |
| 2417 | }); | ||
| 2418 | |||
| 2419 | $('#model_aimlapi_select').append(optgroup); | ||
| 2420 | }); | ||
| 2421 | } | ||
| 2422 | |||
| 2423 | function getAimlapiModelTemplate(option) { | ||
| 2424 | const model = model_list.find(x => x.id === option?.element?.value); | ||
| 2425 | |||
| 2426 | if (!option.id || !model) { | ||
| 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 | chat_completion_sources.COMETAPI, | 2537 | chat_completion_sources.COMETAPI, |
| 2460 | chat_completion_sources.ELECTRONHUB, | 2538 | chat_completion_sources.ELECTRONHUB, |
| 2461 | chat_completion_sources.CHUTES, | 2539 | chat_completion_sources.CHUTES, |
| 2540 | chat_completion_sources.DEEPSEEK, | ||
| 2462 | ]; | 2541 | ]; |
| 2463 | 2542 | ||
| 2464 | if (!reasoningEffortSources.includes(settings.chat_completion_source)) { | 2543 | if (!reasoningEffortSources.includes(settings.chat_completion_source)) { |
| @@ -2466,6 +2545,36 @@ function getReasoningEffort(settings = null, model = null) { | |||
| 2466 | } | 2545 | } |
| 2467 | 2546 | ||
| 2468 | function resolveReasoningEffort() { | 2547 | function resolveReasoningEffort() { |
| 2548 | if (settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { | ||
| 2549 | switch (settings.reasoning_effort) { | ||
| 2550 | case reasoning_effort_types.auto: | ||
| 2551 | return undefined; | ||
| 2552 | case reasoning_effort_types.max: | ||
| 2553 | return reasoning_effort_types.max; | ||
| 2554 | default: | ||
| 2555 | return reasoning_effort_types.high; | ||
| 2556 | } | ||
| 2557 | } | ||
| 2558 | |||
| 2559 | if (settings.chat_completion_source === chat_completion_sources.CUSTOM && /^koboldcpp\/(.+)$/.test(model)) { | ||
| 2560 | switch (settings.reasoning_effort) { | ||
| 2561 | case reasoning_effort_types.auto: | ||
| 2562 | return undefined; | ||
| 2563 | case reasoning_effort_types.min: | ||
| 2564 | return 'minimal'; | ||
| 2565 | case reasoning_effort_types.low: | ||
| 2566 | return 'low'; | ||
| 2567 | case reasoning_effort_types.medium: | ||
| 2568 | return 'medium'; | ||
| 2569 | case reasoning_effort_types.high: | ||
| 2570 | return 'high'; | ||
| 2571 | case reasoning_effort_types.max: | ||
| 2572 | return 'xhigh'; | ||
| 2573 | default: | ||
| 2574 | return settings.reasoning_effort; | ||
| 2575 | } | ||
| 2576 | } | ||
| 2577 | |||
| 2469 | switch (settings.reasoning_effort) { | 2578 | switch (settings.reasoning_effort) { |
| 2470 | case reasoning_effort_types.auto: | 2579 | case reasoning_effort_types.auto: |
| 2471 | return undefined; | 2580 | return undefined; |
| @@ -2474,9 +2583,16 @@ function getReasoningEffort(settings = null, model = null) { | |||
| 2474 | return 'none'; | 2583 | return 'none'; |
| 2475 | } | 2584 | } |
| 2476 | 2585 | ||
| 2477 | return [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source) && /^gpt-5/.test(model) | 2586 | if ([chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source)) { |
| 2478 | ? reasoning_effort_types.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 | case reasoning_effort_types.max: | 2596 | case reasoning_effort_types.max: |
| 2481 | return reasoning_effort_types.high; | 2597 | return reasoning_effort_types.high; |
| 2482 | default: | 2598 | default: |
| @@ -2604,7 +2720,8 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2604 | ]; | 2720 | ]; |
| 2605 | 2721 | ||
| 2606 | const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model); | 2722 | const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model); |
| 2607 | const stream = settings.stream_openai && type !== 'quiet' && !isO1; | 2723 | const isWorkersAIJsonMode = settings.chat_completion_source === chat_completion_sources.WORKERS_AI && jsonSchema; |
| 2724 | const stream = settings.stream_openai && type !== 'quiet' && !isO1 && !isWorkersAIJsonMode; | ||
| 2608 | 2725 | ||
| 2609 | const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue']; | 2726 | const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue']; |
| 2610 | const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source); | 2727 | const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source); |
| @@ -2715,6 +2832,11 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2715 | generate_data.middleout = settings.openrouter_middleout; | 2832 | generate_data.middleout = settings.openrouter_middleout; |
| 2716 | } | 2833 | } |
| 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 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) { | 2840 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) { |
| 2719 | const stopStringsLimit = 5; | 2841 | const stopStringsLimit = 5; |
| 2720 | generate_data.top_k = Number(settings.top_k_openai); | 2842 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -2815,6 +2937,24 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2815 | generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL; | 2937 | generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL; |
| 2816 | } | 2938 | } |
| 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 | // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus | 2958 | // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus |
| 2819 | if (settings.chat_completion_source === chat_completion_sources.NANOGPT) { | 2959 | if (settings.chat_completion_source === chat_completion_sources.NANOGPT) { |
| 2820 | generate_data.top_k = Number(settings.top_k_openai); | 2960 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -2870,7 +3010,7 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2870 | if (/gpt-5-chat-latest/.test(model)) { | 3010 | if (/gpt-5-chat-latest/.test(model)) { |
| 2871 | delete generate_data.tools; | 3011 | delete generate_data.tools; |
| 2872 | delete generate_data.tool_choice; | 3012 | delete generate_data.tool_choice; |
| 2873 | } else if (/gpt-5\.(1|2|3|4)/.test(model) && !/chat-latest/.test(model)) { | 3013 | } else if (/gpt-5\.(1|2|3|4)/.test(model) && !/chat-latest/.test(model) && !generate_data.reasoning_effort) { |
| 2874 | delete generate_data.frequency_penalty; | 3014 | delete generate_data.frequency_penalty; |
| 2875 | delete generate_data.presence_penalty; | 3015 | delete generate_data.presence_penalty; |
| 2876 | delete generate_data.logit_bias; | 3016 | delete generate_data.logit_bias; |
| @@ -3052,7 +3192,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov | |||
| 3052 | } | 3192 | } |
| 3053 | }); | 3193 | }); |
| 3054 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | 3194 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 3055 | } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW, chat_completion_sources.CHUTES].includes(chat_completion_source)) { | 3195 | } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI, chat_completion_sources.SILICONFLOW, chat_completion_sources.CHUTES, chat_completion_sources.WORKERS_AI].includes(chat_completion_source)) { |
| 3056 | if (show_thoughts) { | 3196 | if (show_thoughts) { |
| 3057 | state.reasoning += | 3197 | state.reasoning += |
| 3058 | data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ?? | 3198 | data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ?? |
| @@ -4049,6 +4189,11 @@ function migrateChatCompletionSettings(settings) { | |||
| 4049 | { oldKey: 'claude_use_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, | 4189 | { oldKey: 'claude_use_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, |
| 4050 | { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, | 4190 | { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, |
| 4051 | { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') }, | 4191 | { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') }, |
| 4192 | { oldKey: 'deepseek_model', oldValue: /^deepseek-(chat|reasoner|coder)$/, newKey: 'deepseek_model', newValue: 'deepseek-v4-flash' }, | ||
| 4193 | { oldKey: 'openrouter_sort_models', oldValue: 'alphabetically', newKey: 'sort_models', newValue: 'alphabetically' }, | ||
| 4194 | { oldKey: 'openrouter_sort_models', oldValue: 'pricing.prompt', newKey: 'sort_models', newValue: 'pricing.prompt' }, | ||
| 4195 | { oldKey: 'openrouter_sort_models', oldValue: 'context_length', newKey: 'sort_models', newValue: 'context_length' }, | ||
| 4196 | { oldKey: 'openrouter_group_models', oldValue: true, newKey: 'group_models', newValue: true }, | ||
| 4052 | ]; | 4197 | ]; |
| 4053 | 4198 | ||
| 4054 | for (const migration of migrateMap) { | 4199 | for (const migration of migrateMap) { |
| @@ -4150,9 +4295,11 @@ function loadOpenAISettings(data, settings) { | |||
| 4150 | setNamesBehaviorControls(); | 4295 | setNamesBehaviorControls(); |
| 4151 | setContinuePostfixControls(); | 4296 | setContinuePostfixControls(); |
| 4152 | setToolReasoningControls(); | 4297 | setToolReasoningControls(); |
| 4298 | ToolManager.RECURSE_LIMIT = oai_settings.tool_call_recurse_limit; | ||
| 4153 | 4299 | ||
| 4154 | $('#openrouter_providers_chat').trigger('change'); | 4300 | $('#openrouter_providers_chat').trigger('change'); |
| 4155 | $('#openrouter_quantizations_chat').trigger('change'); | 4301 | $('#openrouter_quantizations_chat').trigger('change'); |
| 4302 | $('#nanogpt_provider').trigger('change'); | ||
| 4156 | $('#chat_completion_source').trigger('change'); | 4303 | $('#chat_completion_source').trigger('change'); |
| 4157 | } | 4304 | } |
| 4158 | 4305 | ||
| @@ -4215,6 +4362,7 @@ async function getStatusOpen() { | |||
| 4215 | chat_completion_sources.VERTEXAI, | 4362 | chat_completion_sources.VERTEXAI, |
| 4216 | chat_completion_sources.PERPLEXITY, | 4363 | chat_completion_sources.PERPLEXITY, |
| 4217 | chat_completion_sources.ZAI, | 4364 | chat_completion_sources.ZAI, |
| 4365 | chat_completion_sources.MINIMAX, | ||
| 4218 | ]; | 4366 | ]; |
| 4219 | if (noValidateSources.includes(oai_settings.chat_completion_source)) { | 4367 | if (noValidateSources.includes(oai_settings.chat_completion_source)) { |
| 4220 | let status = t`Key saved; press \"Test Message\" to verify.`; | 4368 | let status = t`Key saved; press \"Test Message\" to verify.`; |
| @@ -4272,6 +4420,14 @@ async function getStatusOpen() { | |||
| 4272 | data.siliconflow_endpoint = oai_settings.siliconflow_endpoint; | 4420 | data.siliconflow_endpoint = oai_settings.siliconflow_endpoint; |
| 4273 | } | 4421 | } |
| 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 | const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM; | 4431 | const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM; |
| 4276 | if (canBypass) { | 4432 | if (canBypass) { |
| 4277 | setOnlineStatus(t`Status check bypassed`); | 4433 | setOnlineStatus(t`Status check bypassed`); |
| @@ -4791,6 +4947,7 @@ function onSettingsPresetChange() { | |||
| 4791 | $('#chat_completion_source').trigger('change'); | 4947 | $('#chat_completion_source').trigger('change'); |
| 4792 | $('#openrouter_providers_chat').trigger('change'); | 4948 | $('#openrouter_providers_chat').trigger('change'); |
| 4793 | $('#openrouter_quantizations_chat').trigger('change'); | 4949 | $('#openrouter_quantizations_chat').trigger('change'); |
| 4950 | $('#nanogpt_provider').trigger('change'); | ||
| 4794 | } | 4951 | } |
| 4795 | 4952 | ||
| 4796 | $('#openai_logit_bias_preset').trigger('change'); | 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 | function getMaxContextOpenAI(value) { | 4966 | function getMaxContextOpenAI(value) { |
| 4805 | if (oai_settings.max_context_unlocked) { | 4967 | if (oai_settings.max_context_unlocked) { |
| 4806 | return unlocked_max; | 4968 | return unlocked_max; |
| 4807 | } else if (value.startsWith('gpt-5.4')) { | ||
| 4808 | return max_1mil; | ||
| 4809 | } else if (value.startsWith('gpt-5')) { | ||
| 4810 | return max_400k; | ||
| 4811 | } else if (value.includes('gpt-4.1')) { | ||
| 4812 | return max_1mil; | ||
| 4813 | } else if (value.includes('gpt-audio')) { | ||
| 4814 | return max_128k; | ||
| 4815 | } else if (value.startsWith('o1')) { | ||
| 4816 | return max_128k; | ||
| 4817 | } else if (value.startsWith('o4') || value.startsWith('o3')) { | ||
| 4818 | return max_200k; | ||
| 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')) { | ||
| 4820 | 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 | } | 4969 | } |
| 4970 | |||
| 4971 | /** @type {[RegExp, number][]} */ | ||
| 4972 | const contextMap = [ | ||
| 4973 | [/^gpt-5\.[45]/, max_1mil], | ||
| 4974 | [/^gpt-5/, max_400k], | ||
| 4975 | [/gpt-4\.1/, max_1mil], | ||
| 4976 | [/gpt-audio/, max_128k], | ||
| 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 | ||
| 4997 | return max_128k; | ||
| 4998 | } | ||
| 4999 | |||
| 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 | |||
| 5036 | return max_128k; | ||
| 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 | const contextMap = { | 5139 | const contextMap = { |
| 4922 | 'glm-5.1': max_200k, | 5140 | 'glm-5.1': max_200k, |
| 4923 | 'glm-5-turbo': max_200k, | 5141 | 'glm-5-turbo': max_200k, |
| 5142 | 'glm-5v-turbo': max_200k, | ||
| 4924 | 'glm-5': max_200k, | 5143 | 'glm-5': max_200k, |
| 4925 | 'glm-4.7': max_200k, | 5144 | 'glm-4.7': max_200k, |
| 4926 | 'glm-4.7-flash': max_200k, | 5145 | 'glm-4.7-flash': max_200k, |
| @@ -5221,6 +5440,15 @@ async function onModelChange() { | |||
| 5221 | oai_settings.siliconflow_model = value; | 5440 | oai_settings.siliconflow_model = value; |
| 5222 | } | 5441 | } |
| 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 | if ($(this).is('#model_electronhub_select')) { | 5452 | if ($(this).is('#model_electronhub_select')) { |
| 5225 | if (!value || !hasModelsLoaded) { | 5453 | if (!value || !hasModelsLoaded) { |
| 5226 | console.debug('Null ElectronHub model selected. Ignoring.'); | 5454 | console.debug('Null ElectronHub model selected. Ignoring.'); |
| @@ -5247,6 +5475,7 @@ async function onModelChange() { | |||
| 5247 | 5475 | ||
| 5248 | console.log('NanoGPT model changed to', value); | 5476 | console.log('NanoGPT model changed to', value); |
| 5249 | oai_settings.nanogpt_model = value; | 5477 | oai_settings.nanogpt_model = value; |
| 5478 | syncNanoGptProvidersForModel(value, '#nanogpt_provider'); | ||
| 5250 | } | 5479 | } |
| 5251 | 5480 | ||
| 5252 | if ($(this).is('#model_deepseek_select')) { | 5481 | if ($(this).is('#model_deepseek_select')) { |
| @@ -5328,29 +5557,21 @@ async function onModelChange() { | |||
| 5328 | oai_settings.zai_model = value; | 5557 | oai_settings.zai_model = value; |
| 5329 | } | 5558 | } |
| 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 | if (oai_settings.max_context_unlocked) { | 5561 | if (!value || !hasModelsLoaded) { |
| 5333 | $('#openai_max_context').attr('max', max_2mil); | 5562 | console.debug('Null Workers AI model selected. Ignoring.'); |
| 5334 | } else if (value.includes('gemini-2.5-flash-image')) { | 5563 | return; |
| 5335 | $('#openai_max_context').attr('max', max_32k); | ||
| 5336 | } else if (value.includes('gemini-3-pro-image')) { | ||
| 5337 | $('#openai_max_context').attr('max', max_64k); | ||
| 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 | } | 5564 | } |
| 5351 | let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0; | 5565 | console.log('Workers AI model changed to', value); |
| 5352 | oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai); | 5566 | oai_settings.workers_ai_model = value; |
| 5353 | $('#temp_openai').attr('max', makersuite_max_temp).val(oai_settings.temp_openai).trigger('input'); | 5567 | } |
| 5568 | |||
| 5569 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) { | ||
| 5570 | const contextSize = getGeminiMaxContext(value, oai_settings.max_context_unlocked); | ||
| 5571 | const maxTemp = getGeminiMaxTemp(value); | ||
| 5572 | $('#openai_max_context').attr('max', contextSize); | ||
| 5573 | oai_settings.temp_openai = Math.min(maxTemp, oai_settings.temp_openai); | ||
| 5574 | $('#temp_openai').attr('max', maxTemp).val(oai_settings.temp_openai).trigger('input'); | ||
| 5354 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | 5575 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 5355 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | 5576 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| 5356 | } | 5577 | } |
| @@ -5383,7 +5604,7 @@ async function onModelChange() { | |||
| 5383 | if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) { | 5604 | if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) { |
| 5384 | if (oai_settings.max_context_unlocked) { | 5605 | if (oai_settings.max_context_unlocked) { |
| 5385 | $('#openai_max_context').attr('max', unlocked_max); | 5606 | $('#openai_max_context').attr('max', unlocked_max); |
| 5386 | } else if (/^claude-(sonnet-4-5|sonnet-4-6|opus-4-6)/.test(value)) { | 5607 | } else if (/^claude-(sonnet-4-5|sonnet-4-6|opus-4-6|opus-4-7)/.test(value)) { |
| 5387 | $('#openai_max_context').attr('max', max_1mil); | 5608 | $('#openai_max_context').attr('max', max_1mil); |
| 5388 | } else if (/^claude-(3|opus|haiku|sonnet)/.test(value)) { | 5609 | } else if (/^claude-(3|opus|haiku|sonnet)/.test(value)) { |
| 5389 | $('#openai_max_context').attr('max', max_200k); | 5610 | $('#openai_max_context').attr('max', max_200k); |
| @@ -5533,19 +5754,27 @@ async function onModelChange() { | |||
| 5533 | } | 5754 | } |
| 5534 | 5755 | ||
| 5535 | if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { | 5756 | if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { |
| 5757 | const maxContext = oai_settings.max_context_unlocked ? unlocked_max : max_1mil; | ||
| 5758 | $('#openai_max_context').attr('max', maxContext); | ||
| 5759 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | ||
| 5760 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | ||
| 5761 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); | ||
| 5762 | } | ||
| 5763 | |||
| 5764 | if (oai_settings.chat_completion_source === chat_completion_sources.WORKERS_AI) { | ||
| 5536 | if (oai_settings.max_context_unlocked) { | 5765 | if (oai_settings.max_context_unlocked) { |
| 5537 | $('#openai_max_context').attr('max', unlocked_max); | 5766 | $('#openai_max_context').attr('max', unlocked_max); |
| 5538 | } else if (['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) { | ||
| 5539 | $('#openai_max_context').attr('max', max_128k); | ||
| 5540 | } else if (oai_settings.deepseek_model == 'deepseek-coder') { | ||
| 5541 | $('#openai_max_context').attr('max', max_16k); | ||
| 5542 | } else { | 5767 | } else { |
| 5543 | $('#openai_max_context').attr('max', max_64k); | 5768 | const model = model_list.find(m => m.id === oai_settings.workers_ai_model); |
| 5769 | const ctxProp = Array.isArray(model?.properties) && model.properties.find(p => p.property_id === 'context_window'); | ||
| 5770 | const contextLength = ctxProp ? Number(ctxProp.value) : max_8k; | ||
| 5771 | $('#openai_max_context').attr('max', contextLength || max_8k); | ||
| 5544 | } | 5772 | } |
| 5545 | |||
| 5546 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | 5773 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 5547 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | 5774 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| 5548 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); | 5775 | const workersAiMaxTemp = 5.0; |
| 5776 | oai_settings.temp_openai = Math.min(workersAiMaxTemp, oai_settings.temp_openai); | ||
| 5777 | $('#temp_openai').attr('max', workersAiMaxTemp).val(oai_settings.temp_openai).trigger('input'); | ||
| 5549 | } | 5778 | } |
| 5550 | 5779 | ||
| 5551 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { | 5780 | if (oai_settings.chat_completion_source === chat_completion_sources.COMETAPI) { |
| @@ -5637,6 +5866,15 @@ async function onModelChange() { | |||
| 5637 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); | 5866 | $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input'); |
| 5638 | } | 5867 | } |
| 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 | if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { | 5878 | if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { |
| 5641 | const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked); | 5879 | const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked); |
| 5642 | $('#openai_max_context').attr('max', maxContext); | 5880 | $('#openai_max_context').attr('max', maxContext); |
| @@ -5653,18 +5891,6 @@ async function onModelChange() { | |||
| 5653 | eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value); | 5891 | eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value); |
| 5654 | } | 5892 | } |
| 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 | async function onNewPresetClick() { | 5894 | async function onNewPresetClick() { |
| 5669 | const name = await Popup.show.input(t`Preset name:`, t`Hint: Use a character/group name to bind preset to a specific chat.`, oai_settings.preset_settings_openai); | 5895 | const name = await Popup.show.input(t`Preset name:`, t`Hint: Use a character/group name to bind preset to a specific chat.`, oai_settings.preset_settings_openai); |
| 5670 | 5896 | ||
| @@ -5709,6 +5935,8 @@ async function onConnectButtonClick(e) { | |||
| 5709 | [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true }, | 5935 | [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true }, |
| 5710 | [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false }, | 5936 | [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false }, |
| 5711 | [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false }, | 5937 | [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false }, |
| 5938 | [chat_completion_sources.WORKERS_AI]: { key: SECRET_KEYS.WORKERS_AI, selector: '#api_key_workers_ai', proxy: false }, | ||
| 5939 | [chat_completion_sources.MINIMAX]: { key: SECRET_KEYS.MINIMAX, selector: '#api_key_minimax', proxy: false }, | ||
| 5712 | }; | 5940 | }; |
| 5713 | 5941 | ||
| 5714 | // Vertex AI Express version - use API key | 5942 | // Vertex AI Express version - use API key |
| @@ -5774,6 +6002,8 @@ function toggleChatCompletionForms() { | |||
| 5774 | $('#model_chutes_select').trigger('change'); | 6002 | $('#model_chutes_select').trigger('change'); |
| 5775 | } else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) { | 6003 | } else if (oai_settings.chat_completion_source == chat_completion_sources.SILICONFLOW) { |
| 5776 | $('#model_siliconflow_select').trigger('change'); | 6004 | $('#model_siliconflow_select').trigger('change'); |
| 6005 | } else if (oai_settings.chat_completion_source == chat_completion_sources.MINIMAX) { | ||
| 6006 | $('#model_minimax_select').trigger('change'); | ||
| 5777 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { | 6007 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { |
| 5778 | $('#model_electronhub_select').trigger('change'); | 6008 | $('#model_electronhub_select').trigger('change'); |
| 5779 | } else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) { | 6009 | } else if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) { |
| @@ -5798,6 +6028,8 @@ function toggleChatCompletionForms() { | |||
| 5798 | $('#azure_openai_model').trigger('change'); | 6028 | $('#azure_openai_model').trigger('change'); |
| 5799 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { | 6029 | } else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) { |
| 5800 | $('#model_zai_select').trigger('change'); | 6030 | $('#model_zai_select').trigger('change'); |
| 6031 | } else if (oai_settings.chat_completion_source == chat_completion_sources.WORKERS_AI) { | ||
| 6032 | $('#model_workers_ai_select').trigger('change'); | ||
| 5801 | } | 6033 | } |
| 5802 | 6034 | ||
| 5803 | $('[data-source]').each(function () { | 6035 | $('[data-source]').each(function () { |
| @@ -5903,6 +6135,10 @@ export function isImageInliningSupported() { | |||
| 5903 | 'gemini-exp-1206', | 6135 | 'gemini-exp-1206', |
| 5904 | 'learnlm', | 6136 | 'learnlm', |
| 5905 | 'gemini-robotics', | 6137 | 'gemini-robotics', |
| 6138 | 'gemma-3-27b', | ||
| 6139 | 'gemma-3-12b', | ||
| 6140 | 'gemma-3-4b', | ||
| 6141 | 'gemma-4', | ||
| 5906 | // MistralAI | 6142 | // MistralAI |
| 5907 | 'mistral-small-2503', | 6143 | 'mistral-small-2503', |
| 5908 | 'mistral-small-2506', | 6144 | 'mistral-small-2506', |
| @@ -5923,6 +6159,7 @@ export function isImageInliningSupported() { | |||
| 5923 | // Z.AI (GLM) | 6159 | // Z.AI (GLM) |
| 5924 | 'glm-4.5v', | 6160 | 'glm-4.5v', |
| 5925 | 'glm-4.6v', | 6161 | 'glm-4.6v', |
| 6162 | 'glm-5v-turbo', | ||
| 5926 | 'autoglm-phone', | 6163 | 'autoglm-phone', |
| 5927 | // SiliconFlow | 6164 | // SiliconFlow |
| 5928 | 'Qwen/Qwen3-VL-32B-Instruct', | 6165 | 'Qwen/Qwen3-VL-32B-Instruct', |
| @@ -5978,6 +6215,10 @@ export function isImageInliningSupported() { | |||
| 5978 | return visionSupportedModels.some(model => oai_settings.zai_model.includes(model)); | 6215 | return visionSupportedModels.some(model => oai_settings.zai_model.includes(model)); |
| 5979 | case chat_completion_sources.SILICONFLOW: | 6216 | case chat_completion_sources.SILICONFLOW: |
| 5980 | return visionSupportedModels.some(model => oai_settings.siliconflow_model.includes(model)); | 6217 | return visionSupportedModels.some(model => oai_settings.siliconflow_model.includes(model)); |
| 6218 | case chat_completion_sources.WORKERS_AI: { | ||
| 6219 | const waiModel = Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.workers_ai_model); | ||
| 6220 | return Boolean(waiModel && Array.isArray(waiModel.properties) && waiModel.properties.some(p => p.property_id === 'vision' && p.value === 'true')); | ||
| 6221 | } | ||
| 5981 | default: | 6222 | default: |
| 5982 | return false; | 6223 | return false; |
| 5983 | } | 6224 | } |
| @@ -6002,9 +6243,11 @@ export function isVideoInliningSupported() { | |||
| 6002 | 'gemini-2.5', | 6243 | 'gemini-2.5', |
| 6003 | 'gemini-exp-1206', | 6244 | 'gemini-exp-1206', |
| 6004 | 'gemini-3', | 6245 | 'gemini-3', |
| 6246 | 'gemma-4', | ||
| 6005 | // Z.AI (GLM) | 6247 | // Z.AI (GLM) |
| 6006 | 'glm-4.5v', | 6248 | 'glm-4.5v', |
| 6007 | 'glm-4.6v', | 6249 | 'glm-4.6v', |
| 6250 | 'glm-5v-turbo', | ||
| 6008 | ]; | 6251 | ]; |
| 6009 | 6252 | ||
| 6010 | switch (oai_settings.chat_completion_source) { | 6253 | switch (oai_settings.chat_completion_source) { |
| @@ -6632,16 +6875,6 @@ export function initOpenAI() { | |||
| 6632 | saveSettingsDebounced(); | 6875 | saveSettingsDebounced(); |
| 6633 | }); | 6876 | }); |
| 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 | $('#openrouter_allow_fallbacks').on('input', function () { | 6878 | $('#openrouter_allow_fallbacks').on('input', function () { |
| 6646 | oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked'); | 6879 | oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked'); |
| 6647 | updateOpenRouterProvidersWarning('#openrouter_providers_chat'); | 6880 | updateOpenRouterProvidersWarning('#openrouter_providers_chat'); |
| @@ -6653,21 +6886,6 @@ export function initOpenAI() { | |||
| 6653 | saveSettingsDebounced(); | 6886 | saveSettingsDebounced(); |
| 6654 | }); | 6887 | }); |
| 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 | $('#squash_system_messages').on('input', function () { | 6889 | $('#squash_system_messages').on('input', function () { |
| 6672 | oai_settings.squash_system_messages = !!$(this).prop('checked'); | 6890 | oai_settings.squash_system_messages = !!$(this).prop('checked'); |
| 6673 | saveSettingsDebounced(); | 6891 | saveSettingsDebounced(); |
| @@ -6695,6 +6913,13 @@ export function initOpenAI() { | |||
| 6695 | saveSettingsDebounced(); | 6913 | saveSettingsDebounced(); |
| 6696 | }); | 6914 | }); |
| 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 | $('#tool_reasoning_mode').on('input', function () { | 6923 | $('#tool_reasoning_mode').on('input', function () { |
| 6699 | oai_settings.tool_reasoning_mode = getToolReasoningMode({ | 6924 | oai_settings.tool_reasoning_mode = getToolReasoningMode({ |
| 6700 | ...oai_settings, | 6925 | ...oai_settings, |
| @@ -6921,11 +7146,34 @@ export function initOpenAI() { | |||
| 6921 | saveSettingsDebounced(); | 7146 | saveSettingsDebounced(); |
| 6922 | }); | 7147 | }); |
| 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 | $('#bind_preset_to_connection').on('input', function () { | 7160 | $('#bind_preset_to_connection').on('input', function () { |
| 6925 | oai_settings.bind_preset_to_connection = !!$(this).prop('checked'); | 7161 | oai_settings.bind_preset_to_connection = !!$(this).prop('checked'); |
| 6926 | saveSettingsDebounced(); | 7162 | saveSettingsDebounced(); |
| 6927 | }); | 7163 | }); |
| 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 | $('#api_button_openai').on('click', onConnectButtonClick); | 7177 | $('#api_button_openai').on('click', onConnectButtonClick); |
| 6930 | $('#openai_reverse_proxy').on('input', onReverseProxyInput); | 7178 | $('#openai_reverse_proxy').on('input', onReverseProxyInput); |
| 6931 | $('#model_openai_select').on('change', onModelChange); | 7179 | $('#model_openai_select').on('change', onModelChange); |
| @@ -6949,15 +7197,18 @@ export function initOpenAI() { | |||
| 6949 | oai_settings.siliconflow_endpoint = String($(this).val()); | 7197 | oai_settings.siliconflow_endpoint = String($(this).val()); |
| 6950 | saveSettingsDebounced(); | 7198 | saveSettingsDebounced(); |
| 6951 | }); | 7199 | }); |
| 7200 | $('#minimax_endpoint').on('input', function () { | ||
| 7201 | oai_settings.minimax_endpoint = String($(this).val()); | ||
| 7202 | saveSettingsDebounced(); | ||
| 7203 | }); | ||
| 7204 | $('#workers_ai_account_id').on('input', function () { | ||
| 7205 | oai_settings.workers_ai_account_id = String($(this).val()); | ||
| 7206 | saveSettingsDebounced(); | ||
| 7207 | }); | ||
| 6952 | $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange); | 7208 | $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange); |
| 6953 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); | 7209 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); |
| 6954 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); | 7210 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); |
| 6955 | $('#model_openrouter_select').on('change', onModelChange); | 7211 | $('#model_openrouter_select').on('change', onModelChange); |
| 6956 | $('#openrouter_group_models').on('change', onOpenrouterModelSortChange); | ||
| 6957 | $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange); | ||
| 6958 | $('#chutes_sort_models').on('change', onChutesModelSortChange); | ||
| 6959 | $('#electronhub_group_models').on('change', onElectronHubModelSortChange); | ||
| 6960 | $('#electronhub_sort_models').on('change', onElectronHubModelSortChange); | ||
| 6961 | $('#model_ai21_select').on('change', onModelChange); | 7212 | $('#model_ai21_select').on('change', onModelChange); |
| 6962 | $('#model_mistralai_select').on('change', onModelChange); | 7213 | $('#model_mistralai_select').on('change', onModelChange); |
| 6963 | $('#model_cohere_select').on('change', onModelChange); | 7214 | $('#model_cohere_select').on('change', onModelChange); |
| @@ -6965,6 +7216,7 @@ export function initOpenAI() { | |||
| 6965 | $('#model_groq_select').on('change', onModelChange); | 7216 | $('#model_groq_select').on('change', onModelChange); |
| 6966 | $('#model_chutes_select').on('change', onModelChange); | 7217 | $('#model_chutes_select').on('change', onModelChange); |
| 6967 | $('#model_siliconflow_select').on('change', onModelChange); | 7218 | $('#model_siliconflow_select').on('change', onModelChange); |
| 7219 | $('#model_minimax_select').on('change', onModelChange); | ||
| 6968 | $('#model_electronhub_select').on('change', onModelChange); | 7220 | $('#model_electronhub_select').on('change', onModelChange); |
| 6969 | $('#model_nanogpt_select').on('change', onModelChange); | 7221 | $('#model_nanogpt_select').on('change', onModelChange); |
| 6970 | $('#model_deepseek_select').on('change', onModelChange); | 7222 | $('#model_deepseek_select').on('change', onModelChange); |
| @@ -6977,6 +7229,7 @@ export function initOpenAI() { | |||
| 6977 | $('#model_fireworks_select').on('change', onModelChange); | 7229 | $('#model_fireworks_select').on('change', onModelChange); |
| 6978 | $('#azure_openai_model').on('change', onModelChange); | 7230 | $('#azure_openai_model').on('change', onModelChange); |
| 6979 | $('#model_zai_select').on('change', onModelChange); | 7231 | $('#model_zai_select').on('change', onModelChange); |
| 7232 | $('#model_workers_ai_select').on('change', onModelChange); | ||
| 6980 | $('#settings_preset_openai').on('change', onSettingsPresetChange); | 7233 | $('#settings_preset_openai').on('change', onSettingsPresetChange); |
| 6981 | $('#new_oai_preset').on('click', onNewPresetClick); | 7234 | $('#new_oai_preset').on('click', onNewPresetClick); |
| 6982 | $('#delete_oai_preset').on('click', onDeletePresetClick); | 7235 | $('#delete_oai_preset').on('click', onDeletePresetClick); |
| @@ -22,9 +22,35 @@ import { | |||
| 22 | setUserName, | 22 | setUserName, |
| 23 | this_chid, | 23 | this_chid, |
| 24 | } from '../script.js'; | 24 | } from '../script.js'; |
| 25 | import { persona_description_positions, power_user } from './power-user.js'; | 25 | import { power_user } from './power-user.js'; |
| 26 | import { getTokenCountAsync } from './tokenizers.js'; | 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 | import { debounce_timeout } from './constants.js'; | 54 | import { debounce_timeout } from './constants.js'; |
| 29 | import { FILTER_TYPES, FilterHelper } from './filters.js'; | 55 | import { FILTER_TYPES, FilterHelper } from './filters.js'; |
| 30 | import { groups, selected_group } from './group-chats.js'; | 56 | import { groups, selected_group } from './group-chats.js'; |
| @@ -36,10 +62,11 @@ import { saveMetadataDebounced } from './extensions.js'; | |||
| 36 | import { accountStorage } from './util/AccountStorage.js'; | 62 | import { accountStorage } from './util/AccountStorage.js'; |
| 37 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 63 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 38 | import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; | 64 | import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; |
| 39 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 65 | import { commonEnumMatchProviders, commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 40 | import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; | 66 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 41 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 67 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 42 | import { isFirefox } from './browser-fixes.js'; | 68 | import { isFirefox } from './browser-fixes.js'; |
| 69 | import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; | ||
| 43 | 70 | ||
| 44 | /** | 71 | /** |
| 45 | * @typedef {object} PersonaConnection A connection between a character and a character or group entity | 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 | * @property {boolean} locked.character - Whether the persona is locked to the currently open character or group | 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 | const USER_AVATAR_PATH = 'User Avatars/'; | 100 | const USER_AVATAR_PATH = 'User Avatars/'; |
| 62 | 101 | ||
| 63 | let savePersonasPage = 0; | 102 | let savePersonasPage = 0; |
| @@ -217,11 +256,12 @@ function getUserAvatarBlock(avatarId) { | |||
| 217 | /** | 256 | /** |
| 218 | * Initialize missing personas in the power user settings. | 257 | * Initialize missing personas in the power user settings. |
| 219 | * @param {string[]} avatarsList List of avatar file names | 258 | * @param {string[]} avatarsList List of avatar file names |
| 259 | * @returns {Promise<void>} | ||
| 220 | */ | 260 | */ |
| 221 | function addMissingPersonas(avatarsList) { | 261 | async function addMissingPersonas(avatarsList) { |
| 222 | for (const persona of avatarsList) { | 262 | for (const persona of avatarsList) { |
| 223 | if (!power_user.personas[persona]) { | 263 | if (!power_user.personas[persona]) { |
| 224 | initPersona(persona, '[Unnamed Persona]', '', ''); | 264 | await initPersona(persona, '[Unnamed Persona]', '', '', { silent: true }); |
| 225 | } | 265 | } |
| 226 | } | 266 | } |
| 227 | } | 267 | } |
| @@ -249,7 +289,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | |||
| 249 | } | 289 | } |
| 250 | 290 | ||
| 251 | // If any persona is missing from the power user settings, we add it | 291 | // If any persona is missing from the power user settings, we add it |
| 252 | addMissingPersonas(allEntities); | 292 | await addMissingPersonas(allEntities); |
| 253 | // Before printing the personas, we check if we should enable/disable search sorting | 293 | // Before printing the personas, we check if we should enable/disable search sorting |
| 254 | verifyPersonaSearchSortRule(); | 294 | verifyPersonaSearchSortRule(); |
| 255 | 295 | ||
| @@ -429,7 +469,7 @@ export async function createPersona(avatarId) { | |||
| 429 | 469 | ||
| 430 | const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 }); | 470 | const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 }); |
| 431 | 471 | ||
| 432 | initPersona(avatarId, personaName, personaDescription, ''); | 472 | await initPersona(avatarId, personaName, personaDescription, ''); |
| 433 | if (power_user.persona_show_notifications) { | 473 | if (power_user.persona_show_notifications) { |
| 434 | toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`); | 474 | toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`); |
| 435 | } | 475 | } |
| @@ -454,7 +494,7 @@ async function createDummyPersona() { | |||
| 454 | 494 | ||
| 455 | // Date + name (only ASCII) to make it unique | 495 | // Date + name (only ASCII) to make it unique |
| 456 | const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; | 496 | const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; |
| 457 | initPersona(avatarId, personaName, '', personaTitle); | 497 | await initPersona(avatarId, personaName, '', personaTitle); |
| 458 | await uploadUserAvatar(default_user_avatar, avatarId); | 498 | await uploadUserAvatar(default_user_avatar, avatarId); |
| 459 | } | 499 | } |
| 460 | 500 | ||
| @@ -464,20 +504,36 @@ async function createDummyPersona() { | |||
| 464 | * @param {string} personaName Name for the persona | 504 | * @param {string} personaName Name for the persona |
| 465 | * @param {string} personaDescription Optional description for the persona | 505 | * @param {string} personaDescription Optional description for the persona |
| 466 | * @param {string} personaTitle Optional title for the persona | 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 | export function initPersona(avatarId, personaName, personaDescription, personaTitle) { | 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 | power_user.personas[avatarId] = personaName; | 522 | power_user.personas[avatarId] = personaName; |
| 471 | power_user.persona_descriptions[avatarId] = { | 523 | power_user.persona_descriptions[avatarId] = { |
| 472 | description: personaDescription || '', | 524 | description: personaDescription || '', |
| 473 | position: persona_description_positions.IN_PROMPT, | 525 | position: position, |
| 474 | depth: DEFAULT_DEPTH, | 526 | depth: depth, |
| 475 | role: DEFAULT_ROLE, | 527 | role: role, |
| 476 | lorebook: '', | 528 | lorebook: lorebook, |
| 477 | title: personaTitle || '', | 529 | title: personaTitle || '', |
| 478 | }; | 530 | }; |
| 479 | 531 | ||
| 480 | saveSettingsDebounced(); | 532 | saveSettingsDebounced(); |
| 533 | |||
| 534 | if (!silent) { | ||
| 535 | await eventSource.emit(event_types.PERSONA_CREATED, { avatarId, name: personaName, description: personaDescription || '', title: personaTitle || '' }); | ||
| 536 | } | ||
| 481 | } | 537 | } |
| 482 | 538 | ||
| 483 | /** | 539 | /** |
| @@ -540,6 +596,7 @@ export async function convertCharacterToPersona(characterId = null) { | |||
| 540 | } | 596 | } |
| 541 | 597 | ||
| 542 | saveSettingsDebounced(); | 598 | saveSettingsDebounced(); |
| 599 | await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: overwriteName, name, description, title: '' }); | ||
| 543 | 600 | ||
| 544 | console.log('Persona for character created'); | 601 | console.log('Persona for character created'); |
| 545 | toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`); | 602 | toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`); |
| @@ -742,15 +799,16 @@ export async function askForPersonaSelection(title, text, personas, { okButton = | |||
| 742 | /** | 799 | /** |
| 743 | * Automatically selects a persona based on the given name if a matching persona exists. | 800 | * Automatically selects a persona based on the given name if a matching persona exists. |
| 744 | * @param {string} name - The name to search for | 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 | * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise | 804 | * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise |
| 746 | */ | 805 | */ |
| 747 | export async function autoSelectPersona(name) { | 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 | if (value === name) { | 808 | if (persona) { |
| 750 | console.log(`Auto-selecting persona ${key} for name ${name}`); | 809 | console.log(`Auto-selecting persona ${persona.avatar} for name ${name}`); |
| 751 | await setUserAvatar(key); | 810 | await setUserAvatar(persona.avatar); |
| 752 | return true; | 811 | return true; |
| 753 | } | ||
| 754 | } | 812 | } |
| 755 | return false; | 813 | return false; |
| 756 | } | 814 | } |
| @@ -778,6 +836,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) { | |||
| 778 | delete power_user.persona_descriptions[avatarId].title; | 836 | delete power_user.persona_descriptions[avatarId].title; |
| 779 | await getUserAvatars(true, avatarId); | 837 | await getUserAvatars(true, avatarId); |
| 780 | saveSettingsDebounced(); | 838 | saveSettingsDebounced(); |
| 839 | await eventSource.emit(event_types.PERSONA_UPDATED, avatarId); | ||
| 781 | return; | 840 | return; |
| 782 | } | 841 | } |
| 783 | 842 | ||
| @@ -786,6 +845,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) { | |||
| 786 | console.log(`Updated persona title for ${avatarId} to ${newTitle}`); | 845 | console.log(`Updated persona title for ${avatarId} to ${newTitle}`); |
| 787 | await getUserAvatars(true, avatarId); | 846 | await getUserAvatars(true, avatarId); |
| 788 | saveSettingsDebounced(); | 847 | saveSettingsDebounced(); |
| 848 | await eventSource.emit(event_types.PERSONA_UPDATED, avatarId); | ||
| 789 | return; | 849 | return; |
| 790 | } | 850 | } |
| 791 | } | 851 | } |
| @@ -821,6 +881,7 @@ async function renamePersona(avatarId) { | |||
| 821 | } | 881 | } |
| 822 | 882 | ||
| 823 | saveSettingsDebounced(); | 883 | saveSettingsDebounced(); |
| 884 | await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName: currentName, newName }); | ||
| 824 | await getUserAvatars(true, avatarId); | 885 | await getUserAvatars(true, avatarId); |
| 825 | updatePersonaUIStates(); | 886 | updatePersonaUIStates(); |
| 826 | setPersonaDescription(); | 887 | setPersonaDescription(); |
| @@ -886,8 +947,9 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) { | |||
| 886 | const temporary = getPersonaTemporaryLockInfo(); | 947 | const temporary = getPersonaTemporaryLockInfo(); |
| 887 | if (temporary.isTemporary) { | 948 | if (temporary.isTemporary) { |
| 888 | toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, { | 949 | toastr.info(t`This persona is only temporarily chosen. Click for more info.`, t`Temporary Persona`, { |
| 889 | preventDuplicates: true, onclick: () => { | 950 | preventDuplicates: true, |
| 890 | toastr.info(temporary.info.replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false }); | 951 | onclick: () => { |
| 952 | toastr.info(escapeHtml(temporary.info).replaceAll('\n', '<br />'), t`Temporary Persona`, { escapeHtml: false }); | ||
| 891 | }, | 953 | }, |
| 892 | }); | 954 | }); |
| 893 | } | 955 | } |
| @@ -1013,6 +1075,7 @@ async function lockPersona(type = 'chat') { | |||
| 1013 | connections: [], | 1075 | connections: [], |
| 1014 | title: '', | 1076 | title: '', |
| 1015 | }; | 1077 | }; |
| 1078 | await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: user_avatar, name: name1, description: '', title: '' }); | ||
| 1016 | } | 1079 | } |
| 1017 | 1080 | ||
| 1018 | switch (type) { | 1081 | switch (type) { |
| @@ -1055,9 +1118,9 @@ async function lockPersona(type = 'chat') { | |||
| 1055 | if (power_user.persona_show_notifications) { | 1118 | if (power_user.persona_show_notifications) { |
| 1056 | let additional = ''; | 1119 | let additional = ''; |
| 1057 | if (unlinkedCharacters.length) | 1120 | if (unlinkedCharacters.length) |
| 1058 | additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.join(', ')}`}`; | 1121 | additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.map(escapeHtml).join(', ')}`}`; |
| 1059 | if (additional || !isPersonaPanelOpen()) { | 1122 | if (additional || !isPersonaPanelOpen()) { |
| 1060 | toastr.success(t`User persona ${name1} is locked to character ${name2}${additional}`, t`Persona Locked`, { escapeHtml: false }); | 1123 | toastr.success(t`User persona ${escapeHtml(name1)} is locked to character ${escapeHtml(name2)}${additional}`, t`Persona Locked`, { escapeHtml: false }); |
| 1061 | } | 1124 | } |
| 1062 | } | 1125 | } |
| 1063 | } | 1126 | } |
| @@ -1071,21 +1134,37 @@ async function lockPersona(type = 'chat') { | |||
| 1071 | } | 1134 | } |
| 1072 | 1135 | ||
| 1073 | 1136 | ||
| 1137 | /** | ||
| 1138 | * Click handler for the delete persona button. Delegates to deletePersona with the current user avatar. | ||
| 1139 | */ | ||
| 1074 | async function deleteUserAvatar() { | 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 | if (!avatarId) { | 1152 | if (!avatarId) { |
| 1078 | console.warn('No avatar id found'); | 1153 | console.warn('No avatar id found'); |
| 1079 | return; | 1154 | return false; |
| 1080 | } | 1155 | } |
| 1156 | |||
| 1081 | const name = power_user.personas[avatarId] || ''; | 1157 | const name = power_user.personas[avatarId] || ''; |
| 1082 | const confirm = await Popup.show.confirm( | ||
| 1083 | t`Delete Persona` + `: ${name}`, | ||
| 1084 | t`Are you sure you want to delete this avatar?` + '<br />' + t`All information associated with its linked persona will be lost.`); | ||
| 1085 | 1158 | ||
| 1086 | if (!confirm) { | 1159 | if (!silent) { |
| 1087 | console.debug('User cancelled deleting avatar'); | 1160 | const confirm = await Popup.show.confirm( |
| 1088 | return; | 1161 | t`Delete Persona` + `: ${name}`, |
| 1162 | t`Are you sure you want to delete this avatar?` + '<br />' + t`All information associated with its linked persona will be lost.`); | ||
| 1163 | |||
| 1164 | if (!confirm) { | ||
| 1165 | console.debug('User cancelled deleting avatar'); | ||
| 1166 | return false; | ||
| 1167 | } | ||
| 1089 | } | 1168 | } |
| 1090 | 1169 | ||
| 1091 | const request = await fetch('/api/avatars/delete', { | 1170 | const request = await fetch('/api/avatars/delete', { |
| @@ -1102,24 +1181,29 @@ async function deleteUserAvatar() { | |||
| 1102 | delete power_user.persona_descriptions[avatarId]; | 1181 | delete power_user.persona_descriptions[avatarId]; |
| 1103 | 1182 | ||
| 1104 | if (avatarId === power_user.default_persona) { | 1183 | if (avatarId === power_user.default_persona) { |
| 1105 | toastr.warning(t`The default persona was deleted. You will need to set a new default persona.`, t`Default Persona Deleted`); | 1184 | if (!silent) toastr.warning(t`The default persona was deleted. You will need to set a new default persona.`, t`Default Persona Deleted`); |
| 1106 | power_user.default_persona = null; | 1185 | power_user.default_persona = null; |
| 1107 | } | 1186 | } |
| 1108 | 1187 | ||
| 1109 | if (avatarId === chat_metadata.persona) { | 1188 | if (avatarId === chat_metadata.persona) { |
| 1110 | toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona Deleted`); | 1189 | if (!silent) toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona Deleted`); |
| 1111 | delete chat_metadata.persona; | 1190 | delete chat_metadata.persona; |
| 1112 | await saveMetadata(); | 1191 | await saveMetadata(); |
| 1113 | } | 1192 | } |
| 1114 | 1193 | ||
| 1115 | saveSettingsDebounced(); | 1194 | saveSettingsDebounced(); |
| 1195 | await eventSource.emit(event_types.PERSONA_DELETED, { avatarId, name }); | ||
| 1116 | 1196 | ||
| 1117 | // Use the existing mechanism to re-render the persona list and choose the next persona here | 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 | await loadPersonaForCurrentChat({ doRender: true }); | 1199 | await loadPersonaForCurrentChat({ doRender: true }); |
| 1200 | return true; | ||
| 1119 | } | 1201 | } |
| 1202 | |||
| 1203 | return false; | ||
| 1120 | } | 1204 | } |
| 1121 | 1205 | ||
| 1122 | function onPersonaDescriptionInput() { | 1206 | async function onPersonaDescriptionInput() { |
| 1123 | power_user.persona_description = String($('#persona_description').val()); | 1207 | power_user.persona_description = String($('#persona_description').val()); |
| 1124 | countPersonaDescriptionTokens(); | 1208 | countPersonaDescriptionTokens(); |
| 1125 | 1209 | ||
| @@ -1145,25 +1229,35 @@ function onPersonaDescriptionInput() { | |||
| 1145 | .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text')) | 1229 | .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text')) |
| 1146 | .toggleClass('text_muted', !power_user.persona_description); | 1230 | .toggleClass('text_muted', !power_user.persona_description); |
| 1147 | saveSettingsDebounced(); | 1231 | saveSettingsDebounced(); |
| 1232 | |||
| 1233 | if (power_user.personas[user_avatar]) { | ||
| 1234 | await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | ||
| 1235 | } | ||
| 1148 | } | 1236 | } |
| 1149 | 1237 | ||
| 1150 | function onPersonaDescriptionDepthValueInput() { | 1238 | async function onPersonaDescriptionDepthValueInput() { |
| 1151 | power_user.persona_description_depth = Number($('#persona_depth_value').val()); | 1239 | power_user.persona_description_depth = Number($('#persona_depth_value').val()); |
| 1152 | 1240 | ||
| 1153 | if (power_user.personas[user_avatar]) { | 1241 | if (power_user.personas[user_avatar]) { |
| 1154 | const object = getOrCreatePersonaDescriptor(); | 1242 | const object = getOrCreatePersonaDescriptor(); |
| 1155 | object.depth = power_user.persona_description_depth; | 1243 | object.depth = power_user.persona_description_depth; |
| 1244 | saveSettingsDebounced(); | ||
| 1245 | await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | ||
| 1246 | return; | ||
| 1156 | } | 1247 | } |
| 1157 | 1248 | ||
| 1158 | saveSettingsDebounced(); | 1249 | saveSettingsDebounced(); |
| 1159 | } | 1250 | } |
| 1160 | 1251 | ||
| 1161 | function onPersonaDescriptionDepthRoleInput() { | 1252 | async function onPersonaDescriptionDepthRoleInput() { |
| 1162 | power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val()); | 1253 | power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val()); |
| 1163 | 1254 | ||
| 1164 | if (power_user.personas[user_avatar]) { | 1255 | if (power_user.personas[user_avatar]) { |
| 1165 | const object = getOrCreatePersonaDescriptor(); | 1256 | const object = getOrCreatePersonaDescriptor(); |
| 1166 | object.role = power_user.persona_description_role; | 1257 | object.role = power_user.persona_description_role; |
| 1258 | saveSettingsDebounced(); | ||
| 1259 | await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | ||
| 1260 | return; | ||
| 1167 | } | 1261 | } |
| 1168 | 1262 | ||
| 1169 | saveSettingsDebounced(); | 1263 | saveSettingsDebounced(); |
| @@ -1171,9 +1265,9 @@ function onPersonaDescriptionDepthRoleInput() { | |||
| 1171 | 1265 | ||
| 1172 | /** | 1266 | /** |
| 1173 | * Opens a popup to set the lorebook for the current persona. | 1267 | * Opens a popup to set the lorebook for the current persona. |
| 1174 | * @param {JQuery.ClickEvent} event Click event | 1268 | * @param {Pick<JQuery.ClickEvent, 'shiftKey' | 'altKey'>} event Click event |
| 1175 | */ | 1269 | */ |
| 1176 | async function onPersonaLoreButtonClick(event) { | 1270 | async function onPersonaLoreButtonClick({ shiftKey, altKey }) { |
| 1177 | const personaName = power_user.personas[user_avatar]; | 1271 | const personaName = power_user.personas[user_avatar]; |
| 1178 | const selectedLorebook = power_user.persona_description_lorebook; | 1272 | const selectedLorebook = power_user.persona_description_lorebook; |
| 1179 | 1273 | ||
| @@ -1182,7 +1276,7 @@ async function onPersonaLoreButtonClick(event) { | |||
| 1182 | return; | 1276 | return; |
| 1183 | } | 1277 | } |
| 1184 | 1278 | ||
| 1185 | if (selectedLorebook && !event.shiftKey && !event.altKey) { | 1279 | if (selectedLorebook && !shiftKey && !altKey) { |
| 1186 | openWorldInfoEditor(selectedLorebook); | 1280 | openWorldInfoEditor(selectedLorebook); |
| 1187 | return; | 1281 | return; |
| 1188 | } | 1282 | } |
| @@ -1200,7 +1294,7 @@ async function onPersonaLoreButtonClick(event) { | |||
| 1200 | worldSelect.append(option); | 1294 | worldSelect.append(option); |
| 1201 | } | 1295 | } |
| 1202 | 1296 | ||
| 1203 | worldSelect.on('change', function () { | 1297 | worldSelect.on('change', async function () { |
| 1204 | power_user.persona_description_lorebook = String($(this).val()); | 1298 | power_user.persona_description_lorebook = String($(this).val()); |
| 1205 | 1299 | ||
| 1206 | if (power_user.personas[user_avatar]) { | 1300 | if (power_user.personas[user_avatar]) { |
| @@ -1210,12 +1304,16 @@ async function onPersonaLoreButtonClick(event) { | |||
| 1210 | 1304 | ||
| 1211 | $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); | 1305 | $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); |
| 1212 | saveSettingsDebounced(); | 1306 | saveSettingsDebounced(); |
| 1307 | |||
| 1308 | if (power_user.personas[user_avatar]) { | ||
| 1309 | await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | ||
| 1310 | } | ||
| 1213 | }); | 1311 | }); |
| 1214 | 1312 | ||
| 1215 | await callGenericPopup(template, POPUP_TYPE.TEXT); | 1313 | await callGenericPopup(template, POPUP_TYPE.TEXT); |
| 1216 | } | 1314 | } |
| 1217 | 1315 | ||
| 1218 | function onPersonaDescriptionPositionInput() { | 1316 | async function onPersonaDescriptionPositionInput() { |
| 1219 | power_user.persona_description_position = Number( | 1317 | power_user.persona_description_position = Number( |
| 1220 | $('#persona_description_position').find(':selected').val(), | 1318 | $('#persona_description_position').find(':selected').val(), |
| 1221 | ); | 1319 | ); |
| @@ -1223,6 +1321,10 @@ function onPersonaDescriptionPositionInput() { | |||
| 1223 | if (power_user.personas[user_avatar]) { | 1321 | if (power_user.personas[user_avatar]) { |
| 1224 | const object = getOrCreatePersonaDescriptor(); | 1322 | const object = getOrCreatePersonaDescriptor(); |
| 1225 | object.position = power_user.persona_description_position; | 1323 | object.position = power_user.persona_description_position; |
| 1324 | saveSettingsDebounced(); | ||
| 1325 | await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar); | ||
| 1326 | $('#persona_depth_position_settings').toggle(power_user.persona_description_position === persona_description_positions.AT_DEPTH); | ||
| 1327 | return; | ||
| 1226 | } | 1328 | } |
| 1227 | 1329 | ||
| 1228 | saveSettingsDebounced(); | 1330 | saveSettingsDebounced(); |
| @@ -1728,15 +1830,36 @@ async function onPersonasRestoreInput(e) { | |||
| 1728 | $('#personas_restore_input').val(''); | 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. |
| 1733 | 1835 | * @param {object} [options={}] - Optional parameters | |
| 1734 | if (!confirmation) { | 1836 | * @param {number} [options.start=0] - Start index of the message range (inclusive) |
| 1735 | return; | 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}.`; | ||
| 1852 | |||
| 1853 | if (!quiet) { | ||
| 1854 | const confirmation = await Popup.show.confirm(t`Are you sure?`, confirmMessage); | ||
| 1855 | if (!confirmation) { | ||
| 1856 | return; | ||
| 1857 | } | ||
| 1736 | } | 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 | mes.name = name1; | 1863 | mes.name = name1; |
| 1741 | mes.force_avatar = getThumbnailUrl('persona', user_avatar); | 1864 | mes.force_avatar = getThumbnailUrl('persona', user_avatar); |
| 1742 | } | 1865 | } |
| @@ -1763,22 +1886,27 @@ export async function retriggerFirstMessageOnEmptyChat() { | |||
| 1763 | 1886 | ||
| 1764 | /** | 1887 | /** |
| 1765 | * Duplicates a persona. | 1888 | * Duplicates a persona. |
| 1766 | * @param {string} avatarId | 1889 | * @param {string} avatarId Source persona avatar id |
| 1767 | * @returns {Promise<void>} | 1890 | * @param {object} [options] Options |
| 1891 | * @param {boolean} [options.silent=false] If true, skips the confirmation popup | ||
| 1892 | * @param {boolean} [options.select=false] If true, selects/activates the duplicated persona | ||
| 1893 | * @returns {Promise<string>} The avatar id of the new persona, or empty string on failure/cancellation | ||
| 1768 | */ | 1894 | */ |
| 1769 | async function duplicatePersona(avatarId) { | 1895 | async function duplicatePersona(avatarId, { silent = false, select = false } = {}) { |
| 1770 | const personaName = power_user.personas[avatarId]; | 1896 | const personaName = power_user.personas[avatarId]; |
| 1771 | 1897 | ||
| 1772 | if (!personaName) { | 1898 | if (!personaName) { |
| 1773 | toastr.warning('Chosen avatar is not a persona', t`Persona Management`); | 1899 | toastr.warning(t`Chosen avatar is not a persona`, t`Persona Management`); |
| 1774 | return; | 1900 | return ''; |
| 1775 | } | 1901 | } |
| 1776 | 1902 | ||
| 1777 | const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName); | 1903 | if (!silent) { |
| 1904 | const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName); | ||
| 1778 | 1905 | ||
| 1779 | if (!confirm) { | 1906 | if (!confirm) { |
| 1780 | console.debug('User cancelled duplicating persona'); | 1907 | console.debug('User cancelled duplicating persona'); |
| 1781 | return; | 1908 | return ''; |
| 1909 | } | ||
| 1782 | } | 1910 | } |
| 1783 | 1911 | ||
| 1784 | const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; | 1912 | const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; |
| @@ -1795,8 +1923,24 @@ async function duplicatePersona(avatarId) { | |||
| 1795 | }; | 1923 | }; |
| 1796 | 1924 | ||
| 1797 | await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); | 1925 | await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); |
| 1926 | |||
| 1927 | const eventData = { | ||
| 1928 | avatarId: newAvatarId, | ||
| 1929 | name: personaName, | ||
| 1930 | description: descriptor?.description ?? '', | ||
| 1931 | title: descriptor?.title ?? '', | ||
| 1932 | duplicatedFromAvatarId: avatarId, | ||
| 1933 | }; | ||
| 1934 | await eventSource.emit(event_types.PERSONA_CREATED, eventData); | ||
| 1935 | |||
| 1798 | await getUserAvatars(true, newAvatarId); | 1936 | await getUserAvatars(true, newAvatarId); |
| 1799 | saveSettingsDebounced(); | 1937 | saveSettingsDebounced(); |
| 1938 | |||
| 1939 | if (select) { | ||
| 1940 | await setUserAvatar(newAvatarId); | ||
| 1941 | } | ||
| 1942 | |||
| 1943 | return newAvatarId; | ||
| 1800 | } | 1944 | } |
| 1801 | 1945 | ||
| 1802 | /** | 1946 | /** |
| @@ -1807,12 +1951,418 @@ async function migrateNonPersonaUser() { | |||
| 1807 | return; | 1951 | return; |
| 1808 | } | 1952 | } |
| 1809 | 1953 | ||
| 1810 | initPersona(user_avatar, name1, '', ''); | 1954 | await initPersona(user_avatar, name1, '', '', { silent: true }); |
| 1811 | setPersonaDescription(); | 1955 | setPersonaDescription(); |
| 1812 | await getUserAvatars(true, user_avatar); | 1956 | await getUserAvatars(true, user_avatar); |
| 1813 | } | 1957 | } |
| 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 | * Locks or unlocks the persona of the current chat. | 2367 | * Locks or unlocks the persona of the current chat. |
| 1818 | * @param {{type: string}} _args Named arguments | 2368 | * @param {{type: string}} _args Named arguments |
| @@ -1870,10 +2420,9 @@ async function setNameCallback({ mode = 'all' }, name) { | |||
| 1870 | 2420 | ||
| 1871 | // If the name matches a persona avatar, or a name, auto-select it | 2421 | // If the name matches a persona avatar, or a name, auto-select it |
| 1872 | if (['lookup', 'all'].includes(mode)) { | 2422 | if (['lookup', 'all'].includes(mode)) { |
| 1873 | let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1]; | 2423 | const persona = findPersona({ name }); |
| 1874 | if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1]; | ||
| 1875 | if (persona) { | 2424 | if (persona) { |
| 1876 | await autoSelectPersona(persona); | 2425 | await autoSelectPersona(persona.name, { personaKey: persona.avatar }); |
| 1877 | return ''; | 2426 | return ''; |
| 1878 | } else if (mode === 'lookup') { | 2427 | } else if (mode === 'lookup') { |
| 1879 | toastr.warning(`Persona ${name} not found`); | 2428 | toastr.warning(`Persona ${name} not found`); |
| @@ -1889,28 +2438,375 @@ async function setNameCallback({ mode = 'all' }, name) { | |||
| 1889 | return ''; | 2438 | return ''; |
| 1890 | } | 2439 | } |
| 1891 | 2440 | ||
| 1892 | function syncCallback() { | 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 | return ''; | 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 | function registerPersonaSlashCommands() { | 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 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2781 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1899 | name: 'persona-lock', | 2782 | name: 'persona-lock', |
| 1900 | aliases: ['lock', 'bind'], | 2783 | aliases: ['lock', 'bind'], |
| 1901 | callback: lockPersonaCallback, | 2784 | callback: lockPersonaCallback, |
| 1902 | returns: 'The current lock state for the given type', | 2785 | returns: t`The current lock state for the given type`, |
| 1903 | helpString: 'Locks/unlocks a persona (name and avatar) to the current chat. Gets the current lock state for the given type if no state is provided.', | 2786 | helpString: ` |
| 2787 | <div> | ||
| 2788 | ${t`Locks/unlocks the current persona to a chat, character, or as the default. Returns the lock state if no value is provided.`} | ||
| 2789 | </div> | ||
| 2790 | <div> | ||
| 2791 | <strong>${t`Example:`}</strong> | ||
| 2792 | <ul> | ||
| 2793 | <li><pre><code>/persona-lock on</code></pre> ${t`Locks persona to this chat.`}</li> | ||
| 2794 | <li><pre><code>/persona-lock type=character on</code></pre> ${t`Locks persona to the current character.`}</li> | ||
| 2795 | <li><pre><code>/persona-lock type=default on</code></pre> ${t`Sets persona as the default for new chats.`}</li> | ||
| 2796 | <li><pre><code>/persona-lock</code></pre> ${t`Returns whether the persona is locked to this chat.`}</li> | ||
| 2797 | </ul> | ||
| 2798 | </div> | ||
| 2799 | `, | ||
| 1904 | namedArgumentList: [ | 2800 | namedArgumentList: [ |
| 1905 | SlashCommandNamedArgument.fromProps({ | 2801 | SlashCommandNamedArgument.fromProps({ |
| 1906 | name: 'type', | 2802 | name: 'type', |
| 1907 | description: 'The type of the lock, where it should apply to', | 2803 | description: t`The type of the lock, where it should apply to`, |
| 1908 | typeList: [ARGUMENT_TYPE.STRING], | 2804 | typeList: [ARGUMENT_TYPE.STRING], |
| 1909 | defaultValue: 'chat', | 2805 | defaultValue: 'chat', |
| 1910 | enumList: [ | 2806 | enumList: [ |
| 1911 | new SlashCommandEnumValue('chat', 'Lock the persona to the current chat.'), | 2807 | new SlashCommandEnumValue('chat', t`Lock the persona to the current chat.`), |
| 1912 | new SlashCommandEnumValue('character', 'Lock this persona to the currently selected character. If the setting is enabled, multiple personas can be locked to the same character.'), | 2808 | new SlashCommandEnumValue('character', t`Lock this persona to the currently selected character. If the setting is enabled, multiple personas can be locked to the same character.`), |
| 1913 | new SlashCommandEnumValue('default', 'Lock this persona as the default persona for all new chats.'), | 2809 | new SlashCommandEnumValue('default', t`Lock this persona as the default persona for all new chats.`), |
| 1914 | ], | 2810 | ], |
| 1915 | }), | 2811 | }), |
| 1916 | ], | 2812 | ], |
| @@ -1922,31 +2818,97 @@ function registerPersonaSlashCommands() { | |||
| 1922 | }), | 2818 | }), |
| 1923 | ], | 2819 | ], |
| 1924 | })); | 2820 | })); |
| 2821 | |||
| 1925 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2822 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1926 | name: 'persona-set', | 2823 | name: 'persona-set', |
| 1927 | callback: setNameCallback, | 2824 | callback: setNameCallback, |
| 1928 | aliases: ['persona', 'name'], | 2825 | aliases: ['persona', 'name'], |
| 1929 | namedArgumentList: [ | 2826 | namedArgumentList: [ |
| 1930 | new SlashCommandNamedArgument( | 2827 | SlashCommandNamedArgument.fromProps({ |
| 1931 | 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)', | 2828 | name: 'mode', |
| 1932 | [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'], | 2829 | description: t`The mode for persona selection`, |
| 1933 | ), | 2830 | typeList: [ARGUMENT_TYPE.STRING], |
| 2831 | defaultValue: 'all', | ||
| 2832 | enumList: [ | ||
| 2833 | new SlashCommandEnumValue('lookup', t`Search for an existing persona only`), | ||
| 2834 | new SlashCommandEnumValue('temp', t`Set a temporary name only (no persona lookup)`), | ||
| 2835 | new SlashCommandEnumValue('all', t`Try persona lookup first, fall back to temporary name`), | ||
| 2836 | ], | ||
| 2837 | }), | ||
| 1934 | ], | 2838 | ], |
| 1935 | unnamedArgumentList: [ | 2839 | unnamedArgumentList: [ |
| 1936 | SlashCommandArgument.fromProps({ | 2840 | SlashCommandArgument.fromProps({ |
| 1937 | description: 'persona name', | 2841 | description: 'persona name', |
| 1938 | typeList: [ARGUMENT_TYPE.STRING], | 2842 | typeList: [ARGUMENT_TYPE.STRING], |
| 1939 | isRequired: true, | 2843 | isRequired: true, |
| 1940 | enumProvider: commonEnumProviders.personas, | 2844 | enumProvider: commonEnumProviders.personas({ allowPersonaKey: true }), |
| 1941 | }), | 2845 | }), |
| 1942 | ], | 2846 | ], |
| 1943 | helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.', | 2847 | helpString: ` |
| 2848 | <div> | ||
| 2849 | ${t`Selects an existing persona by name or avatar key, or sets a temporary user name.`} | ||
| 2850 | </div> | ||
| 2851 | <div> | ||
| 2852 | ${t`If a matching persona exists, it will be selected with its name and avatar. Otherwise (in "all" or "temp" mode), only the display name is changed temporarily.`} | ||
| 2853 | </div> | ||
| 2854 | <div> | ||
| 2855 | <strong>${t`Example:`}</strong> | ||
| 2856 | <ul> | ||
| 2857 | <li><pre><code>/persona-set Alice</code></pre> ${t`Selects persona "Alice", or sets name to "Alice" if not found.`}</li> | ||
| 2858 | <li><pre><code>/persona-set mode=lookup Alice</code></pre> ${t`Only selects if persona "Alice" exists.`}</li> | ||
| 2859 | </ul> | ||
| 2860 | </div> | ||
| 2861 | `, | ||
| 1944 | })); | 2862 | })); |
| 2863 | |||
| 1945 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2864 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1946 | name: 'persona-sync', | 2865 | name: 'persona-sync', |
| 1947 | aliases: ['sync'], | 2866 | aliases: ['sync'], |
| 1948 | callback: syncCallback, | 2867 | callback: syncCallback, |
| 1949 | helpString: 'Syncs the user persona in user-attributed messages in the current chat.', | 2868 | namedArgumentList: [ |
| 2869 | SlashCommandNamedArgument.fromProps({ | ||
| 2870 | name: 'from', | ||
| 2871 | description: t`only sync messages from a certain persona name`, | ||
| 2872 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 2873 | enumProvider: userMessageNamesEnumProvider, | ||
| 2874 | }), | ||
| 2875 | SlashCommandNamedArgument.fromProps({ | ||
| 2876 | name: 'quiet', | ||
| 2877 | description: t`suppress the confirmation popup`, | ||
| 2878 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 2879 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 2880 | defaultValue: 'true', | ||
| 2881 | }), | ||
| 2882 | ], | ||
| 2883 | unnamedArgumentList: [ | ||
| 2884 | SlashCommandArgument.fromProps({ | ||
| 2885 | description: t`message index (starts with 0) or range, syncs all user messages if not provided`, | ||
| 2886 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], | ||
| 2887 | defaultValue: '0-{{lastMessageId}}', | ||
| 2888 | }), | ||
| 2889 | ], | ||
| 2890 | helpString: ` | ||
| 2891 | <div> | ||
| 2892 | ${t`Syncs the user persona (name and avatar) in user-attributed messages in the current chat.`} | ||
| 2893 | </div> | ||
| 2894 | <div> | ||
| 2895 | ${t`If <code>from</code> is set, only messages with that specific persona name will be synced. Useful when multiple personas have been used in the same chat.`} | ||
| 2896 | </div> | ||
| 2897 | <div> | ||
| 2898 | ${t`If <code>quiet</code> is set to <code>false</code>, a confirmation popup will be shown before syncing.`} | ||
| 2899 | </div> | ||
| 2900 | <div> | ||
| 2901 | <strong>${t`Examples:`}</strong> | ||
| 2902 | <ul> | ||
| 2903 | <li><pre><code>/persona-sync</code></pre> ${t`- Sync all user messages`}</li> | ||
| 2904 | <li><pre><code>/persona-sync 5</code></pre> ${t`- Sync only message 5`}</li> | ||
| 2905 | <li><pre><code>/persona-sync 0-10</code></pre> ${t`- Sync messages 0 through 10`}</li> | ||
| 2906 | <li><pre><code>/persona-sync from=OldPersona 0-20</code></pre> ${t`- Sync only messages with name "OldPersona" in range 0-20`}</li> | ||
| 2907 | <li><pre><code>/persona-sync quiet=false</code></pre> ${t`- Sync all with confirmation popup`}</li> | ||
| 2908 | <li><pre><code>/persona-sync from=TempName quiet=false 5-15</code></pre> ${t`- Sync messages with name "TempName" in range 5-15 with confirmation`}</li> | ||
| 2909 | </ul> | ||
| 2910 | </div> | ||
| 2911 | `, | ||
| 1950 | })); | 2912 | })); |
| 1951 | } | 2913 | } |
| 1952 | 2914 | ||
| @@ -2004,7 +2966,7 @@ export async function initPersonas() { | |||
| 2004 | debouncedPersonaSearch(searchQuery); | 2966 | debouncedPersonaSearch(searchQuery); |
| 2005 | }); | 2967 | }); |
| 2006 | 2968 | ||
| 2007 | $('#sync_name_button').on('click', syncUserNameToPersona); | 2969 | $('#sync_name_button').on('click', async () => await syncUserNameToPersona()); |
| 2008 | $('#avatar_upload_file').on('change', changeUserAvatar); | 2970 | $('#avatar_upload_file').on('change', changeUserAvatar); |
| 2009 | 2971 | ||
| 2010 | $(document).on('click', '#user_avatar_block .avatar-container', async function () { | 2972 | $(document).on('click', '#user_avatar_block .avatar-container', async function () { |
| @@ -2,7 +2,7 @@ import dialogPolyfill from '../lib/dialog-polyfill.esm.js'; | |||
| 2 | import { shouldSendOnEnter } from './RossAscends-mods.js'; | 2 | import { shouldSendOnEnter } from './RossAscends-mods.js'; |
| 3 | import { t } from './i18n.js'; | 3 | import { t } from './i18n.js'; |
| 4 | import { power_user, toastPositionClasses } from './power-user.js'; | 4 | import { power_user, toastPositionClasses } from './power-user.js'; |
| 5 | import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js'; | 5 | import { clamp, removeFromArray, runAfterAnimation, uuidv4 } from './utils.js'; |
| 6 | 6 | ||
| 7 | /** @readonly */ | 7 | /** @readonly */ |
| 8 | /** @enum {Number} */ | 8 | /** @enum {Number} */ |
| @@ -79,8 +79,12 @@ export const POPUP_RESULT = { | |||
| 79 | * @property {string} label - The label text for the input | 79 | * @property {string} label - The label text for the input |
| 80 | * @property {string?} [tooltip=null] - Optional tooltip to be displayed. Default placeholder in input controls, tooltip icon behind the checkbox for those. | 80 | * @property {string?} [tooltip=null] - Optional tooltip to be displayed. Default placeholder in input controls, tooltip icon behind the checkbox for those. |
| 81 | * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set) | 81 | * @property {boolean|string|undefined} [defaultState=false] - The default state when opening the popup (false if not set) |
| 82 | * @property {('checkbox'|'text'|'textarea')?} [type='checkbox'] - The type of the input (default is checkbox) | 82 | * @property {('checkbox'|'text'|'textarea'|'number')?} [type='checkbox'] - The type of the input (default is checkbox) |
| 83 | * @property {number?} [rows=1] - The number of rows for the input field, if the input is 'textarea' | 83 | * @property {number?} [rows=1] - The number of rows for the input field, if the input is 'textarea' |
| 84 | * @property {number?} [min] - The minimum value for number inputs | ||
| 85 | * @property {number?} [max] - The maximum value for number inputs | ||
| 86 | * @property {number?} [step] - The step value for number inputs | ||
| 87 | * @property {boolean?} [disabled=false] - Whether the input should be disabled | ||
| 84 | */ | 88 | */ |
| 85 | 89 | ||
| 86 | /** | 90 | /** |
| @@ -331,6 +335,7 @@ export class Popup { | |||
| 331 | inputElement.type = 'checkbox'; | 335 | inputElement.type = 'checkbox'; |
| 332 | inputElement.id = input.id; | 336 | inputElement.id = input.id; |
| 333 | inputElement.checked = Boolean(input.defaultState ?? false); | 337 | inputElement.checked = Boolean(input.defaultState ?? false); |
| 338 | inputElement.disabled = Boolean(input.disabled ?? false); | ||
| 334 | label.appendChild(inputElement); | 339 | label.appendChild(inputElement); |
| 335 | const labelText = document.createElement('span'); | 340 | const labelText = document.createElement('span'); |
| 336 | labelText.innerText = input.label; | 341 | labelText.innerText = input.label; |
| @@ -356,6 +361,7 @@ export class Popup { | |||
| 356 | inputElement.id = input.id; | 361 | inputElement.id = input.id; |
| 357 | inputElement.value = String(input.defaultState ?? ''); | 362 | inputElement.value = String(input.defaultState ?? ''); |
| 358 | inputElement.placeholder = input.tooltip ?? ''; | 363 | inputElement.placeholder = input.tooltip ?? ''; |
| 364 | inputElement.disabled = Boolean(input.disabled ?? false); | ||
| 359 | setTitleFromTooltip(inputElement, input.tooltip); | 365 | setTitleFromTooltip(inputElement, input.tooltip); |
| 360 | 366 | ||
| 361 | const labelText = document.createElement('span'); | 367 | const labelText = document.createElement('span'); |
| @@ -377,6 +383,7 @@ export class Popup { | |||
| 377 | inputElement.value = String(input.defaultState ?? ''); | 383 | inputElement.value = String(input.defaultState ?? ''); |
| 378 | inputElement.rows = input.rows ?? 1; | 384 | inputElement.rows = input.rows ?? 1; |
| 379 | inputElement.placeholder = input.tooltip ?? ''; | 385 | inputElement.placeholder = input.tooltip ?? ''; |
| 386 | inputElement.disabled = Boolean(input.disabled ?? false); | ||
| 380 | setTitleFromTooltip(inputElement, input.tooltip); | 387 | setTitleFromTooltip(inputElement, input.tooltip); |
| 381 | 388 | ||
| 382 | const labelText = document.createElement('span'); | 389 | const labelText = document.createElement('span'); |
| @@ -387,8 +394,47 @@ export class Popup { | |||
| 387 | label.appendChild(inputElement); | 394 | label.appendChild(inputElement); |
| 388 | 395 | ||
| 389 | this.inputControls.appendChild(label); | 396 | this.inputControls.appendChild(label); |
| 397 | } else if (input.type === 'number') { | ||
| 398 | const label = document.createElement('label'); | ||
| 399 | label.classList.add('text_label', 'justifyCenter'); | ||
| 400 | label.setAttribute('for', input.id); | ||
| 401 | |||
| 402 | const inputElement = document.createElement('input'); | ||
| 403 | inputElement.classList.add('text_pole', 'result-control'); | ||
| 404 | inputElement.type = 'number'; | ||
| 405 | inputElement.id = input.id; | ||
| 406 | inputElement.value = String(input.defaultState ?? ''); | ||
| 407 | inputElement.placeholder = input.tooltip ?? ''; | ||
| 408 | inputElement.min = String(input.min ?? ''); | ||
| 409 | inputElement.max = String(input.max ?? ''); | ||
| 410 | inputElement.step = String(input.step ?? ''); | ||
| 411 | inputElement.disabled = Boolean(input.disabled ?? false); | ||
| 412 | setTitleFromTooltip(inputElement, input.tooltip); | ||
| 413 | |||
| 414 | inputElement.addEventListener('change', () => { | ||
| 415 | const value = parseFloat(inputElement.value); | ||
| 416 | if (isNaN(value)) return; | ||
| 417 | |||
| 418 | const min = Number.isFinite(input.min) ? input.min : -Infinity; | ||
| 419 | const max = Number.isFinite(input.max) ? input.max : Infinity; | ||
| 420 | const clamped = clamp(value, min, max); | ||
| 421 | |||
| 422 | if (clamped !== value) { | ||
| 423 | inputElement.value = String(clamped); | ||
| 424 | toastr.warning(t`Value must be between ${min} and ${max}. Clamped to ${clamped}.`); | ||
| 425 | } | ||
| 426 | }); | ||
| 427 | |||
| 428 | const labelText = document.createElement('span'); | ||
| 429 | labelText.innerText = input.label; | ||
| 430 | labelText.dataset.i18n = input.label; | ||
| 431 | |||
| 432 | label.appendChild(labelText); | ||
| 433 | label.appendChild(inputElement); | ||
| 434 | |||
| 435 | this.inputControls.appendChild(label); | ||
| 390 | } else { | 436 | } else { |
| 391 | console.warn('Unknown custom input type. Only checkbox, text and textarea are supported.', input); | 437 | console.warn('Unknown custom input type. Only checkbox, text, number and textarea are supported.', input); |
| 392 | return; | 438 | return; |
| 393 | } | 439 | } |
| 394 | }); | 440 | }); |
| @@ -672,6 +718,10 @@ export class Popup { | |||
| 672 | } | 718 | } |
| 673 | } | 719 | } |
| 674 | 720 | ||
| 721 | if (!control) { | ||
| 722 | return; | ||
| 723 | } | ||
| 724 | |||
| 675 | if (applyAutoFocus) { | 725 | if (applyAutoFocus) { |
| 676 | control.setAttribute('autofocus', ''); | 726 | control.setAttribute('autofocus', ''); |
| 677 | // Manually enable tabindex too, as this might only be applied by the interactable functionality in the background, but too late for HTML autofocus | 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 | this.inputResults = new Map(this.customInputs.map(input => { | 770 | this.inputResults = new Map(this.customInputs.map(input => { |
| 721 | /** @type {HTMLInputElement} */ | 771 | /** @type {HTMLInputElement} */ |
| 722 | const inputControl = this.dlg.querySelector(`#${input.id}`); | 772 | const inputControl = this.dlg.querySelector(`#${input.id}`); |
| 723 | const value = ['text', 'textarea'].includes(input.type) ? inputControl.value : inputControl.checked; | 773 | const value = ['text', 'textarea', 'number'].includes(input.type) ? inputControl.value : inputControl.checked; |
| 724 | return [inputControl.id, value]; | 774 | return [inputControl.id, value]; |
| 725 | })); | 775 | })); |
| 726 | } | 776 | } |
| @@ -68,6 +68,7 @@ import { bindModelTemplates } from './chat-templates.js'; | |||
| 68 | import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js'; | 68 | import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js'; |
| 69 | import { t } from './i18n.js'; | 69 | import { t } from './i18n.js'; |
| 70 | import { getBackgroundPath, isCustomBackgroundUrl } from './backgrounds.js'; | 70 | import { getBackgroundPath, isCustomBackgroundUrl } from './backgrounds.js'; |
| 71 | import { persona_description_positions as _persona_description_positions } from './personas.js'; | ||
| 71 | 72 | ||
| 72 | export const toastPositionClasses = [ | 73 | export const toastPositionClasses = [ |
| 73 | 'toast-top-left', | 74 | 'toast-top-left', |
| @@ -110,17 +111,7 @@ export const send_on_enter_options = { | |||
| 110 | ENABLED: 1, | 111 | ENABLED: 1, |
| 111 | }; | 112 | }; |
| 112 | 113 | ||
| 113 | export const persona_description_positions = { | 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 | export const power_user = { | 116 | export const power_user = { |
| 126 | charListGrid: false, | 117 | charListGrid: false, |
| @@ -134,6 +134,7 @@ export function extractReasoningFromData(data, { | |||
| 134 | case chat_completion_sources.NANOGPT: | 134 | case chat_completion_sources.NANOGPT: |
| 135 | case chat_completion_sources.SILICONFLOW: | 135 | case chat_completion_sources.SILICONFLOW: |
| 136 | case chat_completion_sources.ZAI: | 136 | case chat_completion_sources.ZAI: |
| 137 | case chat_completion_sources.WORKERS_AI: | ||
| 137 | case chat_completion_sources.CUSTOM: { | 138 | case chat_completion_sources.CUSTOM: { |
| 138 | return data?.choices?.[0]?.message?.reasoning_content | 139 | return data?.choices?.[0]?.message?.reasoning_content |
| 139 | ?? data?.choices?.[0]?.message?.reasoning | 140 | ?? data?.choices?.[0]?.message?.reasoning |
| @@ -1010,6 +1011,44 @@ function registerReasoningSlashCommands() { | |||
| 1010 | }, | 1011 | }, |
| 1011 | })); | 1012 | })); |
| 1012 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1013 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1014 | name: 'reasoning-format', | ||
| 1015 | aliases: ['format-reasoning'], | ||
| 1016 | returns: 'formatted string', | ||
| 1017 | helpString: t`Formats reasoning and content into a single string using Reasoning Formatting settings. Useful for preparing text that can be parsed with /reasoning-parse.`, | ||
| 1018 | namedArgumentList: [ | ||
| 1019 | SlashCommandNamedArgument.fromProps({ | ||
| 1020 | name: 'reasoning', | ||
| 1021 | description: 'The reasoning/thinking text to format', | ||
| 1022 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1023 | isRequired: true, | ||
| 1024 | }), | ||
| 1025 | ], | ||
| 1026 | unnamedArgumentList: [ | ||
| 1027 | SlashCommandArgument.fromProps({ | ||
| 1028 | description: 'The main content text', | ||
| 1029 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1030 | isRequired: false, | ||
| 1031 | }), | ||
| 1032 | ], | ||
| 1033 | callback: (args, value) => { | ||
| 1034 | const reasoning = String(args?.reasoning ?? ''); | ||
| 1035 | const content = String(value ?? ''); | ||
| 1036 | |||
| 1037 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) { | ||
| 1038 | toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`, t`Reasoning Format`); | ||
| 1039 | return ''; | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | if (!reasoning) { | ||
| 1043 | toastr.warning(t`Reasoning argument is required.`, t`Reasoning Format`); | ||
| 1044 | return ''; | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | const { formatted } = formatReasoning(reasoning, content); | ||
| 1048 | return formatted; | ||
| 1049 | }, | ||
| 1050 | })); | ||
| 1051 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1013 | name: 'reasoning-template', | 1052 | name: 'reasoning-template', |
| 1014 | aliases: ['reasoning-formatting', 'reasoning-preset'], | 1053 | aliases: ['reasoning-formatting', 'reasoning-preset'], |
| 1015 | callback: selectReasoningTemplateCallback, | 1054 | callback: selectReasoningTemplateCallback, |
| @@ -1082,7 +1121,7 @@ function registerReasoningSlashCommands() { | |||
| 1082 | helpString: t`Collapse the reasoning block of a message or range of messages.`, | 1121 | helpString: t`Collapse the reasoning block of a message or range of messages.`, |
| 1083 | unnamedArgumentList: reasoningVisibilityArgs, | 1122 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1084 | callback: (_args, value) => { | 1123 | callback: (_args, value) => { |
| 1085 | const details = getReasoningDetailsElements(value); | 1124 | const details = getReasoningDetailsElements(value.toString()); |
| 1086 | if (details) details.removeAttr('open'); | 1125 | if (details) details.removeAttr('open'); |
| 1087 | return ''; | 1126 | return ''; |
| 1088 | }, | 1127 | }, |
| @@ -1094,7 +1133,7 @@ function registerReasoningSlashCommands() { | |||
| 1094 | helpString: t`Expand the reasoning block of a message or range of messages.`, | 1133 | helpString: t`Expand the reasoning block of a message or range of messages.`, |
| 1095 | unnamedArgumentList: reasoningVisibilityArgs, | 1134 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1096 | callback: (_args, value) => { | 1135 | callback: (_args, value) => { |
| 1097 | const details = getReasoningDetailsElements(value); | 1136 | const details = getReasoningDetailsElements(value.toString()); |
| 1098 | if (details) details.attr('open', ''); | 1137 | if (details) details.attr('open', ''); |
| 1099 | return ''; | 1138 | return ''; |
| 1100 | }, | 1139 | }, |
| @@ -1106,7 +1145,7 @@ function registerReasoningSlashCommands() { | |||
| 1106 | helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`, | 1145 | helpString: t`Toggle the reasoning block of a message or range of messages. Expanded blocks will be collapsed, and collapsed blocks will be expanded.`, |
| 1107 | unnamedArgumentList: reasoningVisibilityArgs, | 1146 | unnamedArgumentList: reasoningVisibilityArgs, |
| 1108 | callback: (_args, value) => { | 1147 | callback: (_args, value) => { |
| 1109 | const details = getReasoningDetailsElements(value); | 1148 | const details = getReasoningDetailsElements(value.toString()); |
| 1110 | if (!details) return ''; | 1149 | if (!details) return ''; |
| 1111 | details.each(function () { | 1150 | details.each(function () { |
| 1112 | const $el = $(this); | 1151 | const $el = $(this); |
| @@ -1411,6 +1450,36 @@ export function parseReasoningFromString(str, { strict = true } = {}, template = | |||
| 1411 | } | 1450 | } |
| 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 | * Parse reasoning in an array of swipe strings if auto-parsing is enabled. | 1483 | * Parse reasoning in an array of swipe strings if auto-parsing is enabled. |
| 1415 | * @param {string[]} swipes Array of swipe strings | 1484 | * @param {string[]} swipes Array of swipe strings |
| 1416 | * @param {{extra: Partial<ReasoningMessageExtra>}[]} swipeInfoArray Array of swipe info objects | 1485 | * @param {{extra: Partial<ReasoningMessageExtra>}[]} swipeInfoArray Array of swipe info objects |
| @@ -1,5 +1,5 @@ | |||
| 1 | import { DOMPurify, moment } from '../lib.js'; | 1 | import { DOMPurify, moment, sha256 } from '../lib.js'; |
| 2 | import { event_types, eventSource, getRequestHeaders } from '../script.js'; | 2 | import { event_types, eventSource, getRequestHeaders, saveSettings } from '../script.js'; |
| 3 | import { t } from './i18n.js'; | 3 | import { t } from './i18n.js'; |
| 4 | import { chat_completion_sources } from './openai.js'; | 4 | import { chat_completion_sources } from './openai.js'; |
| 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; | 5 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| @@ -12,7 +12,9 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | |||
| 12 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | 12 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 13 | import { renderTemplateAsync } from './templates.js'; | 13 | import { renderTemplateAsync } from './templates.js'; |
| 14 | import { textgen_types } from './textgen-settings.js'; | 14 | import { textgen_types } from './textgen-settings.js'; |
| 15 | import { copyText, isTrueBoolean } from './utils.js'; | 15 | import { getCurrentUserHandle } from './user.js'; |
| 16 | import { copyText, isTrueBoolean, uuidv4 } from './utils.js'; | ||
| 17 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 16 | 18 | ||
| 17 | export const SECRET_KEYS = { | 19 | export const SECRET_KEYS = { |
| 18 | HORDE: 'api_key_horde', | 20 | HORDE: 'api_key_horde', |
| @@ -76,6 +78,7 @@ export const SECRET_KEYS = { | |||
| 76 | POLLINATIONS: 'api_key_pollinations', | 78 | POLLINATIONS: 'api_key_pollinations', |
| 77 | VOLCENGINE_APP_ID: 'volcengine_app_id', | 79 | VOLCENGINE_APP_ID: 'volcengine_app_id', |
| 78 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', | 80 | VOLCENGINE_ACCESS_KEY: 'volcengine_access_key', |
| 81 | WORKERS_AI: 'api_key_workers_ai', | ||
| 79 | }; | 82 | }; |
| 80 | 83 | ||
| 81 | const FRIENDLY_NAMES = { | 84 | const FRIENDLY_NAMES = { |
| @@ -129,7 +132,7 @@ const FRIENDLY_NAMES = { | |||
| 129 | [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)', | 132 | [SECRET_KEYS.LINGVA_URL]: 'Lingva Endpoint (e.g. https://lingva.ml/api/v1)', |
| 130 | [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)', | 133 | [SECRET_KEYS.ONERING_URL]: 'OneRingTranslator Endpoint (e.g. http://127.0.0.1:4990/translate)', |
| 131 | [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)', | 134 | [SECRET_KEYS.DEEPLX_URL]: 'DeepLX Endpoint (e.g. http://127.0.0.1:1188/translate)', |
| 132 | [SECRET_KEYS.MINIMAX]: 'MiniMax TTS', | 135 | [SECRET_KEYS.MINIMAX]: 'MiniMax', |
| 133 | [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID', | 136 | [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID', |
| 134 | [SECRET_KEYS.MOONSHOT]: 'Moonshot AI', | 137 | [SECRET_KEYS.MOONSHOT]: 'Moonshot AI', |
| 135 | [SECRET_KEYS.COMETAPI]: 'CometAPI', | 138 | [SECRET_KEYS.COMETAPI]: 'CometAPI', |
| @@ -140,6 +143,7 @@ const FRIENDLY_NAMES = { | |||
| 140 | [SECRET_KEYS.POLLINATIONS]: 'Pollinations', | 143 | [SECRET_KEYS.POLLINATIONS]: 'Pollinations', |
| 141 | [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID', | 144 | [SECRET_KEYS.VOLCENGINE_APP_ID]: 'Volcengine App ID', |
| 142 | [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key', | 145 | [SECRET_KEYS.VOLCENGINE_ACCESS_KEY]: 'Volcengine Access Key', |
| 146 | [SECRET_KEYS.WORKERS_AI]: 'Cloudflare Workers AI', | ||
| 143 | }; | 147 | }; |
| 144 | 148 | ||
| 145 | const INPUT_MAP = { | 149 | const INPUT_MAP = { |
| @@ -182,8 +186,9 @@ const INPUT_MAP = { | |||
| 182 | [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai', | 186 | [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai', |
| 183 | [SECRET_KEYS.ZAI]: '#api_key_zai', | 187 | [SECRET_KEYS.ZAI]: '#api_key_zai', |
| 184 | [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow', | 188 | [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow', |
| 185 | [SECRET_KEYS.COMFY_RUNPOD]: '#api_key_comfy_runpod', | 189 | [SECRET_KEYS.MINIMAX]: '#api_key_minimax', |
| 186 | [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations', | 190 | [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations', |
| 191 | [SECRET_KEYS.WORKERS_AI]: '#api_key_workers_ai', | ||
| 187 | }; | 192 | }; |
| 188 | 193 | ||
| 189 | const getLabel = () => moment().format('L LT'); | 194 | const getLabel = () => moment().format('L LT'); |
| @@ -414,7 +419,6 @@ export async function readSecretState() { | |||
| 414 | secret_state = await response.json(); | 419 | secret_state = await response.json(); |
| 415 | updateSecretDisplay(); | 420 | updateSecretDisplay(); |
| 416 | updateInputDataLists(); | 421 | updateInputDataLists(); |
| 417 | await checkOpenRouterAuth(); | ||
| 418 | } | 422 | } |
| 419 | } catch { | 423 | } catch { |
| 420 | console.error('Could not read secrets file'); | 424 | console.error('Could not read secrets file'); |
| @@ -495,6 +499,25 @@ export async function renameSecret(key, id, label) { | |||
| 495 | } | 499 | } |
| 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); | ||
Diff truncated