Merge pull request #3493 from SillyTavern/staging Staging
Signed| @@ -274,7 +274,7 @@ You will need two mandatory directory mappings and a port mapping to allow Silly | |||
| 274 | 1. Open your Command Line | 274 | 1. Open your Command Line |
| 275 | 2. Run the following command | 275 | 2. Run the following command |
| 276 | 276 | ||
| 277 | `docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` | 277 | `docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` |
| 278 | 278 | ||
| 279 | > Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config. | 279 | > Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config. |
| 280 | 280 | ||
| @@ -45,6 +45,7 @@ access.log | |||
| 45 | /vectors/ | 45 | /vectors/ |
| 46 | /cache/ | 46 | /cache/ |
| 47 | public/css/user.css | 47 | public/css/user.css |
| 48 | public/error/ | ||
| 48 | /plugins/ | 49 | /plugins/ |
| 49 | /data | 50 | /data |
| 50 | /default/scaffold | 51 | /default/scaffold |
| @@ -52,3 +53,5 @@ public/scripts/extensions/third-party | |||
| 52 | /certs | 53 | /certs |
| 53 | .aider* | 54 | .aider* |
| 54 | .env | 55 | .env |
| 56 | /StartDev.bat | ||
| 57 | |||
| @@ -6,7 +6,13 @@ cardsCacheCapacity: 100 | |||
| 6 | # -- SERVER CONFIGURATION -- | 6 | # -- SERVER CONFIGURATION -- |
| 7 | # Listen for incoming connections | 7 | # Listen for incoming connections |
| 8 | listen: false | 8 | listen: false |
| 9 | # Listen on a specific address, supports IPv4 and IPv6 | ||
| 10 | listenAddress: | ||
| 11 | ipv4: 0.0.0.0 | ||
| 12 | ipv6: '[::]' | ||
| 9 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! | 13 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! |
| 14 | # - Use option "auto" to automatically detect support | ||
| 15 | # - Use true or false (no qoutes) to enable or disable each protocol | ||
| 10 | protocol: | 16 | protocol: |
| 11 | ipv4: true | 17 | ipv4: true |
| 12 | ipv6: false | 18 | ipv6: false |
| @@ -65,12 +71,14 @@ autheliaAuth: false | |||
| 65 | # the username and passwords for basic auth are the same as those | 71 | # the username and passwords for basic auth are the same as those |
| 66 | # for the individual accounts | 72 | # for the individual accounts |
| 67 | perUserBasicAuth: false | 73 | perUserBasicAuth: false |
| 74 | # Minimum log level to display in the terminal (DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3) | ||
| 75 | minLogLevel: 0 | ||
| 68 | 76 | ||
| 69 | # User session timeout *in seconds* (defaults to 24 hours). | 77 | # User session timeout *in seconds* (defaults to 24 hours). |
| 70 | ## Set to a positive number to expire session after a certain time of inactivity | 78 | ## Set to a positive number to expire session after a certain time of inactivity |
| 71 | ## Set to 0 to expire session when the browser is closed | 79 | ## Set to 0 to expire session when the browser is closed |
| 72 | ## Set to a negative number to disable session expiration | 80 | ## Set to a negative number to disable session expiration |
| 73 | sessionTimeout: 86400 | 81 | sessionTimeout: -1 |
| 74 | # Used to sign session cookies. Will be auto-generated if not set | 82 | # Used to sign session cookies. Will be auto-generated if not set |
| 75 | cookieSecret: '' | 83 | cookieSecret: '' |
| 76 | # Disable CSRF protection - NOT RECOMMENDED | 84 | # Disable CSRF protection - NOT RECOMMENDED |
| @@ -133,24 +141,26 @@ whitelistImportDomains: | |||
| 133 | ## headers: | 141 | ## headers: |
| 134 | ## User-Agent: "Googlebot/2.1 (+http://www.google.com/bot.html)" | 142 | ## User-Agent: "Googlebot/2.1 (+http://www.google.com/bot.html)" |
| 135 | requestOverrides: [] | 143 | requestOverrides: [] |
| 136 | # -- EXTENSIONS CONFIGURATION -- | 144 | |
| 145 | # EXTENSIONS CONFIGURATION | ||
| 146 | extensions: | ||
| 137 | # Enable UI extensions | 147 | # Enable UI extensions |
| 138 | enableExtensions: true | 148 | enabled: true |
| 139 | # Automatically update extensions when a release version changes | 149 | # Automatically update extensions when a release version changes |
| 140 | enableExtensionsAutoUpdate: true | 150 | autoUpdate: true |
| 151 | models: | ||
| 152 | # Enables automatic model download from HuggingFace | ||
| 153 | autoDownload: true | ||
| 154 | # Additional models for extensions. Expects model IDs from HuggingFace model hub in ONNX format | ||
| 155 | classification: Cohee/distilbert-base-uncased-go-emotions-onnx | ||
| 156 | captioning: Xenova/vit-gpt2-image-captioning | ||
| 157 | embedding: Cohee/jina-embeddings-v2-base-en | ||
| 158 | speechToText: Xenova/whisper-small | ||
| 159 | textToSpeech: Xenova/speecht5_tts | ||
| 160 | |||
| 141 | # Additional model tokenizers can be downloaded on demand. | 161 | # Additional model tokenizers can be downloaded on demand. |
| 142 | # Disabling will fallback to another locally available tokenizer. | 162 | # Disabling will fallback to another locally available tokenizer. |
| 143 | enableDownloadableTokenizers: true | 163 | enableDownloadableTokenizers: true |
| 144 | # Extension settings | ||
| 145 | extras: | ||
| 146 | # Disables automatic model download from HuggingFace | ||
| 147 | disableAutoDownload: false | ||
| 148 | # Extra models for plugins. Expects model IDs from HuggingFace model hub in ONNX format | ||
| 149 | classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx | ||
| 150 | captioningModel: Xenova/vit-gpt2-image-captioning | ||
| 151 | embeddingModel: Cohee/jina-embeddings-v2-base-en | ||
| 152 | speechToTextModel: Xenova/whisper-small | ||
| 153 | textToSpeechModel: Xenova/speecht5_tts | ||
| 154 | # -- OPENAI CONFIGURATION -- | 164 | # -- OPENAI CONFIGURATION -- |
| 155 | # A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message | 165 | # A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message |
| 156 | promptPlaceholder: "[Start a new chat]" | 166 | promptPlaceholder: "[Start a new chat]" |
| @@ -177,6 +187,10 @@ ollama: | |||
| 177 | # * 0: Unload the model immediately after the request | 187 | # * 0: Unload the model immediately after the request |
| 178 | # * N (any positive number): Keep the model loaded for N seconds after the request. | 188 | # * N (any positive number): Keep the model loaded for N seconds after the request. |
| 179 | keepAlive: -1 | 189 | keepAlive: -1 |
| 190 | # Controls the "num_batch" (batch size) parameter of the generation request | ||
| 191 | # * -1: Use the default value of the model | ||
| 192 | # * N (positive number): Use the specified value. Must be a power of 2, e.g. 128, 256, 512, etc. | ||
| 193 | batchSize: -1 | ||
| 180 | # -- ANTHROPIC CLAUDE API CONFIGURATION -- | 194 | # -- ANTHROPIC CLAUDE API CONFIGURATION -- |
| 181 | claude: | 195 | claude: |
| 182 | # Enables caching of the system prompt (if supported). | 196 | # Enables caching of the system prompt (if supported). |
| @@ -196,3 +210,5 @@ claude: | |||
| 196 | cachingAtDepth: -1 | 210 | cachingAtDepth: -1 |
| 197 | # -- SERVER PLUGIN CONFIGURATION -- | 211 | # -- SERVER PLUGIN CONFIGURATION -- |
| 198 | enableServerPlugins: false | 212 | enableServerPlugins: false |
| 213 | # Attempt to automatically update server plugins on startup | ||
| 214 | enableServerPluginsAutoUpdate: true | ||
| @@ -672,10 +672,6 @@ | |||
| 672 | "type": "moving_ui" | 672 | "type": "moving_ui" |
| 673 | }, | 673 | }, |
| 674 | { | 674 | { |
| 675 | "filename": "presets/moving-ui/Black Magic Time.json", | ||
| 676 | "type": "moving_ui" | ||
| 677 | }, | ||
| 678 | { | ||
| 679 | "filename": "presets/quick-replies/Default.json", | 675 | "filename": "presets/quick-replies/Default.json", |
| 680 | "type": "quick_replies" | 676 | "type": "quick_replies" |
| 681 | }, | 677 | }, |
| @@ -782,5 +778,13 @@ | |||
| 782 | { | 778 | { |
| 783 | "filename": "presets/context/Mistral V7.json", | 779 | "filename": "presets/context/Mistral V7.json", |
| 784 | "type": "context" | 780 | "type": "context" |
| 781 | }, | ||
| 782 | { | ||
| 783 | "filename": "presets/instruct/DeepSeek-V2.5.json", | ||
| 784 | "type": "instruct" | ||
| 785 | }, | ||
| 786 | { | ||
| 787 | "filename": "presets/context/DeepSeek-V2.5.json", | ||
| 788 | "type": "context" | ||
| 785 | } | 789 | } |
| 786 | ] | 790 | ] |
| @@ -0,0 +1,11 @@ | |||
| 1 | { | ||
| 2 | "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}\n", | ||
| 3 | "example_separator": "", | ||
| 4 | "chat_start": "", | ||
| 5 | "use_stop_strings": false, | ||
| 6 | "allow_jailbreak": false, | ||
| 7 | "always_force_name2": true, | ||
| 8 | "trim_sentences": false, | ||
| 9 | "single_line": false, | ||
| 10 | "name": "DeepSeek-V2.5" | ||
| 11 | } | ||
| @@ -0,0 +1,22 @@ | |||
| 1 | { | ||
| 2 | "input_sequence": "<|User|>", | ||
| 3 | "output_sequence": "<|Assistant|>", | ||
| 4 | "first_output_sequence": "", | ||
| 5 | "last_output_sequence": "", | ||
| 6 | "system_sequence_prefix": "", | ||
| 7 | "system_sequence_suffix": "", | ||
| 8 | "stop_sequence": "", | ||
| 9 | "wrap": false, | ||
| 10 | "macro": true, | ||
| 11 | "names_behavior": "force", | ||
| 12 | "activation_regex": "", | ||
| 13 | "skip_examples": false, | ||
| 14 | "output_suffix": "<|end▁of▁sentence|>", | ||
| 15 | "input_suffix": "", | ||
| 16 | "system_sequence": "", | ||
| 17 | "system_suffix": "", | ||
| 18 | "user_alignment_message": "", | ||
| 19 | "last_system_sequence": "", | ||
| 20 | "system_same_as_user": true, | ||
| 21 | "name": "DeepSeek-V2.5" | ||
| 22 | } | ||
| @@ -1,45 +0,0 @@ | |||
| 1 | { | ||
| 2 | "name": "Black Magic Time", | ||
| 3 | "movingUIState": { | ||
| 4 | "sheld": { | ||
| 5 | "top": 488, | ||
| 6 | "left": 1407, | ||
| 7 | "right": 1, | ||
| 8 | "bottom": 4, | ||
| 9 | "margin": "unset", | ||
| 10 | "width": 471, | ||
| 11 | "height": 439 | ||
| 12 | }, | ||
| 13 | "floatingPrompt": { | ||
| 14 | "width": 369, | ||
| 15 | "height": 441 | ||
| 16 | }, | ||
| 17 | "right-nav-panel": { | ||
| 18 | "top": 0, | ||
| 19 | "left": 1400, | ||
| 20 | "right": 111, | ||
| 21 | "bottom": 446, | ||
| 22 | "margin": "unset", | ||
| 23 | "width": 479, | ||
| 24 | "height": 487 | ||
| 25 | }, | ||
| 26 | "WorldInfo": { | ||
| 27 | "top": 41, | ||
| 28 | "left": 369, | ||
| 29 | "right": 642, | ||
| 30 | "bottom": 51, | ||
| 31 | "margin": "unset", | ||
| 32 | "width": 1034, | ||
| 33 | "height": 858 | ||
| 34 | }, | ||
| 35 | "left-nav-panel": { | ||
| 36 | "top": 442, | ||
| 37 | "left": 0, | ||
| 38 | "right": 1546, | ||
| 39 | "bottom": 25, | ||
| 40 | "margin": "unset", | ||
| 41 | "width": 368, | ||
| 42 | "height": 483 | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 45 | \ No newline at end of file | \ No newline at end of file | |
| @@ -0,0 +1,22 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | |||
| 4 | <head> | ||
| 5 | <title>Forbidden</title> | ||
| 6 | </head> | ||
| 7 | |||
| 8 | <body> | ||
| 9 | <h1>Forbidden</h1> | ||
| 10 | <p> | ||
| 11 | If you are the system administrator, add your IP address to the | ||
| 12 | whitelist or disable whitelist mode by editing | ||
| 13 | <code>config.yaml</code> in the root directory of your installation. | ||
| 14 | </p> | ||
| 15 | <hr /> | ||
| 16 | <p> | ||
| 17 | <em>Connection from {{ipDetails}} has been blocked. This attempt | ||
| 18 | has been logged.</em> | ||
| 19 | </p> | ||
| 20 | </body> | ||
| 21 | |||
| 22 | </html> | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | |||
| 4 | <head> | ||
| 5 | <title>Unauthorized</title> | ||
| 6 | </head> | ||
| 7 | |||
| 8 | <body> | ||
| 9 | <h1>Unauthorized</h1> | ||
| 10 | <p> | ||
| 11 | If you are the system administrator, you can configure the | ||
| 12 | <code>basicAuthUser</code> credentials by editing | ||
| 13 | <code>config.yaml</code> in the root directory of your installation. | ||
| 14 | </p> | ||
| 15 | </body> | ||
| 16 | |||
| 17 | </html> | ||
| @@ -0,0 +1,15 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | |||
| 4 | <head> | ||
| 5 | <title>Not found</title> | ||
| 6 | </head> | ||
| 7 | |||
| 8 | <body> | ||
| 9 | <h1>Not found</h1> | ||
| 10 | <p> | ||
| 11 | The requested URL was not found on this server. | ||
| 12 | </p> | ||
| 13 | </body> | ||
| 14 | |||
| 15 | </html> | ||
| @@ -1,6 +1,24 @@ | |||
| 1 | import { UserDirectoryList, User } from "./src/users"; | 1 | import { UserDirectoryList, User } from "./src/users"; |
| 2 | import { CsrfSyncedToken } from "csrf-sync"; | ||
| 2 | 3 | ||
| 3 | declare global { | 4 | declare global { |
| 5 | declare namespace CookieSessionInterfaces { | ||
| 6 | export interface CookieSessionObject { | ||
| 7 | /** | ||
| 8 | * The CSRF token for the session. | ||
| 9 | */ | ||
| 10 | csrfToken: CsrfSyncedToken; | ||
| 11 | /** | ||
| 12 | * Authenticated user handle. | ||
| 13 | */ | ||
| 14 | handle: string; | ||
| 15 | /** | ||
| 16 | * Last time the session was extended. | ||
| 17 | */ | ||
| 18 | touch: number; | ||
| 19 | } | ||
| 20 | } | ||
| 21 | |||
| 4 | namespace Express { | 22 | namespace Express { |
| 5 | export interface Request { | 23 | export interface Request { |
| 6 | user: { | 24 | user: { |
| @@ -15,11 +33,3 @@ declare global { | |||
| 15 | */ | 33 | */ |
| 16 | var DATA_ROOT: string; | 34 | var DATA_ROOT: string; |
| 17 | } | 35 | } |
| 18 | |||
| 19 | declare module 'express-session' { | ||
| 20 | export interface SessionData { | ||
| 21 | handle: string; | ||
| 22 | touch: number; | ||
| 23 | // other properties... | ||
| 24 | } | ||
| 25 | } | ||
| @@ -15,7 +15,7 @@ | |||
| 15 | "**/node_modules/**", | 15 | "**/node_modules/**", |
| 16 | "**/dist/**", | 16 | "**/dist/**", |
| 17 | "**/.git/**", | 17 | "**/.git/**", |
| 18 | "public/lib/**", | 18 | "public/**", |
| 19 | "backups/**", | 19 | "backups/**", |
| 20 | "data/**", | 20 | "data/**", |
| 21 | "cache/**", | 21 | "cache/**", |
| @@ -1,12 +1,12 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "sillytavern", | 2 | "name": "sillytavern", |
| 3 | "version": "1.12.11", | 3 | "version": "1.12.12", |
| 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.12.11", | 9 | "version": "1.12.12", |
| 10 | "hasInstallScript": true, | 10 | "hasInstallScript": true, |
| 11 | "license": "AGPL-3.0", | 11 | "license": "AGPL-3.0", |
| 12 | "dependencies": { | 12 | "dependencies": { |
| @@ -26,7 +26,7 @@ | |||
| 26 | "cookie-parser": "^1.4.6", | 26 | "cookie-parser": "^1.4.6", |
| 27 | "cookie-session": "^2.1.0", | 27 | "cookie-session": "^2.1.0", |
| 28 | "cors": "^2.8.5", | 28 | "cors": "^2.8.5", |
| 29 | "csrf-csrf": "^2.2.3", | 29 | "csrf-sync": "^4.0.3", |
| 30 | "diff-match-patch": "^1.0.5", | 30 | "diff-match-patch": "^1.0.5", |
| 31 | "dompurify": "^3.1.7", | 31 | "dompurify": "^3.1.7", |
| 32 | "droll": "^0.2.1", | 32 | "droll": "^0.2.1", |
| @@ -41,6 +41,7 @@ | |||
| 41 | "html-entities": "^2.5.2", | 41 | "html-entities": "^2.5.2", |
| 42 | "iconv-lite": "^0.6.3", | 42 | "iconv-lite": "^0.6.3", |
| 43 | "ip-matching": "^2.1.2", | 43 | "ip-matching": "^2.1.2", |
| 44 | "ip-regex": "^5.0.0", | ||
| 44 | "ipaddr.js": "^2.0.1", | 45 | "ipaddr.js": "^2.0.1", |
| 45 | "jimp": "^0.22.10", | 46 | "jimp": "^0.22.10", |
| 46 | "localforage": "^1.10.0", | 47 | "localforage": "^1.10.0", |
| @@ -86,6 +87,7 @@ | |||
| 86 | "@types/cookie-session": "^2.0.49", | 87 | "@types/cookie-session": "^2.0.49", |
| 87 | "@types/cors": "^2.8.17", | 88 | "@types/cors": "^2.8.17", |
| 88 | "@types/deno": "^2.0.0", | 89 | "@types/deno": "^2.0.0", |
| 90 | "@types/dompurify": "^3.0.5", | ||
| 89 | "@types/express": "^4.17.21", | 91 | "@types/express": "^4.17.21", |
| 90 | "@types/jquery": "^3.5.29", | 92 | "@types/jquery": "^3.5.29", |
| 91 | "@types/jquery-cropper": "^1.0.4", | 93 | "@types/jquery-cropper": "^1.0.4", |
| @@ -1179,6 +1181,16 @@ | |||
| 1179 | "dev": true, | 1181 | "dev": true, |
| 1180 | "license": "MIT" | 1182 | "license": "MIT" |
| 1181 | }, | 1183 | }, |
| 1184 | "node_modules/@types/dompurify": { | ||
| 1185 | "version": "3.0.5", | ||
| 1186 | "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", | ||
| 1187 | "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", | ||
| 1188 | "dev": true, | ||
| 1189 | "license": "MIT", | ||
| 1190 | "dependencies": { | ||
| 1191 | "@types/trusted-types": "*" | ||
| 1192 | } | ||
| 1193 | }, | ||
| 1182 | "node_modules/@types/estree": { | 1194 | "node_modules/@types/estree": { |
| 1183 | "version": "1.0.6", | 1195 | "version": "1.0.6", |
| 1184 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", | 1196 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", |
| @@ -1462,6 +1474,13 @@ | |||
| 1462 | "@types/jquery": "*" | 1474 | "@types/jquery": "*" |
| 1463 | } | 1475 | } |
| 1464 | }, | 1476 | }, |
| 1477 | "node_modules/@types/trusted-types": { | ||
| 1478 | "version": "2.0.7", | ||
| 1479 | "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", | ||
| 1480 | "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", | ||
| 1481 | "dev": true, | ||
| 1482 | "license": "MIT" | ||
| 1483 | }, | ||
| 1465 | "node_modules/@types/write-file-atomic": { | 1484 | "node_modules/@types/write-file-atomic": { |
| 1466 | "version": "4.0.3", | 1485 | "version": "4.0.3", |
| 1467 | "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz", | 1486 | "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz", |
| @@ -2987,10 +3006,10 @@ | |||
| 2987 | "node": "*" | 3006 | "node": "*" |
| 2988 | } | 3007 | } |
| 2989 | }, | 3008 | }, |
| 2990 | "node_modules/csrf-csrf": { | 3009 | "node_modules/csrf-sync": { |
| 2991 | "version": "2.2.4", | 3010 | "version": "4.0.3", |
| 2992 | "resolved": "https://registry.npmjs.org/csrf-csrf/-/csrf-csrf-2.2.4.tgz", | 3011 | "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.0.3.tgz", |
| 2993 | "integrity": "sha512-LuhBmy5RfRmEfeqeYqgaAuS1eDpVtKZB/Eiec9xiKQLBynJxrGVRdM2yRT/YMl1Njo/yKh2L9AYsIwSlTPnx2A==", | 3012 | "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==", |
| 2994 | "license": "ISC", | 3013 | "license": "ISC", |
| 2995 | "dependencies": { | 3014 | "dependencies": { |
| 2996 | "http-errors": "^2.0.0" | 3015 | "http-errors": "^2.0.0" |
| @@ -4610,6 +4629,18 @@ | |||
| 4610 | "integrity": "sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg==", | 4629 | "integrity": "sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg==", |
| 4611 | "license": "LGPL-3.0-only" | 4630 | "license": "LGPL-3.0-only" |
| 4612 | }, | 4631 | }, |
| 4632 | "node_modules/ip-regex": { | ||
| 4633 | "version": "5.0.0", | ||
| 4634 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", | ||
| 4635 | "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", | ||
| 4636 | "license": "MIT", | ||
| 4637 | "engines": { | ||
| 4638 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" | ||
| 4639 | }, | ||
| 4640 | "funding": { | ||
| 4641 | "url": "https://github.com/sponsors/sindresorhus" | ||
| 4642 | } | ||
| 4643 | }, | ||
| 4613 | "node_modules/ipaddr.js": { | 4644 | "node_modules/ipaddr.js": { |
| 4614 | "version": "2.1.0", | 4645 | "version": "2.1.0", |
| 4615 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", | 4646 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", |
| @@ -16,7 +16,7 @@ | |||
| 16 | "cookie-parser": "^1.4.6", | 16 | "cookie-parser": "^1.4.6", |
| 17 | "cookie-session": "^2.1.0", | 17 | "cookie-session": "^2.1.0", |
| 18 | "cors": "^2.8.5", | 18 | "cors": "^2.8.5", |
| 19 | "csrf-csrf": "^2.2.3", | 19 | "csrf-sync": "^4.0.3", |
| 20 | "diff-match-patch": "^1.0.5", | 20 | "diff-match-patch": "^1.0.5", |
| 21 | "dompurify": "^3.1.7", | 21 | "dompurify": "^3.1.7", |
| 22 | "droll": "^0.2.1", | 22 | "droll": "^0.2.1", |
| @@ -31,6 +31,7 @@ | |||
| 31 | "html-entities": "^2.5.2", | 31 | "html-entities": "^2.5.2", |
| 32 | "iconv-lite": "^0.6.3", | 32 | "iconv-lite": "^0.6.3", |
| 33 | "ip-matching": "^2.1.2", | 33 | "ip-matching": "^2.1.2", |
| 34 | "ip-regex": "^5.0.0", | ||
| 34 | "ipaddr.js": "^2.0.1", | 35 | "ipaddr.js": "^2.0.1", |
| 35 | "jimp": "^0.22.10", | 36 | "jimp": "^0.22.10", |
| 36 | "localforage": "^1.10.0", | 37 | "localforage": "^1.10.0", |
| @@ -86,9 +87,10 @@ | |||
| 86 | "type": "git", | 87 | "type": "git", |
| 87 | "url": "https://github.com/SillyTavern/SillyTavern.git" | 88 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 88 | }, | 89 | }, |
| 89 | "version": "1.12.11", | 90 | "version": "1.12.12", |
| 90 | "scripts": { | 91 | "scripts": { |
| 91 | "start": "node server.js", | 92 | "start": "node server.js", |
| 93 | "debug": "node server.js --inspect", | ||
| 92 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", | 94 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", |
| 93 | "start:bun": "bun server.js", | 95 | "start:bun": "bun server.js", |
| 94 | "start:no-csrf": "node server.js --disableCsrf", | 96 | "start:no-csrf": "node server.js --disableCsrf", |
| @@ -114,6 +116,7 @@ | |||
| 114 | "@types/cookie-session": "^2.0.49", | 116 | "@types/cookie-session": "^2.0.49", |
| 115 | "@types/cors": "^2.8.17", | 117 | "@types/cors": "^2.8.17", |
| 116 | "@types/deno": "^2.0.0", | 118 | "@types/deno": "^2.0.0", |
| 119 | "@types/dompurify": "^3.0.5", | ||
| 117 | "@types/express": "^4.17.21", | 120 | "@types/express": "^4.17.21", |
| 118 | "@types/jquery": "^3.5.29", | 121 | "@types/jquery": "^3.5.29", |
| 119 | "@types/jquery-cropper": "^1.0.4", | 122 | "@types/jquery-cropper": "^1.0.4", |
| @@ -48,6 +48,13 @@ async function updatePlugins() { | |||
| 48 | console.log(`Updating plugin ${color.green(directory)}...`); | 48 | console.log(`Updating plugin ${color.green(directory)}...`); |
| 49 | const pluginPath = path.join(pluginsPath, directory); | 49 | const pluginPath = path.join(pluginsPath, directory); |
| 50 | const pluginRepo = git(pluginPath); | 50 | const pluginRepo = git(pluginPath); |
| 51 | |||
| 52 | const isRepo = await pluginRepo.checkIsRepo(); | ||
| 53 | if (!isRepo) { | ||
| 54 | console.log(`Directory ${color.yellow(directory)} is not a Git repository`); | ||
| 55 | continue; | ||
| 56 | } | ||
| 57 | |||
| 51 | await pluginRepo.fetch(); | 58 | await pluginRepo.fetch(); |
| 52 | const commitHash = await pluginRepo.revparse(['HEAD']); | 59 | const commitHash = await pluginRepo.revparse(['HEAD']); |
| 53 | const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']); | 60 | const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']); |
| @@ -64,6 +64,46 @@ const keyMigrationMap = [ | |||
| 64 | newKey: 'backups.chat.throttleInterval', | 64 | newKey: 'backups.chat.throttleInterval', |
| 65 | migrate: (value) => value, | 65 | migrate: (value) => value, |
| 66 | }, | 66 | }, |
| 67 | { | ||
| 68 | oldKey: 'enableExtensions', | ||
| 69 | newKey: 'extensions.enabled', | ||
| 70 | migrate: (value) => value, | ||
| 71 | }, | ||
| 72 | { | ||
| 73 | oldKey: 'enableExtensionsAutoUpdate', | ||
| 74 | newKey: 'extensions.autoUpdate', | ||
| 75 | migrate: (value) => value, | ||
| 76 | }, | ||
| 77 | { | ||
| 78 | oldKey: 'extras.disableAutoDownload', | ||
| 79 | newKey: 'extensions.models.autoDownload', | ||
| 80 | migrate: (value) => !value, | ||
| 81 | }, | ||
| 82 | { | ||
| 83 | oldKey: 'extras.classificationModel', | ||
| 84 | newKey: 'extensions.models.classification', | ||
| 85 | migrate: (value) => value, | ||
| 86 | }, | ||
| 87 | { | ||
| 88 | oldKey: 'extras.captioningModel', | ||
| 89 | newKey: 'extensions.models.captioning', | ||
| 90 | migrate: (value) => value, | ||
| 91 | }, | ||
| 92 | { | ||
| 93 | oldKey: 'extras.embeddingModel', | ||
| 94 | newKey: 'extensions.models.embedding', | ||
| 95 | migrate: (value) => value, | ||
| 96 | }, | ||
| 97 | { | ||
| 98 | oldKey: 'extras.speechToTextModel', | ||
| 99 | newKey: 'extensions.models.speechToText', | ||
| 100 | migrate: (value) => value, | ||
| 101 | }, | ||
| 102 | { | ||
| 103 | oldKey: 'extras.textToSpeechModel', | ||
| 104 | newKey: 'extensions.models.textToSpeech', | ||
| 105 | migrate: (value) => value, | ||
| 106 | }, | ||
| 67 | ]; | 107 | ]; |
| 68 | 108 | ||
| 69 | /** | 109 | /** |
| @@ -73,7 +113,7 @@ const keyMigrationMap = [ | |||
| 73 | * @returns {string[]} Array of all keys in the object | 113 | * @returns {string[]} Array of all keys in the object |
| 74 | */ | 114 | */ |
| 75 | function getAllKeys(obj, prefix = '') { | 115 | function getAllKeys(obj, prefix = '') { |
| 76 | if (typeof obj !== 'object' || Array.isArray(obj)) { | 116 | if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) { |
| 77 | return []; | 117 | return []; |
| 78 | } | 118 | } |
| 79 | 119 | ||
| @@ -173,20 +213,60 @@ function addMissingConfigValues() { | |||
| 173 | * Creates the default config files if they don't exist yet. | 213 | * Creates the default config files if they don't exist yet. |
| 174 | */ | 214 | */ |
| 175 | function createDefaultFiles() { | 215 | function createDefaultFiles() { |
| 176 | const files = { | 216 | /** |
| 177 | config: './config.yaml', | 217 | * @typedef DefaultItem |
| 178 | user: './public/css/user.css', | 218 | * @type {object} |
| 179 | }; | 219 | * @property {'file' | 'directory'} type - Whether the item should be copied as a single file or merged into a directory structure. |
| 220 | * @property {string} defaultPath - The path to the default item (typically in `default/`). | ||
| 221 | * @property {string} productionPath - The path to the copied item for production use. | ||
| 222 | */ | ||
| 180 | 223 | ||
| 181 | for (const file of Object.values(files)) { | 224 | /** @type {DefaultItem[]} */ |
| 225 | const defaultItems = [ | ||
| 226 | { | ||
| 227 | type: 'file', | ||
| 228 | defaultPath: './default/config.yaml', | ||
| 229 | productionPath: './config.yaml', | ||
| 230 | }, | ||
| 231 | { | ||
| 232 | type: 'directory', | ||
| 233 | defaultPath: './default/public/', | ||
| 234 | productionPath: './public/', | ||
| 235 | }, | ||
| 236 | ]; | ||
| 237 | |||
| 238 | for (const defaultItem of defaultItems) { | ||
| 182 | try { | 239 | try { |
| 183 | if (!fs.existsSync(file)) { | 240 | if (defaultItem.type === 'file') { |
| 184 | const defaultFilePath = path.join('./default', path.parse(file).base); | 241 | if (!fs.existsSync(defaultItem.productionPath)) { |
| 185 | fs.copyFileSync(defaultFilePath, file); | 242 | fs.copyFileSync( |
| 186 | console.log(color.green(`Created default file: ${file}`)); | 243 | defaultItem.defaultPath, |
| 244 | defaultItem.productionPath, | ||
| 245 | ); | ||
| 246 | console.log( | ||
| 247 | color.green(`Created default file: ${defaultItem.productionPath}`), | ||
| 248 | ); | ||
| 249 | } | ||
| 250 | } else if (defaultItem.type === 'directory') { | ||
| 251 | fs.cpSync(defaultItem.defaultPath, defaultItem.productionPath, { | ||
| 252 | force: false, // Don't overwrite existing files! | ||
| 253 | recursive: true, | ||
| 254 | }); | ||
| 255 | console.log( | ||
| 256 | color.green(`Synchronized missing files: ${defaultItem.productionPath}`), | ||
| 257 | ); | ||
| 258 | } else { | ||
| 259 | throw new Error( | ||
| 260 | 'FATAL: Unexpected default file format in `post-install.js#createDefaultFiles()`.', | ||
| 261 | ); | ||
| 187 | } | 262 | } |
| 188 | } catch (error) { | 263 | } catch (error) { |
| 189 | console.error(color.red(`FATAL: Could not write default file: ${file}`), error); | 264 | console.error( |
| 265 | color.red( | ||
| 266 | `FATAL: Could not write default ${defaultItem.type}: ${defaultItem.productionPath}`, | ||
| 267 | ), | ||
| 268 | error, | ||
| 269 | ); | ||
| 190 | } | 270 | } |
| 191 | } | 271 | } |
| 192 | } | 272 | } |
| @@ -216,8 +216,6 @@ | |||
| 216 | 216 | ||
| 217 | } | 217 | } |
| 218 | 218 | ||
| 219 | #showRawPrompt, | ||
| 220 | #copyPromptToClipboard, | ||
| 221 | #groupCurrentMemberPopoutButton, | 219 | #groupCurrentMemberPopoutButton, |
| 222 | #summaryExtensionPopoutButton { | 220 | #summaryExtensionPopoutButton { |
| 223 | display: none; | 221 | display: none; |
| @@ -72,6 +72,10 @@ dialog { | |||
| 72 | overflow-x: auto; | 72 | overflow-x: auto; |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | .popup.left_aligned_dialogue_popup .popup-content { | ||
| 76 | text-align: start; | ||
| 77 | } | ||
| 78 | |||
| 75 | /* Opening animation */ | 79 | /* Opening animation */ |
| 76 | .popup[opening] { | 80 | .popup[opening] { |
| 77 | animation: pop-in var(--popup-animation-speed) ease-in-out; | 81 | animation: pop-in var(--popup-animation-speed) ease-in-out; |
| @@ -100,6 +100,13 @@ | |||
| 100 | border: 1px solid var(--SmartThemeBorderColor); | 100 | border: 1px solid var(--SmartThemeBorderColor); |
| 101 | } | 101 | } |
| 102 | 102 | ||
| 103 | .select2-container .select2-results .select2-results__option--disabled { | ||
| 104 | color: inherit; | ||
| 105 | background-color: inherit; | ||
| 106 | cursor: not-allowed; | ||
| 107 | filter: brightness(0.5); | ||
| 108 | } | ||
| 109 | |||
| 103 | .select2-container .select2-selection--multiple .select2-selection__choice, | 110 | .select2-container .select2-selection--multiple .select2-selection__choice, |
| 104 | .select2-container .select2-selection--single .select2-selection__choice { | 111 | .select2-container .select2-selection--single .select2-selection__choice { |
| 105 | border-radius: 5px; | 112 | border-radius: 5px; |
| @@ -472,6 +472,11 @@ label[for="trim_spaces"]:has(input:checked) i.warning { | |||
| 472 | display: none; | 472 | display: none; |
| 473 | } | 473 | } |
| 474 | 474 | ||
| 475 | label[for="trim_spaces"]:not(:has(input:checked)) small { | ||
| 476 | color: var(--warning); | ||
| 477 | opacity: 1; | ||
| 478 | } | ||
| 479 | |||
| 475 | #claude_function_prefill_warning { | 480 | #claude_function_prefill_warning { |
| 476 | display: none; | 481 | display: none; |
| 477 | color: red; | 482 | color: red; |
| @@ -488,3 +493,7 @@ label[for="trim_spaces"]:has(input:checked) i.warning { | |||
| 488 | #mistralai_other_models:empty { | 493 | #mistralai_other_models:empty { |
| 489 | display: none; | 494 | display: none; |
| 490 | } | 495 | } |
| 496 | |||
| 497 | #banned_tokens_block_ooba:not(:has(#send_banned_tokens_textgenerationwebui:checked)) #banned_tokens_controls_ooba { | ||
| 498 | filter: brightness(0.5); | ||
| 499 | } | ||
| @@ -730,7 +730,7 @@ | |||
| 730 | <input type="range" id="top_k_openai" name="volume" min="0" max="500" step="1"> | 730 | <input type="range" id="top_k_openai" name="volume" min="0" max="500" step="1"> |
| 731 | </div> | 731 | </div> |
| 732 | <div class="range-block-counter"> | 732 | <div class="range-block-counter"> |
| 733 | <input type="number" min="0" max="200" step="1" data-for="top_k_openai" id="top_k_counter_openai"> | 733 | <input type="number" min="0" max="500" step="1" data-for="top_k_openai" id="top_k_counter_openai"> |
| 734 | </div> | 734 | </div> |
| 735 | </div> | 735 | </div> |
| 736 | </div> | 736 | </div> |
| @@ -1587,6 +1587,10 @@ | |||
| 1587 | <input type="checkbox" id="skip_special_tokens_textgenerationwebui" /> | 1587 | <input type="checkbox" id="skip_special_tokens_textgenerationwebui" /> |
| 1588 | <small data-i18n="Skip Special Tokens">Skip Special Tokens</small> | 1588 | <small data-i18n="Skip Special Tokens">Skip Special Tokens</small> |
| 1589 | </label> | 1589 | </label> |
| 1590 | <label data-tg-type="openrouter" class="checkbox_label flexGrow flexShrink" for="include_reasoning_textgenerationwebui"> | ||
| 1591 | <input type="checkbox" id="include_reasoning_textgenerationwebui" /> | ||
| 1592 | <small data-i18n="Request Model Reasoning">Request Model Reasoning</small> | ||
| 1593 | </label> | ||
| 1590 | <label data-tg-type="ooba, aphrodite, tabby" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui"> | 1594 | <label data-tg-type="ooba, aphrodite, tabby" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui"> |
| 1591 | <input type="checkbox" id="temperature_last_textgenerationwebui" /> | 1595 | <input type="checkbox" id="temperature_last_textgenerationwebui" /> |
| 1592 | <label> | 1596 | <label> |
| @@ -1617,17 +1621,34 @@ | |||
| 1617 | </div> | 1621 | </div> |
| 1618 | <div data-tg-type-mode="except" data-tg-type="generic" id="banned_tokens_block_ooba" class="wide100p"> | 1622 | <div data-tg-type-mode="except" data-tg-type="generic" id="banned_tokens_block_ooba" class="wide100p"> |
| 1619 | <hr class="width100p"> | 1623 | <hr class="width100p"> |
| 1620 | <h4 class="range-block-title justifyCenter"> | 1624 | <div class="range-block-title title_restorable"> |
| 1621 | <span data-i18n="Banned Tokens">Banned Tokens/Strings</span> | 1625 | <div> |
| 1626 | <strong data-i18n="Banned Tokens">Banned Tokens/Strings</strong> | ||
| 1622 | <div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]LLaMA / Mistral / Yi models only" title="Enter sequences you don't want to appear in the output. Unquoted text will be tokenized in the back end and banned as tokens. [token ids] will be banned as-is. Most tokens have a leading space. Use token counter (with the correct tokenizer selected first!) if you are unsure. Enclose text in double quotes to ban the entire string as a set. Quoted Strings and [Token ids] must be on their own line."></div> | 1627 | <div class="margin5 fa-solid fa-circle-info opacity50p " data-i18n="[title]LLaMA / Mistral / Yi models only" title="Enter sequences you don't want to appear in the output. Unquoted text will be tokenized in the back end and banned as tokens. [token ids] will be banned as-is. Most tokens have a leading space. Use token counter (with the correct tokenizer selected first!) if you are unsure. Enclose text in double quotes to ban the entire string as a set. Quoted Strings and [Token ids] must be on their own line."></div> |
| 1623 | </h4> | 1628 | </div> |
| 1629 | <label id="send_banned_tokens_label" for="send_banned_tokens_textgenerationwebui" class="checkbox_label"> | ||
| 1630 | <input id="send_banned_tokens_textgenerationwebui" type="checkbox" style="display:none;" /> | ||
| 1631 | <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small> | ||
| 1632 | </label> | ||
| 1633 | </div> | ||
| 1634 | <div id="banned_tokens_controls_ooba"> | ||
| 1635 | <div class="textAlignCenter"> | ||
| 1636 | <small data-i18n="Global list">Global list</small> | ||
| 1637 | </div> | ||
| 1638 | <div class="wide100p marginBot10"> | ||
| 1639 | <textarea id="global_banned_tokens_textgenerationwebui" class="text_pole textarea_compact" name="global_banned_tokens_textgenerationwebui" rows="3" data-i18n="[placeholder]Example: some text [42, 69, 1337]" placeholder='some text as tokens [420, 69, 1337] "Some verbatim string"'></textarea> | ||
| 1640 | </div> | ||
| 1641 | <div class="textAlignCenter"> | ||
| 1642 | <small data-i18n="Preset-specific list">Preset-specific list</small> | ||
| 1643 | </div> | ||
| 1624 | <div class="wide100p"> | 1644 | <div class="wide100p"> |
| 1625 | <textarea id="banned_tokens_textgenerationwebui" class="text_pole textarea_compact" name="banned_tokens_textgenerationwebui" rows="3" data-i18n="[placeholder]Example: some text [42, 69, 1337]" placeholder='some text as tokens [420, 69, 1337] "Some verbatim string"'></textarea> | 1645 | <textarea id="banned_tokens_textgenerationwebui" class="text_pole textarea_compact" name="banned_tokens_textgenerationwebui" rows="3" data-i18n="[placeholder]Example: some text [42, 69, 1337]" placeholder='some text as tokens [420, 69, 1337] "Some verbatim string"'></textarea> |
| 1626 | </div> | 1646 | </div> |
| 1627 | </div> | 1647 | </div> |
| 1648 | </div> | ||
| 1628 | <div class="range-block wide100p"> | 1649 | <div class="range-block wide100p"> |
| 1629 | <div id="logit_bias_textgenerationwebui" class="range-block-title title_restorable"> | 1650 | <div id="logit_bias_textgenerationwebui" class="range-block-title title_restorable"> |
| 1630 | <span data-i18n="Logit Bias">Logit Bias</span> | 1651 | <strong data-i18n="Logit Bias">Logit Bias</strong> |
| 1631 | <div id="textgen_logit_bias_new_entry" class="menu_button menu_button_icon"> | 1652 | <div id="textgen_logit_bias_new_entry" class="menu_button menu_button_icon"> |
| 1632 | <i class="fa-xs fa-solid fa-plus"></i> | 1653 | <i class="fa-xs fa-solid fa-plus"></i> |
| 1633 | <small data-i18n="Add">Add</small> | 1654 | <small data-i18n="Add">Add</small> |
| @@ -1930,7 +1951,7 @@ | |||
| 1930 | </span> | 1951 | </span> |
| 1931 | </div> | 1952 | </div> |
| 1932 | </div> | 1953 | </div> |
| 1933 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq"> | 1954 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek"> |
| 1934 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> | 1955 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1935 | <input id="openai_function_calling" type="checkbox" /> | 1956 | <input id="openai_function_calling" type="checkbox" /> |
| 1936 | <span data-i18n="Enable function calling">Enable function calling</span> | 1957 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -1953,6 +1974,7 @@ | |||
| 1953 | <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span> | 1974 | <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span> |
| 1954 | </div> | 1975 | </div> |
| 1955 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom"> | 1976 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom"> |
| 1977 | <div class="flex-container oneline-dropdown"> | ||
| 1956 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> | 1978 | <label for="openai_inline_image_quality" data-i18n="Inline Image Quality"> |
| 1957 | Inline Image Quality | 1979 | Inline Image Quality |
| 1958 | </label> | 1980 | </label> |
| @@ -1963,6 +1985,7 @@ | |||
| 1963 | </select> | 1985 | </select> |
| 1964 | </div> | 1986 | </div> |
| 1965 | </div> | 1987 | </div> |
| 1988 | </div> | ||
| 1966 | <div class="range-block" data-source="makersuite"> | 1989 | <div class="range-block" data-source="makersuite"> |
| 1967 | <label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand"> | 1990 | <label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand"> |
| 1968 | <input id="use_makersuite_sysprompt" type="checkbox" /> | 1991 | <input id="use_makersuite_sysprompt" type="checkbox" /> |
| @@ -1977,20 +2000,32 @@ | |||
| 1977 | </span> | 2000 | </span> |
| 1978 | </div> | 2001 | </div> |
| 1979 | </div> | 2002 | </div> |
| 1980 | <div class="range-block" data-source="makersuite"> | 2003 | <div class="range-block" data-source="deepseek,openrouter"> |
| 1981 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> | 2004 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 1982 | <input id="openai_show_thoughts" type="checkbox" /> | 2005 | <input id="openai_show_thoughts" type="checkbox" /> |
| 1983 | <span> | 2006 | <span> |
| 1984 | <span data-i18n="Show model thoughts">Show model thoughts</span> | 2007 | <span data-i18n="Request model reasoning">Request model reasoning</span> |
| 1985 | <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking"></i> | 2008 | <i class="opacity50p fa-solid fa-circle-info" title="DeepSeek Reasoner"></i> |
| 1986 | </span> | 2009 | </span> |
| 1987 | </label> | 2010 | </label> |
| 1988 | <div class="toggle-description justifyLeft marginBot5"> | 2011 | <div class="toggle-description justifyLeft marginBot5"> |
| 1989 | <span data-i18n="Display the model's internal thoughts in the response."> | 2012 | <span data-i18n="Allows the model to return its thinking process."> |
| 1990 | Display the model's internal thoughts in the response. | 2013 | Allows the model to return its thinking process. |
| 1991 | </span> | 2014 | </span> |
| 1992 | </div> | 2015 | </div> |
| 1993 | </div> | 2016 | </div> |
| 2017 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom"> | ||
| 2018 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response."> | ||
| 2019 | <label for="openai_reasoning_effort" data-i18n="Reasoning Effort"> | ||
| 2020 | Reasoning Effort | ||
| 2021 | </label> | ||
| 2022 | <select id="openai_reasoning_effort"> | ||
| 2023 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> | ||
| 2024 | <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option> | ||
| 2025 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> | ||
| 2026 | </select> | ||
| 2027 | </div> | ||
| 2028 | </div> | ||
| 1994 | <div class="range-block" data-source="claude"> | 2029 | <div class="range-block" data-source="claude"> |
| 1995 | <div class="wide100p"> | 2030 | <div class="wide100p"> |
| 1996 | <div class="flex-container alignItemsCenter"> | 2031 | <div class="flex-container alignItemsCenter"> |
| @@ -2692,7 +2727,7 @@ | |||
| 2692 | <option value="windowai">Window AI</option> | 2727 | <option value="windowai">Window AI</option> |
| 2693 | </optgroup> | 2728 | </optgroup> |
| 2694 | </select> | 2729 | </select> |
| 2695 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite"> | 2730 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| 2696 | <div class="inline-drawer-toggle inline-drawer-header"> | 2731 | <div class="inline-drawer-toggle inline-drawer-header"> |
| 2697 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> | 2732 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> |
| 2698 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | 2733 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| @@ -2755,7 +2790,7 @@ | |||
| 2755 | </div> | 2790 | </div> |
| 2756 | </div> | 2791 | </div> |
| 2757 | </div> | 2792 | </div> |
| 2758 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite"> | 2793 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| 2759 | <div class="reverse_proxy_warning"> | 2794 | <div class="reverse_proxy_warning"> |
| 2760 | <b> | 2795 | <b> |
| 2761 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> | 2796 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> |
| @@ -2805,27 +2840,6 @@ | |||
| 2805 | <div> | 2840 | <div> |
| 2806 | <h4 data-i18n="OpenAI Model">OpenAI Model</h4> | 2841 | <h4 data-i18n="OpenAI Model">OpenAI Model</h4> |
| 2807 | <select id="model_openai_select"> | 2842 | <select id="model_openai_select"> |
| 2808 | <optgroup label="GPT-3.5 Turbo"> | ||
| 2809 | <option value="gpt-3.5-turbo">gpt-3.5-turbo</option> | ||
| 2810 | <option value="gpt-3.5-turbo-0125">gpt-3.5-turbo-0125 (2024)</option> | ||
| 2811 | <option value="gpt-3.5-turbo-1106">gpt-3.5-turbo-1106 (2023)</option> | ||
| 2812 | <option value="gpt-3.5-turbo-0613">gpt-3.5-turbo-0613 (2023)</option> | ||
| 2813 | <option value="gpt-3.5-turbo-0301">gpt-3.5-turbo-0301 (2023)</option> | ||
| 2814 | <option value="gpt-3.5-turbo-16k">gpt-3.5-turbo-16k</option> | ||
| 2815 | <option value="gpt-3.5-turbo-16k-0613">gpt-3.5-turbo-16k-0613 (2023)</option> | ||
| 2816 | </optgroup> | ||
| 2817 | <optgroup label="GPT-3.5 Turbo Instruct"> | ||
| 2818 | <option value="gpt-3.5-turbo-instruct">gpt-3.5-turbo-instruct</option> | ||
| 2819 | <option value="gpt-3.5-turbo-instruct-0914">gpt-3.5-turbo-instruct-0914</option> | ||
| 2820 | </optgroup> | ||
| 2821 | <optgroup label="GPT-4"> | ||
| 2822 | <option value="gpt-4">gpt-4</option> | ||
| 2823 | <option value="gpt-4-0613">gpt-4-0613 (2023)</option> | ||
| 2824 | <option value="gpt-4-0314">gpt-4-0314 (2023)</option> | ||
| 2825 | <option value="gpt-4-32k">gpt-4-32k</option> | ||
| 2826 | <option value="gpt-4-32k-0613">gpt-4-32k-0613 (2023)</option> | ||
| 2827 | <option value="gpt-4-32k-0314">gpt-4-32k-0314 (2023)</option> | ||
| 2828 | </optgroup> | ||
| 2829 | <optgroup label="GPT-4o"> | 2843 | <optgroup label="GPT-4o"> |
| 2830 | <option value="gpt-4o">gpt-4o</option> | 2844 | <option value="gpt-4o">gpt-4o</option> |
| 2831 | <option value="gpt-4o-2024-11-20">gpt-4o-2024-11-20</option> | 2845 | <option value="gpt-4o-2024-11-20">gpt-4o-2024-11-20</option> |
| @@ -2833,29 +2847,44 @@ | |||
| 2833 | <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option> | 2847 | <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option> |
| 2834 | <option value="chatgpt-4o-latest">chatgpt-4o-latest</option> | 2848 | <option value="chatgpt-4o-latest">chatgpt-4o-latest</option> |
| 2835 | </optgroup> | 2849 | </optgroup> |
| 2836 | <optgroup label="gpt-4o-mini"> | 2850 | <optgroup label="GPT-4o mini"> |
| 2837 | <option value="gpt-4o-mini">gpt-4o-mini</option> | 2851 | <option value="gpt-4o-mini">gpt-4o-mini</option> |
| 2838 | <option value="gpt-4o-mini-2024-07-18">gpt-4o-mini-2024-07-18</option> | 2852 | <option value="gpt-4o-2024-11-20">gpt-4o-2024-11-20</option> |
| 2853 | <option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option> | ||
| 2854 | <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option> | ||
| 2855 | <option value="chatgpt-4o-latest">chatgpt-4o-latest</option> | ||
| 2839 | </optgroup> | 2856 | </optgroup> |
| 2840 | <optgroup label="GPT-4 Turbo"> | 2857 | <optgroup label="o1 and o1-mini"> |
| 2858 | <option value="o1">o1</option> | ||
| 2859 | <option value="o1-2024-12-17">o1-2024-12-17</option> | ||
| 2860 | <option value="o1-mini">o1-mini</option> | ||
| 2861 | <option value="o1-mini-2024-09-12">o1-mini-2024-09-12</option> | ||
| 2862 | <option value="o1-preview">o1-preview</option> | ||
| 2863 | <option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option> | ||
| 2864 | </optgroup> | ||
| 2865 | <optgroup label="o3"> | ||
| 2866 | <option value="o3-mini">o3-mini</option> | ||
| 2867 | <option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option> | ||
| 2868 | </optgroup> | ||
| 2869 | <optgroup label="GPT-4 Turbo and GPT-4"> | ||
| 2841 | <option value="gpt-4-turbo">gpt-4-turbo</option> | 2870 | <option value="gpt-4-turbo">gpt-4-turbo</option> |
| 2842 | <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option> | 2871 | <option value="gpt-4-turbo-2024-04-09">gpt-4-turbo-2024-04-09</option> |
| 2843 | <option value="gpt-4-turbo-preview">gpt-4-turbo-preview</option> | 2872 | <option value="gpt-4-turbo-preview">gpt-4-turbo-preview</option> |
| 2844 | <option value="gpt-4-vision-preview">gpt-4-vision-preview</option> | ||
| 2845 | <option value="gpt-4-0125-preview">gpt-4-0125-preview (2024)</option> | 2873 | <option value="gpt-4-0125-preview">gpt-4-0125-preview (2024)</option> |
| 2846 | <option value="gpt-4-1106-preview">gpt-4-1106-preview (2023)</option> | 2874 | <option value="gpt-4-1106-preview">gpt-4-1106-preview (2023)</option> |
| 2875 | <option value="gpt-4">gpt-4</option> | ||
| 2876 | <option value="gpt-4-0613">gpt-4-0613 (2023)</option> | ||
| 2877 | <option value="gpt-4-0314">gpt-4-0314 (2023)</option> | ||
| 2847 | </optgroup> | 2878 | </optgroup> |
| 2848 | <optgroup label="o1"> | 2879 | <optgroup label="GPT-3.5 Turbo"> |
| 2849 | <option value="o1-preview">o1-preview</option> | 2880 | <option value="gpt-3.5-turbo">gpt-3.5-turbo</option> |
| 2850 | <option value="o1-mini">o1-mini</option> | 2881 | <option value="gpt-3.5-turbo-0125">gpt-3.5-turbo-0125 (2024)</option> |
| 2882 | <option value="gpt-3.5-turbo-1106">gpt-3.5-turbo-1106 (2023)</option> | ||
| 2883 | <option value="gpt-3.5-turbo-instruct">gpt-3.5-turbo-instruct</option> | ||
| 2851 | </optgroup> | 2884 | </optgroup> |
| 2852 | <optgroup label="Other"> | 2885 | <optgroup label="Other"> |
| 2853 | <option value="text-davinci-003">text-davinci-003</option> | 2886 | <option value="babbage-002">babbage-002</option> |
| 2854 | <option value="text-davinci-002">text-davinci-002</option> | 2887 | <option value="davinci-002">davinci-002</option> |
| 2855 | <option value="text-curie-001">text-curie-001</option> | ||
| 2856 | <option value="text-babbage-001">text-babbage-001</option> | ||
| 2857 | <option value="text-ada-001">text-ada-001</option> | ||
| 2858 | <option value="code-davinci-002">code-davinci-002</option> | ||
| 2859 | </optgroup> | 2888 | </optgroup> |
| 2860 | <optgroup id="openai_external_category" label="External"> | 2889 | <optgroup id="openai_external_category" label="External"> |
| 2861 | </optgroup> | 2890 | </optgroup> |
| @@ -3054,6 +3083,7 @@ | |||
| 3054 | <h4 data-i18n="Google Model">Google Model</h4> | 3083 | <h4 data-i18n="Google Model">Google Model</h4> |
| 3055 | <select id="model_google_select"> | 3084 | <select id="model_google_select"> |
| 3056 | <optgroup label="Primary"> | 3085 | <optgroup label="Primary"> |
| 3086 | <option value="gemini-2.0-flash">Gemini 2.0 Flash</option> | ||
| 3057 | <option value="gemini-1.5-pro">Gemini 1.5 Pro</option> | 3087 | <option value="gemini-1.5-pro">Gemini 1.5 Pro</option> |
| 3058 | <option value="gemini-1.5-flash">Gemini 1.5 Flash</option> | 3088 | <option value="gemini-1.5-flash">Gemini 1.5 Flash</option> |
| 3059 | <option value="gemini-1.0-pro">Gemini 1.0 Pro (Deprecated)</option> | 3089 | <option value="gemini-1.0-pro">Gemini 1.0 Pro (Deprecated)</option> |
| @@ -3062,7 +3092,14 @@ | |||
| 3062 | <option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option> | 3092 | <option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option> |
| 3063 | </optgroup> | 3093 | </optgroup> |
| 3064 | <optgroup label="Subversions"> | 3094 | <optgroup label="Subversions"> |
| 3065 | <option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental</option> | 3095 | <option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option> |
| 3096 | <option value="gemini-2.0-pro-exp-02-05">Gemini 2.0 Pro Experimental 2025-02-05</option> | ||
| 3097 | <option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option> | ||
| 3098 | <option value="gemini-2.0-flash-lite-preview-02-05">Gemini 2.0 Flash-Lite Preview 2025-02-05</option> | ||
| 3099 | <option value="gemini-2.0-flash-001">Gemini 2.0 Flash [001]</option> | ||
| 3100 | <option value="gemini-2.0-flash-thinking-exp">Gemini 2.0 Flash Thinking Experimental</option> | ||
| 3101 | <option value="gemini-2.0-flash-thinking-exp-01-21">Gemini 2.0 Flash Thinking Experimental 2025-01-21</option> | ||
| 3102 | <option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental 2024-12-19</option> | ||
| 3066 | <option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Experimental</option> | 3103 | <option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Experimental</option> |
| 3067 | <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option> | 3104 | <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option> |
| 3068 | <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option> | 3105 | <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option> |
| @@ -3149,34 +3186,22 @@ | |||
| 3149 | </div> | 3186 | </div> |
| 3150 | <h4 data-i18n="Groq Model">Groq Model</h4> | 3187 | <h4 data-i18n="Groq Model">Groq Model</h4> |
| 3151 | <select id="model_groq_select"> | 3188 | <select id="model_groq_select"> |
| 3152 | <optgroup label="Llama 3.3"> | 3189 | <optgroup label="Production Models"> |
| 3190 | <option value="gemma2-9b-it">gemma2-9b-it</option> | ||
| 3153 | <option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile</option> | 3191 | <option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile</option> |
| 3192 | <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option> | ||
| 3193 | <option value="llama3-70b-8192">llama3-70b-8192</option> | ||
| 3194 | <option value="llama3-8b-8192">llama3-8b-8192</option> | ||
| 3195 | <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option> | ||
| 3154 | </optgroup> | 3196 | </optgroup> |
| 3155 | <optgroup label="Llama 3.2"> | 3197 | <optgroup label="Preview Models"> |
| 3198 | <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option> | ||
| 3199 | <option value="llama-3.3-70b-specdec">llama-3.3-70b-specdec</option> | ||
| 3156 | <option value="llama-3.2-1b-preview">llama-3.2-1b-preview</option> | 3200 | <option value="llama-3.2-1b-preview">llama-3.2-1b-preview</option> |
| 3157 | <option value="llama-3.2-3b-preview">llama-3.2-3b-preview</option> | 3201 | <option value="llama-3.2-3b-preview">llama-3.2-3b-preview</option> |
| 3158 | <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option> | 3202 | <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option> |
| 3159 | <option value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option> | 3203 | <option value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option> |
| 3160 | </optgroup> | 3204 | </optgroup> |
| 3161 | <optgroup label="Llama 3.1"> | ||
| 3162 | <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option> | ||
| 3163 | <option value="llama-3.1-70b-versatile">llama-3.1-70b-versatile</option> | ||
| 3164 | <option value="llama-3.1-405b-reasoning">llama-3.1-405b-reasoning</option> | ||
| 3165 | </optgroup> | ||
| 3166 | <optgroup label="Llama 3"> | ||
| 3167 | <option value="llama3-groq-8b-8192-tool-use-preview">llama3-groq-8b-8192-tool-use-preview</option> | ||
| 3168 | <option value="llama3-groq-70b-8192-tool-use-preview">llama3-groq-70b-8192-tool-use-preview</option> | ||
| 3169 | <option value="llama3-8b-8192">llama3-8b-8192</option> | ||
| 3170 | <option value="llama3-70b-8192">llama3-70b-8192</option> | ||
| 3171 | </optgroup> | ||
| 3172 | <optgroup label="Gemma"> | ||
| 3173 | <option value="gemma-7b-it">gemma-7b-it</option> | ||
| 3174 | <option value="gemma2-9b-it">gemma2-9b-it</option> | ||
| 3175 | </optgroup> | ||
| 3176 | <optgroup label="Other"> | ||
| 3177 | <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option> | ||
| 3178 | <option value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option> | ||
| 3179 | </optgroup> | ||
| 3180 | </select> | 3205 | </select> |
| 3181 | </div> | 3206 | </div> |
| 3182 | <div id="nanogpt_form" data-source="nanogpt"> | 3207 | <div id="nanogpt_form" data-source="nanogpt"> |
| @@ -3209,6 +3234,7 @@ | |||
| 3209 | <select id="model_deepseek_select"> | 3234 | <select id="model_deepseek_select"> |
| 3210 | <option value="deepseek-chat">deepseek-chat</option> | 3235 | <option value="deepseek-chat">deepseek-chat</option> |
| 3211 | <option value="deepseek-coder">deepseek-coder</option> | 3236 | <option value="deepseek-coder">deepseek-coder</option> |
| 3237 | <option value="deepseek-reasoner">deepseek-reasoner</option> | ||
| 3212 | </select> | 3238 | </select> |
| 3213 | </div> | 3239 | </div> |
| 3214 | </div> | 3240 | </div> |
| @@ -3224,32 +3250,19 @@ | |||
| 3224 | <h4 data-i18n="Perplexity Model">Perplexity Model</h4> | 3250 | <h4 data-i18n="Perplexity Model">Perplexity Model</h4> |
| 3225 | <select id="model_perplexity_select"> | 3251 | <select id="model_perplexity_select"> |
| 3226 | <optgroup label="Perplexity Sonar Models"> | 3252 | <optgroup label="Perplexity Sonar Models"> |
| 3253 | <option value="sonar">sonar</option> | ||
| 3254 | <option value="sonar-pro">sonar-pro</option> | ||
| 3255 | <option value="sonar-reasoning">sonar-reasoning</option> | ||
| 3256 | </optgroup> | ||
| 3257 | <optgroup label="Deprecated Models"> | ||
| 3258 | <!-- These are scheduled for deprecation after 2/22/2025 --> | ||
| 3227 | <option value="llama-3.1-sonar-small-128k-online">llama-3.1-sonar-small-128k-online</option> | 3259 | <option value="llama-3.1-sonar-small-128k-online">llama-3.1-sonar-small-128k-online</option> |
| 3228 | <option value="llama-3.1-sonar-large-128k-online">llama-3.1-sonar-large-128k-online</option> | 3260 | <option value="llama-3.1-sonar-large-128k-online">llama-3.1-sonar-large-128k-online</option> |
| 3229 | <option value="llama-3.1-sonar-huge-128k-online">llama-3.1-sonar-huge-128k-online</option> | 3261 | <option value="llama-3.1-sonar-huge-128k-online">llama-3.1-sonar-huge-128k-online</option> |
| 3230 | </optgroup> | 3262 | <!-- These are not listed on the site anymore --> |
| 3231 | <optgroup label="Perplexity Chat Models"> | ||
| 3232 | <option value="llama-3.1-sonar-small-128k-chat">llama-3.1-sonar-small-128k-chat</option> | 3263 | <option value="llama-3.1-sonar-small-128k-chat">llama-3.1-sonar-small-128k-chat</option> |
| 3233 | <option value="llama-3.1-sonar-large-128k-chat">llama-3.1-sonar-large-128k-chat</option> | 3264 | <option value="llama-3.1-sonar-large-128k-chat">llama-3.1-sonar-large-128k-chat</option> |
| 3234 | </optgroup> | 3265 | </optgroup> |
| 3235 | <optgroup label="Open-Source Models"> | ||
| 3236 | <option value="llama-3.1-8b-instruct">llama-3.1-8b-instruct</option> | ||
| 3237 | <option value="llama-3.1-70b-instruct">llama-3.1-70b-instruct</option> | ||
| 3238 | </optgroup> | ||
| 3239 | <optgroup label="Deprecated Models"> | ||
| 3240 | <option value="llama-3-sonar-small-32k-chat">llama-3-sonar-small-32k-chat</option> | ||
| 3241 | <option value="llama-3-sonar-small-32k-online">llama-3-sonar-small-32k-online</option> | ||
| 3242 | <option value="llama-3-sonar-large-32k-chat">llama-3-sonar-large-32k-chat</option> | ||
| 3243 | <option value="llama-3-sonar-large-32k-online">llama-3-sonar-large-32k-online</option> | ||
| 3244 | <option value="sonar-small-chat">sonar-small-chat</option> | ||
| 3245 | <option value="sonar-small-online">sonar-small-online</option> | ||
| 3246 | <option value="sonar-medium-chat">sonar-medium-chat</option> | ||
| 3247 | <option value="sonar-medium-online">sonar-medium-online</option> | ||
| 3248 | <option value="llama-3-8b-instruct">llama-3-8b-instruct</option> | ||
| 3249 | <option value="llama-3-70b-instruct">llama-3-70b-instruct</option> | ||
| 3250 | <option value="mistral-7b-instruct">mistral-7b-instruct (v0.2)</option> | ||
| 3251 | <option value="mixtral-8x7b-instruct">mixtral-8x7b-instruct</option> | ||
| 3252 | </optgroup> | ||
| 3253 | </select> | 3266 | </select> |
| 3254 | </div> | 3267 | </div> |
| 3255 | <form id="cohere_form" data-source="cohere" action="javascript:void(null);" method="post" enctype="multipart/form-data"> | 3268 | <form id="cohere_form" data-source="cohere" action="javascript:void(null);" method="post" enctype="multipart/form-data"> |
| @@ -3521,7 +3534,7 @@ | |||
| 3521 | </label> | 3534 | </label> |
| 3522 | <label id="instruct_enabled_label"for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled"> | 3535 | <label id="instruct_enabled_label"for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled"> |
| 3523 | <input id="instruct_enabled" type="checkbox" style="display:none;" /> | 3536 | <input id="instruct_enabled" type="checkbox" style="display:none;" /> |
| 3524 | <small><i class="fa-solid fa-power-off menu_button margin0"></i></small> | 3537 | <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small> |
| 3525 | </label> | 3538 | </label> |
| 3526 | </div> | 3539 | </div> |
| 3527 | </h4> | 3540 | </h4> |
| @@ -3699,7 +3712,7 @@ | |||
| 3699 | <div class="flex-container"> | 3712 | <div class="flex-container"> |
| 3700 | <label id="sysprompt_enabled_label" for="sysprompt_enabled" class="checkbox_label flex1" title="Enable System Prompt" data-i18n="[title]sysprompt_enabled"> | 3713 | <label id="sysprompt_enabled_label" for="sysprompt_enabled" class="checkbox_label flex1" title="Enable System Prompt" data-i18n="[title]sysprompt_enabled"> |
| 3701 | <input id="sysprompt_enabled" type="checkbox" style="display:none;" /> | 3714 | <input id="sysprompt_enabled" type="checkbox" style="display:none;" /> |
| 3702 | <small><i class="fa-solid fa-power-off menu_button margin0"></i></small> | 3715 | <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small> |
| 3703 | </label> | 3716 | </label> |
| 3704 | </div> | 3717 | </div> |
| 3705 | </h4> | 3718 | </h4> |
| @@ -3753,8 +3766,8 @@ | |||
| 3753 | </div> | 3766 | </div> |
| 3754 | <label class="checkbox_label" for="custom_stopping_strings_macro"> | 3767 | <label class="checkbox_label" for="custom_stopping_strings_macro"> |
| 3755 | <input id="custom_stopping_strings_macro" type="checkbox" checked> | 3768 | <input id="custom_stopping_strings_macro" type="checkbox" checked> |
| 3756 | <small data-i18n="Replace Macro in Custom Stopping Strings"> | 3769 | <small data-i18n="Replace Macro in Stop Strings"> |
| 3757 | Replace Macro in Custom Stopping Strings | 3770 | Replace Macro in Stop Strings |
| 3758 | </small> | 3771 | </small> |
| 3759 | </label> | 3772 | </label> |
| 3760 | </div> | 3773 | </div> |
| @@ -3797,6 +3810,66 @@ | |||
| 3797 | </div> | 3810 | </div> |
| 3798 | </div> | 3811 | </div> |
| 3799 | <div> | 3812 | <div> |
| 3813 | <h4 class="standoutHeader"> | ||
| 3814 | <span data-i18n="Reasoning">Reasoning</span> | ||
| 3815 | </h4> | ||
| 3816 | <div> | ||
| 3817 | <div class="flex-container alignItemsBaseline"> | ||
| 3818 | <label class="checkbox_label flex1" for="reasoning_auto_parse" title="Automatically parse reasoning blocks from main content between the reasoning prefix/suffix. Both fields must be defined and non-empty." data-i18n="[title]reasoning_auto_parse"> | ||
| 3819 | <input id="reasoning_auto_parse" type="checkbox" /> | ||
| 3820 | <small data-i18n="Auto-Parse"> | ||
| 3821 | Auto-Parse | ||
| 3822 | </small> | ||
| 3823 | </label> | ||
| 3824 | <label class="checkbox_label flex1" for="reasoning_auto_expand" title="Automatically expand reasoning blocks." data-i18n="[title]reasoning_auto_expand"> | ||
| 3825 | <input id="reasoning_auto_expand" type="checkbox" /> | ||
| 3826 | <small data-i18n="Auto-Expand"> | ||
| 3827 | Auto-Expand | ||
| 3828 | </small> | ||
| 3829 | </label> | ||
| 3830 | <label class="checkbox_label flex1" for="reasoning_show_hidden" title="Show reasoning time for models with hidden reasoning." data-i18n="[title]reasoning_show_hidden"> | ||
| 3831 | <input id="reasoning_show_hidden" type="checkbox" /> | ||
| 3832 | <small data-i18n="Show Hidden"> | ||
| 3833 | Show Hidden | ||
| 3834 | </small> | ||
| 3835 | </label> | ||
| 3836 | </div> | ||
| 3837 | <div class="flex-container alignItemsBaseline"> | ||
| 3838 | <label class="checkbox_label flex1" for="reasoning_add_to_prompts" title="Add existing reasoning blocks to prompts. To add a new reasoning block, use the message edit menu." data-i18n="[title]reasoning_add_to_prompts"> | ||
| 3839 | <input id="reasoning_add_to_prompts" type="checkbox" /> | ||
| 3840 | <small data-i18n="Add to Prompts"> | ||
| 3841 | Add to Prompts | ||
| 3842 | </small> | ||
| 3843 | </label> | ||
| 3844 | <div class="flex1 flex-container alignItemsBaseline" title="Maximum number of reasoning blocks to be added per prompt, counting from the last message." data-i18n="[title]reasoning_max_additions"> | ||
| 3845 | <input id="reasoning_max_additions" class="text_pole textarea_compact widthUnset" type="number" min="0" max="999"></textarea> | ||
| 3846 | <small data-i18n="Max">Max</small> | ||
| 3847 | </div> | ||
| 3848 | </div> | ||
| 3849 | <details> | ||
| 3850 | <summary data-i18n="Reasoning Formatting"> | ||
| 3851 | Reasoning Formatting | ||
| 3852 | </summary> | ||
| 3853 | <div class="flex-container"> | ||
| 3854 | <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix"> | ||
| 3855 | <small data-i18n="Prefix">Prefix</small> | ||
| 3856 | <textarea id="reasoning_prefix" class="text_pole textarea_compact autoSetHeight"></textarea> | ||
| 3857 | </div> | ||
| 3858 | <div class="flex1" title="Inserted after the reasoning content." data-i18n="[title]reasoning_suffix"> | ||
| 3859 | <small data-i18n="Suffix">Suffix</small> | ||
| 3860 | <textarea id="reasoning_suffix" class="text_pole textarea_compact autoSetHeight"></textarea> | ||
| 3861 | </div> | ||
| 3862 | </div> | ||
| 3863 | <div class="flex-container"> | ||
| 3864 | <div class="flex1" title="Inserted between the reasoning and the message content." data-i18n="[title]reasoning_separator"> | ||
| 3865 | <small data-i18n="Separator">Separator</small> | ||
| 3866 | <textarea id="reasoning_separator" class="text_pole textarea_compact autoSetHeight"></textarea> | ||
| 3867 | </div> | ||
| 3868 | </div> | ||
| 3869 | </details> | ||
| 3870 | </div> | ||
| 3871 | </div> | ||
| 3872 | <div> | ||
| 3800 | <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4> | 3873 | <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4> |
| 3801 | <div> | 3874 | <div> |
| 3802 | <small> | 3875 | <small> |
| @@ -4809,6 +4882,7 @@ | |||
| 4809 | </div> | 4882 | </div> |
| 4810 | <div id="extensions_settings" class="flex1 wide50p"> | 4883 | <div id="extensions_settings" class="flex1 wide50p"> |
| 4811 | <div id="assets_container" class="extension_container"></div> | 4884 | <div id="assets_container" class="extension_container"></div> |
| 4885 | <div id="typing_indicator_container" class="extension_container"></div> | ||
| 4812 | <div id="expressions_container" class="extension_container"></div> | 4886 | <div id="expressions_container" class="extension_container"></div> |
| 4813 | <div id="sd_container" class="extension_container"></div> | 4887 | <div id="sd_container" class="extension_container"></div> |
| 4814 | <div id="tts_container" class="extension_container"></div> | 4888 | <div id="tts_container" class="extension_container"></div> |
| @@ -5804,7 +5878,7 @@ | |||
| 5804 | <div class="inline-drawer-content flex-container paddingBottom5px wide100p"> | 5878 | <div class="inline-drawer-content flex-container paddingBottom5px wide100p"> |
| 5805 | <div class="flex-container wide100p alignitemscenter"> | 5879 | <div class="flex-container wide100p alignitemscenter"> |
| 5806 | <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter"> | 5880 | <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter"> |
| 5807 | <div class="world_entry_form_control flex1"> | 5881 | <div class="world_entry_form_control keyprimary flex1"> |
| 5808 | <small class="displayNone"> | 5882 | <small class="displayNone"> |
| 5809 | <span data-i18n="Comma separated (required)"> | 5883 | <span data-i18n="Comma separated (required)"> |
| 5810 | Comma separated (required) | 5884 | Comma separated (required) |
| @@ -6218,14 +6292,31 @@ | |||
| 6218 | <div class="mes_edit_buttons"> | 6292 | <div class="mes_edit_buttons"> |
| 6219 | <div class="mes_edit_done menu_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirm"></div> | 6293 | <div class="mes_edit_done menu_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirm"></div> |
| 6220 | <div class="mes_edit_copy menu_button fa-solid fa-copy" title="Copy this message" data-i18n="[title]Copy this message"></div> | 6294 | <div class="mes_edit_copy menu_button fa-solid fa-copy" title="Copy this message" data-i18n="[title]Copy this message"></div> |
| 6221 | <div class="mes_edit_delete menu_button fa-solid fa-trash-can" title="Delete this message" data-i18n="[title]Delete this message"> | 6295 | <div class="mes_edit_add_reasoning menu_button fa-solid fa-lightbulb" title="Add a reasoning block" data-i18n="[title]Add a reasoning block"></div> |
| 6222 | </div> | 6296 | <div class="mes_edit_delete menu_button fa-solid fa-trash-can" title="Delete this message" data-i18n="[title]Delete this message"></div> |
| 6223 | <div class="mes_edit_up menu_button fa-solid fa-chevron-up " title="Move message up" data-i18n="[title]Move message up"></div> | 6297 | <div class="mes_edit_up menu_button fa-solid fa-chevron-up " title="Move message up" data-i18n="[title]Move message up"></div> |
| 6224 | <div class="mes_edit_down menu_button fa-solid fa-chevron-down" title="Move message down" data-i18n="[title]Move message down"> | 6298 | <div class="mes_edit_down menu_button fa-solid fa-chevron-down" title="Move message down" data-i18n="[title]Move message down"></div> |
| 6225 | </div> | ||
| 6226 | <div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div> | 6299 | <div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div> |
| 6227 | </div> | 6300 | </div> |
| 6228 | </div> | 6301 | </div> |
| 6302 | <details class="mes_reasoning_details"> | ||
| 6303 | <summary class="mes_reasoning_summary flex-container"> | ||
| 6304 | <div class="mes_reasoning_header_block flex-container"> | ||
| 6305 | <div class="mes_reasoning_header flex-container"> | ||
| 6306 | <span class="mes_reasoning_header_title" data-i18n="Thought for some time">Thought for some time</span> | ||
| 6307 | <div class="mes_reasoning_arrow fa-solid fa-chevron-up"></div> | ||
| 6308 | </div> | ||
| 6309 | </div> | ||
| 6310 | <div class="mes_reasoning_actions flex-container"> | ||
| 6311 | <div class="mes_reasoning_edit_done menu_button edit_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div> | ||
| 6312 | <div class="mes_reasoning_delete menu_button edit_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div> | ||
| 6313 | <div class="mes_reasoning_edit_cancel menu_button edit_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div> | ||
| 6314 | <div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div> | ||
| 6315 | <div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div> | ||
| 6316 | </div> | ||
| 6317 | </summary> | ||
| 6318 | <div class="mes_reasoning"></div> | ||
| 6319 | </details> | ||
| 6229 | <div class="mes_text"></div> | 6320 | <div class="mes_text"></div> |
| 6230 | <div class="mes_img_container"> | 6321 | <div class="mes_img_container"> |
| 6231 | <div class="mes_img_controls"> | 6322 | <div class="mes_img_controls"> |
| @@ -6325,7 +6416,10 @@ | |||
| 6325 | <img alt="Avatar" src="" /> | 6416 | <img alt="Avatar" src="" /> |
| 6326 | </div> | 6417 | </div> |
| 6327 | <div class="group_member_name"> | 6418 | <div class="group_member_name"> |
| 6328 | <div class="ch_name"></div> | 6419 | <div class="character_name_block"> |
| 6420 | <span class="ch_name"></span> | ||
| 6421 | <small class="ch_additional_info character_version"></small> | ||
| 6422 | </div> | ||
| 6329 | <div class="tags tags_inline"></div> | 6423 | <div class="tags tags_inline"></div> |
| 6330 | </div> | 6424 | </div> |
| 6331 | <input class="ch_fav" value="" hidden /> | 6425 | <input class="ch_fav" value="" hidden /> |
| @@ -6437,9 +6531,6 @@ | |||
| 6437 | </div> | 6531 | </div> |
| 6438 | 6532 | ||
| 6439 | <!-- chat and input bar --> | 6533 | <!-- chat and input bar --> |
| 6440 | <div id="typing_indicator_template" class="template_element"> | ||
| 6441 | <div class="typing_indicator"><span class="typing_indicator_name">CHAR</span> is typing</div> | ||
| 6442 | </div> | ||
| 6443 | <div id="message_file_template" class="template_element"> | 6534 | <div id="message_file_template" class="template_element"> |
| 6444 | <div class="mes_file_container"> | 6535 | <div class="mes_file_container"> |
| 6445 | <div class="fa-lg fa-solid fa-file-alt mes_file_icon"></div> | 6536 | <div class="fa-lg fa-solid fa-file-alt mes_file_icon"></div> |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "فصل بفواصل دون مسافة بينها", | 482 | "separate with commas w/o space between": "فصل بفواصل دون مسافة بينها", |
| 483 | "Custom Stopping Strings": "سلاسل توقف مخصصة", | 483 | "Custom Stopping Strings": "سلاسل توقف مخصصة", |
| 484 | "JSON serialized array of strings": "مصفوفة سلسلة JSON متسلسلة", | 484 | "JSON serialized array of strings": "مصفوفة سلسلة JSON متسلسلة", |
| 485 | "Replace Macro in Custom Stopping Strings": "استبدال الماكرو في سلاسل التوقف المخصصة", | 485 | "Replace Macro in Stop Strings": "استبدال الماكرو في سلاسل التوقف المخصصة", |
| 486 | "Auto-Continue": "المتابعة التلقائية", | 486 | "Auto-Continue": "المتابعة التلقائية", |
| 487 | "Allow for Chat Completion APIs": "السماح بواجهات برمجة التطبيقات لإكمال الدردشة", | 487 | "Allow for Chat Completion APIs": "السماح بواجهات برمجة التطبيقات لإكمال الدردشة", |
| 488 | "Target length (tokens)": "الطول المستهدف (رموز)", | 488 | "Target length (tokens)": "الطول المستهدف (رموز)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (رابط مباشر أو معرف)", | 1376 | "char_import_2": "Chub Lorebook (رابط مباشر أو معرف)", |
| 1377 | "char_import_3": "حرف JanitorAI (رابط مباشر أو UUID)", | 1377 | "char_import_3": "حرف JanitorAI (رابط مباشر أو UUID)", |
| 1378 | "char_import_4": "حرف Pygmalion.chat (رابط مباشر أو UUID)", | 1378 | "char_import_4": "حرف Pygmalion.chat (رابط مباشر أو UUID)", |
| 1379 | "char_import_5": "حرف AICharacterCard.com (رابط مباشر أو معرف)", | 1379 | "char_import_5": "حرف AICharacterCards.com (رابط مباشر أو معرف)", |
| 1380 | "char_import_6": "رابط PNG المباشر (راجع", | 1380 | "char_import_6": "رابط PNG المباشر (راجع", |
| 1381 | "char_import_7": "للمضيفين المسموح بهم)", | 1381 | "char_import_7": "للمضيفين المسموح بهم)", |
| 1382 | "char_import_8": "شخصية RisuRealm (رابط مباشر)", | 1382 | "char_import_8": "شخصية RisuRealm (رابط مباشر)", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "getrennt durch Kommas ohne Leerzeichen dazwischen", | 482 | "separate with commas w/o space between": "getrennt durch Kommas ohne Leerzeichen dazwischen", |
| 483 | "Custom Stopping Strings": "Benutzerdefinierte Stoppzeichenfolgen", | 483 | "Custom Stopping Strings": "Benutzerdefinierte Stoppzeichenfolgen", |
| 484 | "JSON serialized array of strings": "JSON serialisierte Reihe von Zeichenfolgen", | 484 | "JSON serialized array of strings": "JSON serialisierte Reihe von Zeichenfolgen", |
| 485 | "Replace Macro in Custom Stopping Strings": "Makro in benutzerdefinierten Stoppzeichenfolgen ersetzen", | 485 | "Replace Macro in Stop Strings": "Makro in benutzerdefinierten Stoppzeichenfolgen ersetzen", |
| 486 | "Auto-Continue": "Automatisch fortsetzen", | 486 | "Auto-Continue": "Automatisch fortsetzen", |
| 487 | "Allow for Chat Completion APIs": "Erlaube Chat-Vervollständigungs-APIs", | 487 | "Allow for Chat Completion APIs": "Erlaube Chat-Vervollständigungs-APIs", |
| 488 | "Target length (tokens)": "Ziel-Länge (Tokens)", | 488 | "Target length (tokens)": "Ziel-Länge (Tokens)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (Direktlink oder ID)", | 1376 | "char_import_2": "Chub Lorebook (Direktlink oder ID)", |
| 1377 | "char_import_3": "JanitorAI-Charakter (Direktlink oder UUID)", | 1377 | "char_import_3": "JanitorAI-Charakter (Direktlink oder UUID)", |
| 1378 | "char_import_4": "Pygmalion.chat-Charakter (Direktlink oder UUID)", | 1378 | "char_import_4": "Pygmalion.chat-Charakter (Direktlink oder UUID)", |
| 1379 | "char_import_5": "AICharacterCard.com-Charakter (Direktlink oder ID)", | 1379 | "char_import_5": "AICharacterCards.com-Charakter (Direktlink oder ID)", |
| 1380 | "char_import_6": "Direkter PNG-Link (siehe", | 1380 | "char_import_6": "Direkter PNG-Link (siehe", |
| 1381 | "char_import_7": "für erlaubte Hosts)", | 1381 | "char_import_7": "für erlaubte Hosts)", |
| 1382 | "char_import_8": "RisuRealm-Charakter (Direktlink)", | 1382 | "char_import_8": "RisuRealm-Charakter (Direktlink)", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "separe con comas sin espacio entre ellas", | 482 | "separate with commas w/o space between": "separe con comas sin espacio entre ellas", |
| 483 | "Custom Stopping Strings": "Cadenas de Detención Personalizadas", | 483 | "Custom Stopping Strings": "Cadenas de Detención Personalizadas", |
| 484 | "JSON serialized array of strings": "Arreglo de cadenas serializado en JSON", | 484 | "JSON serialized array of strings": "Arreglo de cadenas serializado en JSON", |
| 485 | "Replace Macro in Custom Stopping Strings": "Reemplazar macro en Cadenas de Detención Personalizadas", | 485 | "Replace Macro in Stop Strings": "Reemplazar macro en Cadenas de Detención Personalizadas", |
| 486 | "Auto-Continue": "Autocontinuar", | 486 | "Auto-Continue": "Autocontinuar", |
| 487 | "Allow for Chat Completion APIs": "Permitir para APIs de Completado de Chat", | 487 | "Allow for Chat Completion APIs": "Permitir para APIs de Completado de Chat", |
| 488 | "Target length (tokens)": "Longitud objetivo (tokens)", | 488 | "Target length (tokens)": "Longitud objetivo (tokens)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (enlace directo o ID)", | 1376 | "char_import_2": "Chub Lorebook (enlace directo o ID)", |
| 1377 | "char_import_3": "Carácter de JanitorAI (enlace directo o UUID)", | 1377 | "char_import_3": "Carácter de JanitorAI (enlace directo o UUID)", |
| 1378 | "char_import_4": "Carácter Pygmalion.chat (enlace directo o UUID)", | 1378 | "char_import_4": "Carácter Pygmalion.chat (enlace directo o UUID)", |
| 1379 | "char_import_5": "Carácter AICharacterCard.com (enlace directo o ID)", | 1379 | "char_import_5": "Carácter AICharacterCards.com (enlace directo o ID)", |
| 1380 | "char_import_6": "Enlace PNG directo (consulte", | 1380 | "char_import_6": "Enlace PNG directo (consulte", |
| 1381 | "char_import_7": "para hosts permitidos)", | 1381 | "char_import_7": "para hosts permitidos)", |
| 1382 | "char_import_8": "Personaje RisuRealm (Enlace directo)", | 1382 | "char_import_8": "Personaje RisuRealm (Enlace directo)", |
| @@ -434,7 +434,7 @@ | |||
| 434 | "Non-markdown strings": "Chaînes non Markdown", | 434 | "Non-markdown strings": "Chaînes non Markdown", |
| 435 | "Custom Stopping Strings": "Chaînes d'arrêt personnalisées", | 435 | "Custom Stopping Strings": "Chaînes d'arrêt personnalisées", |
| 436 | "JSON serialized array of strings": "Tableau de chaînes sérialisé JSON", | 436 | "JSON serialized array of strings": "Tableau de chaînes sérialisé JSON", |
| 437 | "Replace Macro in Custom Stopping Strings": "Remplacer les macro dans les chaînes d'arrêt personnalisées", | 437 | "Replace Macro in Stop Strings": "Remplacer les macro dans les chaînes d'arrêt personnalisées", |
| 438 | "Auto-Continue": "Auto-Continue", | 438 | "Auto-Continue": "Auto-Continue", |
| 439 | "Allow for Chat Completion APIs": "Autoriser les APIs de complétion de chat", | 439 | "Allow for Chat Completion APIs": "Autoriser les APIs de complétion de chat", |
| 440 | "Target length (tokens)": "Longueur cible (tokens)", | 440 | "Target length (tokens)": "Longueur cible (tokens)", |
| @@ -1297,7 +1297,7 @@ | |||
| 1297 | "char_import_2": "Lorebook de Chub (lien direct ou ID)", | 1297 | "char_import_2": "Lorebook de Chub (lien direct ou ID)", |
| 1298 | "char_import_3": "Personnage de JanitorAI (lien direct ou UUID)", | 1298 | "char_import_3": "Personnage de JanitorAI (lien direct ou UUID)", |
| 1299 | "char_import_4": "Personnage de Pygmalion.chat (lien direct ou UUID)", | 1299 | "char_import_4": "Personnage de Pygmalion.chat (lien direct ou UUID)", |
| 1300 | "char_import_5": "Personnage de AICharacterCard.com (lien direct ou identifiant)", | 1300 | "char_import_5": "Personnage de AICharacterCards.com (lien direct ou identifiant)", |
| 1301 | "char_import_6": "Lien PNG direct (voir", | 1301 | "char_import_6": "Lien PNG direct (voir", |
| 1302 | "char_import_7": "pour les hôtes autorisés)", | 1302 | "char_import_7": "pour les hôtes autorisés)", |
| 1303 | "char_import_8": "Personnage de RisuRealm (lien direct)", | 1303 | "char_import_8": "Personnage de RisuRealm (lien direct)", |
| @@ -1385,8 +1385,8 @@ | |||
| 1385 | "enable_functions_desc_1": "Autorise l'utilisation", | 1385 | "enable_functions_desc_1": "Autorise l'utilisation", |
| 1386 | "enable_functions_desc_2": "outils de fonction", | 1386 | "enable_functions_desc_2": "outils de fonction", |
| 1387 | "enable_functions_desc_3": "Peut être utilisé par diverses extensions pour fournir des fonctionnalités supplémentaires.", | 1387 | "enable_functions_desc_3": "Peut être utilisé par diverses extensions pour fournir des fonctionnalités supplémentaires.", |
| 1388 | "Show model thoughts": "Afficher les pensées du modèle", | 1388 | "Request model reasoning": "Demander les pensées du modèle", |
| 1389 | "Display the model's internal thoughts in the response.": "Afficher les pensées internes du modèle dans la réponse.", | 1389 | "Allows the model to return its thinking process.": "Permet au modèle de retourner son processus de réflexion.", |
| 1390 | "Confirm token parsing with": "Confirmer l'analyse des tokens avec", | 1390 | "Confirm token parsing with": "Confirmer l'analyse des tokens avec", |
| 1391 | "openai_logit_bias_no_items": "Aucun élément", | 1391 | "openai_logit_bias_no_items": "Aucun élément", |
| 1392 | "api_no_connection": "Pas de connection...", | 1392 | "api_no_connection": "Pas de connection...", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "aðskilið með kommum án bila milli", | 482 | "separate with commas w/o space between": "aðskilið með kommum án bila milli", |
| 483 | "Custom Stopping Strings": "Eigin stopp-strengir", | 483 | "Custom Stopping Strings": "Eigin stopp-strengir", |
| 484 | "JSON serialized array of strings": "JSON raðað fylki af strengjum", | 484 | "JSON serialized array of strings": "JSON raðað fylki af strengjum", |
| 485 | "Replace Macro in Custom Stopping Strings": "Skiptu út í macro í sérsniðnum stoppa strengjum", | 485 | "Replace Macro in Stop Strings": "Skiptu út í macro í sérsniðnum stoppa strengjum", |
| 486 | "Auto-Continue": "Sjálfvirk Forná", | 486 | "Auto-Continue": "Sjálfvirk Forná", |
| 487 | "Allow for Chat Completion APIs": "Leyfa fyrir spjall Loka APIs", | 487 | "Allow for Chat Completion APIs": "Leyfa fyrir spjall Loka APIs", |
| 488 | "Target length (tokens)": "Markaðarlengd (texti)", | 488 | "Target length (tokens)": "Markaðarlengd (texti)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (beinn hlekkur eða auðkenni)", | 1376 | "char_import_2": "Chub Lorebook (beinn hlekkur eða auðkenni)", |
| 1377 | "char_import_3": "JanitorAI karakter (beinn hlekkur eða UUID)", | 1377 | "char_import_3": "JanitorAI karakter (beinn hlekkur eða UUID)", |
| 1378 | "char_import_4": "Pygmalion.chat karakter (beinn hlekkur eða UUID)", | 1378 | "char_import_4": "Pygmalion.chat karakter (beinn hlekkur eða UUID)", |
| 1379 | "char_import_5": "AICharacterCard.com Karakter (beinn hlekkur eða auðkenni)", | 1379 | "char_import_5": "AICharacterCards.com Karakter (beinn hlekkur eða auðkenni)", |
| 1380 | "char_import_6": "Beinn PNG hlekkur (sjá", | 1380 | "char_import_6": "Beinn PNG hlekkur (sjá", |
| 1381 | "char_import_7": "fyrir leyfilega gestgjafa)", | 1381 | "char_import_7": "fyrir leyfilega gestgjafa)", |
| 1382 | "char_import_8": "RisuRealm karakter (beinn hlekkur)", | 1382 | "char_import_8": "RisuRealm karakter (beinn hlekkur)", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "separati con virgole senza spazio tra loro", | 482 | "separate with commas w/o space between": "separati con virgole senza spazio tra loro", |
| 483 | "Custom Stopping Strings": "Stringhe di Stop Personalizzate", | 483 | "Custom Stopping Strings": "Stringhe di Stop Personalizzate", |
| 484 | "JSON serialized array of strings": "Matrice serializzata JSON di stringhe", | 484 | "JSON serialized array of strings": "Matrice serializzata JSON di stringhe", |
| 485 | "Replace Macro in Custom Stopping Strings": "Sostituisci Macro in Stringhe di Arresto Personalizzate", | 485 | "Replace Macro in Stop Strings": "Sostituisci Macro in Stringhe di Arresto Personalizzate", |
| 486 | "Auto-Continue": "Auto-continua", | 486 | "Auto-Continue": "Auto-continua", |
| 487 | "Allow for Chat Completion APIs": "Consenti per API di completamento chat", | 487 | "Allow for Chat Completion APIs": "Consenti per API di completamento chat", |
| 488 | "Target length (tokens)": "Lunghezza obiettivo (token)", | 488 | "Target length (tokens)": "Lunghezza obiettivo (token)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Lorebook di Chub (collegamento diretto o ID)", | 1376 | "char_import_2": "Lorebook di Chub (collegamento diretto o ID)", |
| 1377 | "char_import_3": "Carattere JanitorAI (collegamento diretto o UUID)", | 1377 | "char_import_3": "Carattere JanitorAI (collegamento diretto o UUID)", |
| 1378 | "char_import_4": "Carattere Pygmalion.chat (collegamento diretto o UUID)", | 1378 | "char_import_4": "Carattere Pygmalion.chat (collegamento diretto o UUID)", |
| 1379 | "char_import_5": "Carattere AICharacterCard.com (Link diretto o ID)", | 1379 | "char_import_5": "Carattere AICharacterCards.com (Link diretto o ID)", |
| 1380 | "char_import_6": "Collegamento PNG diretto (fare riferimento a", | 1380 | "char_import_6": "Collegamento PNG diretto (fare riferimento a", |
| 1381 | "char_import_7": "per gli host consentiti)", | 1381 | "char_import_7": "per gli host consentiti)", |
| 1382 | "char_import_8": "Personaggio RisuRealm (collegamento diretto)", | 1382 | "char_import_8": "Personaggio RisuRealm (collegamento diretto)", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "間にスペースのないカンマで区切ります", | 482 | "separate with commas w/o space between": "間にスペースのないカンマで区切ります", |
| 483 | "Custom Stopping Strings": "カスタム停止文字列", | 483 | "Custom Stopping Strings": "カスタム停止文字列", |
| 484 | "JSON serialized array of strings": "文字列のJSONシリアル化配列", | 484 | "JSON serialized array of strings": "文字列のJSONシリアル化配列", |
| 485 | "Replace Macro in Custom Stopping Strings": "カスタム停止文字列内のマクロを置換する", | 485 | "Replace Macro in Stop Strings": "カスタム停止文字列内のマクロを置換する", |
| 486 | "Auto-Continue": "自動継続", | 486 | "Auto-Continue": "自動継続", |
| 487 | "Allow for Chat Completion APIs": "チャット補完APIを許可", | 487 | "Allow for Chat Completion APIs": "チャット補完APIを許可", |
| 488 | "Target length (tokens)": "ターゲット長さ(トークン)", | 488 | "Target length (tokens)": "ターゲット長さ(トークン)", |
| @@ -1378,7 +1378,7 @@ | |||
| 1378 | "char_import_2": "Chub ロアブック (直接リンクまたは ID)", | 1378 | "char_import_2": "Chub ロアブック (直接リンクまたは ID)", |
| 1379 | "char_import_3": "JanitorAI キャラクター (直接リンクまたは UUID)", | 1379 | "char_import_3": "JanitorAI キャラクター (直接リンクまたは UUID)", |
| 1380 | "char_import_4": "Pygmalion.chat キャラクター (直接リンクまたは UUID)", | 1380 | "char_import_4": "Pygmalion.chat キャラクター (直接リンクまたは UUID)", |
| 1381 | "char_import_5": "AICharacterCard.com キャラクター (直接リンクまたは ID)", | 1381 | "char_import_5": "AICharacterCards.com キャラクター (直接リンクまたは ID)", |
| 1382 | "char_import_6": "直接PNGリンク(参照", | 1382 | "char_import_6": "直接PNGリンク(参照", |
| 1383 | "char_import_7": "許可されたホストの場合)", | 1383 | "char_import_7": "許可されたホストの場合)", |
| 1384 | "char_import_8": "RisuRealm キャラクター (直接リンク)", | 1384 | "char_import_8": "RisuRealm キャラクター (直接リンク)", |
| @@ -211,7 +211,7 @@ | |||
| 211 | "Sampler Priority": "샘플러 우선 순위", | 211 | "Sampler Priority": "샘플러 우선 순위", |
| 212 | "Ooba only. Determines the order of samplers.": "Ooba 전용. 샘플러의 순서를 결정합니다.", | 212 | "Ooba only. Determines the order of samplers.": "Ooba 전용. 샘플러의 순서를 결정합니다.", |
| 213 | "Character Names Behavior": "캐릭터 이름 동작", | 213 | "Character Names Behavior": "캐릭터 이름 동작", |
| 214 | "[title]character_names_none": "캐릭터 이름 접두사를 추가하지 않습니다. 그룹 채팅에서는 좋지 않을 수 있으므로, 이 설정을 선택할 때는 주의해야 합니다.", | 214 | "character_names_none": "캐릭터 이름 접두사를 추가하지 않습니다. 그룹 채팅에서는 좋지 않을 수 있으므로, 이 설정을 선택할 때는 주의해야 합니다.", |
| 215 | "Helps the model to associate messages with characters.": "모델이 메시지를 캐릭터와 연관시키는 데 도움이 됩니다.", | 215 | "Helps the model to associate messages with characters.": "모델이 메시지를 캐릭터와 연관시키는 데 도움이 됩니다.", |
| 216 | "None": "없음", | 216 | "None": "없음", |
| 217 | "None (not injected)": "없음 (삽입되지 않음)", | 217 | "None (not injected)": "없음 (삽입되지 않음)", |
| @@ -404,7 +404,7 @@ | |||
| 404 | "Custom API Key": "커스텀 API 키", | 404 | "Custom API Key": "커스텀 API 키", |
| 405 | "Available Models": "사용 가능한 모델", | 405 | "Available Models": "사용 가능한 모델", |
| 406 | "Prompt Post-Processing": "신속한 후처리", | 406 | "Prompt Post-Processing": "신속한 후처리", |
| 407 | "[title]API Connections;[no_connection_text]api_no_connection": "연결이 되지 않았습니다...", | 407 | "api_no_connection": "연결이 되지 않았습니다...", |
| 408 | "Applies additional processing to the prompt before sending it to the API.": "API로 보내기 전에 프롬프트에 추가 처리를 적용합니다.", | 408 | "Applies additional processing to the prompt before sending it to the API.": "API로 보내기 전에 프롬프트에 추가 처리를 적용합니다.", |
| 409 | "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "짧은 테스트 메시지를 보내어 API 연결을 확인합니다. 이에 대해 유료 크레딧이 지불될 수 있음을 인식하세요!", | 409 | "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "짧은 테스트 메시지를 보내어 API 연결을 확인합니다. 이에 대해 유료 크레딧이 지불될 수 있음을 인식하세요!", |
| 410 | "Test Message": "테스트 메시지", | 410 | "Test Message": "테스트 메시지", |
| @@ -492,7 +492,7 @@ | |||
| 492 | "separate with commas w/o space between": "쉼표로 구분 (공백 없이)", | 492 | "separate with commas w/o space between": "쉼표로 구분 (공백 없이)", |
| 493 | "Custom Stopping Strings": "사용자 정의 중지 문자열", | 493 | "Custom Stopping Strings": "사용자 정의 중지 문자열", |
| 494 | "JSON serialized array of strings": "문자열의 JSON 직렬화된 배열", | 494 | "JSON serialized array of strings": "문자열의 JSON 직렬화된 배열", |
| 495 | "Replace Macro in Custom Stopping Strings": "사용자 정의 중단 문자열에서 매크로 교체", | 495 | "Replace Macro in Stop Strings": "사용자 정의 중단 문자열에서 매크로 교체", |
| 496 | "Auto-Continue": "자동 계속하기", | 496 | "Auto-Continue": "자동 계속하기", |
| 497 | "Allow for Chat Completion APIs": "채팅 완성 API 허용", | 497 | "Allow for Chat Completion APIs": "채팅 완성 API 허용", |
| 498 | "Target length (tokens)": "대상 길이 (토큰)", | 498 | "Target length (tokens)": "대상 길이 (토큰)", |
| @@ -625,7 +625,7 @@ | |||
| 625 | "Single-row message input area. Mobile only, no effect on PC": "한 줄짜리 메시지 입력 영역. 모바일 전용, PC에는 영향 없음", | 625 | "Single-row message input area. Mobile only, no effect on PC": "한 줄짜리 메시지 입력 영역. 모바일 전용, PC에는 영향 없음", |
| 626 | "Compact Input Area (Mobile)": "조그마한 입력 영역 (모바일)", | 626 | "Compact Input Area (Mobile)": "조그마한 입력 영역 (모바일)", |
| 627 | "Swipe # for All Messages": "모든 스와이프 메시지에 대해 번호 매기기", | 627 | "Swipe # for All Messages": "모든 스와이프 메시지에 대해 번호 매기기", |
| 628 | "[title]Display swipe numbers for all messages, not just the last.": "마지막 메시지만이 아니라 모든 메시지에 대한 스와이프 번호를 표시합니다.", | 628 | "Display swipe numbers for all messages, not just the last.": "마지막 메시지만이 아니라 모든 메시지에 대한 스와이프 번호를 표시합니다.", |
| 629 | "In the Character Management panel, show quick selection buttons for favorited characters": "캐릭터 관리 패널에서 즐겨찾는 캐릭터에 대한 빠른 선택 버튼을 표시합니다", | 629 | "In the Character Management panel, show quick selection buttons for favorited characters": "캐릭터 관리 패널에서 즐겨찾는 캐릭터에 대한 빠른 선택 버튼을 표시합니다", |
| 630 | "Characters Hotswap": "캐릭터 핫스왑", | 630 | "Characters Hotswap": "캐릭터 핫스왑", |
| 631 | "Enable magnification for zoomed avatar display.": "마우스 포인터를 아바타 위에 올려두면 아바타가 확대 됩니다.", | 631 | "Enable magnification for zoomed avatar display.": "마우스 포인터를 아바타 위에 올려두면 아바타가 확대 됩니다.", |
| @@ -1395,7 +1395,7 @@ | |||
| 1395 | "char_import_2": "Chub Lorebook(직접 링크 또는 ID)", | 1395 | "char_import_2": "Chub Lorebook(직접 링크 또는 ID)", |
| 1396 | "char_import_3": "JanitorAI 캐릭터(직접 링크 또는 UUID)", | 1396 | "char_import_3": "JanitorAI 캐릭터(직접 링크 또는 UUID)", |
| 1397 | "char_import_4": "Pygmalion.chat 문자(직접 링크 또는 UUID)", | 1397 | "char_import_4": "Pygmalion.chat 문자(직접 링크 또는 UUID)", |
| 1398 | "char_import_5": "AICharacterCard.com 캐릭터(직접 링크 또는 ID)", | 1398 | "char_import_5": "AICharacterCards.com 캐릭터(직접 링크 또는 ID)", |
| 1399 | "char_import_6": "직접 PNG 링크(참조", | 1399 | "char_import_6": "직접 PNG 링크(참조", |
| 1400 | "char_import_7": "허용된 호스트의 경우)", | 1400 | "char_import_7": "허용된 호스트의 경우)", |
| 1401 | "char_import_8": "RisuRealm 캐릭터 (직접링크)", | 1401 | "char_import_8": "RisuRealm 캐릭터 (직접링크)", |
| @@ -1538,7 +1538,7 @@ | |||
| 1538 | "Only apply color as accent": "색상은 오직 강조로써만 적용됩니다", | 1538 | "Only apply color as accent": "색상은 오직 강조로써만 적용됩니다", |
| 1539 | "qr--colorClear": "색상 지우기", | 1539 | "qr--colorClear": "색상 지우기", |
| 1540 | "Color": "색상", | 1540 | "Color": "색상", |
| 1541 | "[title]world_button_title": "캐릭터 로어. 클릭하여 로드하세요. Shift를 클릭하면 '월드 인포 링크' 팝업이 열립니다.", | 1541 | "world_button_title": "캐릭터 로어. 클릭하여 로드하세요. Shift를 클릭하면 '월드 인포 링크' 팝업이 열립니다.", |
| 1542 | "Select TTS Provider": "TTS 공급자 선택", | 1542 | "Select TTS Provider": "TTS 공급자 선택", |
| 1543 | "tts_enabled": "활성화", | 1543 | "tts_enabled": "활성화", |
| 1544 | "Narrate user messages": "사용자 메시지 나레이션", | 1544 | "Narrate user messages": "사용자 메시지 나레이션", |
| @@ -1583,15 +1583,15 @@ | |||
| 1583 | "Prompt Content": "프롬프트 내용", | 1583 | "Prompt Content": "프롬프트 내용", |
| 1584 | "Instruct Sequences": "지시 시퀀스", | 1584 | "Instruct Sequences": "지시 시퀀스", |
| 1585 | "Prefer Character Card Instructions": "캐릭터 카드의 지시사항을 선호", | 1585 | "Prefer Character Card Instructions": "캐릭터 카드의 지시사항을 선호", |
| 1586 | "[title]If checked and the character card contains a Post-History Instructions override, use that instead": "활성화 된 경우, 캐릭터 카드에 Post-History 지시 무시 항목이 포함되어 있으면, 카드 지시사항의 내용으로 대신 사용합니다.", | 1586 | "If checked and the character card contains a Post-History Instructions override, use that instead": "활성화 된 경우, 캐릭터 카드에 Post-History 지시 무시 항목이 포함되어 있으면, 카드 지시사항의 내용으로 대신 사용합니다.", |
| 1587 | "Auto-select Input Text": "입력 텍스트 자동 선택", | 1587 | "Auto-select Input Text": "입력 텍스트 자동 선택", |
| 1588 | "[title]Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields.": "일부 텍스트 필드를 클릭하거나 선택할 때 자동으로 입력된 텍스트가 선택되도록 설정합니다. 팝업 입력창과 기타 커스텀 입력 필드에 적용됩니다.", | 1588 | "Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields.": "일부 텍스트 필드를 클릭하거나 선택할 때 자동으로 입력된 텍스트가 선택되도록 설정합니다. 팝업 입력창과 기타 커스텀 입력 필드에 적용됩니다.", |
| 1589 | "Markdown Hotkeys": "마크다운 입력 단축키", | 1589 | "Markdown Hotkeys": "마크다운 입력 단축키", |
| 1590 | "[title]markdown_hotkeys_desc": "특정 텍스트 입력창에서 마크다운 형식 문자를 입력하기 위한 단축키를 활성화합니다. '/help hotkeys'를 참고하세요.", | 1590 | "markdown_hotkeys_desc": "특정 텍스트 입력창에서 마크다운 형식 문자를 입력하기 위한 단축키를 활성화합니다. '/help hotkeys'를 참고하세요.", |
| 1591 | "Show group chat queue": "그룹 채팅 대기열 표시", | 1591 | "Show group chat queue": "그룹 채팅 대기열 표시", |
| 1592 | "[title]In group chat, highlight the character(s) that are currently queued to generate responses and the order in which they will respond.": "그룹 채팅에서 응답을 생성하기 위해 현재 대기 중인 캐릭터와 응답할 순서를 강조 표시합니다.", | 1592 | "In group chat, highlight the character(s) that are currently queued to generate responses and the order in which they will respond.": "그룹 채팅에서 응답을 생성하기 위해 현재 대기 중인 캐릭터와 응답할 순서를 강조 표시합니다.", |
| 1593 | "Quick 'Impersonate' button": "빠른 '사칭' 버튼", | 1593 | "Quick 'Impersonate' button": "빠른 '사칭' 버튼", |
| 1594 | "[title]Show a button in the input area to ask the AI to impersonate your character for a single message": "입력 영역에 AI에게 한 메시지 동안 당신의 캐릭터 연기를 사칭하도록 요청하는 버튼을 표시합니다.", | 1594 | "Show a button in the input area to ask the AI to impersonate your character for a single message": "입력 영역에 AI에게 한 메시지 동안 당신의 캐릭터 연기를 사칭하도록 요청하는 버튼을 표시합니다.", |
| 1595 | "Injection Template": "삽입 템플릿", | 1595 | "Injection Template": "삽입 템플릿", |
| 1596 | "Query messages": "쿼리 메시지 수", | 1596 | "Query messages": "쿼리 메시지 수", |
| 1597 | "Score threshold": "점수 임계값", | 1597 | "Score threshold": "점수 임계값", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "gescheiden met komma's zonder spatie ertussen", | 482 | "separate with commas w/o space between": "gescheiden met komma's zonder spatie ertussen", |
| 483 | "Custom Stopping Strings": "Aangepaste Stopreeksen", | 483 | "Custom Stopping Strings": "Aangepaste Stopreeksen", |
| 484 | "JSON serialized array of strings": "JSON geserialiseerde reeks van strings", | 484 | "JSON serialized array of strings": "JSON geserialiseerde reeks van strings", |
| 485 | "Replace Macro in Custom Stopping Strings": "Macro vervangen in aangepaste stopreeksen", | 485 | "Replace Macro in Stop Strings": "Macro vervangen in aangepaste stopreeksen", |
| 486 | "Auto-Continue": "Automatisch doorgaan", | 486 | "Auto-Continue": "Automatisch doorgaan", |
| 487 | "Allow for Chat Completion APIs": "Chatvervolledigings-API's toestaan", | 487 | "Allow for Chat Completion APIs": "Chatvervolledigings-API's toestaan", |
| 488 | "Target length (tokens)": "Doellengte (tokens)", | 488 | "Target length (tokens)": "Doellengte (tokens)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (directe link of ID)", | 1376 | "char_import_2": "Chub Lorebook (directe link of ID)", |
| 1377 | "char_import_3": "JanitorAI-personage (directe link of UUID)", | 1377 | "char_import_3": "JanitorAI-personage (directe link of UUID)", |
| 1378 | "char_import_4": "Pygmalion.chat-teken (directe link of UUID)", | 1378 | "char_import_4": "Pygmalion.chat-teken (directe link of UUID)", |
| 1379 | "char_import_5": "AICharacterCard.com-teken (directe link of ID)", | 1379 | "char_import_5": "AICharacterCards.com-teken (directe link of ID)", |
| 1380 | "char_import_6": "Directe PNG-link (zie", | 1380 | "char_import_6": "Directe PNG-link (zie", |
| 1381 | "char_import_7": "voor toegestane hosts)", | 1381 | "char_import_7": "voor toegestane hosts)", |
| 1382 | "char_import_8": "RisuRealm-personage (directe link)", | 1382 | "char_import_8": "RisuRealm-personage (directe link)", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "separe com vírgulas sem espaço entre", | 482 | "separate with commas w/o space between": "separe com vírgulas sem espaço entre", |
| 483 | "Custom Stopping Strings": "Cadeias de parada personalizadas", | 483 | "Custom Stopping Strings": "Cadeias de parada personalizadas", |
| 484 | "JSON serialized array of strings": "Matriz de strings serializada em JSON", | 484 | "JSON serialized array of strings": "Matriz de strings serializada em JSON", |
| 485 | "Replace Macro in Custom Stopping Strings": "Substituir Macro em Strings de Parada Personalizadas", | 485 | "Replace Macro in Stop Strings": "Substituir Macro em Strings de Parada Personalizadas", |
| 486 | "Auto-Continue": "Auto-Continuar", | 486 | "Auto-Continue": "Auto-Continuar", |
| 487 | "Allow for Chat Completion APIs": "Permitir APIs de Completar Chat", | 487 | "Allow for Chat Completion APIs": "Permitir APIs de Completar Chat", |
| 488 | "Target length (tokens)": "Comprimento alvo (tokens)", | 488 | "Target length (tokens)": "Comprimento alvo (tokens)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (link direto ou ID)", | 1376 | "char_import_2": "Chub Lorebook (link direto ou ID)", |
| 1377 | "char_import_3": "Personagem JanitorAI (Link Direto ou UUID)", | 1377 | "char_import_3": "Personagem JanitorAI (Link Direto ou UUID)", |
| 1378 | "char_import_4": "Caractere Pygmalion.chat (Link Direto ou UUID)", | 1378 | "char_import_4": "Caractere Pygmalion.chat (Link Direto ou UUID)", |
| 1379 | "char_import_5": "Personagem AICharacterCard.com (link direto ou ID)", | 1379 | "char_import_5": "Personagem AICharacterCards.com (link direto ou ID)", |
| 1380 | "char_import_6": "Link PNG direto (consulte", | 1380 | "char_import_6": "Link PNG direto (consulte", |
| 1381 | "char_import_7": "para hosts permitidos)", | 1381 | "char_import_7": "para hosts permitidos)", |
| 1382 | "char_import_8": "Personagem RisuRealm (link direto)", | 1382 | "char_import_8": "Personagem RisuRealm (link direto)", |
| @@ -161,7 +161,7 @@ | |||
| 161 | "View hidden API keys": "Посмотреть скрытые API-ключи", | 161 | "View hidden API keys": "Посмотреть скрытые API-ключи", |
| 162 | "Advanced Formatting": "Расширенное форматирование", | 162 | "Advanced Formatting": "Расширенное форматирование", |
| 163 | "Context Template": "Шаблон контекста", | 163 | "Context Template": "Шаблон контекста", |
| 164 | "Replace Macro in Custom Stopping Strings": "Заменять макросы в пользовательских стоп-строках", | 164 | "Replace Macro in Stop Strings": "Заменять макросы в пользовательских стоп-строках", |
| 165 | "Story String": "Строка истории", | 165 | "Story String": "Строка истории", |
| 166 | "Example Separator": "Разделитель примеров сообщений", | 166 | "Example Separator": "Разделитель примеров сообщений", |
| 167 | "Chat Start": "Начало чата", | 167 | "Chat Start": "Начало чата", |
| @@ -966,7 +966,7 @@ | |||
| 966 | "char_import_2": "Лорбук с Chub (прямая ссылка или ID)", | 966 | "char_import_2": "Лорбук с Chub (прямая ссылка или ID)", |
| 967 | "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)", | 967 | "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)", |
| 968 | "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)", | 968 | "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)", |
| 969 | "char_import_5": "Персонаж с AICharacterCard.com (прямая ссылка или ID)", | 969 | "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)", |
| 970 | "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", | 970 | "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", |
| 971 | "char_import_7": ")", | 971 | "char_import_7": ")", |
| 972 | "Grammar String": "Грамматика", | 972 | "Grammar String": "Грамматика", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "розділяйте комами без пропусків між ними", | 482 | "separate with commas w/o space between": "розділяйте комами без пропусків між ними", |
| 483 | "Custom Stopping Strings": "Власні рядки зупинки", | 483 | "Custom Stopping Strings": "Власні рядки зупинки", |
| 484 | "JSON serialized array of strings": "JSON-серіалізований масив рядків", | 484 | "JSON serialized array of strings": "JSON-серіалізований масив рядків", |
| 485 | "Replace Macro in Custom Stopping Strings": "Замінювати макроси у власних рядках зупинки", | 485 | "Replace Macro in Stop Strings": "Замінювати макроси у власних рядках зупинки", |
| 486 | "Auto-Continue": "Автоматичне продовження", | 486 | "Auto-Continue": "Автоматичне продовження", |
| 487 | "Allow for Chat Completion APIs": "Дозволити для Chat Completion API", | 487 | "Allow for Chat Completion APIs": "Дозволити для Chat Completion API", |
| 488 | "Target length (tokens)": "Цільова довжина (токени)", | 488 | "Target length (tokens)": "Цільова довжина (токени)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub Lorebook (пряме посилання або ID)", | 1376 | "char_import_2": "Chub Lorebook (пряме посилання або ID)", |
| 1377 | "char_import_3": "Символ JanitorAI (пряме посилання або UUID)", | 1377 | "char_import_3": "Символ JanitorAI (пряме посилання або UUID)", |
| 1378 | "char_import_4": "Символ Pygmalion.chat (пряме посилання або UUID)", | 1378 | "char_import_4": "Символ Pygmalion.chat (пряме посилання або UUID)", |
| 1379 | "char_import_5": "Символ AICharacterCard.com (пряме посилання або ідентифікатор)", | 1379 | "char_import_5": "Символ AICharacterCards.com (пряме посилання або ідентифікатор)", |
| 1380 | "char_import_6": "Пряме посилання на PNG (див", | 1380 | "char_import_6": "Пряме посилання на PNG (див", |
| 1381 | "char_import_7": "для дозволених хостів)", | 1381 | "char_import_7": "для дозволених хостів)", |
| 1382 | "char_import_8": "Персонаж RisuRealm (пряме посилання)", | 1382 | "char_import_8": "Персонаж RisuRealm (пряме посилання)", |
| @@ -482,7 +482,7 @@ | |||
| 482 | "separate with commas w/o space between": "phân tách bằng dấu phẩy không có khoảng trắng giữa", | 482 | "separate with commas w/o space between": "phân tách bằng dấu phẩy không có khoảng trắng giữa", |
| 483 | "Custom Stopping Strings": "Chuỗi dừng tùy chỉnh", | 483 | "Custom Stopping Strings": "Chuỗi dừng tùy chỉnh", |
| 484 | "JSON serialized array of strings": "Mảng chuỗi được tuần tự hóa JSON", | 484 | "JSON serialized array of strings": "Mảng chuỗi được tuần tự hóa JSON", |
| 485 | "Replace Macro in Custom Stopping Strings": "Thay thế Macro trong Chuỗi Dừng Tùy chỉnh", | 485 | "Replace Macro in Stop Strings": "Thay thế Macro trong Chuỗi Dừng Tùy chỉnh", |
| 486 | "Auto-Continue": "Tự động Tiếp tục", | 486 | "Auto-Continue": "Tự động Tiếp tục", |
| 487 | "Allow for Chat Completion APIs": "Cho phép các API hoàn thành Trò chuyện", | 487 | "Allow for Chat Completion APIs": "Cho phép các API hoàn thành Trò chuyện", |
| 488 | "Target length (tokens)": "Độ dài mục tiêu (token)", | 488 | "Target length (tokens)": "Độ dài mục tiêu (token)", |
| @@ -1376,7 +1376,7 @@ | |||
| 1376 | "char_import_2": "Chub (Nhập URL trực tiếp hoặc ID)", | 1376 | "char_import_2": "Chub (Nhập URL trực tiếp hoặc ID)", |
| 1377 | "char_import_3": "JanitorAI (Nhập URL trực tiếp hoặc UUID)", | 1377 | "char_import_3": "JanitorAI (Nhập URL trực tiếp hoặc UUID)", |
| 1378 | "char_import_4": "Pygmalion.chat (Nhập URL trực tiếp hoặc UUID)", | 1378 | "char_import_4": "Pygmalion.chat (Nhập URL trực tiếp hoặc UUID)", |
| 1379 | "char_import_5": "AICharacterCard.com (Nhập URL trực tiếp hoặc ID)", | 1379 | "char_import_5": "AICharacterCards.com (Nhập URL trực tiếp hoặc ID)", |
| 1380 | "char_import_6": "Nhập PNG trực tiếp (tham khảo", | 1380 | "char_import_6": "Nhập PNG trực tiếp (tham khảo", |
| 1381 | "char_import_7": "đối với các máy chủ được phép)", | 1381 | "char_import_7": "đối với các máy chủ được phép)", |
| 1382 | "char_import_8": "RisuRealm (URL trực tiếp)", | 1382 | "char_import_8": "RisuRealm (URL trực tiếp)", |
| @@ -215,7 +215,7 @@ | |||
| 215 | "Classifier Free Guidance. More helpful tip coming soon": "无分类器指导(CFG)。更多有用的提示敬请期待。", | 215 | "Classifier Free Guidance. More helpful tip coming soon": "无分类器指导(CFG)。更多有用的提示敬请期待。", |
| 216 | "Scale": "缩放比例", | 216 | "Scale": "缩放比例", |
| 217 | "Negative Prompt": "负面提示词", | 217 | "Negative Prompt": "负面提示词", |
| 218 | "Used if CFG Scale is unset globally, per chat or character": "如果无分类器指导(CFG)缩放比例未在全局设置,它将作用于每个聊天或每个角色", | 218 | "Used if CFG Scale is unset globally, per chat or character": "如果CFG缩放比例未被全局设置,它将作用于所有聊天或角色", |
| 219 | "Add text here that would make the AI generate things you don't want in your outputs.": "请在此处添加文本,以避免生成您不希望出现在输出中的内容。", | 219 | "Add text here that would make the AI generate things you don't want in your outputs.": "请在此处添加文本,以避免生成您不希望出现在输出中的内容。", |
| 220 | "Grammar String": "语法字符串", | 220 | "Grammar String": "语法字符串", |
| 221 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 或 EBNF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。", | 221 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF 或 EBNF,取决于使用的后端。如果您使用这个,您应该知道该用哪一个。", |
| @@ -266,8 +266,8 @@ | |||
| 266 | "Use system prompt": "使用系统提示词", | 266 | "Use system prompt": "使用系统提示词", |
| 267 | "Merges_all_system_messages_desc_1": "合并所有系统消息,直到第一条具有非系统角色的消息,然后通过", | 267 | "Merges_all_system_messages_desc_1": "合并所有系统消息,直到第一条具有非系统角色的消息,然后通过", |
| 268 | "Merges_all_system_messages_desc_2": "字段发送。", | 268 | "Merges_all_system_messages_desc_2": "字段发送。", |
| 269 | "Show model thoughts": "展示思维链", | 269 | "Request model reasoning": "请求思维链", |
| 270 | "Display the model's internal thoughts in the response.": "展示模型在回复时的内部思维链。", | 270 | "Allows the model to return its thinking process.": "允许模型返回其思维过程。", |
| 271 | "Assistant Prefill": "AI预填", | 271 | "Assistant Prefill": "AI预填", |
| 272 | "Expand the editor": "展开编辑器", | 272 | "Expand the editor": "展开编辑器", |
| 273 | "Start Claude's answer with...": "以如下内容开始Claude的回答...", | 273 | "Start Claude's answer with...": "以如下内容开始Claude的回答...", |
| @@ -559,7 +559,7 @@ | |||
| 559 | "Prompt Content": "提示词内容", | 559 | "Prompt Content": "提示词内容", |
| 560 | "Custom Stopping Strings": "自定义停止字符串", | 560 | "Custom Stopping Strings": "自定义停止字符串", |
| 561 | "JSON serialized array of strings": "JSON序列化的字符串数组", | 561 | "JSON serialized array of strings": "JSON序列化的字符串数组", |
| 562 | "Replace Macro in Custom Stopping Strings": "替换自定义停止字符串中的宏", | 562 | "Replace Macro in Stop Strings": "替换自定义停止字符串中的宏", |
| 563 | "Token Padding": "词符填充", | 563 | "Token Padding": "词符填充", |
| 564 | "Miscellaneous": "杂项", | 564 | "Miscellaneous": "杂项", |
| 565 | "Non-markdown strings": "非 Markdown 字符串", | 565 | "Non-markdown strings": "非 Markdown 字符串", |
| @@ -1191,9 +1191,9 @@ | |||
| 1191 | "welcome_message_part_8": "您可随时通过", | 1191 | "welcome_message_part_8": "您可随时通过", |
| 1192 | "welcome_message_part_9": "图标来更改此设置。", | 1192 | "welcome_message_part_9": "图标来更改此设置。", |
| 1193 | "Persona Name:": "用户角色名称:", | 1193 | "Persona Name:": "用户角色名称:", |
| 1194 | "Temporarily disable automatic replies from this character": "暂时禁用此角色的自动回复", | 1194 | "Temporarily disable automatic replies from this character": "临时禁言此角色", |
| 1195 | "Enable automatic replies from this character": "启用此角色的自动回复", | 1195 | "Enable automatic replies from this character": "解除禁言此角色", |
| 1196 | "Trigger a message from this character": "从此角色触发消息", | 1196 | "Trigger a message from this character": "强制触发该角色发言", |
| 1197 | "Move up": "向上移动", | 1197 | "Move up": "向上移动", |
| 1198 | "Move down": "向下移动", | 1198 | "Move down": "向下移动", |
| 1199 | "View character card": "查看角色卡片", | 1199 | "View character card": "查看角色卡片", |
| @@ -1208,7 +1208,7 @@ | |||
| 1208 | "View contents": "查看内容", | 1208 | "View contents": "查看内容", |
| 1209 | "Remove the file": "删除文件", | 1209 | "Remove the file": "删除文件", |
| 1210 | "Author's Note": "作者注释", | 1210 | "Author's Note": "作者注释", |
| 1211 | "Unique to this chat": "此聊天独有", | 1211 | "Unique to this chat": "仅对此聊天生效", |
| 1212 | "Checkpoints inherit the Note from their parent, and can be changed individually after that.": "检查点从其父级继承注释,之后可以单独更改。", | 1212 | "Checkpoints inherit the Note from their parent, and can be changed individually after that.": "检查点从其父级继承注释,之后可以单独更改。", |
| 1213 | "Include in World Info Scanning": "纳入世界信息扫描", | 1213 | "Include in World Info Scanning": "纳入世界信息扫描", |
| 1214 | "Before Main Prompt / Story String": "主提示词/故事线之前", | 1214 | "Before Main Prompt / Story String": "主提示词/故事线之前", |
| @@ -1224,13 +1224,13 @@ | |||
| 1224 | "Replace Author's Note": "替换作者注", | 1224 | "Replace Author's Note": "替换作者注", |
| 1225 | "Default Author's Note": "默认作者注", | 1225 | "Default Author's Note": "默认作者注", |
| 1226 | "Will be automatically added as the Author's Note for all new chats.": "将自动添加为所有新聊天的作者注释。", | 1226 | "Will be automatically added as the Author's Note for all new chats.": "将自动添加为所有新聊天的作者注释。", |
| 1227 | "Chat CFG": "聊天CFG", | 1227 | "Chat CFG": "本聊天的CFG缩放", |
| 1228 | "1 = disabled": "“1”为已禁用", | 1228 | "1 = disabled": "“1”为禁用", |
| 1229 | "write short replies, write replies using past tense": "写简短的回复,用过去时写回复", | 1229 | "write short replies, write replies using past tense": "写简短的回复,用过去时写回复", |
| 1230 | "Positive Prompt": "正面提示词", | 1230 | "Positive Prompt": "正面提示词", |
| 1231 | "Use character CFG scales": "单独为各个角色设置CFG缩放", | 1231 | "Use character CFG scales": "单独为各个角色设置CFG缩放", |
| 1232 | "Character CFG": "角色CFG配置", | 1232 | "Character CFG": "角色CFG配置", |
| 1233 | "Will be automatically added as the CFG for this character.": "将自动添加为该角色的 CFG。", | 1233 | "Will be automatically added as the CFG for this character.": "将自动添加到该角色的CFG设置中。", |
| 1234 | "Global CFG": "全局CFG", | 1234 | "Global CFG": "全局CFG", |
| 1235 | "Will be used as the default CFG options for every chat unless overridden.": "除非被覆盖,否则将用作每次聊天的默认 CFG 选项。", | 1235 | "Will be used as the default CFG options for every chat unless overridden.": "除非被覆盖,否则将用作每次聊天的默认 CFG 选项。", |
| 1236 | "CFG Prompt Cascading": "CFG 提示词级联", | 1236 | "CFG Prompt Cascading": "CFG 提示词级联", |
| @@ -1486,7 +1486,7 @@ | |||
| 1486 | "ext_regex_replace_string_placeholder": "使用 {{match}} 包含来自“查找正则表达式”或“$1”、“$2”等的匹配文本作为捕获组。", | 1486 | "ext_regex_replace_string_placeholder": "使用 {{match}} 包含来自“查找正则表达式”或“$1”、“$2”等的匹配文本作为捕获组。", |
| 1487 | "Trim Out": "修剪掉", | 1487 | "Trim Out": "修剪掉", |
| 1488 | "ext_regex_trim_placeholder": "在替换之前全局修剪正则表达式匹配中任何不需要的部分。用回车键分隔每个元素。", | 1488 | "ext_regex_trim_placeholder": "在替换之前全局修剪正则表达式匹配中任何不需要的部分。用回车键分隔每个元素。", |
| 1489 | "ext_regex_affects": "影响", | 1489 | "ext_regex_affects": "作用范围", |
| 1490 | "ext_regex_user_input_desc": "用户发送的消息", | 1490 | "ext_regex_user_input_desc": "用户发送的消息", |
| 1491 | "ext_regex_user_input": "用户输入", | 1491 | "ext_regex_user_input": "用户输入", |
| 1492 | "ext_regex_ai_input_desc": "从生成式API中获取的信息。", | 1492 | "ext_regex_ai_input_desc": "从生成式API中获取的信息。", |
| @@ -1720,9 +1720,9 @@ | |||
| 1720 | "Chat Lorebook for": "聊天知识书", | 1720 | "Chat Lorebook for": "聊天知识书", |
| 1721 | "chat_world_template_txt": "选定的世界信息将绑定到此聊天。生成 AI 回复时,\n它将与全球和角色传说书中的条目相结合。", | 1721 | "chat_world_template_txt": "选定的世界信息将绑定到此聊天。生成 AI 回复时,\n它将与全球和角色传说书中的条目相结合。", |
| 1722 | "chat_rename_1": "输入聊天的新名称:", | 1722 | "chat_rename_1": "输入聊天的新名称:", |
| 1723 | "chat_rename_2": "注意!!使用已有文件名会导致错误!!", | 1723 | "chat_rename_2": "注意!!与其他文件重名会导致错误!!", |
| 1724 | "chat_rename_3": "此举会将次聊天与标记为“检查点”的聊天解绑。", | 1724 | "chat_rename_3": "此举会将此聊天与标记为“检查点”的聊天解绑。", |
| 1725 | "chat_rename_4": "不需要在结尾添加 '.JSONL'", | 1725 | "chat_rename_4": "(不需要在结尾添加 '.JSONL' 后缀)", |
| 1726 | "Enter Checkpoint Name:": "输入检查点名称:", | 1726 | "Enter Checkpoint Name:": "输入检查点名称:", |
| 1727 | "(Leave empty to auto-generate)": "(留空以自动生成)", | 1727 | "(Leave empty to auto-generate)": "(留空以自动生成)", |
| 1728 | "The currently existing checkpoint will be unlinked and replaced with the new checkpoint, but can still be found in the Chat Management.": "当前检查点将会被解绑并替换为新的检查点,但仍可在聊天管理中找到。", | 1728 | "The currently existing checkpoint will be unlinked and replaced with the new checkpoint, but can still be found in the Chat Management.": "当前检查点将会被解绑并替换为新的检查点,但仍可在聊天管理中找到。", |
| @@ -1829,7 +1829,7 @@ | |||
| 1829 | "char_import_2": "Chub 知识书(直链或ID)", | 1829 | "char_import_2": "Chub 知识书(直链或ID)", |
| 1830 | "char_import_3": "JanitorAI 角色(直链或UUID)", | 1830 | "char_import_3": "JanitorAI 角色(直链或UUID)", |
| 1831 | "char_import_4": "Pygmalion.chat 角色(直链或UUID)", | 1831 | "char_import_4": "Pygmalion.chat 角色(直链或UUID)", |
| 1832 | "char_import_5": "AICharacterCard.com 角色(直链或ID)", | 1832 | "char_import_5": "AICharacterCards.com 角色(直链或ID)", |
| 1833 | "char_import_6": "被允许的PNG直链(请参阅", | 1833 | "char_import_6": "被允许的PNG直链(请参阅", |
| 1834 | "char_import_7": ")", | 1834 | "char_import_7": ")", |
| 1835 | "char_import_8": "RisuRealm 角色(直链)", | 1835 | "char_import_8": "RisuRealm 角色(直链)", |
| @@ -1838,7 +1838,7 @@ | |||
| 1838 | "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装", | 1838 | "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装", |
| 1839 | "Disclaimer:": "免责声明:", | 1839 | "Disclaimer:": "免责声明:", |
| 1840 | "Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.": "使用外部的扩展程序可能存在意料外的副作用和安全隐患。在导入扩展程序前,请一定确认其来源可信。我们不为第三方扩展程序造成的任何损失负责。", | 1840 | "Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.": "使用外部的扩展程序可能存在意料外的副作用和安全隐患。在导入扩展程序前,请一定确认其来源可信。我们不为第三方扩展程序造成的任何损失负责。", |
| 1841 | "Prompt Itemization": "将提示词分条", | 1841 | "Prompt Itemization": "提示词拆分", |
| 1842 | "Show Raw Prompt": "显示原始提示词", | 1842 | "Show Raw Prompt": "显示原始提示词", |
| 1843 | "Copy Prompt": "复制提示词", | 1843 | "Copy Prompt": "复制提示词", |
| 1844 | "Show Prompt Differences": "显示提示词差异", | 1844 | "Show Prompt Differences": "显示提示词差异", |
| @@ -1975,7 +1975,7 @@ | |||
| 1975 | "Enter your password below to confirm:": "输入您的密码以确认:", | 1975 | "Enter your password below to confirm:": "输入您的密码以确认:", |
| 1976 | "Chat Scenario Override": "聊天场景覆盖", | 1976 | "Chat Scenario Override": "聊天场景覆盖", |
| 1977 | "Remove": "移除", | 1977 | "Remove": "移除", |
| 1978 | "Unique to this chat.": "Unique to this chat.", | 1978 | "Unique to this chat.": "仅对此聊天生效。", |
| 1979 | "All group members will use the following scenario text instead of what is specified in their character cards.": "All group members will use the following scenario text instead of what is specified in their character cards.", | 1979 | "All group members will use the following scenario text instead of what is specified in their character cards.": "All group members will use the following scenario text instead of what is specified in their character cards.", |
| 1980 | "The following scenario text will be used instead of the value set in the character card.": "The following scenario text will be used instead of the value set in the character card.", | 1980 | "The following scenario text will be used instead of the value set in the character card.": "The following scenario text will be used instead of the value set in the character card.", |
| 1981 | "Checkpoints inherit the scenario override from their parent, and can be changed individually after that.": "Checkpoints inherit the scenario override from their parent, and can be changed individually after that.", | 1981 | "Checkpoints inherit the scenario override from their parent, and can be changed individually after that.": "Checkpoints inherit the scenario override from their parent, and can be changed individually after that.", |
| @@ -2045,8 +2045,8 @@ | |||
| 2045 | "Post a GitHub issue": "在 GitHub 发布问题", | 2045 | "Post a GitHub issue": "在 GitHub 发布问题", |
| 2046 | "Contact the developers": "联系开发者", | 2046 | "Contact the developers": "联系开发者", |
| 2047 | "If you're connected to an API, try asking me something!": "若您已经配置好API,尝试发送些什么吧!", | 2047 | "If you're connected to an API, try asking me something!": "若您已经配置好API,尝试发送些什么吧!", |
| 2048 | "Title/Memo": "标题/备忘录", | 2048 | "Title/Memo": "标题(备忘)", |
| 2049 | "Strategy": "Strategy", | 2049 | "Strategy": "触发策略", |
| 2050 | "Position": "位置", | 2050 | "Position": "插入位置", |
| 2051 | "Trigger %": "触发率 %" | 2051 | "Trigger %": "触发概率%" |
| 2052 | } | 2052 | } |
| @@ -483,7 +483,7 @@ | |||
| 483 | "separate with commas w/o space between": "用逗號分隔,之間無空格", | 483 | "separate with commas w/o space between": "用逗號分隔,之間無空格", |
| 484 | "Custom Stopping Strings": "自訂停止字串", | 484 | "Custom Stopping Strings": "自訂停止字串", |
| 485 | "JSON serialized array of strings": "JSON 序列化字串數組", | 485 | "JSON serialized array of strings": "JSON 序列化字串數組", |
| 486 | "Replace Macro in Custom Stopping Strings": "取代自訂停止字串中的巨集", | 486 | "Replace Macro in Stop Strings": "取代自訂停止字串中的巨集", |
| 487 | "Auto-Continue": "自動繼續", | 487 | "Auto-Continue": "自動繼續", |
| 488 | "Allow for Chat Completion APIs": "允許聊天補全 API", | 488 | "Allow for Chat Completion APIs": "允許聊天補全 API", |
| 489 | "Target length (tokens)": "目標長度(符元)", | 489 | "Target length (tokens)": "目標長度(符元)", |
| @@ -1381,7 +1381,7 @@ | |||
| 1381 | "char_import_2": "Chub Lorebook(直接連結或 ID)", | 1381 | "char_import_2": "Chub Lorebook(直接連結或 ID)", |
| 1382 | "char_import_3": "JanitorAI 角色(直接連結或 ID)", | 1382 | "char_import_3": "JanitorAI 角色(直接連結或 ID)", |
| 1383 | "char_import_4": "Pygmalion.chat 角色(直接連結或 ID)", | 1383 | "char_import_4": "Pygmalion.chat 角色(直接連結或 ID)", |
| 1384 | "char_import_5": "AICharacterCard.com 角色(直接連結或 ID)", | 1384 | "char_import_5": "AICharacterCards.com 角色(直接連結或 ID)", |
| 1385 | "char_import_6": "直接 PNG 連結(請參閱", | 1385 | "char_import_6": "直接 PNG 連結(請參閱", |
| 1386 | "char_import_7": "對於允許的主機)", | 1386 | "char_import_7": "對於允許的主機)", |
| 1387 | "char_import_8": "RisuRealm角色(直接連結)", | 1387 | "char_import_8": "RisuRealm角色(直接連結)", |
| @@ -2357,8 +2357,8 @@ | |||
| 2357 | "Forbid": "禁止", | 2357 | "Forbid": "禁止", |
| 2358 | "Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here.": "僅限 Aphrodite 使用。決定採樣器的順序。偏移總是在 softmax 後應用,因此不包括在此。", | 2358 | "Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here.": "僅限 Aphrodite 使用。決定採樣器的順序。偏移總是在 softmax 後應用,因此不包括在此。", |
| 2359 | "Aphrodite only. Determines the order of samplers.": "僅限 Aphrodite 使用。決定採樣器的順序。", | 2359 | "Aphrodite only. Determines the order of samplers.": "僅限 Aphrodite 使用。決定採樣器的順序。", |
| 2360 | "Show model thoughts": "顯示模型思維鏈", | 2360 | "Request model reasoning": "請求模型思維鏈", |
| 2361 | "Display the model's internal thoughts in the response.": "在回應中顯示模型的思維鏈(內部思考過程)。", | 2361 | "Allows the model to return its thinking process.": "讓模型回傳其思考過程。", |
| 2362 | "Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]": "通用(兼容 OpenAI)[LM Studio, LiteLLM 等]", | 2362 | "Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]": "通用(兼容 OpenAI)[LM Studio, LiteLLM 等]", |
| 2363 | "Model ID (optional)": "模型 ID(可選)", | 2363 | "Model ID (optional)": "模型 ID(可選)", |
| 2364 | "DeepSeek API Key": "DeepSeek API 金鑰", | 2364 | "DeepSeek API Key": "DeepSeek API 金鑰", |
| @@ -95,6 +95,7 @@ import { | |||
| 95 | resetMovableStyles, | 95 | resetMovableStyles, |
| 96 | forceCharacterEditorTokenize, | 96 | forceCharacterEditorTokenize, |
| 97 | applyPowerUserSettings, | 97 | applyPowerUserSettings, |
| 98 | generatedTextFiltered, | ||
| 98 | } from './scripts/power-user.js'; | 99 | } from './scripts/power-user.js'; |
| 99 | 100 | ||
| 100 | import { | 101 | import { |
| @@ -169,6 +170,7 @@ import { | |||
| 169 | toggleDrawer, | 170 | toggleDrawer, |
| 170 | isElementInViewport, | 171 | isElementInViewport, |
| 171 | copyText, | 172 | copyText, |
| 173 | escapeHtml, | ||
| 172 | } from './scripts/utils.js'; | 174 | } from './scripts/utils.js'; |
| 173 | import { debounce_timeout } from './scripts/constants.js'; | 175 | import { debounce_timeout } from './scripts/constants.js'; |
| 174 | 176 | ||
| @@ -267,6 +269,8 @@ import { initSettingsSearch } from './scripts/setting-search.js'; | |||
| 267 | import { initBulkEdit } from './scripts/bulk-edit.js'; | 269 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 268 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; | 270 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 269 | import { getContext } from './scripts/st-context.js'; | 271 | import { getContext } from './scripts/st-context.js'; |
| 272 | import { extractReasoningFromData, initReasoning, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; | ||
| 273 | import { accountStorage } from './scripts/util/AccountStorage.js'; | ||
| 270 | 274 | ||
| 271 | // API OBJECT FOR EXTERNAL WIRING | 275 | // API OBJECT FOR EXTERNAL WIRING |
| 272 | globalThis.SillyTavern = { | 276 | globalThis.SillyTavern = { |
| @@ -416,7 +420,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { | |||
| 416 | const entityId = getCurrentEntityId(); | 420 | const entityId = getCurrentEntityId(); |
| 417 | const warningShownKey = `mediaWarningShown:${entityId}`; | 421 | const warningShownKey = `mediaWarningShown:${entityId}`; |
| 418 | 422 | ||
| 419 | if (localStorage.getItem(warningShownKey) === null) { | 423 | if (accountStorage.getItem(warningShownKey) === null) { |
| 420 | const warningToast = toastr.warning( | 424 | const warningToast = toastr.warning( |
| 421 | t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, | 425 | t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, |
| 422 | t`External media has been blocked`, | 426 | t`External media has been blocked`, |
| @@ -427,7 +431,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { | |||
| 427 | }, | 431 | }, |
| 428 | ); | 432 | ); |
| 429 | 433 | ||
| 430 | localStorage.setItem(warningShownKey, 'true'); | 434 | accountStorage.setItem(warningShownKey, 'true'); |
| 431 | } | 435 | } |
| 432 | } | 436 | } |
| 433 | }); | 437 | }); |
| @@ -443,6 +447,7 @@ export const event_types = { | |||
| 443 | MESSAGE_DELETED: 'message_deleted', | 447 | MESSAGE_DELETED: 'message_deleted', |
| 444 | MESSAGE_UPDATED: 'message_updated', | 448 | MESSAGE_UPDATED: 'message_updated', |
| 445 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', | 449 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', |
| 450 | MORE_MESSAGES_LOADED: 'more_messages_loaded', | ||
| 446 | IMPERSONATE_READY: 'impersonate_ready', | 451 | IMPERSONATE_READY: 'impersonate_ready', |
| 447 | CHAT_CHANGED: 'chat_id_changed', | 452 | CHAT_CHANGED: 'chat_id_changed', |
| 448 | GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS', | 453 | GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS', |
| @@ -491,6 +496,7 @@ export const event_types = { | |||
| 491 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ | 496 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ |
| 492 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', | 497 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| 493 | STREAM_TOKEN_RECEIVED: 'stream_token_received', | 498 | STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| 499 | STREAM_REASONING_DONE: 'stream_reasoning_done', | ||
| 494 | FILE_ATTACHMENT_DELETED: 'file_attachment_deleted', | 500 | FILE_ATTACHMENT_DELETED: 'file_attachment_deleted', |
| 495 | WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate', | 501 | WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate', |
| 496 | OPEN_CHARACTER_LIBRARY: 'open_character_library', | 502 | OPEN_CHARACTER_LIBRARY: 'open_character_library', |
| @@ -723,6 +729,7 @@ async function getSystemMessages() { | |||
| 723 | is_user: false, | 729 | is_user: false, |
| 724 | is_system: true, | 730 | is_system: true, |
| 725 | mes: await renderTemplateAsync('assistantNote'), | 731 | mes: await renderTemplateAsync('assistantNote'), |
| 732 | uses_system_ui: true, | ||
| 726 | extra: { | 733 | extra: { |
| 727 | isSmallSys: true, | 734 | isSmallSys: true, |
| 728 | }, | 735 | }, |
| @@ -980,6 +987,7 @@ async function firstLoadInit() { | |||
| 980 | initServerHistory(); | 987 | initServerHistory(); |
| 981 | initSettingsSearch(); | 988 | initSettingsSearch(); |
| 982 | initBulkEdit(); | 989 | initBulkEdit(); |
| 990 | initReasoning(); | ||
| 983 | await initScrapers(); | 991 | await initScrapers(); |
| 984 | doDailyExtensionUpdatesCheck(); | 992 | doDailyExtensionUpdatesCheck(); |
| 985 | await hideLoader(); | 993 | await hideLoader(); |
| @@ -1483,7 +1491,7 @@ export async function printCharacters(fullRefresh = false) { | |||
| 1483 | 1491 | ||
| 1484 | $('#rm_print_characters_pagination').pagination({ | 1492 | $('#rm_print_characters_pagination').pagination({ |
| 1485 | dataSource: entities, | 1493 | dataSource: entities, |
| 1486 | pageSize: Number(localStorage.getItem(storageKey)) || per_page_default, | 1494 | pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default, |
| 1487 | sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], | 1495 | sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], |
| 1488 | pageRange: 1, | 1496 | pageRange: 1, |
| 1489 | pageNumber: saveCharactersPage || 1, | 1497 | pageNumber: saveCharactersPage || 1, |
| @@ -1527,7 +1535,7 @@ export async function printCharacters(fullRefresh = false) { | |||
| 1527 | eventSource.emit(event_types.CHARACTER_PAGE_LOADED); | 1535 | eventSource.emit(event_types.CHARACTER_PAGE_LOADED); |
| 1528 | }, | 1536 | }, |
| 1529 | afterSizeSelectorChange: function (e) { | 1537 | afterSizeSelectorChange: function (e) { |
| 1530 | localStorage.setItem(storageKey, e.target.value); | 1538 | accountStorage.setItem(storageKey, e.target.value); |
| 1531 | }, | 1539 | }, |
| 1532 | afterPaging: function (e) { | 1540 | afterPaging: function (e) { |
| 1533 | saveCharactersPage = e; | 1541 | saveCharactersPage = e; |
| @@ -1829,7 +1837,7 @@ export async function replaceCurrentChat() { | |||
| 1829 | } | 1837 | } |
| 1830 | } | 1838 | } |
| 1831 | 1839 | ||
| 1832 | export function showMoreMessages(messagesToLoad = null) { | 1840 | export async function showMoreMessages(messagesToLoad = null) { |
| 1833 | const firstDisplayedMesId = $('#chat').children('.mes').first().attr('mesid'); | 1841 | const firstDisplayedMesId = $('#chat').children('.mes').first().attr('mesid'); |
| 1834 | let messageId = Number(firstDisplayedMesId); | 1842 | let messageId = Number(firstDisplayedMesId); |
| 1835 | let count = messagesToLoad || power_user.chat_truncation || Number.MAX_SAFE_INTEGER; | 1843 | let count = messagesToLoad || power_user.chat_truncation || Number.MAX_SAFE_INTEGER; |
| @@ -1859,6 +1867,8 @@ export function showMoreMessages(messagesToLoad = null) { | |||
| 1859 | const newHeight = $('#chat').prop('scrollHeight'); | 1867 | const newHeight = $('#chat').prop('scrollHeight'); |
| 1860 | $('#chat').scrollTop(newHeight - prevHeight); | 1868 | $('#chat').scrollTop(newHeight - prevHeight); |
| 1861 | } | 1869 | } |
| 1870 | |||
| 1871 | await eventSource.emit(event_types.MORE_MESSAGES_LOADED); | ||
| 1862 | } | 1872 | } |
| 1863 | 1873 | ||
| 1864 | export async function printMessages() { | 1874 | export async function printMessages() { |
| @@ -1987,14 +1997,15 @@ export async function sendTextareaMessage() { | |||
| 1987 | * @param {boolean} isUser If the message was sent by the user | 1997 | * @param {boolean} isUser If the message was sent by the user |
| 1988 | * @param {number} messageId Message index in chat array | 1998 | * @param {number} messageId Message index in chat array |
| 1989 | * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides | 1999 | * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides |
| 2000 | * @param {boolean} [isReasoning] If the message is reasoning output | ||
| 1990 | * @returns {string} HTML string | 2001 | * @returns {string} HTML string |
| 1991 | */ | 2002 | */ |
| 1992 | export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}) { | 2003 | export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}, isReasoning = false) { |
| 1993 | if (!mes) { | 2004 | if (!mes) { |
| 1994 | return ''; | 2005 | return ''; |
| 1995 | } | 2006 | } |
| 1996 | 2007 | ||
| 1997 | if (Number(messageId) === 0 && !isSystem && !isUser) { | 2008 | if (Number(messageId) === 0 && !isSystem && !isUser && !isReasoning) { |
| 1998 | const mesBeforeReplace = mes; | 2009 | const mesBeforeReplace = mes; |
| 1999 | const chatMessage = chat[messageId]; | 2010 | const chatMessage = chat[messageId]; |
| 2000 | mes = substituteParams(mes, undefined, ch_name); | 2011 | mes = substituteParams(mes, undefined, ch_name); |
| @@ -2023,6 +2034,9 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san | |||
| 2023 | if (!isSystem) { | 2034 | if (!isSystem) { |
| 2024 | function getRegexPlacement() { | 2035 | function getRegexPlacement() { |
| 2025 | try { | 2036 | try { |
| 2037 | if (isReasoning) { | ||
| 2038 | return regex_placement.REASONING; | ||
| 2039 | } | ||
| 2026 | if (isUser) { | 2040 | if (isUser) { |
| 2027 | return regex_placement.USER_INPUT; | 2041 | return regex_placement.USER_INPUT; |
| 2028 | } else if (chat[messageId]?.extra?.type === 'narrator') { | 2042 | } else if (chat[messageId]?.extra?.type === 'narrator') { |
| @@ -2056,6 +2070,17 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san | |||
| 2056 | mes = mes.replaceAll('<', '<').replaceAll('>', '>'); | 2070 | mes = mes.replaceAll('<', '<').replaceAll('>', '>'); |
| 2057 | } | 2071 | } |
| 2058 | 2072 | ||
| 2073 | // Make sure reasoning strings are always shown, even if they include "<" or ">" | ||
| 2074 | [power_user.reasoning.prefix, power_user.reasoning.suffix].forEach((reasoningString) => { | ||
| 2075 | if (!reasoningString || !reasoningString.trim().length) { | ||
| 2076 | return; | ||
| 2077 | } | ||
| 2078 | // Only replace the first occurrence of the reasoning string | ||
| 2079 | if (mes.includes(reasoningString)) { | ||
| 2080 | mes = mes.replace(reasoningString, escapeHtml(reasoningString)); | ||
| 2081 | } | ||
| 2082 | }); | ||
| 2083 | |||
| 2059 | if (!isSystem) { | 2084 | if (!isSystem) { |
| 2060 | // Save double quotes in tags as a special character to prevent them from being encoded | 2085 | // Save double quotes in tags as a special character to prevent them from being encoded |
| 2061 | if (!power_user.encode_tags) { | 2086 | if (!power_user.encode_tags) { |
| @@ -2166,26 +2191,29 @@ function insertSVGIcon(mes, extra) { | |||
| 2166 | modelName = extra.api; | 2191 | modelName = extra.api; |
| 2167 | } | 2192 | } |
| 2168 | 2193 | ||
| 2169 | const image = new Image(); | 2194 | const insertOrReplaceSVG = (image, className, targetSelector, insertBefore) => { |
| 2170 | // Add classes for styling and identification | ||
| 2171 | image.classList.add('icon-svg', 'timestamp-icon'); | ||
| 2172 | image.src = `/img/${modelName}.svg`; | ||
| 2173 | image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`; | ||
| 2174 | |||
| 2175 | image.onload = async function () { | 2195 | image.onload = async function () { |
| 2176 | // Check if an SVG already exists adjacent to the timestamp | 2196 | let existingSVG = insertBefore ? mes.find(targetSelector).prev(`.${className}`) : mes.find(targetSelector).next(`.${className}`); |
| 2177 | let existingSVG = mes.find('.timestamp').next('.timestamp-icon'); | ||
| 2178 | |||
| 2179 | if (existingSVG.length) { | 2197 | if (existingSVG.length) { |
| 2180 | // Replace existing SVG | ||
| 2181 | existingSVG.replaceWith(image); | 2198 | existingSVG.replaceWith(image); |
| 2182 | } else { | 2199 | } else { |
| 2183 | // Append the new SVG if none exists | 2200 | if (insertBefore) mes.find(targetSelector).before(image); |
| 2184 | mes.find('.timestamp').after(image); | 2201 | else mes.find(targetSelector).after(image); |
| 2185 | } | 2202 | } |
| 2186 | |||
| 2187 | await SVGInject(image); | 2203 | await SVGInject(image); |
| 2188 | }; | 2204 | }; |
| 2205 | }; | ||
| 2206 | |||
| 2207 | const createModelImage = (className, targetSelector, insertBefore) => { | ||
| 2208 | const image = new Image(); | ||
| 2209 | image.classList.add('icon-svg', className); | ||
| 2210 | image.src = `/img/${modelName}.svg`; | ||
| 2211 | image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`; | ||
| 2212 | insertOrReplaceSVG(image, className, targetSelector, insertBefore); | ||
| 2213 | }; | ||
| 2214 | |||
| 2215 | createModelImage('timestamp-icon', '.timestamp'); | ||
| 2216 | createModelImage('thinking-icon', '.mes_reasoning_header_title', true); | ||
| 2189 | } | 2217 | } |
| 2190 | 2218 | ||
| 2191 | 2219 | ||
| @@ -2227,6 +2255,8 @@ function getMessageFromTemplate({ | |||
| 2227 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); | 2255 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); |
| 2228 | bookmarkLink && updateBookmarkDisplay(mes); | 2256 | bookmarkLink && updateBookmarkDisplay(mes); |
| 2229 | 2257 | ||
| 2258 | updateReasoningUI(mes); | ||
| 2259 | |||
| 2230 | if (power_user.timestamp_model_icon && extra?.api) { | 2260 | if (power_user.timestamp_model_icon && extra?.api) { |
| 2231 | insertSVGIcon(mes, extra); | 2261 | insertSVGIcon(mes, extra); |
| 2232 | } | 2262 | } |
| @@ -2234,10 +2264,22 @@ function getMessageFromTemplate({ | |||
| 2234 | return mes; | 2264 | return mes; |
| 2235 | } | 2265 | } |
| 2236 | 2266 | ||
| 2237 | export function updateMessageBlock(messageId, message) { | 2267 | /** |
| 2268 | * Re-renders a message block with updated content. | ||
| 2269 | * @param {number} messageId Message ID | ||
| 2270 | * @param {object} message Message object | ||
| 2271 | * @param {object} [options={}] Optional arguments | ||
| 2272 | * @param {boolean} [options.rerenderMessage=true] Whether to re-render the message content (inside <c>.mes_text</c>) | ||
| 2273 | */ | ||
| 2274 | export function updateMessageBlock(messageId, message, { rerenderMessage = true } = {}) { | ||
| 2238 | const messageElement = $(`#chat [mesid="${messageId}"]`); | 2275 | const messageElement = $(`#chat [mesid="${messageId}"]`); |
| 2276 | if (rerenderMessage) { | ||
| 2239 | const text = message?.extra?.display_text ?? message.mes; | 2277 | const text = message?.extra?.display_text ?? message.mes; |
| 2240 | messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId)); | 2278 | messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false)); |
| 2279 | } | ||
| 2280 | |||
| 2281 | updateReasoningUI(messageElement); | ||
| 2282 | |||
| 2241 | addCopyToCodeBlocks(messageElement); | 2283 | addCopyToCodeBlocks(messageElement); |
| 2242 | appendMediaToMessage(message, messageElement); | 2284 | appendMediaToMessage(message, messageElement); |
| 2243 | } | 2285 | } |
| @@ -2394,8 +2436,9 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2394 | mes.is_user, | 2436 | mes.is_user, |
| 2395 | chat.indexOf(mes), | 2437 | chat.indexOf(mes), |
| 2396 | sanitizerOverrides, | 2438 | sanitizerOverrides, |
| 2439 | false, | ||
| 2397 | ); | 2440 | ); |
| 2398 | const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1); | 2441 | const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1, {}, false); |
| 2399 | let bookmarkLink = mes?.extra?.bookmark_link ?? ''; | 2442 | let bookmarkLink = mes?.extra?.bookmark_link ?? ''; |
| 2400 | 2443 | ||
| 2401 | let params = { | 2444 | let params = { |
| @@ -2412,7 +2455,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2412 | timestamp: timestamp, | 2455 | timestamp: timestamp, |
| 2413 | extra: mes.extra, | 2456 | extra: mes.extra, |
| 2414 | tokenCount: mes.extra?.token_count ?? 0, | 2457 | tokenCount: mes.extra?.token_count ?? 0, |
| 2415 | ...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count), | 2458 | ...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count, mes.extra?.reasoning_duration), |
| 2416 | }; | 2459 | }; |
| 2417 | 2460 | ||
| 2418 | const renderedMessage = getMessageFromTemplate(params); | 2461 | const renderedMessage = getMessageFromTemplate(params); |
| @@ -2465,6 +2508,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2465 | swipeMessage.attr('swipeid', params.swipeId); | 2508 | swipeMessage.attr('swipeid', params.swipeId); |
| 2466 | swipeMessage.find('.mes_text').html(messageText).attr('title', title); | 2509 | swipeMessage.find('.mes_text').html(messageText).attr('title', title); |
| 2467 | swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`); | 2510 | swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`); |
| 2511 | updateReasoningUI(swipeMessage); | ||
| 2468 | appendMediaToMessage(mes, swipeMessage); | 2512 | appendMediaToMessage(mes, swipeMessage); |
| 2469 | if (power_user.timestamp_model_icon && params.extra?.api) { | 2513 | if (power_user.timestamp_model_icon && params.extra?.api) { |
| 2470 | insertSVGIcon(swipeMessage, params.extra); | 2514 | insertSVGIcon(swipeMessage, params.extra); |
| @@ -2531,13 +2575,14 @@ export function formatCharacterAvatar(characterAvatar) { | |||
| 2531 | * @param {Date} gen_started Date when generation was started | 2575 | * @param {Date} gen_started Date when generation was started |
| 2532 | * @param {Date} gen_finished Date when generation was finished | 2576 | * @param {Date} gen_finished Date when generation was finished |
| 2533 | * @param {number} tokenCount Number of tokens generated (0 if not available) | 2577 | * @param {number} tokenCount Number of tokens generated (0 if not available) |
| 2578 | * @param {number?} [reasoningDuration=null] Reasoning duration (null if no reasoning was done) | ||
| 2534 | * @returns {Object} Object containing the formatted timer value and title | 2579 | * @returns {Object} Object containing the formatted timer value and title |
| 2535 | * @example | 2580 | * @example |
| 2536 | * const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount); | 2581 | * const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount); |
| 2537 | * console.log(timerValue); // 1.2s | 2582 | * console.log(timerValue); // 1.2s |
| 2538 | * console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s | 2583 | * console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s |
| 2539 | */ | 2584 | */ |
| 2540 | function formatGenerationTimer(gen_started, gen_finished, tokenCount) { | 2585 | function formatGenerationTimer(gen_started, gen_finished, tokenCount, reasoningDuration = null) { |
| 2541 | if (!gen_started || !gen_finished) { | 2586 | if (!gen_started || !gen_finished) { |
| 2542 | return {}; | 2587 | return {}; |
| 2543 | } | 2588 | } |
| @@ -2551,8 +2596,9 @@ function formatGenerationTimer(gen_started, gen_finished, tokenCount) { | |||
| 2551 | `Generation queued: ${start.format(dateFormat)}`, | 2596 | `Generation queued: ${start.format(dateFormat)}`, |
| 2552 | `Reply received: ${finish.format(dateFormat)}`, | 2597 | `Reply received: ${finish.format(dateFormat)}`, |
| 2553 | `Time to generate: ${seconds} seconds`, | 2598 | `Time to generate: ${seconds} seconds`, |
| 2599 | reasoningDuration > 0 ? `Time to think: ${reasoningDuration / 1000} seconds` : '', | ||
| 2554 | tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(1)} t/s` : '', | 2600 | tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(1)} t/s` : '', |
| 2555 | ].join('\n'); | 2601 | ].filter(x => x).join('\n').trim(); |
| 2556 | 2602 | ||
| 2557 | if (isNaN(seconds) || seconds < 0) { | 2603 | if (isNaN(seconds) || seconds < 0) { |
| 2558 | return { timerValue: '', timerTitle }; | 2604 | return { timerValue: '', timerTitle }; |
| @@ -2740,7 +2786,8 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q | |||
| 2740 | TempResponseLength.save(main_api, responseLength); | 2786 | TempResponseLength.save(main_api, responseLength); |
| 2741 | eventHook = TempResponseLength.setupEventHook(main_api); | 2787 | eventHook = TempResponseLength.setupEventHook(main_api); |
| 2742 | } | 2788 | } |
| 2743 | return await Generate('quiet', options); | 2789 | const result = await Generate('quiet', options); |
| 2790 | return removeReasoningFromString(result); | ||
| 2744 | } finally { | 2791 | } finally { |
| 2745 | if (responseLengthCustomized && TempResponseLength.isCustomized()) { | 2792 | if (responseLengthCustomized && TempResponseLength.isCustomized()) { |
| 2746 | TempResponseLength.restore(main_api); | 2793 | TempResponseLength.restore(main_api); |
| @@ -3040,8 +3087,8 @@ export function isStreamingEnabled() { | |||
| 3040 | (main_api == 'openai' && | 3087 | (main_api == 'openai' && |
| 3041 | oai_settings.stream_openai && | 3088 | oai_settings.stream_openai && |
| 3042 | !noStreamSources.includes(oai_settings.chat_completion_source) && | 3089 | !noStreamSources.includes(oai_settings.chat_completion_source) && |
| 3043 | !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && oai_settings.openai_model.startsWith('o1-')) && | 3090 | !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model)) |
| 3044 | !(oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE && oai_settings.google_model.includes('bison'))) | 3091 | ) |
| 3045 | || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming) | 3092 | || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming) |
| 3046 | || (main_api == 'novel' && nai_settings.streaming_novel) | 3093 | || (main_api == 'novel' && nai_settings.streaming_novel) |
| 3047 | || (main_api == 'textgenerationwebui' && textgen_settings.streaming)); | 3094 | || (main_api == 'textgenerationwebui' && textgen_settings.streaming)); |
| @@ -3070,9 +3117,13 @@ class StreamingProcessor { | |||
| 3070 | constructor(type, forceName2, timeStarted, continueMessage) { | 3117 | constructor(type, forceName2, timeStarted, continueMessage) { |
| 3071 | this.result = ''; | 3118 | this.result = ''; |
| 3072 | this.messageId = -1; | 3119 | this.messageId = -1; |
| 3120 | /** @type {HTMLElement} */ | ||
| 3073 | this.messageDom = null; | 3121 | this.messageDom = null; |
| 3122 | /** @type {HTMLElement} */ | ||
| 3074 | this.messageTextDom = null; | 3123 | this.messageTextDom = null; |
| 3124 | /** @type {HTMLElement} */ | ||
| 3075 | this.messageTimerDom = null; | 3125 | this.messageTimerDom = null; |
| 3126 | /** @type {HTMLElement} */ | ||
| 3076 | this.messageTokenCounterDom = null; | 3127 | this.messageTokenCounterDom = null; |
| 3077 | /** @type {HTMLTextAreaElement} */ | 3128 | /** @type {HTMLTextAreaElement} */ |
| 3078 | this.sendTextarea = document.querySelector('#send_textarea'); | 3129 | this.sendTextarea = document.querySelector('#send_textarea'); |
| @@ -3089,6 +3140,8 @@ class StreamingProcessor { | |||
| 3089 | /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */ | 3140 | /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */ |
| 3090 | this.messageLogprobs = []; | 3141 | this.messageLogprobs = []; |
| 3091 | this.toolCalls = []; | 3142 | this.toolCalls = []; |
| 3143 | // Initialize reasoning in its own handler | ||
| 3144 | this.reasoningHandler = new ReasoningHandler(timeStarted); | ||
| 3092 | } | 3145 | } |
| 3093 | 3146 | ||
| 3094 | #checkDomElements(messageId) { | 3147 | #checkDomElements(messageId) { |
| @@ -3098,6 +3151,7 @@ class StreamingProcessor { | |||
| 3098 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); | 3151 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); |
| 3099 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); | 3152 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); |
| 3100 | } | 3153 | } |
| 3154 | this.reasoningHandler.updateDom(messageId); | ||
| 3101 | } | 3155 | } |
| 3102 | 3156 | ||
| 3103 | #updateMessageBlockVisibility() { | 3157 | #updateMessageBlockVisibility() { |
| @@ -3107,22 +3161,12 @@ class StreamingProcessor { | |||
| 3107 | } | 3161 | } |
| 3108 | } | 3162 | } |
| 3109 | 3163 | ||
| 3110 | showMessageButtons(messageId) { | 3164 | markUIGenStarted() { |
| 3111 | if (messageId == -1) { | 3165 | deactivateSendButtons(); |
| 3112 | return; | ||
| 3113 | } | ||
| 3114 | |||
| 3115 | showStopButton(); | ||
| 3116 | $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'none' }); | ||
| 3117 | } | ||
| 3118 | |||
| 3119 | hideMessageButtons(messageId) { | ||
| 3120 | if (messageId == -1) { | ||
| 3121 | return; | ||
| 3122 | } | 3166 | } |
| 3123 | 3167 | ||
| 3124 | hideStopButton(); | 3168 | markUIGenStopped() { |
| 3125 | $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'flex' }); | 3169 | activateSendButtons(); |
| 3126 | } | 3170 | } |
| 3127 | 3171 | ||
| 3128 | async onStartStreaming(text) { | 3172 | async onStartStreaming(text) { |
| @@ -3131,20 +3175,18 @@ class StreamingProcessor { | |||
| 3131 | if (this.type == 'impersonate') { | 3175 | if (this.type == 'impersonate') { |
| 3132 | this.sendTextarea.value = ''; | 3176 | this.sendTextarea.value = ''; |
| 3133 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); | 3177 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); |
| 3134 | } | 3178 | } else { |
| 3135 | else { | 3179 | await saveReply(this.type, text, true, '', [], ''); |
| 3136 | await saveReply(this.type, text, true); | ||
| 3137 | messageId = chat.length - 1; | 3180 | messageId = chat.length - 1; |
| 3138 | this.#checkDomElements(messageId); | 3181 | this.#checkDomElements(messageId); |
| 3139 | this.showMessageButtons(messageId); | 3182 | this.markUIGenStarted(); |
| 3140 | } | 3183 | } |
| 3141 | |||
| 3142 | hideSwipeButtons(); | 3184 | hideSwipeButtons(); |
| 3143 | scrollChatToBottom(); | 3185 | scrollChatToBottom(); |
| 3144 | return messageId; | 3186 | return messageId; |
| 3145 | } | 3187 | } |
| 3146 | 3188 | ||
| 3147 | onProgressStreaming(messageId, text, isFinal) { | 3189 | async onProgressStreaming(messageId, text, isFinal) { |
| 3148 | const isImpersonate = this.type == 'impersonate'; | 3190 | const isImpersonate = this.type == 'impersonate'; |
| 3149 | const isContinue = this.type == 'continue'; | 3191 | const isContinue = this.type == 'continue'; |
| 3150 | 3192 | ||
| @@ -3156,11 +3198,9 @@ class StreamingProcessor { | |||
| 3156 | 3198 | ||
| 3157 | let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal, this.stoppingStrings); | 3199 | let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal, this.stoppingStrings); |
| 3158 | 3200 | ||
| 3159 | // Predict unbalanced asterisks / quotes during streaming | ||
| 3160 | const charsToBalance = ['*', '"', '```']; | 3201 | const charsToBalance = ['*', '"', '```']; |
| 3161 | for (const char of charsToBalance) { | 3202 | for (const char of charsToBalance) { |
| 3162 | if (!isFinal && isOdd(countOccurrences(processedText, char))) { | 3203 | if (!isFinal && isOdd(countOccurrences(processedText, char))) { |
| 3163 | // Add character at the end to balance it | ||
| 3164 | const separator = char.length > 1 ? '\n' : ''; | 3204 | const separator = char.length > 1 ? '\n' : ''; |
| 3165 | processedText = processedText.trimEnd() + separator + char; | 3205 | processedText = processedText.trimEnd() + separator + char; |
| 3166 | } | 3206 | } |
| @@ -3169,23 +3209,25 @@ class StreamingProcessor { | |||
| 3169 | if (isImpersonate) { | 3209 | if (isImpersonate) { |
| 3170 | this.sendTextarea.value = processedText; | 3210 | this.sendTextarea.value = processedText; |
| 3171 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); | 3211 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); |
| 3172 | } | 3212 | } else { |
| 3173 | else { | 3213 | const mesChanged = chat[messageId]['mes'] !== processedText; |
| 3174 | this.#checkDomElements(messageId); | 3214 | this.#checkDomElements(messageId); |
| 3175 | this.#updateMessageBlockVisibility(); | 3215 | this.#updateMessageBlockVisibility(); |
| 3176 | const currentTime = new Date(); | 3216 | const currentTime = new Date(); |
| 3177 | // Don't waste time calculating token count for streaming | ||
| 3178 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0; | ||
| 3179 | const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount); | ||
| 3180 | chat[messageId]['mes'] = processedText; | 3217 | chat[messageId]['mes'] = processedText; |
| 3181 | chat[messageId]['gen_started'] = this.timeStarted; | 3218 | chat[messageId]['gen_started'] = this.timeStarted; |
| 3182 | chat[messageId]['gen_finished'] = currentTime; | 3219 | chat[messageId]['gen_finished'] = currentTime; |
| 3183 | |||
| 3184 | if (currentTokenCount) { | ||
| 3185 | if (!chat[messageId]['extra']) { | 3220 | if (!chat[messageId]['extra']) { |
| 3186 | chat[messageId]['extra'] = {}; | 3221 | chat[messageId]['extra'] = {}; |
| 3187 | } | 3222 | } |
| 3188 | 3223 | ||
| 3224 | // Update reasoning | ||
| 3225 | await this.reasoningHandler.process(messageId, mesChanged); | ||
| 3226 | |||
| 3227 | // Token count update. | ||
| 3228 | const tokenCountText = this.reasoningHandler.reasoning + processedText; | ||
| 3229 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(tokenCountText, 0) : 0; | ||
| 3230 | if (currentTokenCount) { | ||
| 3189 | chat[messageId]['extra']['token_count'] = currentTokenCount; | 3231 | chat[messageId]['extra']['token_count'] = currentTokenCount; |
| 3190 | if (this.messageTokenCounterDom instanceof HTMLElement) { | 3232 | if (this.messageTokenCounterDom instanceof HTMLElement) { |
| 3191 | this.messageTokenCounterDom.textContent = `${currentTokenCount}t`; | 3233 | this.messageTokenCounterDom.textContent = `${currentTokenCount}t`; |
| @@ -3203,14 +3245,19 @@ class StreamingProcessor { | |||
| 3203 | chat[messageId].is_system, | 3245 | chat[messageId].is_system, |
| 3204 | chat[messageId].is_user, | 3246 | chat[messageId].is_user, |
| 3205 | messageId, | 3247 | messageId, |
| 3248 | {}, | ||
| 3249 | false, | ||
| 3206 | ); | 3250 | ); |
| 3207 | if (this.messageTextDom instanceof HTMLElement) { | 3251 | if (this.messageTextDom instanceof HTMLElement) { |
| 3208 | this.messageTextDom.innerHTML = formattedText; | 3252 | this.messageTextDom.innerHTML = formattedText; |
| 3209 | } | 3253 | } |
| 3254 | |||
| 3255 | const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount, this.reasoningHandler.getDuration()); | ||
| 3210 | if (this.messageTimerDom instanceof HTMLElement) { | 3256 | if (this.messageTimerDom instanceof HTMLElement) { |
| 3211 | this.messageTimerDom.textContent = timePassed.timerValue; | 3257 | this.messageTimerDom.textContent = timePassed.timerValue; |
| 3212 | this.messageTimerDom.title = timePassed.timerTitle; | 3258 | this.messageTimerDom.title = timePassed.timerTitle; |
| 3213 | } | 3259 | } |
| 3260 | |||
| 3214 | this.setFirstSwipe(messageId); | 3261 | this.setFirstSwipe(messageId); |
| 3215 | } | 3262 | } |
| 3216 | 3263 | ||
| @@ -3220,10 +3267,12 @@ class StreamingProcessor { | |||
| 3220 | } | 3267 | } |
| 3221 | 3268 | ||
| 3222 | async onFinishStreaming(messageId, text) { | 3269 | async onFinishStreaming(messageId, text) { |
| 3223 | this.hideMessageButtons(this.messageId); | 3270 | this.markUIGenStopped(); |
| 3224 | this.onProgressStreaming(messageId, text, true); | 3271 | await this.onProgressStreaming(messageId, text, true); |
| 3225 | addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`)); | 3272 | addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`)); |
| 3226 | 3273 | ||
| 3274 | await this.reasoningHandler.finish(messageId); | ||
| 3275 | |||
| 3227 | if (Array.isArray(this.swipes) && this.swipes.length > 0) { | 3276 | if (Array.isArray(this.swipes) && this.swipes.length > 0) { |
| 3228 | const message = chat[messageId]; | 3277 | const message = chat[messageId]; |
| 3229 | const swipeInfo = { | 3278 | const swipeInfo = { |
| @@ -3251,39 +3300,11 @@ class StreamingProcessor { | |||
| 3251 | unblockGeneration(); | 3300 | unblockGeneration(); |
| 3252 | generatedPromptCache = ''; | 3301 | generatedPromptCache = ''; |
| 3253 | 3302 | ||
| 3254 | //console.log("Generated text size:", text.length, text) | ||
| 3255 | |||
| 3256 | const isAborted = this.abortController.signal.aborted; | 3303 | const isAborted = this.abortController.signal.aborted; |
| 3257 | if (power_user.auto_swipe && !isAborted) { | 3304 | if (!isAborted && power_user.auto_swipe && generatedTextFiltered(text)) { |
| 3258 | function containsBlacklistedWords(str, blacklist, threshold) { | 3305 | return swipe_right(); |
| 3259 | const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); | ||
| 3260 | const matches = str.match(regex) || []; | ||
| 3261 | return matches.length >= threshold; | ||
| 3262 | } | 3306 | } |
| 3263 | 3307 | ||
| 3264 | const generatedTextFiltered = (text) => { | ||
| 3265 | if (text) { | ||
| 3266 | if (power_user.auto_swipe_minimum_length) { | ||
| 3267 | if (text.length < power_user.auto_swipe_minimum_length && text.length !== 0) { | ||
| 3268 | console.log('Generated text size too small'); | ||
| 3269 | return true; | ||
| 3270 | } | ||
| 3271 | } | ||
| 3272 | if (power_user.auto_swipe_blacklist_threshold) { | ||
| 3273 | if (containsBlacklistedWords(text, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) { | ||
| 3274 | console.log('Generated text has blacklisted words'); | ||
| 3275 | return true; | ||
| 3276 | } | ||
| 3277 | } | ||
| 3278 | } | ||
| 3279 | return false; | ||
| 3280 | }; | ||
| 3281 | |||
| 3282 | if (generatedTextFiltered(text)) { | ||
| 3283 | swipe_right(); | ||
| 3284 | return; | ||
| 3285 | } | ||
| 3286 | } | ||
| 3287 | playMessageSound(); | 3308 | playMessageSound(); |
| 3288 | } | 3309 | } |
| 3289 | 3310 | ||
| @@ -3291,7 +3312,7 @@ class StreamingProcessor { | |||
| 3291 | this.abortController.abort(); | 3312 | this.abortController.abort(); |
| 3292 | this.isStopped = true; | 3313 | this.isStopped = true; |
| 3293 | 3314 | ||
| 3294 | this.hideMessageButtons(this.messageId); | 3315 | this.markUIGenStopped(); |
| 3295 | generatedPromptCache = ''; | 3316 | generatedPromptCache = ''; |
| 3296 | unblockGeneration(); | 3317 | unblockGeneration(); |
| 3297 | 3318 | ||
| @@ -3317,7 +3338,7 @@ class StreamingProcessor { | |||
| 3317 | } | 3338 | } |
| 3318 | 3339 | ||
| 3319 | /** | 3340 | /** |
| 3320 | * @returns {Generator<{ text: string, swipes: string[], logprobs: import('./scripts/logprobs.js').TokenLogprobs, toolCalls: any[] }, void, void>} | 3341 | * @returns {Generator<{ text: string, swipes: string[], logprobs: import('./scripts/logprobs.js').TokenLogprobs, toolCalls: any[], state: any }, void, void>} |
| 3321 | */ | 3342 | */ |
| 3322 | *nullStreamingGeneration() { | 3343 | *nullStreamingGeneration() { |
| 3323 | throw new Error('Generation function for streaming is not hooked up'); | 3344 | throw new Error('Generation function for streaming is not hooked up'); |
| @@ -3339,10 +3360,10 @@ class StreamingProcessor { | |||
| 3339 | try { | 3360 | try { |
| 3340 | const sw = new Stopwatch(1000 / power_user.streaming_fps); | 3361 | const sw = new Stopwatch(1000 / power_user.streaming_fps); |
| 3341 | const timestamps = []; | 3362 | const timestamps = []; |
| 3342 | for await (const { text, swipes, logprobs, toolCalls } of this.generator()) { | 3363 | for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) { |
| 3343 | timestamps.push(Date.now()); | 3364 | timestamps.push(Date.now()); |
| 3344 | if (this.isStopped) { | 3365 | if (this.isStopped || this.abortController.signal.aborted) { |
| 3345 | return; | 3366 | return this.result; |
| 3346 | } | 3367 | } |
| 3347 | 3368 | ||
| 3348 | this.toolCalls = toolCalls; | 3369 | this.toolCalls = toolCalls; |
| @@ -3351,8 +3372,10 @@ class StreamingProcessor { | |||
| 3351 | if (logprobs) { | 3372 | if (logprobs) { |
| 3352 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); | 3373 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); |
| 3353 | } | 3374 | } |
| 3375 | // Get the updated reasoning string into the handler | ||
| 3376 | this.reasoningHandler.updateReasoning(this.messageId, state?.reasoning ?? ''); | ||
| 3354 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); | 3377 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3355 | await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text)); | 3378 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3356 | } | 3379 | } |
| 3357 | const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000; | 3380 | const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000; |
| 3358 | console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`); | 3381 | console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`); |
| @@ -3428,7 +3451,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3428 | break; | 3451 | break; |
| 3429 | } | 3452 | } |
| 3430 | case 'textgenerationwebui': | 3453 | case 'textgenerationwebui': |
| 3431 | generateData = getTextGenGenerationData(prompt, amount_gen, false, false, null, 'quiet'); | 3454 | generateData = await getTextGenGenerationData(prompt, amount_gen, false, false, null, 'quiet'); |
| 3432 | TempResponseLength.restore(api); | 3455 | TempResponseLength.restore(api); |
| 3433 | break; | 3456 | break; |
| 3434 | case 'openai': { | 3457 | case 'openai': { |
| @@ -3836,6 +3859,27 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3836 | }; | 3859 | }; |
| 3837 | })); | 3860 | })); |
| 3838 | 3861 | ||
| 3862 | const reasoning = new PromptReasoning(); | ||
| 3863 | for (let i = coreChat.length - 1; i >= 0; i--) { | ||
| 3864 | const depth = coreChat.length - i - 1; | ||
| 3865 | const isPrefix = isContinue && i === coreChat.length - 1; | ||
| 3866 | coreChat[i] = { | ||
| 3867 | ...coreChat[i], | ||
| 3868 | mes: reasoning.addToMessage( | ||
| 3869 | coreChat[i].mes, | ||
| 3870 | getRegexedString( | ||
| 3871 | String(coreChat[i].extra?.reasoning ?? ''), | ||
| 3872 | regex_placement.REASONING, | ||
| 3873 | { isPrompt: true, depth: depth }, | ||
| 3874 | ), | ||
| 3875 | isPrefix, | ||
| 3876 | ), | ||
| 3877 | }; | ||
| 3878 | if (reasoning.isLimitReached()) { | ||
| 3879 | break; | ||
| 3880 | } | ||
| 3881 | } | ||
| 3882 | |||
| 3839 | // Determine token limit | 3883 | // Determine token limit |
| 3840 | let this_max_context = getMaxContextSize(); | 3884 | let this_max_context = getMaxContextSize(); |
| 3841 | 3885 | ||
| @@ -4394,7 +4438,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4394 | // For prompt bit itemization | 4438 | // For prompt bit itemization |
| 4395 | let mesSendString = ''; | 4439 | let mesSendString = ''; |
| 4396 | 4440 | ||
| 4397 | function getCombinedPrompt(isNegative) { | 4441 | async function getCombinedPrompt(isNegative) { |
| 4398 | // Only return if the guidance scale doesn't exist or the value is 1 | 4442 | // Only return if the guidance scale doesn't exist or the value is 1 |
| 4399 | // Also don't return if constructing the neutral prompt | 4443 | // Also don't return if constructing the neutral prompt |
| 4400 | if (isNegative && !useCfgPrompt) { | 4444 | if (isNegative && !useCfgPrompt) { |
| @@ -4421,10 +4465,16 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4421 | // TODO: Make all extension prompts use an array/splice method | 4465 | // TODO: Make all extension prompts use an array/splice method |
| 4422 | const lengthDiff = mesSend.length - cfgPrompt.depth; | 4466 | const lengthDiff = mesSend.length - cfgPrompt.depth; |
| 4423 | const cfgDepth = lengthDiff >= 0 ? lengthDiff : 0; | 4467 | const cfgDepth = lengthDiff >= 0 ? lengthDiff : 0; |
| 4468 | const cfgMessage = finalMesSend[cfgDepth]; | ||
| 4469 | if (cfgMessage) { | ||
| 4470 | if (!Array.isArray(finalMesSend[cfgDepth].extensionPrompts)) { | ||
| 4471 | finalMesSend[cfgDepth].extensionPrompts = []; | ||
| 4472 | } | ||
| 4424 | finalMesSend[cfgDepth].extensionPrompts.push(`${cfgPrompt.value}\n`); | 4473 | finalMesSend[cfgDepth].extensionPrompts.push(`${cfgPrompt.value}\n`); |
| 4425 | } | 4474 | } |
| 4426 | } | 4475 | } |
| 4427 | } | 4476 | } |
| 4477 | } | ||
| 4428 | 4478 | ||
| 4429 | // Add prompt bias after everything else | 4479 | // Add prompt bias after everything else |
| 4430 | // Always run with continue | 4480 | // Always run with continue |
| @@ -4497,13 +4547,13 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4497 | }; | 4547 | }; |
| 4498 | 4548 | ||
| 4499 | // Before returning the combined prompt, give available context related information to all subscribers. | 4549 | // Before returning the combined prompt, give available context related information to all subscribers. |
| 4500 | eventSource.emitAndWait(event_types.GENERATE_BEFORE_COMBINE_PROMPTS, data); | 4550 | await eventSource.emit(event_types.GENERATE_BEFORE_COMBINE_PROMPTS, data); |
| 4501 | 4551 | ||
| 4502 | // If one or multiple subscribers return a value, forfeit the responsibillity of flattening the context. | 4552 | // If one or multiple subscribers return a value, forfeit the responsibillity of flattening the context. |
| 4503 | return !data.combinedPrompt ? combine() : data.combinedPrompt; | 4553 | return !data.combinedPrompt ? combine() : data.combinedPrompt; |
| 4504 | } | 4554 | } |
| 4505 | 4555 | ||
| 4506 | let finalPrompt = getCombinedPrompt(false); | 4556 | let finalPrompt = await getCombinedPrompt(false); |
| 4507 | 4557 | ||
| 4508 | const eventData = { prompt: finalPrompt, dryRun: dryRun }; | 4558 | const eventData = { prompt: finalPrompt, dryRun: dryRun }; |
| 4509 | await eventSource.emit(event_types.GENERATE_AFTER_COMBINE_PROMPTS, eventData); | 4559 | await eventSource.emit(event_types.GENERATE_AFTER_COMBINE_PROMPTS, eventData); |
| @@ -4537,8 +4587,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4537 | } | 4587 | } |
| 4538 | break; | 4588 | break; |
| 4539 | case 'textgenerationwebui': { | 4589 | case 'textgenerationwebui': { |
| 4540 | const cfgValues = useCfgPrompt ? { guidanceScale: cfgGuidanceScale, negativePrompt: getCombinedPrompt(true) } : null; | 4590 | const cfgValues = useCfgPrompt ? { guidanceScale: cfgGuidanceScale, negativePrompt: await getCombinedPrompt(true) } : null; |
| 4541 | generate_data = getTextGenGenerationData(finalPrompt, maxLength, isImpersonate, isContinue, cfgValues, type); | 4591 | generate_data = await getTextGenGenerationData(finalPrompt, maxLength, isImpersonate, isContinue, cfgValues, type); |
| 4542 | break; | 4592 | break; |
| 4543 | } | 4593 | } |
| 4544 | case 'novel': { | 4594 | case 'novel': { |
| @@ -4738,11 +4788,17 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4738 | //const getData = await response.json(); | 4788 | //const getData = await response.json(); |
| 4739 | let getMessage = extractMessageFromData(data); | 4789 | let getMessage = extractMessageFromData(data); |
| 4740 | let title = extractTitleFromData(data); | 4790 | let title = extractTitleFromData(data); |
| 4791 | let reasoning = extractReasoningFromData(data); | ||
| 4741 | kobold_horde_model = title; | 4792 | kobold_horde_model = title; |
| 4742 | 4793 | ||
| 4743 | const swipes = extractMultiSwipes(data, type); | 4794 | const swipes = extractMultiSwipes(data, type); |
| 4744 | 4795 | ||
| 4745 | messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); | 4796 | messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false); |
| 4797 | reasoning = getRegexedString(reasoning, regex_placement.REASONING); | ||
| 4798 | |||
| 4799 | if (power_user.trim_spaces) { | ||
| 4800 | reasoning = reasoning.trim(); | ||
| 4801 | } | ||
| 4746 | 4802 | ||
| 4747 | if (isContinue) { | 4803 | if (isContinue) { |
| 4748 | getMessage = continue_mag + getMessage; | 4804 | getMessage = continue_mag + getMessage; |
| @@ -4764,10 +4820,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4764 | else { | 4820 | else { |
| 4765 | // Without streaming we'll be having a full message on continuation. Treat it as a last chunk. | 4821 | // Without streaming we'll be having a full message on continuation. Treat it as a last chunk. |
| 4766 | if (originalType !== 'continue') { | 4822 | if (originalType !== 'continue') { |
| 4767 | ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes)); | 4823 | ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes, reasoning)); |
| 4768 | } | 4824 | } |
| 4769 | else { | 4825 | else { |
| 4770 | ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes)); | 4826 | ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes, reasoning)); |
| 4771 | } | 4827 | } |
| 4772 | 4828 | ||
| 4773 | // This relies on `saveReply` having been called to add the message to the chat, so it must be last. | 4829 | // This relies on `saveReply` having been called to add the message to the chat, so it must be last. |
| @@ -4801,32 +4857,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4801 | } | 4857 | } |
| 4802 | 4858 | ||
| 4803 | const isAborted = abortController && abortController.signal.aborted; | 4859 | const isAborted = abortController && abortController.signal.aborted; |
| 4804 | if (power_user.auto_swipe && !isAborted) { | 4860 | if (!isAborted && power_user.auto_swipe && generatedTextFiltered(getMessage)) { |
| 4805 | console.debug('checking for autoswipeblacklist on non-streaming message'); | ||
| 4806 | function containsBlacklistedWords(getMessage, blacklist, threshold) { | ||
| 4807 | console.debug('checking blacklisted words'); | ||
| 4808 | const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); | ||
| 4809 | const matches = getMessage.match(regex) || []; | ||
| 4810 | return matches.length >= threshold; | ||
| 4811 | } | ||
| 4812 | |||
| 4813 | const generatedTextFiltered = (getMessage) => { | ||
| 4814 | if (power_user.auto_swipe_blacklist_threshold) { | ||
| 4815 | if (containsBlacklistedWords(getMessage, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) { | ||
| 4816 | console.debug('Generated text has blacklisted words'); | ||
| 4817 | return true; | ||
| 4818 | } | ||
| 4819 | } | ||
| 4820 | |||
| 4821 | return false; | ||
| 4822 | }; | ||
| 4823 | if (generatedTextFiltered(getMessage)) { | ||
| 4824 | console.debug('swiping right automatically'); | ||
| 4825 | is_send_press = false; | 4861 | is_send_press = false; |
| 4826 | swipe_right(); | 4862 | return swipe_right(); |
| 4827 | // TODO: do we want to resolve after an auto-swipe? | ||
| 4828 | return; | ||
| 4829 | } | ||
| 4830 | } | 4863 | } |
| 4831 | 4864 | ||
| 4832 | console.debug('/api/chats/save called by /Generate'); | 4865 | console.debug('/api/chats/save called by /Generate'); |
| @@ -5481,7 +5514,7 @@ async function promptItemize(itemizedPrompts, requestedMesId) { | |||
| 5481 | toastr.info(t`Copied!`); | 5514 | toastr.info(t`Copied!`); |
| 5482 | }); | 5515 | }); |
| 5483 | 5516 | ||
| 5484 | popup.dlg.querySelector('#showRawPrompt').addEventListener('click', function () { | 5517 | popup.dlg.querySelector('#showRawPrompt').addEventListener('click', async function () { |
| 5485 | //console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); | 5518 | //console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); |
| 5486 | console.log(PromptArrayItemForRawPromptDisplay); | 5519 | console.log(PromptArrayItemForRawPromptDisplay); |
| 5487 | console.log(itemizedPrompts); | 5520 | console.log(itemizedPrompts); |
| @@ -5489,6 +5522,17 @@ async function promptItemize(itemizedPrompts, requestedMesId) { | |||
| 5489 | 5522 | ||
| 5490 | const rawPrompt = flatten(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); | 5523 | const rawPrompt = flatten(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); |
| 5491 | 5524 | ||
| 5525 | // Mobile needs special handholding. The side-view on the popup wouldn't work, | ||
| 5526 | // so we just show an additional popup for this. | ||
| 5527 | if (isMobile()) { | ||
| 5528 | const content = document.createElement('div'); | ||
| 5529 | content.classList.add('tokenItemizingMaintext'); | ||
| 5530 | content.innerText = rawPrompt; | ||
| 5531 | const popup = new Popup(content, POPUP_TYPE.TEXT, null, { allowVerticalScrolling: true, leftAlign: true }); | ||
| 5532 | await popup.show(); | ||
| 5533 | return; | ||
| 5534 | } | ||
| 5535 | |||
| 5492 | //let DisplayStringifiedPrompt = JSON.stringify(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt).replace(/\n+/g, '<br>'); | 5536 | //let DisplayStringifiedPrompt = JSON.stringify(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt).replace(/\n+/g, '<br>'); |
| 5493 | const rawPromptWrapper = document.getElementById('rawPromptWrapper'); | 5537 | const rawPromptWrapper = document.getElementById('rawPromptWrapper'); |
| 5494 | rawPromptWrapper.innerText = rawPrompt; | 5538 | rawPromptWrapper.innerText = rawPrompt; |
| @@ -5851,7 +5895,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc | |||
| 5851 | return getMessage; | 5895 | return getMessage; |
| 5852 | } | 5896 | } |
| 5853 | 5897 | ||
| 5854 | export async function saveReply(type, getMessage, fromStreaming, title, swipes) { | 5898 | export async function saveReply(type, getMessage, fromStreaming, title, swipes, reasoning) { |
| 5855 | if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined || | 5899 | if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined || |
| 5856 | chat[chat.length - 1]['is_user'])) { | 5900 | chat[chat.length - 1]['is_user'])) { |
| 5857 | type = 'normal'; | 5901 | type = 'normal'; |
| @@ -5861,6 +5905,15 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 5861 | chat[chat.length - 1]['extra'] = {}; | 5905 | chat[chat.length - 1]['extra'] = {}; |
| 5862 | } | 5906 | } |
| 5863 | 5907 | ||
| 5908 | // Coerce null/undefined to empty string | ||
| 5909 | if (chat.length && !chat[chat.length - 1]['extra']['reasoning']) { | ||
| 5910 | chat[chat.length - 1]['extra']['reasoning'] = ''; | ||
| 5911 | } | ||
| 5912 | |||
| 5913 | if (!reasoning) { | ||
| 5914 | reasoning = ''; | ||
| 5915 | } | ||
| 5916 | |||
| 5864 | let oldMessage = ''; | 5917 | let oldMessage = ''; |
| 5865 | const generationFinished = new Date(); | 5918 | const generationFinished = new Date(); |
| 5866 | const img = extractImageFromMessage(getMessage); | 5919 | const img = extractImageFromMessage(getMessage); |
| @@ -5876,8 +5929,11 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 5876 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); | 5929 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5877 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); | 5930 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5878 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); | 5931 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5932 | chat[chat.length - 1]['extra']['reasoning'] = reasoning; | ||
| 5933 | chat[chat.length - 1]['extra']['reasoning_duration'] = null; | ||
| 5879 | if (power_user.message_token_count_enabled) { | 5934 | if (power_user.message_token_count_enabled) { |
| 5880 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0); | 5935 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| 5936 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | ||
| 5881 | } | 5937 | } |
| 5882 | const chat_id = (chat.length - 1); | 5938 | const chat_id = (chat.length - 1); |
| 5883 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); | 5939 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); |
| @@ -5896,8 +5952,11 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 5896 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); | 5952 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5897 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); | 5953 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5898 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); | 5954 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5955 | chat[chat.length - 1]['extra']['reasoning'] = reasoning; | ||
| 5956 | chat[chat.length - 1]['extra']['reasoning_duration'] = null; | ||
| 5899 | if (power_user.message_token_count_enabled) { | 5957 | if (power_user.message_token_count_enabled) { |
| 5900 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0); | 5958 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| 5959 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | ||
| 5901 | } | 5960 | } |
| 5902 | const chat_id = (chat.length - 1); | 5961 | const chat_id = (chat.length - 1); |
| 5903 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); | 5962 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); |
| @@ -5913,8 +5972,11 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 5913 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); | 5972 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5914 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); | 5973 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5915 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); | 5974 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5975 | chat[chat.length - 1]['extra']['reasoning'] += reasoning; | ||
| 5976 | // We don't know if the reasoning duration extended, so we don't update it here on purpose. | ||
| 5916 | if (power_user.message_token_count_enabled) { | 5977 | if (power_user.message_token_count_enabled) { |
| 5917 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0); | 5978 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| 5979 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | ||
| 5918 | } | 5980 | } |
| 5919 | const chat_id = (chat.length - 1); | 5981 | const chat_id = (chat.length - 1); |
| 5920 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); | 5982 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); |
| @@ -5930,6 +5992,8 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 5930 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); | 5992 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5931 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); | 5993 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5932 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); | 5994 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5995 | chat[chat.length - 1]['extra']['reasoning'] = reasoning; | ||
| 5996 | chat[chat.length - 1]['extra']['reasoning_duration'] = null; | ||
| 5933 | if (power_user.trim_spaces) { | 5997 | if (power_user.trim_spaces) { |
| 5934 | getMessage = getMessage.trim(); | 5998 | getMessage = getMessage.trim(); |
| 5935 | } | 5999 | } |
| @@ -5939,7 +6003,8 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 5939 | chat[chat.length - 1]['gen_finished'] = generationFinished; | 6003 | chat[chat.length - 1]['gen_finished'] = generationFinished; |
| 5940 | 6004 | ||
| 5941 | if (power_user.message_token_count_enabled) { | 6005 | if (power_user.message_token_count_enabled) { |
| 5942 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0); | 6006 | const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes']; |
| 6007 | chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | ||
| 5943 | } | 6008 | } |
| 5944 | 6009 | ||
| 5945 | if (selected_group) { | 6010 | if (selected_group) { |
| @@ -6004,6 +6069,19 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | |||
| 6004 | return { type, getMessage }; | 6069 | return { type, getMessage }; |
| 6005 | } | 6070 | } |
| 6006 | 6071 | ||
| 6072 | export function syncCurrentSwipeInfoExtras() { | ||
| 6073 | if (!chat.length) { | ||
| 6074 | return; | ||
| 6075 | } | ||
| 6076 | const currentMessage = chat[chat.length - 1]; | ||
| 6077 | if (currentMessage && Array.isArray(currentMessage.swipe_info) && typeof currentMessage.swipe_id === 'number') { | ||
| 6078 | const swipeInfo = currentMessage.swipe_info[currentMessage.swipe_id]; | ||
| 6079 | if (swipeInfo && typeof swipeInfo === 'object') { | ||
| 6080 | swipeInfo.extra = structuredClone(currentMessage.extra); | ||
| 6081 | } | ||
| 6082 | } | ||
| 6083 | } | ||
| 6084 | |||
| 6007 | function saveImageToMessage(img, mes) { | 6085 | function saveImageToMessage(img, mes) { |
| 6008 | if (mes && img.image) { | 6086 | if (mes && img.image) { |
| 6009 | if (!mes.extra || typeof mes.extra !== 'object') { | 6087 | if (!mes.extra || typeof mes.extra !== 'object') { |
| @@ -6056,20 +6134,21 @@ function extractImageFromMessage(getMessage) { | |||
| 6056 | return { getMessage, image, title }; | 6134 | return { getMessage, image, title }; |
| 6057 | } | 6135 | } |
| 6058 | 6136 | ||
| 6137 | /** | ||
| 6138 | * A function mainly used to switch 'generating' state - setting it to false and activating the buttons again | ||
| 6139 | */ | ||
| 6059 | export function activateSendButtons() { | 6140 | export function activateSendButtons() { |
| 6060 | is_send_press = false; | 6141 | is_send_press = false; |
| 6061 | $('#send_but').removeClass('displayNone'); | ||
| 6062 | $('#mes_continue').removeClass('displayNone'); | ||
| 6063 | $('#mes_impersonate').removeClass('displayNone'); | ||
| 6064 | $('.mes_buttons:last').show(); | ||
| 6065 | hideStopButton(); | 6142 | hideStopButton(); |
| 6143 | delete document.body.dataset.generating; | ||
| 6066 | } | 6144 | } |
| 6067 | 6145 | ||
| 6146 | /** | ||
| 6147 | * A function mainly used to switch 'generating' state - setting it to true and deactivating the buttons | ||
| 6148 | */ | ||
| 6068 | export function deactivateSendButtons() { | 6149 | export function deactivateSendButtons() { |
| 6069 | $('#send_but').addClass('displayNone'); | ||
| 6070 | $('#mes_continue').addClass('displayNone'); | ||
| 6071 | $('#mes_impersonate').addClass('displayNone'); | ||
| 6072 | showStopButton(); | 6150 | showStopButton(); |
| 6151 | document.body.dataset.generating = 'true'; | ||
| 6073 | } | 6152 | } |
| 6074 | 6153 | ||
| 6075 | export function resetChatState() { | 6154 | export function resetChatState() { |
| @@ -6765,10 +6844,11 @@ export async function getSettings() { | |||
| 6765 | $('#your_name').val(name1); | 6844 | $('#your_name').val(name1); |
| 6766 | } | 6845 | } |
| 6767 | 6846 | ||
| 6847 | accountStorage.init(settings?.accountStorage); | ||
| 6768 | await setUserControls(data.enable_accounts); | 6848 | await setUserControls(data.enable_accounts); |
| 6769 | 6849 | ||
| 6770 | // Allow subscribers to mutate settings | 6850 | // Allow subscribers to mutate settings |
| 6771 | eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); | 6851 | await eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); |
| 6772 | 6852 | ||
| 6773 | //Load KoboldAI settings | 6853 | //Load KoboldAI settings |
| 6774 | koboldai_setting_names = data.koboldai_setting_names; | 6854 | koboldai_setting_names = data.koboldai_setting_names; |
| @@ -6865,7 +6945,7 @@ export async function getSettings() { | |||
| 6865 | loadProxyPresets(settings); | 6945 | loadProxyPresets(settings); |
| 6866 | 6946 | ||
| 6867 | // Allow subscribers to mutate settings | 6947 | // Allow subscribers to mutate settings |
| 6868 | eventSource.emit(event_types.SETTINGS_LOADED_AFTER, settings); | 6948 | await eventSource.emit(event_types.SETTINGS_LOADED_AFTER, settings); |
| 6869 | 6949 | ||
| 6870 | // Set context size after loading power user (may override the max value) | 6950 | // Set context size after loading power user (may override the max value) |
| 6871 | $('#max_context').val(max_context); | 6951 | $('#max_context').val(max_context); |
| @@ -6925,7 +7005,7 @@ export async function getSettings() { | |||
| 6925 | } | 7005 | } |
| 6926 | await validateDisabledSamplers(); | 7006 | await validateDisabledSamplers(); |
| 6927 | settingsReady = true; | 7007 | settingsReady = true; |
| 6928 | eventSource.emit(event_types.SETTINGS_LOADED); | 7008 | await eventSource.emit(event_types.SETTINGS_LOADED); |
| 6929 | } | 7009 | } |
| 6930 | 7010 | ||
| 6931 | function selectKoboldGuiPreset() { | 7011 | function selectKoboldGuiPreset() { |
| @@ -6936,7 +7016,8 @@ function selectKoboldGuiPreset() { | |||
| 6936 | 7016 | ||
| 6937 | export async function saveSettings(loopCounter = 0) { | 7017 | export async function saveSettings(loopCounter = 0) { |
| 6938 | if (!settingsReady) { | 7018 | if (!settingsReady) { |
| 6939 | console.warn('Settings not ready, aborting save'); | 7019 | console.warn('Settings not ready, scheduling another save'); |
| 7020 | saveSettingsDebounced(); | ||
| 6940 | return; | 7021 | return; |
| 6941 | } | 7022 | } |
| 6942 | 7023 | ||
| @@ -6957,6 +7038,7 @@ export async function saveSettings(loopCounter = 0) { | |||
| 6957 | url: '/api/settings/save', | 7038 | url: '/api/settings/save', |
| 6958 | data: JSON.stringify({ | 7039 | data: JSON.stringify({ |
| 6959 | firstRun: firstRun, | 7040 | firstRun: firstRun, |
| 7041 | accountStorage: accountStorage.getState(), | ||
| 6960 | currentVersion: currentVersion, | 7042 | currentVersion: currentVersion, |
| 6961 | username: name1, | 7043 | username: name1, |
| 6962 | active_character: active_character, | 7044 | active_character: active_character, |
| @@ -7022,8 +7104,10 @@ export function setGenerationParamsFromPreset(preset) { | |||
| 7022 | // Common code for message editor done and auto-save | 7104 | // Common code for message editor done and auto-save |
| 7023 | function updateMessage(div) { | 7105 | function updateMessage(div) { |
| 7024 | const mesBlock = div.closest('.mes_block'); | 7106 | const mesBlock = div.closest('.mes_block'); |
| 7025 | let text = mesBlock.find('.edit_textarea').val(); | 7107 | let text = mesBlock.find('.edit_textarea').val() |
| 7026 | const mes = chat[this_edit_mes_id]; | 7108 | ?? mesBlock.find('.mes_text').text(); |
| 7109 | const mesElement = div.closest('.mes'); | ||
| 7110 | const mes = chat[mesElement.attr('mesid')]; | ||
| 7027 | 7111 | ||
| 7028 | let regexPlacement; | 7112 | let regexPlacement; |
| 7029 | if (mes.is_user) { | 7113 | if (mes.is_user) { |
| @@ -7107,9 +7191,11 @@ function messageEditAuto(div) { | |||
| 7107 | mes.is_system, | 7191 | mes.is_system, |
| 7108 | mes.is_user, | 7192 | mes.is_user, |
| 7109 | this_edit_mes_id, | 7193 | this_edit_mes_id, |
| 7194 | {}, | ||
| 7195 | false, | ||
| 7110 | )); | 7196 | )); |
| 7111 | mesBlock.find('.mes_bias').empty(); | 7197 | mesBlock.find('.mes_bias').empty(); |
| 7112 | mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1)); | 7198 | mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1, {}, false)); |
| 7113 | saveChatDebounced(); | 7199 | saveChatDebounced(); |
| 7114 | } | 7200 | } |
| 7115 | 7201 | ||
| @@ -7131,13 +7217,20 @@ async function messageEditDone(div) { | |||
| 7131 | mes.is_system, | 7217 | mes.is_system, |
| 7132 | mes.is_user, | 7218 | mes.is_user, |
| 7133 | this_edit_mes_id, | 7219 | this_edit_mes_id, |
| 7220 | {}, | ||
| 7221 | false, | ||
| 7134 | ), | 7222 | ), |
| 7135 | ); | 7223 | ); |
| 7136 | mesBlock.find('.mes_bias').empty(); | 7224 | mesBlock.find('.mes_bias').empty(); |
| 7137 | mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1)); | 7225 | mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1, {}, false)); |
| 7138 | appendMediaToMessage(mes, div.closest('.mes')); | 7226 | appendMediaToMessage(mes, div.closest('.mes')); |
| 7139 | addCopyToCodeBlocks(div.closest('.mes')); | 7227 | addCopyToCodeBlocks(div.closest('.mes')); |
| 7140 | 7228 | ||
| 7229 | const reasoningEditDone = mesBlock.find('.mes_reasoning_edit_done:visible'); | ||
| 7230 | if (reasoningEditDone.length > 0) { | ||
| 7231 | reasoningEditDone.trigger('click'); | ||
| 7232 | } | ||
| 7233 | |||
| 7141 | await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id); | 7234 | await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id); |
| 7142 | this_edit_mes_id = undefined; | 7235 | this_edit_mes_id = undefined; |
| 7143 | await saveChatConditional(); | 7236 | await saveChatConditional(); |
| @@ -7401,7 +7494,7 @@ export function select_rm_info(type, charId, previousCharId = null) { | |||
| 7401 | } | 7494 | } |
| 7402 | 7495 | ||
| 7403 | try { | 7496 | try { |
| 7404 | const perPage = Number(localStorage.getItem('Characters_PerPage')) || per_page_default; | 7497 | const perPage = Number(accountStorage.getItem('Characters_PerPage')) || per_page_default; |
| 7405 | const page = Math.floor(charIndex / perPage) + 1; | 7498 | const page = Math.floor(charIndex / perPage) + 1; |
| 7406 | const selector = `#rm_print_characters_block [title*="${avatarFileName}"]`; | 7499 | const selector = `#rm_print_characters_block [title*="${avatarFileName}"]`; |
| 7407 | $('#rm_print_characters_pagination').pagination('go', page); | 7500 | $('#rm_print_characters_pagination').pagination('go', page); |
| @@ -7433,7 +7526,7 @@ export function select_rm_info(type, charId, previousCharId = null) { | |||
| 7433 | return; | 7526 | return; |
| 7434 | } | 7527 | } |
| 7435 | 7528 | ||
| 7436 | const perPage = Number(localStorage.getItem('Characters_PerPage')) || per_page_default; | 7529 | const perPage = Number(accountStorage.getItem('Characters_PerPage')) || per_page_default; |
| 7437 | const page = Math.floor(charIndex / perPage) + 1; | 7530 | const page = Math.floor(charIndex / perPage) + 1; |
| 7438 | $('#rm_print_characters_pagination').pagination('go', page); | 7531 | $('#rm_print_characters_pagination').pagination('go', page); |
| 7439 | const selector = `#rm_print_characters_block [grid="${charId}"]`; | 7532 | const selector = `#rm_print_characters_block [grid="${charId}"]`; |
| @@ -7982,11 +8075,25 @@ function updateEditArrowClasses() { | |||
| 7982 | } | 8075 | } |
| 7983 | } | 8076 | } |
| 7984 | 8077 | ||
| 7985 | function closeMessageEditor() { | 8078 | /** |
| 8079 | * Closes the message editor. | ||
| 8080 | * @param {'message'|'reasoning'|'all'} what What to close. Default is 'all'. | ||
| 8081 | */ | ||
| 8082 | export function closeMessageEditor(what = 'all') { | ||
| 8083 | if (what === 'message' || what === 'all') { | ||
| 7986 | if (this_edit_mes_id) { | 8084 | if (this_edit_mes_id) { |
| 7987 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click(); | 8085 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click(); |
| 7988 | } | 8086 | } |
| 7989 | } | 8087 | } |
| 8088 | if (what === 'reasoning' || what === 'all') { | ||
| 8089 | document.querySelectorAll('.reasoning_edit_textarea').forEach((el) => { | ||
| 8090 | const cancelButton = el.closest('.mes')?.querySelector('.mes_reasoning_edit_cancel'); | ||
| 8091 | if (cancelButton instanceof HTMLElement) { | ||
| 8092 | cancelButton.click(); | ||
| 8093 | } | ||
| 8094 | }); | ||
| 8095 | } | ||
| 8096 | } | ||
| 7990 | 8097 | ||
| 7991 | export function setGenerationProgress(progress) { | 8098 | export function setGenerationProgress(progress) { |
| 7992 | if (!progress) { | 8099 | if (!progress) { |
| @@ -8417,6 +8524,9 @@ function swipe_left() { // when we swipe left..but no generation. | |||
| 8417 | streamingProcessor.onStopStreaming(); | 8524 | streamingProcessor.onStopStreaming(); |
| 8418 | } | 8525 | } |
| 8419 | 8526 | ||
| 8527 | // Make sure ad-hoc changes to extras are saved before swiping away | ||
| 8528 | syncCurrentSwipeInfoExtras(); | ||
| 8529 | |||
| 8420 | const swipe_duration = 120; | 8530 | const swipe_duration = 120; |
| 8421 | const swipe_range = '700px'; | 8531 | const swipe_range = '700px'; |
| 8422 | chat[chat.length - 1]['swipe_id']--; | 8532 | chat[chat.length - 1]['swipe_id']--; |
| @@ -8468,7 +8578,8 @@ function swipe_left() { // when we swipe left..but no generation. | |||
| 8468 | } | 8578 | } |
| 8469 | 8579 | ||
| 8470 | const swipeMessage = $('#chat').find(`[mesid="${chat.length - 1}"]`); | 8580 | const swipeMessage = $('#chat').find(`[mesid="${chat.length - 1}"]`); |
| 8471 | const tokenCount = await getTokenCountAsync(chat[chat.length - 1].mes, 0); | 8581 | const tokenCountText = (chat[chat.length - 1]?.extra?.reasoning || '') + chat[chat.length - 1].mes; |
| 8582 | const tokenCount = await getTokenCountAsync(tokenCountText, 0); | ||
| 8472 | chat[chat.length - 1]['extra']['token_count'] = tokenCount; | 8583 | chat[chat.length - 1]['extra']['token_count'] = tokenCount; |
| 8473 | swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`); | 8584 | swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`); |
| 8474 | } | 8585 | } |
| @@ -8551,6 +8662,9 @@ const swipe_right = () => { | |||
| 8551 | return unblockGeneration(); | 8662 | return unblockGeneration(); |
| 8552 | } | 8663 | } |
| 8553 | 8664 | ||
| 8665 | // Make sure ad-hoc changes to extras are saved before swiping away | ||
| 8666 | syncCurrentSwipeInfoExtras(); | ||
| 8667 | |||
| 8554 | const swipe_duration = 200; | 8668 | const swipe_duration = 200; |
| 8555 | const swipe_range = 700; | 8669 | const swipe_range = 700; |
| 8556 | //console.log(swipe_range); | 8670 | //console.log(swipe_range); |
| @@ -8617,11 +8731,6 @@ const swipe_right = () => { | |||
| 8617 | easing: animation_easing, | 8731 | easing: animation_easing, |
| 8618 | queue: false, | 8732 | queue: false, |
| 8619 | complete: async function () { | 8733 | complete: async function () { |
| 8620 | /*if (!selected_group) { | ||
| 8621 | var typingIndicator = $("#typing_indicator_template .typing_indicator").clone(); | ||
| 8622 | typingIndicator.find(".typing_indicator_name").text(characters[this_chid].name); | ||
| 8623 | } */ | ||
| 8624 | /* $("#chat").append(typingIndicator); */ | ||
| 8625 | const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop('scrollHeight') - $('#chat').outerHeight()) - 10); | 8734 | const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop('scrollHeight') - $('#chat').outerHeight()) - 10); |
| 8626 | //console.log(parseInt(chat[chat.length-1]['swipe_id'])); | 8735 | //console.log(parseInt(chat[chat.length-1]['swipe_id'])); |
| 8627 | //console.log(chat[chat.length-1]['swipes'].length); | 8736 | //console.log(chat[chat.length-1]['swipes'].length); |
| @@ -8632,6 +8741,7 @@ const swipe_right = () => { | |||
| 8632 | // resets the timer | 8741 | // resets the timer |
| 8633 | swipeMessage.find('.mes_timer').html(''); | 8742 | swipeMessage.find('.mes_timer').html(''); |
| 8634 | swipeMessage.find('.tokenCounterDisplay').text(''); | 8743 | swipeMessage.find('.tokenCounterDisplay').text(''); |
| 8744 | updateReasoningUI(swipeMessage, { reset: true }); | ||
| 8635 | } else { | 8745 | } else { |
| 8636 | //console.log('showing previously generated swipe candidate, or "..."'); | 8746 | //console.log('showing previously generated swipe candidate, or "..."'); |
| 8637 | //console.log('onclick right swipe calling addOneMessage'); | 8747 | //console.log('onclick right swipe calling addOneMessage'); |
| @@ -8642,7 +8752,8 @@ const swipe_right = () => { | |||
| 8642 | chat[chat.length - 1].extra = {}; | 8752 | chat[chat.length - 1].extra = {}; |
| 8643 | } | 8753 | } |
| 8644 | 8754 | ||
| 8645 | const tokenCount = await getTokenCountAsync(chat[chat.length - 1].mes, 0); | 8755 | const tokenCountText = (chat[chat.length - 1]?.extra?.reasoning || '') + chat[chat.length - 1].mes; |
| 8756 | const tokenCount = await getTokenCountAsync(tokenCountText, 0); | ||
| 8646 | chat[chat.length - 1]['extra']['token_count'] = tokenCount; | 8757 | chat[chat.length - 1]['extra']['token_count'] = tokenCount; |
| 8647 | swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`); | 8758 | swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`); |
| 8648 | } | 8759 | } |
| @@ -8680,7 +8791,6 @@ const swipe_right = () => { | |||
| 8680 | if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { | 8791 | if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { |
| 8681 | console.debug('caught here 2'); | 8792 | console.debug('caught here 2'); |
| 8682 | is_send_press = true; | 8793 | is_send_press = true; |
| 8683 | $('.mes_buttons:last').hide(); | ||
| 8684 | await Generate('swipe'); | 8794 | await Generate('swipe'); |
| 8685 | } else { | 8795 | } else { |
| 8686 | if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) { | 8796 | if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) { |
| @@ -9282,6 +9392,9 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {}) | |||
| 9282 | continue; | 9392 | continue; |
| 9283 | } | 9393 | } |
| 9284 | 9394 | ||
| 9395 | accountStorage.removeItem(`AlertWI_${character.avatar}`); | ||
| 9396 | accountStorage.removeItem(`AlertRegex_${character.avatar}`); | ||
| 9397 | accountStorage.removeItem(`mediaWarningShown:${character.avatar}`); | ||
| 9285 | delete tag_map[character.avatar]; | 9398 | delete tag_map[character.avatar]; |
| 9286 | select_rm_info('char_delete', character.name); | 9399 | select_rm_info('char_delete', character.name); |
| 9287 | 9400 | ||
| @@ -9444,7 +9557,8 @@ function addDebugFunctions() { | |||
| 9444 | message.extra = {}; | 9557 | message.extra = {}; |
| 9445 | } | 9558 | } |
| 9446 | 9559 | ||
| 9447 | message.extra.token_count = await getTokenCountAsync(message.mes, 0); | 9560 | const tokenCountText = (message?.extra?.reasoning || '') + message.mes; |
| 9561 | message.extra.token_count = await getTokenCountAsync(tokenCountText, 0); | ||
| 9448 | } | 9562 | } |
| 9449 | 9563 | ||
| 9450 | await saveChatConditional(); | 9564 | await saveChatConditional(); |
| @@ -9483,8 +9597,8 @@ function addDebugFunctions() { | |||
| 9483 | }); | 9597 | }); |
| 9484 | 9598 | ||
| 9485 | registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => { | 9599 | registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => { |
| 9486 | localStorage.setItem('RegenerateWithCtrlEnter', localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true'); | 9600 | accountStorage.setItem('RegenerateWithCtrlEnter', accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true'); |
| 9487 | toastr.info('Regenerate warning is now ' + (localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled')); | 9601 | toastr.info('Regenerate warning is now ' + (accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled')); |
| 9488 | }); | 9602 | }); |
| 9489 | 9603 | ||
| 9490 | registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => { | 9604 | registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => { |
| @@ -10626,6 +10740,12 @@ jQuery(async function () { | |||
| 10626 | var edit_mes_id = $(this).closest('.mes').attr('mesid'); | 10740 | var edit_mes_id = $(this).closest('.mes').attr('mesid'); |
| 10627 | this_edit_mes_id = edit_mes_id; | 10741 | this_edit_mes_id = edit_mes_id; |
| 10628 | 10742 | ||
| 10743 | // Also edit reasoning, if it exists | ||
| 10744 | const reasoningEdit = $(this).closest('.mes_block').find('.mes_reasoning_edit:visible'); | ||
| 10745 | if (reasoningEdit.length > 0) { | ||
| 10746 | reasoningEdit.trigger('click'); | ||
| 10747 | } | ||
| 10748 | |||
| 10629 | var text = chat[edit_mes_id]['mes']; | 10749 | var text = chat[edit_mes_id]['mes']; |
| 10630 | if (chat[edit_mes_id]['is_user']) { | 10750 | if (chat[edit_mes_id]['is_user']) { |
| 10631 | this_edit_mes_chname = name1; | 10751 | this_edit_mes_chname = name1; |
| @@ -10753,10 +10873,17 @@ jQuery(async function () { | |||
| 10753 | chat[this_edit_mes_id].is_system, | 10873 | chat[this_edit_mes_id].is_system, |
| 10754 | chat[this_edit_mes_id].is_user, | 10874 | chat[this_edit_mes_id].is_user, |
| 10755 | this_edit_mes_id, | 10875 | this_edit_mes_id, |
| 10876 | {}, | ||
| 10877 | false, | ||
| 10756 | )); | 10878 | )); |
| 10757 | appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes')); | 10879 | appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes')); |
| 10758 | addCopyToCodeBlocks($(this).closest('.mes')); | 10880 | addCopyToCodeBlocks($(this).closest('.mes')); |
| 10759 | 10881 | ||
| 10882 | const reasoningEditDone = $(this).closest('.mes_block').find('.mes_reasoning_edit_cancel:visible'); | ||
| 10883 | if (reasoningEditDone.length > 0) { | ||
| 10884 | reasoningEditDone.trigger('click'); | ||
| 10885 | } | ||
| 10886 | |||
| 10760 | await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id); | 10887 | await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id); |
| 10761 | this_edit_mes_id = undefined; | 10888 | this_edit_mes_id = undefined; |
| 10762 | }); | 10889 | }); |
| @@ -11213,14 +11340,15 @@ jQuery(async function () { | |||
| 11213 | 11340 | ||
| 11214 | $(document).keyup(function (e) { | 11341 | $(document).keyup(function (e) { |
| 11215 | if (e.key === 'Escape') { | 11342 | if (e.key === 'Escape') { |
| 11216 | const isEditVisible = $('#curEditTextarea').is(':visible'); | 11343 | const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0; |
| 11217 | if (isEditVisible && power_user.auto_save_msg_edits === false) { | 11344 | if (isEditVisible && power_user.auto_save_msg_edits === false) { |
| 11218 | closeMessageEditor(); | 11345 | closeMessageEditor('all'); |
| 11219 | $('#send_textarea').focus(); | 11346 | $('#send_textarea').focus(); |
| 11220 | return; | 11347 | return; |
| 11221 | } | 11348 | } |
| 11222 | if (isEditVisible && power_user.auto_save_msg_edits === true) { | 11349 | if (isEditVisible && power_user.auto_save_msg_edits === true) { |
| 11223 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click(); | 11350 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click(); |
| 11351 | closeMessageEditor('reasoning'); | ||
| 11224 | $('#send_textarea').focus(); | 11352 | $('#send_textarea').focus(); |
| 11225 | return; | 11353 | return; |
| 11226 | } | 11354 | } |
| @@ -11298,7 +11426,7 @@ jQuery(async function () { | |||
| 11298 | ); | 11426 | ); |
| 11299 | break;*/ | 11427 | break;*/ |
| 11300 | default: | 11428 | default: |
| 11301 | eventSource.emit('charManagementDropdown', target); | 11429 | await eventSource.emit('charManagementDropdown', target); |
| 11302 | } | 11430 | } |
| 11303 | $('#char-management-dropdown').prop('selectedIndex', 0); | 11431 | $('#char-management-dropdown').prop('selectedIndex', 0); |
| 11304 | }); | 11432 | }); |
| @@ -11454,13 +11582,13 @@ jQuery(async function () { | |||
| 11454 | $('#avatar-and-name-block').slideToggle(); | 11582 | $('#avatar-and-name-block').slideToggle(); |
| 11455 | }); | 11583 | }); |
| 11456 | 11584 | ||
| 11457 | $(document).on('mouseup touchend', '#show_more_messages', () => { | 11585 | $(document).on('mouseup touchend', '#show_more_messages', async function () { |
| 11458 | showMoreMessages(); | 11586 | await showMoreMessages(); |
| 11459 | }); | 11587 | }); |
| 11460 | 11588 | ||
| 11461 | $(document).on('click', '.open_characters_library', async function () { | 11589 | $(document).on('click', '.open_characters_library', async function () { |
| 11462 | await getCharacters(); | 11590 | await getCharacters(); |
| 11463 | eventSource.emit(event_types.OPEN_CHARACTER_LIBRARY); | 11591 | await eventSource.emit(event_types.OPEN_CHARACTER_LIBRARY); |
| 11464 | }); | 11592 | }); |
| 11465 | 11593 | ||
| 11466 | // Added here to prevent execution before script.js is loaded and get rid of quirky timeouts | 11594 | // Added here to prevent execution before script.js is loaded and get rid of quirky timeouts |
| @@ -27,7 +27,6 @@ import { | |||
| 27 | send_on_enter_options, | 27 | send_on_enter_options, |
| 28 | } from './power-user.js'; | 28 | } from './power-user.js'; |
| 29 | 29 | ||
| 30 | import { LoadLocal, SaveLocal, LoadLocalBool } from './f-localStorage.js'; | ||
| 31 | import { selected_group, is_group_generating, openGroupById } from './group-chats.js'; | 30 | import { selected_group, is_group_generating, openGroupById } from './group-chats.js'; |
| 32 | import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js'; | 31 | import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js'; |
| 33 | import { | 32 | import { |
| @@ -41,6 +40,8 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex | |||
| 41 | import { debounce_timeout } from './constants.js'; | 40 | import { debounce_timeout } from './constants.js'; |
| 42 | 41 | ||
| 43 | import { Popup } from './popup.js'; | 42 | import { Popup } from './popup.js'; |
| 43 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 44 | import { getCurrentUserHandle } from './user.js'; | ||
| 44 | 45 | ||
| 45 | var RPanelPin = document.getElementById('rm_button_panel_pin'); | 46 | var RPanelPin = document.getElementById('rm_button_panel_pin'); |
| 46 | var LPanelPin = document.getElementById('lm_button_panel_pin'); | 47 | var LPanelPin = document.getElementById('lm_button_panel_pin'); |
| @@ -409,32 +410,34 @@ function RA_autoconnect(PrevApi) { | |||
| 409 | function OpenNavPanels() { | 410 | function OpenNavPanels() { |
| 410 | if (!isMobile()) { | 411 | if (!isMobile()) { |
| 411 | //auto-open R nav if locked and previously open | 412 | //auto-open R nav if locked and previously open |
| 412 | if (LoadLocalBool('NavLockOn') == true && LoadLocalBool('NavOpened') == true) { | 413 | if (accountStorage.getItem('NavLockOn') == 'true' && accountStorage.getItem('NavOpened') == 'true') { |
| 413 | //console.log("RA -- clicking right nav to open"); | 414 | //console.log("RA -- clicking right nav to open"); |
| 414 | $('#rightNavDrawerIcon').click(); | 415 | $('#rightNavDrawerIcon').click(); |
| 415 | } | 416 | } |
| 416 | 417 | ||
| 417 | //auto-open L nav if locked and previously open | 418 | //auto-open L nav if locked and previously open |
| 418 | if (LoadLocalBool('LNavLockOn') == true && LoadLocalBool('LNavOpened') == true) { | 419 | if (accountStorage.getItem('LNavLockOn') == 'true' && accountStorage.getItem('LNavOpened') == 'true') { |
| 419 | console.debug('RA -- clicking left nav to open'); | 420 | console.debug('RA -- clicking left nav to open'); |
| 420 | $('#leftNavDrawerIcon').click(); | 421 | $('#leftNavDrawerIcon').click(); |
| 421 | } | 422 | } |
| 422 | 423 | ||
| 423 | //auto-open WI if locked and previously open | 424 | //auto-open WI if locked and previously open |
| 424 | if (LoadLocalBool('WINavLockOn') == true && LoadLocalBool('WINavOpened') == true) { | 425 | if (accountStorage.getItem('WINavLockOn') == 'true' && accountStorage.getItem('WINavOpened') == 'true') { |
| 425 | console.debug('RA -- clicking WI to open'); | 426 | console.debug('RA -- clicking WI to open'); |
| 426 | $('#WIDrawerIcon').click(); | 427 | $('#WIDrawerIcon').click(); |
| 427 | } | 428 | } |
| 428 | } | 429 | } |
| 429 | } | 430 | } |
| 430 | 431 | ||
| 432 | const getUserInputKey = () => getCurrentUserHandle() + '_userInput'; | ||
| 433 | |||
| 431 | function restoreUserInput() { | 434 | function restoreUserInput() { |
| 432 | if (!power_user.restore_user_input) { | 435 | if (!power_user.restore_user_input) { |
| 433 | console.debug('restoreUserInput disabled'); | 436 | console.debug('restoreUserInput disabled'); |
| 434 | return; | 437 | return; |
| 435 | } | 438 | } |
| 436 | 439 | ||
| 437 | const userInput = LoadLocal('userInput'); | 440 | const userInput = localStorage.getItem(getUserInputKey()); |
| 438 | if (userInput) { | 441 | if (userInput) { |
| 439 | $('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true })); | 442 | $('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true })); |
| 440 | } | 443 | } |
| @@ -442,7 +445,8 @@ function restoreUserInput() { | |||
| 442 | 445 | ||
| 443 | function saveUserInput() { | 446 | function saveUserInput() { |
| 444 | const userInput = String($('#send_textarea').val()); | 447 | const userInput = String($('#send_textarea').val()); |
| 445 | SaveLocal('userInput', userInput); | 448 | localStorage.setItem(getUserInputKey(), userInput); |
| 449 | console.debug('User Input -- ', userInput); | ||
| 446 | } | 450 | } |
| 447 | const saveUserInputDebounced = debounce(saveUserInput); | 451 | const saveUserInputDebounced = debounce(saveUserInput); |
| 448 | 452 | ||
| @@ -739,7 +743,7 @@ export function initRossMods() { | |||
| 739 | 743 | ||
| 740 | //toggle pin class when lock toggle clicked | 744 | //toggle pin class when lock toggle clicked |
| 741 | $(RPanelPin).on('click', function () { | 745 | $(RPanelPin).on('click', function () { |
| 742 | SaveLocal('NavLockOn', $(RPanelPin).prop('checked')); | 746 | accountStorage.setItem('NavLockOn', $(RPanelPin).prop('checked')); |
| 743 | if ($(RPanelPin).prop('checked') == true) { | 747 | if ($(RPanelPin).prop('checked') == true) { |
| 744 | //console.log('adding pin class to right nav'); | 748 | //console.log('adding pin class to right nav'); |
| 745 | $(RightNavPanel).addClass('pinnedOpen'); | 749 | $(RightNavPanel).addClass('pinnedOpen'); |
| @@ -757,7 +761,7 @@ export function initRossMods() { | |||
| 757 | } | 761 | } |
| 758 | }); | 762 | }); |
| 759 | $(LPanelPin).on('click', function () { | 763 | $(LPanelPin).on('click', function () { |
| 760 | SaveLocal('LNavLockOn', $(LPanelPin).prop('checked')); | 764 | accountStorage.setItem('LNavLockOn', $(LPanelPin).prop('checked')); |
| 761 | if ($(LPanelPin).prop('checked') == true) { | 765 | if ($(LPanelPin).prop('checked') == true) { |
| 762 | //console.log('adding pin class to Left nav'); | 766 | //console.log('adding pin class to Left nav'); |
| 763 | $(LeftNavPanel).addClass('pinnedOpen'); | 767 | $(LeftNavPanel).addClass('pinnedOpen'); |
| @@ -776,7 +780,7 @@ export function initRossMods() { | |||
| 776 | }); | 780 | }); |
| 777 | 781 | ||
| 778 | $(WIPanelPin).on('click', function () { | 782 | $(WIPanelPin).on('click', function () { |
| 779 | SaveLocal('WINavLockOn', $(WIPanelPin).prop('checked')); | 783 | accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked')); |
| 780 | if ($(WIPanelPin).prop('checked') == true) { | 784 | if ($(WIPanelPin).prop('checked') == true) { |
| 781 | console.debug('adding pin class to WI'); | 785 | console.debug('adding pin class to WI'); |
| 782 | $(WorldInfo).addClass('pinnedOpen'); | 786 | $(WorldInfo).addClass('pinnedOpen'); |
| @@ -796,8 +800,8 @@ export function initRossMods() { | |||
| 796 | }); | 800 | }); |
| 797 | 801 | ||
| 798 | // read the state of right Nav Lock and apply to rightnav classlist | 802 | // read the state of right Nav Lock and apply to rightnav classlist |
| 799 | $(RPanelPin).prop('checked', LoadLocalBool('NavLockOn')); | 803 | $(RPanelPin).prop('checked', accountStorage.getItem('NavLockOn') == 'true'); |
| 800 | if (LoadLocalBool('NavLockOn') == true) { | 804 | if (accountStorage.getItem('NavLockOn') == 'true') { |
| 801 | //console.log('setting pin class via local var'); | 805 | //console.log('setting pin class via local var'); |
| 802 | $(RightNavPanel).addClass('pinnedOpen'); | 806 | $(RightNavPanel).addClass('pinnedOpen'); |
| 803 | $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); | 807 | $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); |
| @@ -808,8 +812,8 @@ export function initRossMods() { | |||
| 808 | $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); | 812 | $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); |
| 809 | } | 813 | } |
| 810 | // read the state of left Nav Lock and apply to leftnav classlist | 814 | // read the state of left Nav Lock and apply to leftnav classlist |
| 811 | $(LPanelPin).prop('checked', LoadLocalBool('LNavLockOn')); | 815 | $(LPanelPin).prop('checked', accountStorage.getItem('LNavLockOn') === 'true'); |
| 812 | if (LoadLocalBool('LNavLockOn') == true) { | 816 | if (accountStorage.getItem('LNavLockOn') == 'true') { |
| 813 | //console.log('setting pin class via local var'); | 817 | //console.log('setting pin class via local var'); |
| 814 | $(LeftNavPanel).addClass('pinnedOpen'); | 818 | $(LeftNavPanel).addClass('pinnedOpen'); |
| 815 | $(LeftNavDrawerIcon).addClass('drawerPinnedOpen'); | 819 | $(LeftNavDrawerIcon).addClass('drawerPinnedOpen'); |
| @@ -821,8 +825,8 @@ export function initRossMods() { | |||
| 821 | } | 825 | } |
| 822 | 826 | ||
| 823 | // read the state of left Nav Lock and apply to leftnav classlist | 827 | // read the state of left Nav Lock and apply to leftnav classlist |
| 824 | $(WIPanelPin).prop('checked', LoadLocalBool('WINavLockOn')); | 828 | $(WIPanelPin).prop('checked', accountStorage.getItem('WINavLockOn') === 'true'); |
| 825 | if (LoadLocalBool('WINavLockOn') == true) { | 829 | if (accountStorage.getItem('WINavLockOn') == 'true') { |
| 826 | //console.log('setting pin class via local var'); | 830 | //console.log('setting pin class via local var'); |
| 827 | $(WorldInfo).addClass('pinnedOpen'); | 831 | $(WorldInfo).addClass('pinnedOpen'); |
| 828 | $(WIDrawerIcon).addClass('drawerPinnedOpen'); | 832 | $(WIDrawerIcon).addClass('drawerPinnedOpen'); |
| @@ -837,22 +841,22 @@ export function initRossMods() { | |||
| 837 | //save state of Right nav being open or closed | 841 | //save state of Right nav being open or closed |
| 838 | $('#rightNavDrawerIcon').on('click', function () { | 842 | $('#rightNavDrawerIcon').on('click', function () { |
| 839 | if (!$('#rightNavDrawerIcon').hasClass('openIcon')) { | 843 | if (!$('#rightNavDrawerIcon').hasClass('openIcon')) { |
| 840 | SaveLocal('NavOpened', 'true'); | 844 | accountStorage.setItem('NavOpened', 'true'); |
| 841 | } else { SaveLocal('NavOpened', 'false'); } | 845 | } else { accountStorage.setItem('NavOpened', 'false'); } |
| 842 | }); | 846 | }); |
| 843 | 847 | ||
| 844 | //save state of Left nav being open or closed | 848 | //save state of Left nav being open or closed |
| 845 | $('#leftNavDrawerIcon').on('click', function () { | 849 | $('#leftNavDrawerIcon').on('click', function () { |
| 846 | if (!$('#leftNavDrawerIcon').hasClass('openIcon')) { | 850 | if (!$('#leftNavDrawerIcon').hasClass('openIcon')) { |
| 847 | SaveLocal('LNavOpened', 'true'); | 851 | accountStorage.setItem('LNavOpened', 'true'); |
| 848 | } else { SaveLocal('LNavOpened', 'false'); } | 852 | } else { accountStorage.setItem('LNavOpened', 'false'); } |
| 849 | }); | 853 | }); |
| 850 | 854 | ||
| 851 | //save state of Left nav being open or closed | 855 | //save state of Left nav being open or closed |
| 852 | $('#WorldInfo').on('click', function () { | 856 | $('#WorldInfo').on('click', function () { |
| 853 | if (!$('#WorldInfo').hasClass('openIcon')) { | 857 | if (!$('#WorldInfo').hasClass('openIcon')) { |
| 854 | SaveLocal('WINavOpened', 'true'); | 858 | accountStorage.setItem('WINavOpened', 'true'); |
| 855 | } else { SaveLocal('WINavOpened', 'false'); } | 859 | } else { accountStorage.setItem('WINavOpened', 'false'); } |
| 856 | }); | 860 | }); |
| 857 | 861 | ||
| 858 | var chatbarInFocus = false; | 862 | var chatbarInFocus = false; |
| @@ -868,8 +872,8 @@ export function initRossMods() { | |||
| 868 | OpenNavPanels(); | 872 | OpenNavPanels(); |
| 869 | }, 300); | 873 | }, 300); |
| 870 | 874 | ||
| 871 | $(SelectedCharacterTab).click(function () { SaveLocal('SelectedNavTab', 'rm_button_selected_ch'); }); | 875 | $(SelectedCharacterTab).click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); }); |
| 872 | $('#rm_button_characters').click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); }); | 876 | $('#rm_button_characters').click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_characters'); }); |
| 873 | 877 | ||
| 874 | // when a char is selected from the list, save them as the auto-load character for next page load | 878 | // when a char is selected from the list, save them as the auto-load character for next page load |
| 875 | 879 | ||
| @@ -1063,14 +1067,21 @@ export function initRossMods() { | |||
| 1063 | // Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead | 1067 | // Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead |
| 1064 | if (event.ctrlKey && event.key == 'Enter') { | 1068 | if (event.ctrlKey && event.key == 'Enter') { |
| 1065 | const editMesDone = $('.mes_edit_done:visible'); | 1069 | const editMesDone = $('.mes_edit_done:visible'); |
| 1070 | const reasoningMesDone = $('.mes_reasoning_edit_done:visible'); | ||
| 1066 | if (editMesDone.length > 0) { | 1071 | if (editMesDone.length > 0) { |
| 1067 | console.debug('Accepting edits with Ctrl+Enter'); | 1072 | console.debug('Accepting edits with Ctrl+Enter'); |
| 1068 | $('#send_textarea').focus(); | 1073 | $('#send_textarea').trigger('focus'); |
| 1069 | editMesDone.trigger('click'); | 1074 | editMesDone.trigger('click'); |
| 1070 | return; | 1075 | return; |
| 1071 | } else if (is_send_press == false) { | 1076 | } else if (reasoningMesDone.length > 0) { |
| 1077 | console.debug('Accepting edits with Ctrl+Enter'); | ||
| 1078 | $('#send_textarea').trigger('focus'); | ||
| 1079 | reasoningMesDone.trigger('click'); | ||
| 1080 | return; | ||
| 1081 | } | ||
| 1082 | else if (is_send_press == false) { | ||
| 1072 | const skipConfirmKey = 'RegenerateWithCtrlEnter'; | 1083 | const skipConfirmKey = 'RegenerateWithCtrlEnter'; |
| 1073 | const skipConfirm = LoadLocalBool(skipConfirmKey); | 1084 | const skipConfirm = accountStorage.getItem(skipConfirmKey) === 'true'; |
| 1074 | function doRegenerate() { | 1085 | function doRegenerate() { |
| 1075 | console.debug('Regenerating with Ctrl+Enter'); | 1086 | console.debug('Regenerating with Ctrl+Enter'); |
| 1076 | $('#option_regenerate').trigger('click'); | 1087 | $('#option_regenerate').trigger('click'); |
| @@ -1082,13 +1093,15 @@ export function initRossMods() { | |||
| 1082 | let regenerateWithCtrlEnter = false; | 1093 | let regenerateWithCtrlEnter = false; |
| 1083 | const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', { | 1094 | const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', { |
| 1084 | customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }], | 1095 | customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }], |
| 1085 | onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false, | 1096 | onClose: (popup) => { |
| 1097 | regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false; | ||
| 1098 | }, | ||
| 1086 | }); | 1099 | }); |
| 1087 | if (!result) { | 1100 | if (!result) { |
| 1088 | return; | 1101 | return; |
| 1089 | } | 1102 | } |
| 1090 | 1103 | ||
| 1091 | SaveLocal(skipConfirmKey, regenerateWithCtrlEnter); | 1104 | accountStorage.setItem(skipConfirmKey, String(regenerateWithCtrlEnter)); |
| 1092 | doRegenerate(); | 1105 | doRegenerate(); |
| 1093 | } | 1106 | } |
| 1094 | return; | 1107 | return; |
| @@ -566,7 +566,7 @@ export function initAuthorsNote() { | |||
| 566 | namedArgumentList: [], | 566 | namedArgumentList: [], |
| 567 | unnamedArgumentList: [ | 567 | unnamedArgumentList: [ |
| 568 | new SlashCommandArgument( | 568 | new SlashCommandArgument( |
| 569 | 'position', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'user', 'assistant'], | 569 | 'role', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'user', 'assistant'], |
| 570 | ), | 570 | ), |
| 571 | ], | 571 | ], |
| 572 | helpString: ` | 572 | helpString: ` |
| @@ -96,8 +96,13 @@ function highlightLockedBackground() { | |||
| 96 | }); | 96 | }); |
| 97 | } | 97 | } |
| 98 | 98 | ||
| 99 | /** | ||
| 100 | * Locks the background for the current chat | ||
| 101 | * @param {Event} e Click event | ||
| 102 | * @returns {string} Empty string | ||
| 103 | */ | ||
| 99 | function onLockBackgroundClick(e) { | 104 | function onLockBackgroundClick(e) { |
| 100 | e.stopPropagation(); | 105 | e?.stopPropagation(); |
| 101 | 106 | ||
| 102 | const chatName = getCurrentChatId(); | 107 | const chatName = getCurrentChatId(); |
| 103 | 108 | ||
| @@ -106,7 +111,7 @@ function onLockBackgroundClick(e) { | |||
| 106 | return ''; | 111 | return ''; |
| 107 | } | 112 | } |
| 108 | 113 | ||
| 109 | const relativeBgImage = getUrlParameter(this); | 114 | const relativeBgImage = getUrlParameter(this) ?? background_settings.url; |
| 110 | 115 | ||
| 111 | saveBackgroundMetadata(relativeBgImage); | 116 | saveBackgroundMetadata(relativeBgImage); |
| 112 | setCustomBackground(); | 117 | setCustomBackground(); |
| @@ -114,8 +119,13 @@ function onLockBackgroundClick(e) { | |||
| 114 | return ''; | 119 | return ''; |
| 115 | } | 120 | } |
| 116 | 121 | ||
| 122 | /** | ||
| 123 | * Locks the background for the current chat | ||
| 124 | * @param {Event} e Click event | ||
| 125 | * @returns {string} Empty string | ||
| 126 | */ | ||
| 117 | function onUnlockBackgroundClick(e) { | 127 | function onUnlockBackgroundClick(e) { |
| 118 | e.stopPropagation(); | 128 | e?.stopPropagation(); |
| 119 | removeBackgroundMetadata(); | 129 | removeBackgroundMetadata(); |
| 120 | unsetCustomBackground(); | 130 | unsetCustomBackground(); |
| 121 | highlightLockedBackground(); | 131 | highlightLockedBackground(); |
| @@ -482,10 +492,10 @@ function highlightNewBackground(bg) { | |||
| 482 | */ | 492 | */ |
| 483 | function setFittingClass(fitting) { | 493 | function setFittingClass(fitting) { |
| 484 | const backgrounds = $('#bg1, #bg_custom'); | 494 | const backgrounds = $('#bg1, #bg_custom'); |
| 485 | backgrounds.toggleClass('cover', fitting === 'cover'); | 495 | for (const option of ['cover', 'contain', 'stretch', 'center']) { |
| 486 | backgrounds.toggleClass('contain', fitting === 'contain'); | 496 | backgrounds.toggleClass(option, option === fitting); |
| 487 | backgrounds.toggleClass('stretch', fitting === 'stretch'); | 497 | } |
| 488 | backgrounds.toggleClass('center', fitting === 'center'); | 498 | background_settings.fitting = fitting; |
| 489 | } | 499 | } |
| 490 | 500 | ||
| 491 | function onBackgroundFilterInput() { | 501 | function onBackgroundFilterInput() { |
| @@ -513,12 +523,12 @@ export function initBackgrounds() { | |||
| 513 | $('#add_bg_button').on('change', onBackgroundUploadSelected); | 523 | $('#add_bg_button').on('change', onBackgroundUploadSelected); |
| 514 | $('#bg-filter').on('input', onBackgroundFilterInput); | 524 | $('#bg-filter').on('input', onBackgroundFilterInput); |
| 515 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg', | 525 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg', |
| 516 | callback: onLockBackgroundClick, | 526 | callback: () => onLockBackgroundClick(new CustomEvent('click')), |
| 517 | aliases: ['bglock'], | 527 | aliases: ['bglock'], |
| 518 | helpString: 'Locks a background for the currently selected chat', | 528 | helpString: 'Locks a background for the currently selected chat', |
| 519 | })); | 529 | })); |
| 520 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg', | 530 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg', |
| 521 | callback: onUnlockBackgroundClick, | 531 | callback: () => onUnlockBackgroundClick(new CustomEvent('click')), |
| 522 | aliases: ['bgunlock'], | 532 | aliases: ['bgunlock'], |
| 523 | helpString: 'Unlocks a background for the currently selected chat', | 533 | helpString: 'Unlocks a background for the currently selected chat', |
| 524 | })); | 534 | })); |
| @@ -59,6 +59,17 @@ const hash_derivations = { | |||
| 59 | // Tulu-3-8B | 59 | // Tulu-3-8B |
| 60 | // Tulu-3-70B | 60 | // Tulu-3-70B |
| 61 | 'Tulu' | 61 | 'Tulu' |
| 62 | , | ||
| 63 | |||
| 64 | // DeepSeek V2.5 | ||
| 65 | '54d400beedcd17f464e10063e0577f6f798fa896266a912d8a366f8a2fcc0bca': | ||
| 66 | 'DeepSeek-V2.5' | ||
| 67 | , | ||
| 68 | |||
| 69 | // DeepSeek R1 | ||
| 70 | 'b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1': | ||
| 71 | 'DeepSeek-V2.5' | ||
| 72 | , | ||
| 62 | }; | 73 | }; |
| 63 | 74 | ||
| 64 | const substr_derivations = { | 75 | const substr_derivations = { |
| @@ -87,6 +98,6 @@ export async function deriveTemplatesFromChatTemplate(chat_template, hash) { | |||
| 87 | } | 98 | } |
| 88 | } | 99 | } |
| 89 | 100 | ||
| 90 | console.log(`Unknown chat template hash: ${hash} for [${chat_template}]`); | 101 | console.warn(`Unknown chat template hash: ${hash} for [${chat_template}]`); |
| 91 | return null; | 102 | return null; |
| 92 | } | 103 | } |
| @@ -11,6 +11,7 @@ import { | |||
| 11 | getCurrentChatId, | 11 | getCurrentChatId, |
| 12 | getRequestHeaders, | 12 | getRequestHeaders, |
| 13 | hideSwipeButtons, | 13 | hideSwipeButtons, |
| 14 | name1, | ||
| 14 | name2, | 15 | name2, |
| 15 | reloadCurrentChat, | 16 | reloadCurrentChat, |
| 16 | saveChatDebounced, | 17 | saveChatDebounced, |
| @@ -21,6 +22,7 @@ import { | |||
| 21 | chat_metadata, | 22 | chat_metadata, |
| 22 | neutralCharacterName, | 23 | neutralCharacterName, |
| 23 | updateChatMetadata, | 24 | updateChatMetadata, |
| 25 | system_message_types, | ||
| 24 | } from '../script.js'; | 26 | } from '../script.js'; |
| 25 | import { selected_group } from './group-chats.js'; | 27 | import { selected_group } from './group-chats.js'; |
| 26 | import { power_user } from './power-user.js'; | 28 | import { power_user } from './power-user.js'; |
| @@ -34,6 +36,7 @@ import { | |||
| 34 | humanFileSize, | 36 | humanFileSize, |
| 35 | saveBase64AsFile, | 37 | saveBase64AsFile, |
| 36 | extractTextFromOffice, | 38 | extractTextFromOffice, |
| 39 | download, | ||
| 37 | } from './utils.js'; | 40 | } from './utils.js'; |
| 38 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; | 41 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; |
| 39 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 42 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| @@ -41,6 +44,8 @@ import { ScraperManager } from './scrapers.js'; | |||
| 41 | import { DragAndDropHandler } from './dragdrop.js'; | 44 | import { DragAndDropHandler } from './dragdrop.js'; |
| 42 | import { renderTemplateAsync } from './templates.js'; | 45 | import { renderTemplateAsync } from './templates.js'; |
| 43 | import { t } from './i18n.js'; | 46 | import { t } from './i18n.js'; |
| 47 | import { humanizedDateTime } from './RossAscends-mods.js'; | ||
| 48 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 44 | 49 | ||
| 45 | /** | 50 | /** |
| 46 | * @typedef {Object} FileAttachment | 51 | * @typedef {Object} FileAttachment |
| @@ -617,21 +622,56 @@ async function enlargeMessageImage() { | |||
| 617 | } | 622 | } |
| 618 | 623 | ||
| 619 | async function deleteMessageImage() { | 624 | async function deleteMessageImage() { |
| 620 | const value = await callGenericPopup('<h3>Delete image from message?<br>This action can\'t be undone.</h3>', POPUP_TYPE.CONFIRM); | 625 | const value = await callGenericPopup('<h3>Delete image from message?<br>This action can\'t be undone.</h3>', POPUP_TYPE.TEXT, '', { |
| 626 | okButton: t`Delete one`, | ||
| 627 | customButtons: [ | ||
| 628 | { | ||
| 629 | text: t`Delete all`, | ||
| 630 | appendAtEnd: true, | ||
| 631 | result: POPUP_RESULT.CUSTOM1, | ||
| 632 | }, | ||
| 633 | { | ||
| 634 | text: t`Cancel`, | ||
| 635 | appendAtEnd: true, | ||
| 636 | result: POPUP_RESULT.CANCELLED, | ||
| 637 | }, | ||
| 638 | ], | ||
| 639 | }); | ||
| 621 | 640 | ||
| 622 | if (value !== POPUP_RESULT.AFFIRMATIVE) { | 641 | if (!value) { |
| 623 | return; | 642 | return; |
| 624 | } | 643 | } |
| 625 | 644 | ||
| 626 | const mesBlock = $(this).closest('.mes'); | 645 | const mesBlock = $(this).closest('.mes'); |
| 627 | const mesId = mesBlock.attr('mesid'); | 646 | const mesId = mesBlock.attr('mesid'); |
| 628 | const message = chat[mesId]; | 647 | const message = chat[mesId]; |
| 648 | |||
| 649 | let isLastImage = true; | ||
| 650 | |||
| 651 | if (Array.isArray(message.extra.image_swipes)) { | ||
| 652 | const indexOf = message.extra.image_swipes.indexOf(message.extra.image); | ||
| 653 | if (indexOf > -1) { | ||
| 654 | message.extra.image_swipes.splice(indexOf, 1); | ||
| 655 | isLastImage = message.extra.image_swipes.length === 0; | ||
| 656 | if (!isLastImage) { | ||
| 657 | const newIndex = Math.min(indexOf, message.extra.image_swipes.length - 1); | ||
| 658 | message.extra.image = message.extra.image_swipes[newIndex]; | ||
| 659 | } | ||
| 660 | } | ||
| 661 | } | ||
| 662 | |||
| 663 | if (isLastImage || value === POPUP_RESULT.CUSTOM1) { | ||
| 629 | delete message.extra.image; | 664 | delete message.extra.image; |
| 630 | delete message.extra.inline_image; | 665 | delete message.extra.inline_image; |
| 631 | delete message.extra.title; | 666 | delete message.extra.title; |
| 632 | delete message.extra.append_title; | 667 | delete message.extra.append_title; |
| 668 | delete message.extra.image_swipes; | ||
| 633 | mesBlock.find('.mes_img_container').removeClass('img_extra'); | 669 | mesBlock.find('.mes_img_container').removeClass('img_extra'); |
| 634 | mesBlock.find('.mes_img').attr('src', ''); | 670 | mesBlock.find('.mes_img').attr('src', ''); |
| 671 | } else { | ||
| 672 | appendMediaToMessage(message, mesBlock); | ||
| 673 | } | ||
| 674 | |||
| 635 | await saveChatConditional(); | 675 | await saveChatConditional(); |
| 636 | } | 676 | } |
| 637 | 677 | ||
| @@ -1039,8 +1079,8 @@ async function openAttachmentManager() { | |||
| 1039 | renderAttachments(); | 1079 | renderAttachments(); |
| 1040 | }); | 1080 | }); |
| 1041 | 1081 | ||
| 1042 | let sortField = localStorage.getItem('DataBank_sortField') || 'created'; | 1082 | let sortField = accountStorage.getItem('DataBank_sortField') || 'created'; |
| 1043 | let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc'; | 1083 | let sortOrder = accountStorage.getItem('DataBank_sortOrder') || 'desc'; |
| 1044 | let filterString = ''; | 1084 | let filterString = ''; |
| 1045 | 1085 | ||
| 1046 | const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {})); | 1086 | const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {})); |
| @@ -1056,8 +1096,8 @@ async function openAttachmentManager() { | |||
| 1056 | 1096 | ||
| 1057 | sortField = this.selectedOptions[0].dataset.sortField; | 1097 | sortField = this.selectedOptions[0].dataset.sortField; |
| 1058 | sortOrder = this.selectedOptions[0].dataset.sortOrder; | 1098 | sortOrder = this.selectedOptions[0].dataset.sortOrder; |
| 1059 | localStorage.setItem('DataBank_sortField', sortField); | 1099 | accountStorage.setItem('DataBank_sortField', sortField); |
| 1060 | localStorage.setItem('DataBank_sortOrder', sortOrder); | 1100 | accountStorage.setItem('DataBank_sortOrder', sortOrder); |
| 1061 | renderAttachments(); | 1101 | renderAttachments(); |
| 1062 | }); | 1102 | }); |
| 1063 | function handleBulkAction(action) { | 1103 | function handleBulkAction(action) { |
| @@ -1437,6 +1477,19 @@ jQuery(function () { | |||
| 1437 | await viewMessageFile(messageId); | 1477 | await viewMessageFile(messageId); |
| 1438 | }); | 1478 | }); |
| 1439 | 1479 | ||
| 1480 | $(document).on('click', '.assistant_note_export', async function () { | ||
| 1481 | const chatToSave = [ | ||
| 1482 | { | ||
| 1483 | user_name: name1, | ||
| 1484 | character_name: name2, | ||
| 1485 | chat_metadata: chat_metadata, | ||
| 1486 | }, | ||
| 1487 | ...chat.filter(x => x?.extra?.type !== system_message_types.ASSISTANT_NOTE), | ||
| 1488 | ]; | ||
| 1489 | |||
| 1490 | download(JSON.stringify(chatToSave, null, 4), `Assistant - ${humanizedDateTime()}.json`, 'application/json'); | ||
| 1491 | }); | ||
| 1492 | |||
| 1440 | // Do not change. #attachFile is added by extension. | 1493 | // Do not change. #attachFile is added by extension. |
| 1441 | $(document).on('click', '#attachFile', function () { | 1494 | $(document).on('click', '#attachFile', function () { |
| 1442 | $('#file_form_input').trigger('click'); | 1495 | $('#file_form_input').trigger('click'); |
| @@ -9,6 +9,7 @@ import { getContext } from './st-context.js'; | |||
| 9 | import { isAdmin } from './user.js'; | 9 | import { isAdmin } from './user.js'; |
| 10 | import { t } from './i18n.js'; | 10 | import { t } from './i18n.js'; |
| 11 | import { debounce_timeout } from './constants.js'; | 11 | import { debounce_timeout } from './constants.js'; |
| 12 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 12 | 13 | ||
| 13 | export { | 14 | export { |
| 14 | getContext, | 15 | getContext, |
| @@ -714,7 +715,7 @@ async function showExtensionsDetails() { | |||
| 714 | htmlExternal.append(htmlLoading); | 715 | htmlExternal.append(htmlLoading); |
| 715 | 716 | ||
| 716 | const sortOrderKey = 'extensions_sortByName'; | 717 | const sortOrderKey = 'extensions_sortByName'; |
| 717 | const sortByName = localStorage.getItem(sortOrderKey) === 'true'; | 718 | const sortByName = accountStorage.getItem(sortOrderKey) === 'true'; |
| 718 | const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder; | 719 | const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder; |
| 719 | const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData); | 720 | const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData); |
| 720 | 721 | ||
| @@ -745,7 +746,7 @@ async function showExtensionsDetails() { | |||
| 745 | text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`, | 746 | text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`, |
| 746 | action: async () => { | 747 | action: async () => { |
| 747 | abortController.abort(); | 748 | abortController.abort(); |
| 748 | localStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true'); | 749 | accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true'); |
| 749 | await showExtensionsDetails(); | 750 | await showExtensionsDetails(); |
| 750 | }, | 751 | }, |
| 751 | }; | 752 | }; |
| @@ -1153,11 +1154,11 @@ async function checkForExtensionUpdates(force) { | |||
| 1153 | const currentDate = new Date().toDateString(); | 1154 | const currentDate = new Date().toDateString(); |
| 1154 | 1155 | ||
| 1155 | // Don't nag more than once a day | 1156 | // Don't nag more than once a day |
| 1156 | if (localStorage.getItem(STORAGE_NAG_KEY) === currentDate) { | 1157 | if (accountStorage.getItem(STORAGE_NAG_KEY) === currentDate) { |
| 1157 | return; | 1158 | return; |
| 1158 | } | 1159 | } |
| 1159 | 1160 | ||
| 1160 | localStorage.setItem(STORAGE_NAG_KEY, currentDate); | 1161 | accountStorage.setItem(STORAGE_NAG_KEY, currentDate); |
| 1161 | } | 1162 | } |
| 1162 | 1163 | ||
| 1163 | const isCurrentUserAdmin = isAdmin(); | 1164 | const isCurrentUserAdmin = isAdmin(); |
| @@ -8,6 +8,7 @@ import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from | |||
| 8 | import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; | 8 | import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; | 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; |
| 10 | import { executeSlashCommands } from '../../slash-commands.js'; | 10 | import { executeSlashCommands } from '../../slash-commands.js'; |
| 11 | import { accountStorage } from '../../util/AccountStorage.js'; | ||
| 11 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; | 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 12 | export { MODULE_NAME }; | 13 | export { MODULE_NAME }; |
| 13 | 14 | ||
| @@ -432,14 +433,14 @@ jQuery(async () => { | |||
| 432 | connectButton.on('click', async function () { | 433 | connectButton.on('click', async function () { |
| 433 | const url = DOMPurify.sanitize(String(assetsJsonUrl.val())); | 434 | const url = DOMPurify.sanitize(String(assetsJsonUrl.val())); |
| 434 | const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; | 435 | const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; |
| 435 | const skipConfirm = localStorage.getItem(rememberKey) === 'true'; | 436 | const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; |
| 436 | 437 | ||
| 437 | const confirmation = skipConfirm || await Popup.show.confirm('Loading Asset List', `<span>Are you sure you want to connect to the following url?</span><var>${url}</var>`, { | 438 | const confirmation = skipConfirm || await Popup.show.confirm('Loading Asset List', `<span>Are you sure you want to connect to the following url?</span><var>${url}</var>`, { |
| 438 | customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], | 439 | customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], |
| 439 | onClose: popup => { | 440 | onClose: popup => { |
| 440 | if (popup.result) { | 441 | if (popup.result) { |
| 441 | const rememberValue = popup.inputResults.get('assets-remember'); | 442 | const rememberValue = popup.inputResults.get('assets-remember'); |
| 442 | localStorage.setItem(rememberKey, String(rememberValue)); | 443 | accountStorage.setItem(rememberKey, String(rememberValue)); |
| 443 | } | 444 | } |
| 444 | }, | 445 | }, |
| 445 | }); | 446 | }); |
| @@ -10,7 +10,7 @@ | |||
| 10 | <select id="caption_source" class="text_pole"> | 10 | <select id="caption_source" class="text_pole"> |
| 11 | <option value="local" data-i18n="Local">Local</option> | 11 | <option value="local" data-i18n="Local">Local</option> |
| 12 | <option value="multimodal" data-i18n="Multimodal (OpenAI / Anthropic / llama / Google)">Multimodal (OpenAI / Anthropic / llama / Google)</option> | 12 | <option value="multimodal" data-i18n="Multimodal (OpenAI / Anthropic / llama / Google)">Multimodal (OpenAI / Anthropic / llama / Google)</option> |
| 13 | <option value="extras" data-i18n="Extras">Extras</option> | 13 | <option value="extras" data-i18n="Extras">Extras (deprecated)</option> |
| 14 | <option value="horde" data-i18n="Horde">Horde</option> | 14 | <option value="horde" data-i18n="Horde">Horde</option> |
| 15 | </select> | 15 | </select> |
| 16 | <div id="caption_multimodal_block" class="flex-container wide100p"> | 16 | <div id="caption_multimodal_block" class="flex-container wide100p"> |
| @@ -53,7 +53,15 @@ | |||
| 53 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> | 53 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> |
| 54 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> | 54 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> |
| 55 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> | 55 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> |
| 56 | <option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option> | ||
| 57 | <option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option> | ||
| 58 | <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option> | ||
| 59 | <option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option> | ||
| 60 | <option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option> | ||
| 61 | <option data-type="google" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option> | ||
| 56 | <option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option> | 62 | <option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option> |
| 63 | <option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp</option> | ||
| 64 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21</option> | ||
| 57 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option> | 65 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option> |
| 58 | <option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option> | 66 | <option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option> |
| 59 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> | 67 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> |
| @@ -30,6 +30,7 @@ const CC_COMMANDS = [ | |||
| 30 | 'api-url', | 30 | 'api-url', |
| 31 | 'model', | 31 | 'model', |
| 32 | 'proxy', | 32 | 'proxy', |
| 33 | 'stop-strings', | ||
| 33 | ]; | 34 | ]; |
| 34 | 35 | ||
| 35 | const TC_COMMANDS = [ | 36 | const TC_COMMANDS = [ |
| @@ -43,6 +44,7 @@ const TC_COMMANDS = [ | |||
| 43 | 'context', | 44 | 'context', |
| 44 | 'instruct-state', | 45 | 'instruct-state', |
| 45 | 'tokenizer', | 46 | 'tokenizer', |
| 47 | 'stop-strings', | ||
| 46 | ]; | 48 | ]; |
| 47 | 49 | ||
| 48 | const FANCY_NAMES = { | 50 | const FANCY_NAMES = { |
| @@ -57,6 +59,7 @@ const FANCY_NAMES = { | |||
| 57 | 'instruct': 'Instruct Template', | 59 | 'instruct': 'Instruct Template', |
| 58 | 'context': 'Context Template', | 60 | 'context': 'Context Template', |
| 59 | 'tokenizer': 'Tokenizer', | 61 | 'tokenizer': 'Tokenizer', |
| 62 | 'stop-strings': 'Custom Stopping Strings', | ||
| 60 | }; | 63 | }; |
| 61 | 64 | ||
| 62 | /** | 65 | /** |
| @@ -138,6 +141,7 @@ const profilesProvider = () => [ | |||
| 138 | * @property {string} [context] Context Template | 141 | * @property {string} [context] Context Template |
| 139 | * @property {string} [instruct-state] Instruct Mode | 142 | * @property {string} [instruct-state] Instruct Mode |
| 140 | * @property {string} [tokenizer] Tokenizer | 143 | * @property {string} [tokenizer] Tokenizer |
| 144 | * @property {string} [stop-strings] Custom Stopping Strings | ||
| 141 | * @property {string[]} [exclude] Commands to exclude | 145 | * @property {string[]} [exclude] Commands to exclude |
| 142 | */ | 146 | */ |
| 143 | 147 | ||
| @@ -2178,7 +2178,6 @@ function migrateSettings() { | |||
| 2178 | typeList: [ARGUMENT_TYPE.STRING], | 2178 | typeList: [ARGUMENT_TYPE.STRING], |
| 2179 | isRequired: true, | 2179 | isRequired: true, |
| 2180 | enumProvider: commonEnumProviders.characters('character'), | 2180 | enumProvider: commonEnumProviders.characters('character'), |
| 2181 | forceEnum: true, | ||
| 2182 | }), | 2181 | }), |
| 2183 | ], | 2182 | ], |
| 2184 | helpString: 'Returns the last set sprite / expression for the named character.', | 2183 | helpString: 'Returns the last set sprite / expression for the named character.', |
| @@ -23,7 +23,7 @@ | |||
| 23 | <small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small> | 23 | <small data-i18n="Select the API for classifying expressions.">Select the API for classifying expressions.</small> |
| 24 | <select id="expression_api" class="flex1 margin0"> | 24 | <select id="expression_api" class="flex1 margin0"> |
| 25 | <option value="0" data-i18n="Local">Local</option> | 25 | <option value="0" data-i18n="Local">Local</option> |
| 26 | <option value="1" data-i18n="Extras">Extras</option> | 26 | <option value="1" data-i18n="Extras">Extras (deprecated)</option> |
| 27 | <option value="2" data-i18n="Main API">Main API</option> | 27 | <option value="2" data-i18n="Main API">Main API</option> |
| 28 | <option value="3" data-i18n="WebLLM Extension">WebLLM Extension</option> | 28 | <option value="3" data-i18n="WebLLM Extension">WebLLM Extension</option> |
| 29 | </select> | 29 | </select> |
| @@ -441,7 +441,6 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |||
| 441 | description: 'character name', | 441 | description: 'character name', |
| 442 | typeList: [ARGUMENT_TYPE.STRING], | 442 | typeList: [ARGUMENT_TYPE.STRING], |
| 443 | enumProvider: commonEnumProviders.characters('character'), | 443 | enumProvider: commonEnumProviders.characters('character'), |
| 444 | forceEnum: true, | ||
| 445 | }), | 444 | }), |
| 446 | SlashCommandNamedArgument.fromProps({ | 445 | SlashCommandNamedArgument.fromProps({ |
| 447 | name: 'group', | 446 | name: 'group', |
| @@ -12,7 +12,7 @@ | |||
| 12 | <label for="summary_source" data-i18n="ext_sum_with">Summarize with:</label> | 12 | <label for="summary_source" data-i18n="ext_sum_with">Summarize with:</label> |
| 13 | <select id="summary_source"> | 13 | <select id="summary_source"> |
| 14 | <option value="main" data-i18n="ext_sum_main_api">Main API</option> | 14 | <option value="main" data-i18n="ext_sum_main_api">Main API</option> |
| 15 | <option value="extras">Extras API</option> | 15 | <option value="extras">Extras API (deprecated)</option> |
| 16 | <option value="webllm" data-i18n="ext_sum_webllm">WebLLM Extension</option> | 16 | <option value="webllm" data-i18n="ext_sum_webllm">WebLLM Extension</option> |
| 17 | </select><br> | 17 | </select><br> |
| 18 | 18 | ||
| @@ -10,6 +10,7 @@ import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecut | |||
| 10 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | 10 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; |
| 11 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; | 11 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; |
| 12 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | 12 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 13 | import { accountStorage } from '../../../util/AccountStorage.js'; | ||
| 13 | import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; | 14 | import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; |
| 14 | import { log, quickReplyApi, warn } from '../index.js'; | 15 | import { log, quickReplyApi, warn } from '../index.js'; |
| 15 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; | 16 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; |
| @@ -544,9 +545,9 @@ export class QuickReply { | |||
| 544 | this.editorSyntax = messageSyntaxInner; | 545 | this.editorSyntax = messageSyntaxInner; |
| 545 | /**@type {HTMLInputElement}*/ | 546 | /**@type {HTMLInputElement}*/ |
| 546 | const wrap = dom.querySelector('#qr--modal-wrap'); | 547 | const wrap = dom.querySelector('#qr--modal-wrap'); |
| 547 | wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); | 548 | wrap.checked = JSON.parse(accountStorage.getItem('qr--wrap') ?? 'false'); |
| 548 | wrap.addEventListener('click', () => { | 549 | wrap.addEventListener('click', () => { |
| 549 | localStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); | 550 | accountStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); |
| 550 | updateWrap(); | 551 | updateWrap(); |
| 551 | }); | 552 | }); |
| 552 | const updateWrap = () => { | 553 | const updateWrap = () => { |
| @@ -594,27 +595,27 @@ export class QuickReply { | |||
| 594 | }; | 595 | }; |
| 595 | /**@type {HTMLInputElement}*/ | 596 | /**@type {HTMLInputElement}*/ |
| 596 | const tabSize = dom.querySelector('#qr--modal-tabSize'); | 597 | const tabSize = dom.querySelector('#qr--modal-tabSize'); |
| 597 | tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4'); | 598 | tabSize.value = JSON.parse(accountStorage.getItem('qr--tabSize') ?? '4'); |
| 598 | const updateTabSize = () => { | 599 | const updateTabSize = () => { |
| 599 | message.style.tabSize = tabSize.value; | 600 | message.style.tabSize = tabSize.value; |
| 600 | messageSyntaxInner.style.tabSize = tabSize.value; | 601 | messageSyntaxInner.style.tabSize = tabSize.value; |
| 601 | updateScrollDebounced(); | 602 | updateScrollDebounced(); |
| 602 | }; | 603 | }; |
| 603 | tabSize.addEventListener('change', () => { | 604 | tabSize.addEventListener('change', () => { |
| 604 | localStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); | 605 | accountStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); |
| 605 | updateTabSize(); | 606 | updateTabSize(); |
| 606 | }); | 607 | }); |
| 607 | /**@type {HTMLInputElement}*/ | 608 | /**@type {HTMLInputElement}*/ |
| 608 | const executeShortcut = dom.querySelector('#qr--modal-executeShortcut'); | 609 | const executeShortcut = dom.querySelector('#qr--modal-executeShortcut'); |
| 609 | executeShortcut.checked = JSON.parse(localStorage.getItem('qr--executeShortcut') ?? 'true'); | 610 | executeShortcut.checked = JSON.parse(accountStorage.getItem('qr--executeShortcut') ?? 'true'); |
| 610 | executeShortcut.addEventListener('click', () => { | 611 | executeShortcut.addEventListener('click', () => { |
| 611 | localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); | 612 | accountStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); |
| 612 | }); | 613 | }); |
| 613 | /**@type {HTMLInputElement}*/ | 614 | /**@type {HTMLInputElement}*/ |
| 614 | const syntax = dom.querySelector('#qr--modal-syntax'); | 615 | const syntax = dom.querySelector('#qr--modal-syntax'); |
| 615 | syntax.checked = JSON.parse(localStorage.getItem('qr--syntax') ?? 'true'); | 616 | syntax.checked = JSON.parse(accountStorage.getItem('qr--syntax') ?? 'true'); |
| 616 | syntax.addEventListener('click', () => { | 617 | syntax.addEventListener('click', () => { |
| 617 | localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); | 618 | accountStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); |
| 618 | updateSyntaxEnabled(); | 619 | updateSyntaxEnabled(); |
| 619 | }); | 620 | }); |
| 620 | if (navigator.keyboard) { | 621 | if (navigator.keyboard) { |
| @@ -1,15 +1,14 @@ | |||
| 1 | import { getRequestHeaders, substituteParams } from '../../../../script.js'; | 1 | import { getRequestHeaders, substituteParams } from '../../../../script.js'; |
| 2 | import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js'; | 2 | import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js'; |
| 3 | import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js'; | 3 | import { executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js'; |
| 4 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; | ||
| 5 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; | 4 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 6 | import { debounceAsync, log, warn } from '../index.js'; | 5 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; |
| 6 | import { debounceAsync, warn } from '../index.js'; | ||
| 7 | import { QuickReply } from './QuickReply.js'; | 7 | import { QuickReply } from './QuickReply.js'; |
| 8 | 8 | ||
| 9 | export class QuickReplySet { | 9 | export class QuickReplySet { |
| 10 | /**@type {QuickReplySet[]}*/ static list = []; | 10 | /**@type {QuickReplySet[]}*/ static list = []; |
| 11 | 11 | ||
| 12 | |||
| 13 | static from(props) { | 12 | static from(props) { |
| 14 | props.qrList = []; //props.qrList?.map(it=>QuickReply.from(it)); | 13 | props.qrList = []; //props.qrList?.map(it=>QuickReply.from(it)); |
| 15 | const instance = Object.assign(new this(), props); | 14 | const instance = Object.assign(new this(), props); |
| @@ -24,9 +23,6 @@ export class QuickReplySet { | |||
| 24 | return this.list.find(it=>it.name == name); | 23 | return this.list.find(it=>it.name == name); |
| 25 | } | 24 | } |
| 26 | 25 | ||
| 27 | |||
| 28 | |||
| 29 | |||
| 30 | /**@type {string}*/ name; | 26 | /**@type {string}*/ name; |
| 31 | /**@type {boolean}*/ disableSend = false; | 27 | /**@type {boolean}*/ disableSend = false; |
| 32 | /**@type {boolean}*/ placeBeforeInput = false; | 28 | /**@type {boolean}*/ placeBeforeInput = false; |
| @@ -34,19 +30,12 @@ export class QuickReplySet { | |||
| 34 | /**@type {string}*/ color = 'transparent'; | 30 | /**@type {string}*/ color = 'transparent'; |
| 35 | /**@type {boolean}*/ onlyBorderColor = false; | 31 | /**@type {boolean}*/ onlyBorderColor = false; |
| 36 | /**@type {QuickReply[]}*/ qrList = []; | 32 | /**@type {QuickReply[]}*/ qrList = []; |
| 37 | |||
| 38 | /**@type {number}*/ idIndex = 0; | 33 | /**@type {number}*/ idIndex = 0; |
| 39 | |||
| 40 | /**@type {boolean}*/ isDeleted = false; | 34 | /**@type {boolean}*/ isDeleted = false; |
| 41 | |||
| 42 | /**@type {function}*/ save; | 35 | /**@type {function}*/ save; |
| 43 | |||
| 44 | /**@type {HTMLElement}*/ dom; | 36 | /**@type {HTMLElement}*/ dom; |
| 45 | /**@type {HTMLElement}*/ settingsDom; | 37 | /**@type {HTMLElement}*/ settingsDom; |
| 46 | 38 | ||
| 47 | |||
| 48 | |||
| 49 | |||
| 50 | constructor() { | 39 | constructor() { |
| 51 | this.save = debounceAsync(()=>this.performSave(), 200); | 40 | this.save = debounceAsync(()=>this.performSave(), 200); |
| 52 | } | 41 | } |
| @@ -55,9 +44,6 @@ export class QuickReplySet { | |||
| 55 | this.qrList.forEach(qr=>this.hookQuickReply(qr)); | 44 | this.qrList.forEach(qr=>this.hookQuickReply(qr)); |
| 56 | } | 45 | } |
| 57 | 46 | ||
| 58 | |||
| 59 | |||
| 60 | |||
| 61 | unrender() { | 47 | unrender() { |
| 62 | this.dom?.remove(); | 48 | this.dom?.remove(); |
| 63 | this.dom = null; | 49 | this.dom = null; |
| @@ -100,9 +86,6 @@ export class QuickReplySet { | |||
| 100 | } | 86 | } |
| 101 | } | 87 | } |
| 102 | 88 | ||
| 103 | |||
| 104 | |||
| 105 | |||
| 106 | renderSettings() { | 89 | renderSettings() { |
| 107 | if (!this.settingsDom) { | 90 | if (!this.settingsDom) { |
| 108 | this.settingsDom = document.createElement('div'); { | 91 | this.settingsDom = document.createElement('div'); { |
| @@ -123,9 +106,6 @@ export class QuickReplySet { | |||
| 123 | this.settingsDom.append(qr.renderSettings(idx)); | 106 | this.settingsDom.append(qr.renderSettings(idx)); |
| 124 | } | 107 | } |
| 125 | 108 | ||
| 126 | |||
| 127 | |||
| 128 | |||
| 129 | /** | 109 | /** |
| 130 | * | 110 | * |
| 131 | * @param {QuickReply} qr | 111 | * @param {QuickReply} qr |
| @@ -138,6 +118,7 @@ export class QuickReplySet { | |||
| 138 | closure.scope.setMacro('arg::*', ''); | 118 | closure.scope.setMacro('arg::*', ''); |
| 139 | return (await closure.execute())?.pipe; | 119 | return (await closure.execute())?.pipe; |
| 140 | } | 120 | } |
| 121 | |||
| 141 | /** | 122 | /** |
| 142 | * | 123 | * |
| 143 | * @param {QuickReply} qr The QR to execute. | 124 | * @param {QuickReply} qr The QR to execute. |
| @@ -207,6 +188,7 @@ export class QuickReplySet { | |||
| 207 | document.querySelector('#send_but').click(); | 188 | document.querySelector('#send_but').click(); |
| 208 | } | 189 | } |
| 209 | } | 190 | } |
| 191 | |||
| 210 | /** | 192 | /** |
| 211 | * @param {QuickReply} qr | 193 | * @param {QuickReply} qr |
| 212 | * @param {string} [message] - optional altered message to be used | 194 | * @param {string} [message] - optional altered message to be used |
| @@ -220,9 +202,6 @@ export class QuickReplySet { | |||
| 220 | }); | 202 | }); |
| 221 | } | 203 | } |
| 222 | 204 | ||
| 223 | |||
| 224 | |||
| 225 | |||
| 226 | addQuickReply(data = {}) { | 205 | addQuickReply(data = {}) { |
| 227 | const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1; | 206 | const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1; |
| 228 | data.id = | 207 | data.id = |
| @@ -239,6 +218,7 @@ export class QuickReplySet { | |||
| 239 | this.save(); | 218 | this.save(); |
| 240 | return qr; | 219 | return qr; |
| 241 | } | 220 | } |
| 221 | |||
| 242 | addQuickReplyFromText(qrJson) { | 222 | addQuickReplyFromText(qrJson) { |
| 243 | let data; | 223 | let data; |
| 244 | if (qrJson) { | 224 | if (qrJson) { |
| @@ -371,7 +351,6 @@ export class QuickReplySet { | |||
| 371 | this.save(); | 351 | this.save(); |
| 372 | } | 352 | } |
| 373 | 353 | ||
| 374 | |||
| 375 | toJSON() { | 354 | toJSON() { |
| 376 | return { | 355 | return { |
| 377 | version: 2, | 356 | version: 2, |
| @@ -386,7 +365,6 @@ export class QuickReplySet { | |||
| 386 | }; | 365 | }; |
| 387 | } | 366 | } |
| 388 | 367 | ||
| 389 | |||
| 390 | async performSave() { | 368 | async performSave() { |
| 391 | const response = await fetch('/api/quick-replies/save', { | 369 | const response = await fetch('/api/quick-replies/save', { |
| 392 | method: 'POST', | 370 | method: 'POST', |
| @@ -883,6 +883,10 @@ export class SlashCommandHandler { | |||
| 883 | } | 883 | } |
| 884 | } | 884 | } |
| 885 | getQuickReply(args) { | 885 | getQuickReply(args) { |
| 886 | if (!args.id && !args.label) { | ||
| 887 | toastr.error('Please provide a valid id or label.'); | ||
| 888 | return ''; | ||
| 889 | } | ||
| 886 | try { | 890 | try { |
| 887 | return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label)); | 891 | return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label)); |
| 888 | } catch (ex) { | 892 | } catch (ex) { |
| @@ -346,7 +346,7 @@ export class SettingsUi { | |||
| 346 | } | 346 | } |
| 347 | 347 | ||
| 348 | async addQrSet() { | 348 | async addQrSet() { |
| 349 | const name = await Popup.show.input('Create a new World Info', 'Enter a name for the new Quick Reply Set:'); | 349 | const name = await Popup.show.input('Create a new Quick Reply Set', 'Enter a name for the new Quick Reply Set:'); |
| 350 | if (name && name.length > 0) { | 350 | if (name && name.length > 0) { |
| 351 | const oldQrs = QuickReplySet.get(name); | 351 | const oldQrs = QuickReplySet.get(name); |
| 352 | if (oldQrs) { | 352 | if (oldQrs) { |
| @@ -94,6 +94,12 @@ | |||
| 94 | <span data-i18n="World Info">World Info</span> | 94 | <span data-i18n="World Info">World Info</span> |
| 95 | </label> | 95 | </label> |
| 96 | </div> | 96 | </div> |
| 97 | <div data-i18n="[title]ext_regex_reasoning_desc" title="Reasoning block contents. When 'Only Format Prompt' is checked, it will also affect the reasoning contents added to the prompt."> | ||
| 98 | <label class="checkbox flex-container"> | ||
| 99 | <input type="checkbox" name="replace_position" value="6"> | ||
| 100 | <span data-i18n="Reasoning">Reasoning</span> | ||
| 101 | </label> | ||
| 102 | </div> | ||
| 97 | <div class="flex-container wide100p marginTop5"> | 103 | <div class="flex-container wide100p marginTop5"> |
| 98 | <div class="flex1 flex-container flexNoGap"> | 104 | <div class="flex1 flex-container flexNoGap"> |
| 99 | <small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system."> | 105 | <small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system."> |
| @@ -20,6 +20,7 @@ const regex_placement = { | |||
| 20 | SLASH_COMMAND: 3, | 20 | SLASH_COMMAND: 3, |
| 21 | // 4 - sendAs (legacy) | 21 | // 4 - sendAs (legacy) |
| 22 | WORLD_INFO: 5, | 22 | WORLD_INFO: 5, |
| 23 | REASONING: 6, | ||
| 23 | }; | 24 | }; |
| 24 | 25 | ||
| 25 | export const substitute_find_regex = { | 26 | export const substitute_find_regex = { |
| @@ -94,7 +95,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown, | |||
| 94 | // Script applies to Generate and input is Generate | 95 | // Script applies to Generate and input is Generate |
| 95 | (script.promptOnly && isPrompt) || | 96 | (script.promptOnly && isPrompt) || |
| 96 | // Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand | 97 | // Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand |
| 97 | (!script.markdownOnly && !script.promptOnly && !isMarkdown) | 98 | (!script.markdownOnly && !script.promptOnly && !isMarkdown && !isPrompt) |
| 98 | ) { | 99 | ) { |
| 99 | if (isEdit && !script.runOnEdit) { | 100 | if (isEdit && !script.runOnEdit) { |
| 100 | console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`); | 101 | console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`); |
| @@ -10,6 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | |||
| 10 | import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; | 10 | import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; |
| 11 | import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js'; | 11 | import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js'; |
| 12 | import { t } from '../../i18n.js'; | 12 | import { t } from '../../i18n.js'; |
| 13 | import { accountStorage } from '../../util/AccountStorage.js'; | ||
| 13 | 14 | ||
| 14 | /** | 15 | /** |
| 15 | * @typedef {object} RegexScript | 16 | * @typedef {object} RegexScript |
| @@ -18,7 +19,7 @@ import { t } from '../../i18n.js'; | |||
| 18 | * @property {string} replaceString - The replace string | 19 | * @property {string} replaceString - The replace string |
| 19 | * @property {string[]} trimStrings - The trim strings | 20 | * @property {string[]} trimStrings - The trim strings |
| 20 | * @property {string?} findRegex - The find regex | 21 | * @property {string?} findRegex - The find regex |
| 21 | * @property {string?} substituteRegex - The substitute regex | 22 | * @property {number?} substituteRegex - The substitute regex |
| 22 | */ | 23 | */ |
| 23 | 24 | ||
| 24 | /** | 25 | /** |
| @@ -440,8 +441,8 @@ async function checkEmbeddedRegexScripts() { | |||
| 440 | if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) { | 441 | if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) { |
| 441 | const checkKey = `AlertRegex_${characters[chid].avatar}`; | 442 | const checkKey = `AlertRegex_${characters[chid].avatar}`; |
| 442 | 443 | ||
| 443 | if (!localStorage.getItem(checkKey)) { | 444 | if (!accountStorage.getItem(checkKey)) { |
| 444 | localStorage.setItem(checkKey, 'true'); | 445 | accountStorage.setItem(checkKey, 'true'); |
| 445 | const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); | 446 | const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); |
| 446 | const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' }); | 447 | const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' }); |
| 447 | 448 | ||
| @@ -81,6 +81,7 @@ const sources = { | |||
| 81 | huggingface: 'huggingface', | 81 | huggingface: 'huggingface', |
| 82 | nanogpt: 'nanogpt', | 82 | nanogpt: 'nanogpt', |
| 83 | bfl: 'bfl', | 83 | bfl: 'bfl', |
| 84 | falai: 'falai', | ||
| 84 | }; | 85 | }; |
| 85 | 86 | ||
| 86 | const initiators = { | 87 | const initiators = { |
| @@ -1169,6 +1170,10 @@ async function onBflKeyClick() { | |||
| 1169 | return onApiKeyClick('BFL API Key:', SECRET_KEYS.BFL); | 1170 | return onApiKeyClick('BFL API Key:', SECRET_KEYS.BFL); |
| 1170 | } | 1171 | } |
| 1171 | 1172 | ||
| 1173 | async function onFalaiKeyClick() { | ||
| 1174 | return onApiKeyClick('FALAI API Key:', SECRET_KEYS.FALAI); | ||
| 1175 | } | ||
| 1176 | |||
| 1172 | function onBflUpsamplingInput() { | 1177 | function onBflUpsamplingInput() { |
| 1173 | extension_settings.sd.bfl_upsampling = !!$('#sd_bfl_upsampling').prop('checked'); | 1178 | extension_settings.sd.bfl_upsampling = !!$('#sd_bfl_upsampling').prop('checked'); |
| 1174 | saveSettingsDebounced(); | 1179 | saveSettingsDebounced(); |
| @@ -1299,6 +1304,7 @@ async function onModelChange() { | |||
| 1299 | sources.huggingface, | 1304 | sources.huggingface, |
| 1300 | sources.nanogpt, | 1305 | sources.nanogpt, |
| 1301 | sources.bfl, | 1306 | sources.bfl, |
| 1307 | sources.falai, | ||
| 1302 | ]; | 1308 | ]; |
| 1303 | 1309 | ||
| 1304 | if (cloudSources.includes(extension_settings.sd.source)) { | 1310 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1707,6 +1713,9 @@ async function loadModels() { | |||
| 1707 | case sources.bfl: | 1713 | case sources.bfl: |
| 1708 | models = await loadBflModels(); | 1714 | models = await loadBflModels(); |
| 1709 | break; | 1715 | break; |
| 1716 | case sources.falai: | ||
| 1717 | models = await loadFalaiModels(); | ||
| 1718 | break; | ||
| 1710 | } | 1719 | } |
| 1711 | 1720 | ||
| 1712 | for (const model of models) { | 1721 | for (const model of models) { |
| @@ -1744,6 +1753,21 @@ async function loadBflModels() { | |||
| 1744 | ]; | 1753 | ]; |
| 1745 | } | 1754 | } |
| 1746 | 1755 | ||
| 1756 | async function loadFalaiModels() { | ||
| 1757 | $('#sd_falai_key').toggleClass('success', !!secret_state[SECRET_KEYS.FALAI]); | ||
| 1758 | |||
| 1759 | const result = await fetch('/api/sd/falai/models', { | ||
| 1760 | method: 'POST', | ||
| 1761 | headers: getRequestHeaders(), | ||
| 1762 | }); | ||
| 1763 | |||
| 1764 | if (result.ok) { | ||
| 1765 | return await result.json(); | ||
| 1766 | } | ||
| 1767 | |||
| 1768 | return []; | ||
| 1769 | } | ||
| 1770 | |||
| 1747 | async function loadPollinationsModels() { | 1771 | async function loadPollinationsModels() { |
| 1748 | const result = await fetch('/api/sd/pollinations/models', { | 1772 | const result = await fetch('/api/sd/pollinations/models', { |
| 1749 | method: 'POST', | 1773 | method: 'POST', |
| @@ -2081,6 +2105,9 @@ async function loadSchedulers() { | |||
| 2081 | case sources.bfl: | 2105 | case sources.bfl: |
| 2082 | schedulers = ['N/A']; | 2106 | schedulers = ['N/A']; |
| 2083 | break; | 2107 | break; |
| 2108 | case sources.falai: | ||
| 2109 | schedulers = ['N/A']; | ||
| 2110 | break; | ||
| 2084 | } | 2111 | } |
| 2085 | 2112 | ||
| 2086 | for (const scheduler of schedulers) { | 2113 | for (const scheduler of schedulers) { |
| @@ -2735,6 +2762,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2735 | case sources.bfl: | 2762 | case sources.bfl: |
| 2736 | result = await generateBflImage(prefixedPrompt, signal); | 2763 | result = await generateBflImage(prefixedPrompt, signal); |
| 2737 | break; | 2764 | break; |
| 2765 | case sources.falai: | ||
| 2766 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); | ||
| 2767 | break; | ||
| 2738 | } | 2768 | } |
| 2739 | 2769 | ||
| 2740 | if (!result.data) { | 2770 | if (!result.data) { |
| @@ -3496,6 +3526,40 @@ async function generateBflImage(prompt, signal) { | |||
| 3496 | } | 3526 | } |
| 3497 | } | 3527 | } |
| 3498 | 3528 | ||
| 3529 | /** | ||
| 3530 | * Generates an image using the FAL.AI API. | ||
| 3531 | * @param {string} prompt - The main instruction used to guide the image generation. | ||
| 3532 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. | ||
| 3533 | * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request. | ||
| 3534 | * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete. | ||
| 3535 | */ | ||
| 3536 | async function generateFalaiImage(prompt, negativePrompt, signal) { | ||
| 3537 | const result = await fetch('/api/sd/falai/generate', { | ||
| 3538 | method: 'POST', | ||
| 3539 | headers: getRequestHeaders(), | ||
| 3540 | signal: signal, | ||
| 3541 | body: JSON.stringify({ | ||
| 3542 | prompt: prompt, | ||
| 3543 | negative_prompt: negativePrompt, | ||
| 3544 | model: extension_settings.sd.model, | ||
| 3545 | steps: clamp(extension_settings.sd.steps, 1, 50), | ||
| 3546 | guidance: clamp(extension_settings.sd.scale, 1.5, 5), | ||
| 3547 | width: clamp(extension_settings.sd.width, 256, 1440), | ||
| 3548 | height: clamp(extension_settings.sd.height, 256, 1440), | ||
| 3549 | seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, | ||
| 3550 | }), | ||
| 3551 | }); | ||
| 3552 | |||
| 3553 | if (result.ok) { | ||
| 3554 | const data = await result.json(); | ||
| 3555 | return { format: 'jpg', data: data.image }; | ||
| 3556 | } else { | ||
| 3557 | const text = await result.text(); | ||
| 3558 | console.log(text); | ||
| 3559 | throw new Error(text); | ||
| 3560 | } | ||
| 3561 | } | ||
| 3562 | |||
| 3499 | async function onComfyOpenWorkflowEditorClick() { | 3563 | async function onComfyOpenWorkflowEditorClick() { |
| 3500 | let workflow = await (await fetch('/api/sd/comfy/workflow', { | 3564 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| 3501 | method: 'POST', | 3565 | method: 'POST', |
| @@ -3782,6 +3846,8 @@ function isValidState() { | |||
| 3782 | return secret_state[SECRET_KEYS.NANOGPT]; | 3846 | return secret_state[SECRET_KEYS.NANOGPT]; |
| 3783 | case sources.bfl: | 3847 | case sources.bfl: |
| 3784 | return secret_state[SECRET_KEYS.BFL]; | 3848 | return secret_state[SECRET_KEYS.BFL]; |
| 3849 | case sources.falai: | ||
| 3850 | return secret_state[SECRET_KEYS.FALAI]; | ||
| 3785 | } | 3851 | } |
| 3786 | } | 3852 | } |
| 3787 | 3853 | ||
| @@ -4443,6 +4509,7 @@ jQuery(async () => { | |||
| 4443 | $('#sd_function_tool').on('input', onFunctionToolInput); | 4509 | $('#sd_function_tool').on('input', onFunctionToolInput); |
| 4444 | $('#sd_bfl_key').on('click', onBflKeyClick); | 4510 | $('#sd_bfl_key').on('click', onBflKeyClick); |
| 4445 | $('#sd_bfl_upsampling').on('input', onBflUpsamplingInput); | 4511 | $('#sd_bfl_upsampling').on('input', onBflUpsamplingInput); |
| 4512 | $('#sd_falai_key').on('click', onFalaiKeyClick); | ||
| 4446 | 4513 | ||
| 4447 | if (!CSS.supports('field-sizing', 'content')) { | 4514 | if (!CSS.supports('field-sizing', 'content')) { |
| 4448 | $('.sd_settings .inline-drawer-toggle').on('click', function () { | 4515 | $('.sd_settings .inline-drawer-toggle').on('click', function () { |
| @@ -41,7 +41,8 @@ | |||
| 41 | <option value="blockentropy">Block Entropy</option> | 41 | <option value="blockentropy">Block Entropy</option> |
| 42 | <option value="comfy">ComfyUI</option> | 42 | <option value="comfy">ComfyUI</option> |
| 43 | <option value="drawthings">DrawThings HTTP API</option> | 43 | <option value="drawthings">DrawThings HTTP API</option> |
| 44 | <option value="extras">Extras API (local / remote)</option> | 44 | <option value="extras">Extras API (deprecated)</option> |
| 45 | <option value="falai">FAL.AI</option> | ||
| 45 | <option value="huggingface">HuggingFace Inference API (serverless)</option> | 46 | <option value="huggingface">HuggingFace Inference API (serverless)</option> |
| 46 | <option value="nanogpt">NanoGPT</option> | 47 | <option value="nanogpt">NanoGPT</option> |
| 47 | <option value="novel">NovelAI Diffusion</option> | 48 | <option value="novel">NovelAI Diffusion</option> |
| @@ -256,6 +257,20 @@ | |||
| 256 | </label> | 257 | </label> |
| 257 | </div> | 258 | </div> |
| 258 | 259 | ||
| 260 | <div data-sd-source="falai"> | ||
| 261 | <div class="flex-container flexnowrap alignItemsBaseline marginBot5"> | ||
| 262 | <a href="https://fal.ai/dashboard" target="_blank" rel="noopener noreferrer"> | ||
| 263 | <strong data-i18n="API Key">API Key</strong> | ||
| 264 | <i class="fa-solid fa-share-from-square"></i> | ||
| 265 | </a> | ||
| 266 | <span class="expander"></span> | ||
| 267 | <div id="sd_falai_key" class="menu_button menu_button_icon"> | ||
| 268 | <i class="fa-fw fa-solid fa-key"></i> | ||
| 269 | <span data-i18n="Click to set">Click to set</span> | ||
| 270 | </div> | ||
| 271 | </div> | ||
| 272 | </div> | ||
| 273 | |||
| 259 | <div class="flex-container"> | 274 | <div class="flex-container"> |
| 260 | <div class="flex1"> | 275 | <div class="flex1"> |
| 261 | <label for="sd_model" data-i18n="Model">Model</label> | 276 | <label for="sd_model" data-i18n="Model">Model</label> |
| @@ -30,6 +30,7 @@ import { GoogleTranslateTtsProvider } from './google-translate.js'; | |||
| 30 | export { talkingAnimation }; | 30 | export { talkingAnimation }; |
| 31 | 31 | ||
| 32 | const UPDATE_INTERVAL = 1000; | 32 | const UPDATE_INTERVAL = 1000; |
| 33 | const wrapper = new ModuleWorkerWrapper(moduleWorker); | ||
| 33 | 34 | ||
| 34 | let voiceMapEntries = []; | 35 | let voiceMapEntries = []; |
| 35 | let voiceMap = {}; // {charName:voiceid, charName2:voiceid2} | 36 | let voiceMap = {}; // {charName:voiceid, charName2:voiceid2} |
| @@ -120,7 +121,7 @@ async function onNarrateOneMessage() { | |||
| 120 | } | 121 | } |
| 121 | 122 | ||
| 122 | resetTtsPlayback(); | 123 | resetTtsPlayback(); |
| 123 | ttsJobQueue.push(message); | 124 | processAndQueueTtsMessage(message); |
| 124 | moduleWorker(); | 125 | moduleWorker(); |
| 125 | } | 126 | } |
| 126 | 127 | ||
| @@ -147,7 +148,7 @@ async function onNarrateText(args, text) { | |||
| 147 | } | 148 | } |
| 148 | 149 | ||
| 149 | resetTtsPlayback(); | 150 | resetTtsPlayback(); |
| 150 | ttsJobQueue.push({ mes: text, name: name }); | 151 | processAndQueueTtsMessage({ mes: text, name: name }); |
| 151 | await moduleWorker(); | 152 | await moduleWorker(); |
| 152 | 153 | ||
| 153 | // Return back to the chat voices | 154 | // Return back to the chat voices |
| @@ -220,6 +221,36 @@ function isTtsProcessing() { | |||
| 220 | return processing; | 221 | return processing; |
| 221 | } | 222 | } |
| 222 | 223 | ||
| 224 | /** | ||
| 225 | * Splits a message into lines and adds each non-empty line to the TTS job queue. | ||
| 226 | * @param {Object} message - The message object to be processed. | ||
| 227 | * @param {string} message.mes - The text of the message to be split into lines. | ||
| 228 | * @param {string} message.name - The name associated with the message. | ||
| 229 | * @returns {void} | ||
| 230 | */ | ||
| 231 | function processAndQueueTtsMessage(message) { | ||
| 232 | if (!extension_settings.tts.narrate_by_paragraphs) { | ||
| 233 | ttsJobQueue.push(message); | ||
| 234 | return; | ||
| 235 | } | ||
| 236 | |||
| 237 | const lines = message.mes.split('\n'); | ||
| 238 | |||
| 239 | for (let i = 0; i < lines.length; i++) { | ||
| 240 | const line = lines[i]; | ||
| 241 | |||
| 242 | if (line.length === 0) { | ||
| 243 | continue; | ||
| 244 | } | ||
| 245 | |||
| 246 | ttsJobQueue.push( | ||
| 247 | Object.assign({}, message, { | ||
| 248 | mes: line, | ||
| 249 | }), | ||
| 250 | ); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 223 | function debugTtsPlayback() { | 254 | function debugTtsPlayback() { |
| 224 | console.log(JSON.stringify( | 255 | console.log(JSON.stringify( |
| 225 | { | 256 | { |
| @@ -350,7 +381,7 @@ function onAudioControlClicked() { | |||
| 350 | talkingAnimation(false); | 381 | talkingAnimation(false); |
| 351 | } else { | 382 | } else { |
| 352 | // Default play behavior if not processing or playing is to play the last message. | 383 | // Default play behavior if not processing or playing is to play the last message. |
| 353 | ttsJobQueue.push(context.chat[context.chat.length - 1]); | 384 | processAndQueueTtsMessage(context.chat[context.chat.length - 1]); |
| 354 | } | 385 | } |
| 355 | updateUiAudioPlayState(); | 386 | updateUiAudioPlayState(); |
| 356 | } | 387 | } |
| @@ -376,6 +407,7 @@ function completeCurrentAudioJob() { | |||
| 376 | currentAudioJob = null; | 407 | currentAudioJob = null; |
| 377 | talkingAnimation(false); //stop lip animation | 408 | talkingAnimation(false); //stop lip animation |
| 378 | // updateUiPlayState(); | 409 | // updateUiPlayState(); |
| 410 | wrapper.update(); | ||
| 379 | } | 411 | } |
| 380 | 412 | ||
| 381 | /** | 413 | /** |
| @@ -466,7 +498,7 @@ async function processTtsQueue() { | |||
| 466 | } | 498 | } |
| 467 | 499 | ||
| 468 | if (extension_settings.tts.skip_tags) { | 500 | if (extension_settings.tts.skip_tags) { |
| 469 | text = text.replace(/<.*?>.*?<\/.*?>/g, '').trim(); | 501 | text = text.replace(/<.*?>[\s\S]*?<\/.*?>/g, '').trim(); |
| 470 | } | 502 | } |
| 471 | 503 | ||
| 472 | if (!extension_settings.tts.pass_asterisks) { | 504 | if (!extension_settings.tts.pass_asterisks) { |
| @@ -569,6 +601,7 @@ function loadSettings() { | |||
| 569 | $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only); | 601 | $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only); |
| 570 | $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); | 602 | $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); |
| 571 | $('#tts_periodic_auto_generation').prop('checked', extension_settings.tts.periodic_auto_generation); | 603 | $('#tts_periodic_auto_generation').prop('checked', extension_settings.tts.periodic_auto_generation); |
| 604 | $('#tts_narrate_by_paragraphs').prop('checked', extension_settings.tts.narrate_by_paragraphs); | ||
| 572 | $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); | 605 | $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); |
| 573 | $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); | 606 | $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); |
| 574 | $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); | 607 | $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); |
| @@ -638,6 +671,11 @@ function onPeriodicAutoGenerationClick() { | |||
| 638 | saveSettingsDebounced(); | 671 | saveSettingsDebounced(); |
| 639 | } | 672 | } |
| 640 | 673 | ||
| 674 | function onNarrateByParagraphsClick() { | ||
| 675 | extension_settings.tts.narrate_by_paragraphs = !!$('#tts_narrate_by_paragraphs').prop('checked'); | ||
| 676 | saveSettingsDebounced(); | ||
| 677 | } | ||
| 678 | |||
| 641 | 679 | ||
| 642 | function onNarrateDialoguesClick() { | 680 | function onNarrateDialoguesClick() { |
| 643 | extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked'); | 681 | extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked'); |
| @@ -816,7 +854,12 @@ async function onMessageEvent(messageId, lastCharIndex) { | |||
| 816 | lastChatId = context.chatId; | 854 | lastChatId = context.chatId; |
| 817 | 855 | ||
| 818 | console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); | 856 | console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); |
| 857 | |||
| 858 | if (extension_settings.tts.periodic_auto_generation) { | ||
| 819 | ttsJobQueue.push(message); | 859 | ttsJobQueue.push(message); |
| 860 | } else { | ||
| 861 | processAndQueueTtsMessage(message); | ||
| 862 | } | ||
| 820 | } | 863 | } |
| 821 | 864 | ||
| 822 | async function onMessageDeleted() { | 865 | async function onMessageDeleted() { |
| @@ -1156,6 +1199,7 @@ jQuery(async function () { | |||
| 1156 | $('#tts_pass_asterisks').on('click', onPassAsterisksClick); | 1199 | $('#tts_pass_asterisks').on('click', onPassAsterisksClick); |
| 1157 | $('#tts_auto_generation').on('click', onAutoGenerationClick); | 1200 | $('#tts_auto_generation').on('click', onAutoGenerationClick); |
| 1158 | $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick); | 1201 | $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick); |
| 1202 | $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick); | ||
| 1159 | $('#tts_narrate_user').on('click', onNarrateUserClick); | 1203 | $('#tts_narrate_user').on('click', onNarrateUserClick); |
| 1160 | 1204 | ||
| 1161 | $('#playback_rate').on('input', function () { | 1205 | $('#playback_rate').on('input', function () { |
| @@ -1177,7 +1221,6 @@ jQuery(async function () { | |||
| 1177 | loadSettings(); // Depends on Extension Controls and loadTtsProvider | 1221 | loadSettings(); // Depends on Extension Controls and loadTtsProvider |
| 1178 | loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies | 1222 | loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies |
| 1179 | addAudioControl(); // Depends on Extension Controls | 1223 | addAudioControl(); // Depends on Extension Controls |
| 1180 | const wrapper = new ModuleWorkerWrapper(moduleWorker); | ||
| 1181 | setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things | 1224 | setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things |
| 1182 | eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback); | 1225 | eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback); |
| 1183 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); | 1226 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| @@ -30,6 +30,10 @@ | |||
| 30 | <input type="checkbox" id="tts_periodic_auto_generation"> | 30 | <input type="checkbox" id="tts_periodic_auto_generation"> |
| 31 | <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small> | 31 | <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small> |
| 32 | </label> | 32 | </label> |
| 33 | <label class="checkbox_label" for="tts_narrate_by_paragraphs"> | ||
| 34 | <input type="checkbox" id="tts_narrate_by_paragraphs"> | ||
| 35 | <small data-i18n="Narrate by paragraphs (when not streaming)">Narrate by paragraphs (when not streaming)</small> | ||
| 36 | </label> | ||
| 33 | <label class="checkbox_label" for="tts_narrate_quoted"> | 37 | <label class="checkbox_label" for="tts_narrate_quoted"> |
| 34 | <input type="checkbox" id="tts_narrate_quoted"> | 38 | <input type="checkbox" id="tts_narrate_quoted"> |
| 35 | <small data-i18n="Only narrate quotes">Only narrate "quotes"</small> | 39 | <small data-i18n="Only narrate quotes">Only narrate "quotes"</small> |
| @@ -1638,14 +1638,12 @@ jQuery(async () => { | |||
| 1638 | } | 1638 | } |
| 1639 | return textResult; | 1639 | return textResult; |
| 1640 | }; | 1640 | }; |
| 1641 | |||
| 1642 | if (args.return === 'chunks') { | 1641 | if (args.return === 'chunks') { |
| 1643 | return getChunksText(); | 1642 | return getChunksText(); |
| 1644 | } | 1643 | } |
| 1645 | 1644 | ||
| 1646 | // @ts-ignore | 1645 | // @ts-ignore |
| 1647 | return slashCommandReturnHelper.doReturn(args.return ?? 'object', urls, { objectToStringFunc: list => list.join('\n') }); | 1646 | return slashCommandReturnHelper.doReturn(args.return ?? 'object', urls, { objectToStringFunc: list => list.join('\n') }); |
| 1648 | |||
| 1649 | }, | 1647 | }, |
| 1650 | aliases: ['databank-search', 'data-bank-search'], | 1648 | aliases: ['databank-search', 'data-bank-search'], |
| 1651 | helpString: 'Search the Data Bank for a specific query using vector similarity. Returns a list of file URLs with the most relevant content.', | 1649 | helpString: 'Search the Data Bank for a specific query using vector similarity. Returns a list of file URLs with the most relevant content.', |
| @@ -1660,10 +1658,10 @@ jQuery(async () => { | |||
| 1660 | defaultValue: 'object', | 1658 | defaultValue: 'object', |
| 1661 | enumList: [ | 1659 | enumList: [ |
| 1662 | new SlashCommandEnumValue('chunks', 'Return the actual content chunks', enumTypes.enum, '{}'), | 1660 | new SlashCommandEnumValue('chunks', 'Return the actual content chunks', enumTypes.enum, '{}'), |
| 1663 | ...slashCommandReturnHelper.enumList({ allowObject: true }) | 1661 | ...slashCommandReturnHelper.enumList({ allowObject: true }), |
| 1664 | ], | 1662 | ], |
| 1665 | forceEnum: true, | 1663 | forceEnum: true, |
| 1666 | }) | 1664 | }), |
| 1667 | ], | 1665 | ], |
| 1668 | unnamedArgumentList: [ | 1666 | unnamedArgumentList: [ |
| 1669 | new SlashCommandArgument('Query to search by.', ARGUMENT_TYPE.STRING, true, false), | 1667 | new SlashCommandArgument('Query to search by.', ARGUMENT_TYPE.STRING, true, false), |
| @@ -11,7 +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="cohere">Cohere</option> | 13 | <option value="cohere">Cohere</option> |
| 14 | <option value="extras">Extras</option> | 14 | <option value="extras">Extras (deprecated)</option> |
| 15 | <option value="palm">Google AI Studio</option> | 15 | <option value="palm">Google AI Studio</option> |
| 16 | <option value="llamacpp">llama.cpp</option> | 16 | <option value="llamacpp">llama.cpp</option> |
| 17 | <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option> | 17 | <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option> |
| @@ -1,18 +1,30 @@ | |||
| 1 | ////////////////// LOCAL STORAGE HANDLING ///////////////////// | 1 | ////////////////// LOCAL STORAGE HANDLING ///////////////////// |
| 2 | 2 | ||
| 3 | /** | ||
| 4 | * @deprecated THIS FUNCTION IS OBSOLETE. DO NOT USE | ||
| 5 | */ | ||
| 3 | export function SaveLocal(target, val) { | 6 | export function SaveLocal(target, val) { |
| 4 | localStorage.setItem(target, val); | 7 | localStorage.setItem(target, val); |
| 5 | console.debug('SaveLocal -- ' + target + ' : ' + val); | 8 | console.debug('SaveLocal -- ' + target + ' : ' + val); |
| 6 | } | 9 | } |
| 10 | /** | ||
| 11 | * @deprecated THIS FUNCTION IS OBSOLETE. DO NOT USE | ||
| 12 | */ | ||
| 7 | export function LoadLocal(target) { | 13 | export function LoadLocal(target) { |
| 8 | console.debug('LoadLocal -- ' + target); | 14 | console.debug('LoadLocal -- ' + target); |
| 9 | return localStorage.getItem(target); | 15 | return localStorage.getItem(target); |
| 10 | 16 | ||
| 11 | } | 17 | } |
| 18 | /** | ||
| 19 | * @deprecated THIS FUNCTION IS OBSOLETE. DO NOT USE | ||
| 20 | */ | ||
| 12 | export function LoadLocalBool(target) { | 21 | export function LoadLocalBool(target) { |
| 13 | let result = localStorage.getItem(target) === 'true'; | 22 | let result = localStorage.getItem(target) === 'true'; |
| 14 | return result; | 23 | return result; |
| 15 | } | 24 | } |
| 25 | /** | ||
| 26 | * @deprecated THIS FUNCTION IS OBSOLETE. DO NOT USE | ||
| 27 | */ | ||
| 16 | export function CheckLocal() { | 28 | export function CheckLocal() { |
| 17 | console.log('----------local storage---------'); | 29 | console.log('----------local storage---------'); |
| 18 | var i; | 30 | var i; |
| @@ -22,6 +34,9 @@ export function CheckLocal() { | |||
| 22 | console.log('------------------------------'); | 34 | console.log('------------------------------'); |
| 23 | } | 35 | } |
| 24 | 36 | ||
| 37 | /** | ||
| 38 | * @deprecated THIS FUNCTION IS OBSOLETE. DO NOT USE | ||
| 39 | */ | ||
| 25 | export function ClearLocal() { localStorage.clear(); console.log('Removed All Local Storage'); } | 40 | export function ClearLocal() { localStorage.clear(); console.log('Removed All Local Storage'); } |
| 26 | 41 | ||
| 27 | ///////////////////////////////////////////////////////////////////////// | 42 | ///////////////////////////////////////////////////////////////////////// |
| @@ -78,9 +78,11 @@ import { FILTER_TYPES, FilterHelper } from './filters.js'; | |||
| 78 | import { isExternalMediaAllowed } from './chats.js'; | 78 | import { isExternalMediaAllowed } from './chats.js'; |
| 79 | import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 79 | import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| 80 | import { t } from './i18n.js'; | 80 | import { t } from './i18n.js'; |
| 81 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 81 | 82 | ||
| 82 | export { | 83 | export { |
| 83 | selected_group, | 84 | selected_group, |
| 85 | openGroupId, | ||
| 84 | is_group_automode_enabled, | 86 | is_group_automode_enabled, |
| 85 | hideMutedSprites, | 87 | hideMutedSprites, |
| 86 | is_group_generating, | 88 | is_group_generating, |
| @@ -291,10 +293,11 @@ export function getGroupNames() { | |||
| 291 | 293 | ||
| 292 | /** | 294 | /** |
| 293 | * Finds the character ID for a group member. | 295 | * Finds the character ID for a group member. |
| 294 | * @param {string} arg 0-based member index or character name | 296 | * @param {number|string} arg 0-based member index or character name |
| 295 | * @returns {number} 0-based character ID | 297 | * @param {Boolean} full Whether to return a key-value object containing extra data |
| 298 | * @returns {number|Object} 0-based character ID or key-value object if full is true | ||
| 296 | */ | 299 | */ |
| 297 | export function findGroupMemberId(arg) { | 300 | export function findGroupMemberId(arg, full = false) { |
| 298 | arg = arg?.trim(); | 301 | arg = arg?.trim(); |
| 299 | 302 | ||
| 300 | if (!arg) { | 303 | if (!arg) { |
| @@ -310,15 +313,19 @@ export function findGroupMemberId(arg) { | |||
| 310 | } | 313 | } |
| 311 | 314 | ||
| 312 | const index = parseInt(arg); | 315 | const index = parseInt(arg); |
| 313 | const searchByName = isNaN(index); | 316 | const searchByString = isNaN(index); |
| 314 | 317 | ||
| 315 | if (searchByName) { | 318 | if (searchByString) { |
| 316 | const memberNames = group.members.map(x => ({ name: characters.find(y => y.avatar === x)?.name, index: characters.findIndex(y => y.avatar === x) })); | 319 | const memberNames = group.members.map(x => ({ |
| 317 | const fuse = new Fuse(memberNames, { keys: ['name'] }); | 320 | avatar: x, |
| 321 | name: characters.find(y => y.avatar === x)?.name, | ||
| 322 | index: characters.findIndex(y => y.avatar === x), | ||
| 323 | })); | ||
| 324 | const fuse = new Fuse(memberNames, { keys: ['avatar', 'name'] }); | ||
| 318 | const result = fuse.search(arg); | 325 | const result = fuse.search(arg); |
| 319 | 326 | ||
| 320 | if (!result.length) { | 327 | if (!result.length) { |
| 321 | console.warn(`WARN: No group member found with name ${arg}`); | 328 | console.warn(`WARN: No group member found using string ${arg}`); |
| 322 | return; | 329 | return; |
| 323 | } | 330 | } |
| 324 | 331 | ||
| @@ -329,9 +336,11 @@ export function findGroupMemberId(arg) { | |||
| 329 | return; | 336 | return; |
| 330 | } | 337 | } |
| 331 | 338 | ||
| 332 | console.log(`Triggering group member ${chid} (${arg}) from search result`, result[0]); | 339 | console.log(`Targeting group member ${chid} (${arg}) from search result`, result[0]); |
| 333 | return chid; | 340 | |
| 334 | } else { | 341 | return !full ? chid : { ...{ id: chid }, ...result[0].item }; |
| 342 | } | ||
| 343 | else { | ||
| 335 | const memberAvatar = group.members[index]; | 344 | const memberAvatar = group.members[index]; |
| 336 | 345 | ||
| 337 | if (memberAvatar === undefined) { | 346 | if (memberAvatar === undefined) { |
| @@ -346,8 +355,14 @@ export function findGroupMemberId(arg) { | |||
| 346 | return; | 355 | return; |
| 347 | } | 356 | } |
| 348 | 357 | ||
| 349 | console.log(`Triggering group member ${memberAvatar} at index ${index}`); | 358 | console.log(`Targeting group member ${memberAvatar} at index ${index}`); |
| 350 | return chid; | 359 | |
| 360 | return !full ? chid : { | ||
| 361 | id: chid, | ||
| 362 | avatar: memberAvatar, | ||
| 363 | name: characters.find(y => y.avatar === memberAvatar)?.name, | ||
| 364 | index: index, | ||
| 365 | }; | ||
| 351 | } | 366 | } |
| 352 | } | 367 | } |
| 353 | 368 | ||
| @@ -804,7 +819,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { | |||
| 804 | 819 | ||
| 805 | /** @type {any} Caution: JS war crimes ahead */ | 820 | /** @type {any} Caution: JS war crimes ahead */ |
| 806 | let textResult = ''; | 821 | let textResult = ''; |
| 807 | let typingIndicator = $('#chat .typing_indicator'); | ||
| 808 | const group = groups.find((x) => x.id === selected_group); | 822 | const group = groups.find((x) => x.id === selected_group); |
| 809 | 823 | ||
| 810 | if (!group || !Array.isArray(group.members) || !group.members.length) { | 824 | if (!group || !Array.isArray(group.members) || !group.members.length) { |
| @@ -820,14 +834,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { | |||
| 820 | setCharacterId(undefined); | 834 | setCharacterId(undefined); |
| 821 | const userInput = String($('#send_textarea').val()); | 835 | const userInput = String($('#send_textarea').val()); |
| 822 | 836 | ||
| 823 | if (typingIndicator.length === 0 && !isStreamingEnabled()) { | ||
| 824 | typingIndicator = $( | ||
| 825 | '#typing_indicator_template .typing_indicator', | ||
| 826 | ).clone(); | ||
| 827 | typingIndicator.hide(); | ||
| 828 | $('#chat').append(typingIndicator); | ||
| 829 | } | ||
| 830 | |||
| 831 | // id of this specific batch for regeneration purposes | 837 | // id of this specific batch for regeneration purposes |
| 832 | group_generation_id = Date.now(); | 838 | group_generation_id = Date.now(); |
| 833 | const lastMessage = chat[chat.length - 1]; | 839 | const lastMessage = chat[chat.length - 1]; |
| @@ -905,14 +911,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { | |||
| 905 | } | 911 | } |
| 906 | await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId); | 912 | await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId); |
| 907 | 913 | ||
| 908 | if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { | ||
| 909 | // update indicator and scroll down | ||
| 910 | typingIndicator | ||
| 911 | .find('.typing_indicator_name') | ||
| 912 | .text(characters[chId].name); | ||
| 913 | typingIndicator.show(); | ||
| 914 | } | ||
| 915 | |||
| 916 | // Wait for generation to finish | 914 | // Wait for generation to finish |
| 917 | textResult = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); | 915 | textResult = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) }); |
| 918 | let messageChunk = textResult?.messageChunk; | 916 | let messageChunk = textResult?.messageChunk; |
| @@ -929,8 +927,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { | |||
| 929 | } | 927 | } |
| 930 | } | 928 | } |
| 931 | } finally { | 929 | } finally { |
| 932 | typingIndicator.hide(); | ||
| 933 | |||
| 934 | is_group_generating = false; | 930 | is_group_generating = false; |
| 935 | setSendButtonState(false); | 931 | setSendButtonState(false); |
| 936 | setCharacterId(undefined); | 932 | setCharacterId(undefined); |
| @@ -1314,10 +1310,10 @@ function printGroupCandidates() { | |||
| 1314 | formatNavigator: PAGINATION_TEMPLATE, | 1310 | formatNavigator: PAGINATION_TEMPLATE, |
| 1315 | showNavigator: true, | 1311 | showNavigator: true, |
| 1316 | showSizeChanger: true, | 1312 | showSizeChanger: true, |
| 1317 | pageSize: Number(localStorage.getItem(storageKey)) || 5, | 1313 | pageSize: Number(accountStorage.getItem(storageKey)) || 5, |
| 1318 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], | 1314 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], |
| 1319 | afterSizeSelectorChange: function (e) { | 1315 | afterSizeSelectorChange: function (e) { |
| 1320 | localStorage.setItem(storageKey, e.target.value); | 1316 | accountStorage.setItem(storageKey, e.target.value); |
| 1321 | }, | 1317 | }, |
| 1322 | callback: function (data) { | 1318 | callback: function (data) { |
| 1323 | $('#rm_group_add_members').empty(); | 1319 | $('#rm_group_add_members').empty(); |
| @@ -1341,10 +1337,10 @@ function printGroupMembers() { | |||
| 1341 | formatNavigator: PAGINATION_TEMPLATE, | 1337 | formatNavigator: PAGINATION_TEMPLATE, |
| 1342 | showNavigator: true, | 1338 | showNavigator: true, |
| 1343 | showSizeChanger: true, | 1339 | showSizeChanger: true, |
| 1344 | pageSize: Number(localStorage.getItem(storageKey)) || 5, | 1340 | pageSize: Number(accountStorage.getItem(storageKey)) || 5, |
| 1345 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], | 1341 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], |
| 1346 | afterSizeSelectorChange: function (e) { | 1342 | afterSizeSelectorChange: function (e) { |
| 1347 | localStorage.setItem(storageKey, e.target.value); | 1343 | accountStorage.setItem(storageKey, e.target.value); |
| 1348 | }, | 1344 | }, |
| 1349 | callback: function (data) { | 1345 | callback: function (data) { |
| 1350 | $('.rm_group_members').empty(); | 1346 | $('.rm_group_members').empty(); |
| @@ -1367,6 +1363,15 @@ function getGroupCharacterBlock(character) { | |||
| 1367 | template.find('.ch_fav').val(isFav); | 1363 | template.find('.ch_fav').val(isFav); |
| 1368 | template.toggleClass('is_fav', isFav); | 1364 | template.toggleClass('is_fav', isFav); |
| 1369 | 1365 | ||
| 1366 | const auxFieldName = power_user.aux_field || 'character_version'; | ||
| 1367 | const auxFieldValue = (character.data && character.data[auxFieldName]) || ''; | ||
| 1368 | if (auxFieldValue) { | ||
| 1369 | template.find('.character_version').text(auxFieldValue); | ||
| 1370 | } | ||
| 1371 | else { | ||
| 1372 | template.find('.character_version').hide(); | ||
| 1373 | } | ||
| 1374 | |||
| 1370 | let queuePosition = groupChatQueueOrder.get(character.avatar); | 1375 | let queuePosition = groupChatQueueOrder.get(character.avatar); |
| 1371 | if (queuePosition) { | 1376 | if (queuePosition) { |
| 1372 | template.find('.queue_position').text(queuePosition); | 1377 | template.find('.queue_position').text(queuePosition); |
| @@ -188,7 +188,7 @@ export async function generateKoboldWithStreaming(generate_data, signal) { | |||
| 188 | if (data?.token) { | 188 | if (data?.token) { |
| 189 | text += data.token; | 189 | text += data.token; |
| 190 | } | 190 | } |
| 191 | yield { text, swipes: [], toolCalls: [] }; | 191 | yield { text, swipes: [], toolCalls: [], state: {} }; |
| 192 | } | 192 | } |
| 193 | }; | 193 | }; |
| 194 | } | 194 | } |
| @@ -27,21 +27,42 @@ export async function hideLoader() { | |||
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | return new Promise((resolve) => { | 29 | return new Promise((resolve) => { |
| 30 | // Spinner blurs/fades out | 30 | const spinner = $('#load-spinner'); |
| 31 | $('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () { | 31 | if (!spinner.length) { |
| 32 | console.warn('Spinner element not found, skipping animation'); | ||
| 33 | cleanup(); | ||
| 34 | return; | ||
| 35 | } | ||
| 36 | |||
| 37 | // Check if transitions are enabled | ||
| 38 | const transitionDuration = spinner[0] ? getComputedStyle(spinner[0]).transitionDuration : '0s'; | ||
| 39 | const hasTransitions = parseFloat(transitionDuration) > 0; | ||
| 40 | |||
| 41 | if (hasTransitions) { | ||
| 42 | Promise.race([ | ||
| 43 | new Promise((r) => setTimeout(r, 500)), // Fallback timeout | ||
| 44 | new Promise((r) => spinner.one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', r)), | ||
| 45 | ]).finally(cleanup); | ||
| 46 | } else { | ||
| 47 | cleanup(); | ||
| 48 | } | ||
| 49 | |||
| 50 | function cleanup() { | ||
| 32 | $('#loader').remove(); | 51 | $('#loader').remove(); |
| 33 | // Yoink preloader entirely; it only exists to cover up unstyled content while loading JS | 52 | // Yoink preloader entirely; it only exists to cover up unstyled content while loading JS |
| 34 | // If it's present, we remove it once and then it's gone. | 53 | // If it's present, we remove it once and then it's gone. |
| 35 | yoinkPreloader(); | 54 | yoinkPreloader(); |
| 36 | 55 | ||
| 37 | loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE).then(() => { | 56 | loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE) |
| 57 | .catch((err) => console.error('Error completing loaderPopup:', err)) | ||
| 58 | .finally(() => { | ||
| 38 | loaderPopup = null; | 59 | loaderPopup = null; |
| 39 | resolve(); | 60 | resolve(); |
| 40 | }); | 61 | }); |
| 41 | }); | 62 | } |
| 42 | 63 | ||
| 43 | $('#load-spinner') | 64 | // Apply the styles |
| 44 | .css({ | 65 | spinner.css({ |
| 45 | 'filter': 'blur(15px)', | 66 | 'filter': 'blur(15px)', |
| 46 | 'opacity': '0', | 67 | 'opacity': '0', |
| 47 | }); | 68 | }); |
| @@ -746,7 +746,7 @@ export async function generateNovelWithStreaming(generate_data, signal) { | |||
| 746 | text += data.token; | 746 | text += data.token; |
| 747 | } | 747 | } |
| 748 | 748 | ||
| 749 | yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [] }; | 749 | yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [], state: {} }; |
| 750 | } | 750 | } |
| 751 | }; | 751 | }; |
| 752 | } | 752 | } |
| @@ -73,6 +73,7 @@ import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js | |||
| 73 | import { Popup, POPUP_RESULT } from './popup.js'; | 73 | import { Popup, POPUP_RESULT } from './popup.js'; |
| 74 | import { t } from './i18n.js'; | 74 | import { t } from './i18n.js'; |
| 75 | import { ToolManager } from './tool-calling.js'; | 75 | import { ToolManager } from './tool-calling.js'; |
| 76 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 76 | 77 | ||
| 77 | export { | 78 | export { |
| 78 | openai_messages_count, | 79 | openai_messages_count, |
| @@ -82,7 +83,6 @@ export { | |||
| 82 | setOpenAIMessageExamples, | 83 | setOpenAIMessageExamples, |
| 83 | setupChatCompletionPromptManager, | 84 | setupChatCompletionPromptManager, |
| 84 | sendOpenAIRequest, | 85 | sendOpenAIRequest, |
| 85 | getChatCompletionModel, | ||
| 86 | TokenHandler, | 86 | TokenHandler, |
| 87 | IdentifierNotFoundError, | 87 | IdentifierNotFoundError, |
| 88 | Message, | 88 | Message, |
| @@ -258,8 +258,8 @@ const default_settings = { | |||
| 258 | ai21_model: 'jamba-1.5-large', | 258 | ai21_model: 'jamba-1.5-large', |
| 259 | mistralai_model: 'mistral-large-latest', | 259 | mistralai_model: 'mistral-large-latest', |
| 260 | cohere_model: 'command-r-plus', | 260 | cohere_model: 'command-r-plus', |
| 261 | perplexity_model: 'llama-3.1-70b-instruct', | 261 | perplexity_model: 'sonar-pro', |
| 262 | groq_model: 'llama-3.1-70b-versatile', | 262 | groq_model: 'llama-3.3-70b-versatile', |
| 263 | nanogpt_model: 'gpt-4o-mini', | 263 | nanogpt_model: 'gpt-4o-mini', |
| 264 | zerooneai_model: 'yi-large', | 264 | zerooneai_model: 'yi-large', |
| 265 | blockentropy_model: 'be-70b-base-llama3.1', | 265 | blockentropy_model: 'be-70b-base-llama3.1', |
| @@ -298,7 +298,8 @@ const default_settings = { | |||
| 298 | names_behavior: character_names_behavior.DEFAULT, | 298 | names_behavior: character_names_behavior.DEFAULT, |
| 299 | continue_postfix: continue_postfix_types.SPACE, | 299 | continue_postfix: continue_postfix_types.SPACE, |
| 300 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 300 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 301 | show_thoughts: false, | 301 | show_thoughts: true, |
| 302 | reasoning_effort: 'medium', | ||
| 302 | seed: -1, | 303 | seed: -1, |
| 303 | n: 1, | 304 | n: 1, |
| 304 | }; | 305 | }; |
| @@ -337,7 +338,7 @@ const oai_settings = { | |||
| 337 | ai21_model: 'jamba-1.5-large', | 338 | ai21_model: 'jamba-1.5-large', |
| 338 | mistralai_model: 'mistral-large-latest', | 339 | mistralai_model: 'mistral-large-latest', |
| 339 | cohere_model: 'command-r-plus', | 340 | cohere_model: 'command-r-plus', |
| 340 | perplexity_model: 'llama-3.1-70b-instruct', | 341 | perplexity_model: 'sonar-pro', |
| 341 | groq_model: 'llama-3.1-70b-versatile', | 342 | groq_model: 'llama-3.1-70b-versatile', |
| 342 | nanogpt_model: 'gpt-4o-mini', | 343 | nanogpt_model: 'gpt-4o-mini', |
| 343 | zerooneai_model: 'yi-large', | 344 | zerooneai_model: 'yi-large', |
| @@ -377,7 +378,8 @@ const oai_settings = { | |||
| 377 | names_behavior: character_names_behavior.DEFAULT, | 378 | names_behavior: character_names_behavior.DEFAULT, |
| 378 | continue_postfix: continue_postfix_types.SPACE, | 379 | continue_postfix: continue_postfix_types.SPACE, |
| 379 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 380 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 380 | show_thoughts: false, | 381 | show_thoughts: true, |
| 382 | reasoning_effort: 'medium', | ||
| 381 | seed: -1, | 383 | seed: -1, |
| 382 | n: 1, | 384 | n: 1, |
| 383 | }; | 385 | }; |
| @@ -412,7 +414,7 @@ async function validateReverseProxy() { | |||
| 412 | throw err; | 414 | throw err; |
| 413 | } | 415 | } |
| 414 | const rememberKey = `Proxy_SkipConfirm_${getStringHash(oai_settings.reverse_proxy)}`; | 416 | const rememberKey = `Proxy_SkipConfirm_${getStringHash(oai_settings.reverse_proxy)}`; |
| 415 | const skipConfirm = localStorage.getItem(rememberKey) === 'true'; | 417 | const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; |
| 416 | 418 | ||
| 417 | const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) })); | 419 | const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) })); |
| 418 | 420 | ||
| @@ -423,7 +425,7 @@ async function validateReverseProxy() { | |||
| 423 | throw new Error('Proxy connection denied.'); | 425 | throw new Error('Proxy connection denied.'); |
| 424 | } | 426 | } |
| 425 | 427 | ||
| 426 | localStorage.setItem(rememberKey, String(true)); | 428 | accountStorage.setItem(rememberKey, String(true)); |
| 427 | } | 429 | } |
| 428 | 430 | ||
| 429 | /** | 431 | /** |
| @@ -1096,8 +1098,8 @@ async function preparePromptsForChatCompletion({ Scenario, charPersonality, name | |||
| 1096 | // Unordered prompts without marker | 1098 | // Unordered prompts without marker |
| 1097 | { role: 'system', content: impersonationPrompt, identifier: 'impersonate' }, | 1099 | { role: 'system', content: impersonationPrompt, identifier: 'impersonate' }, |
| 1098 | { role: 'system', content: quietPrompt, identifier: 'quietPrompt' }, | 1100 | { role: 'system', content: quietPrompt, identifier: 'quietPrompt' }, |
| 1099 | { role: 'system', content: bias, identifier: 'bias' }, | ||
| 1100 | { role: 'system', content: groupNudge, identifier: 'groupNudge' }, | 1101 | { role: 'system', content: groupNudge, identifier: 'groupNudge' }, |
| 1102 | { role: 'assistant', content: bias, identifier: 'bias' }, | ||
| 1101 | ]; | 1103 | ]; |
| 1102 | 1104 | ||
| 1103 | // Tavern Extras - Summary | 1105 | // Tavern Extras - Summary |
| @@ -1443,9 +1445,7 @@ async function sendWindowAIRequest(messages, signal, stream) { | |||
| 1443 | } | 1445 | } |
| 1444 | 1446 | ||
| 1445 | const onStreamResult = (res, err) => { | 1447 | const onStreamResult = (res, err) => { |
| 1446 | if (err) { | 1448 | if (err) return; |
| 1447 | return; | ||
| 1448 | } | ||
| 1449 | 1449 | ||
| 1450 | const thisContent = res?.message?.content; | 1450 | const thisContent = res?.message?.content; |
| 1451 | 1451 | ||
| @@ -1497,7 +1497,7 @@ async function sendWindowAIRequest(messages, signal, stream) { | |||
| 1497 | } | 1497 | } |
| 1498 | } | 1498 | } |
| 1499 | 1499 | ||
| 1500 | function getChatCompletionModel() { | 1500 | export function getChatCompletionModel() { |
| 1501 | switch (oai_settings.chat_completion_source) { | 1501 | switch (oai_settings.chat_completion_source) { |
| 1502 | case chat_completion_sources.CLAUDE: | 1502 | case chat_completion_sources.CLAUDE: |
| 1503 | return oai_settings.claude_model; | 1503 | return oai_settings.claude_model; |
| @@ -1869,7 +1869,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 1869 | const isQuiet = type === 'quiet'; | 1869 | const isQuiet = type === 'quiet'; |
| 1870 | const isImpersonate = type === 'impersonate'; | 1870 | const isImpersonate = type === 'impersonate'; |
| 1871 | const isContinue = type === 'continue'; | 1871 | const isContinue = type === 'continue'; |
| 1872 | const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isGoogle && oai_settings.google_model.includes('bison')) && !(isOAI && oai_settings.openai_model.startsWith('o1-')); | 1872 | const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isOAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model)); |
| 1873 | const useLogprobs = !!power_user.request_token_probabilities; | 1873 | const useLogprobs = !!power_user.request_token_probabilities; |
| 1874 | const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom); | 1874 | const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom); |
| 1875 | 1875 | ||
| @@ -1913,16 +1913,21 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 1913 | 'user_name': name1, | 1913 | 'user_name': name1, |
| 1914 | 'char_name': name2, | 1914 | 'char_name': name2, |
| 1915 | 'group_names': getGroupNames(), | 1915 | 'group_names': getGroupNames(), |
| 1916 | 'show_thoughts': Boolean(oai_settings.show_thoughts), | 1916 | 'include_reasoning': Boolean(oai_settings.show_thoughts), |
| 1917 | 'reasoning_effort': String(oai_settings.reasoning_effort), | ||
| 1917 | }; | 1918 | }; |
| 1918 | 1919 | ||
| 1920 | if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) { | ||
| 1921 | await ToolManager.registerFunctionToolsOpenAI(generate_data); | ||
| 1922 | } | ||
| 1923 | |||
| 1919 | // Empty array will produce a validation error | 1924 | // Empty array will produce a validation error |
| 1920 | if (!Array.isArray(generate_data.stop) || !generate_data.stop.length) { | 1925 | if (!Array.isArray(generate_data.stop) || !generate_data.stop.length) { |
| 1921 | delete generate_data.stop; | 1926 | delete generate_data.stop; |
| 1922 | } | 1927 | } |
| 1923 | 1928 | ||
| 1924 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite | 1929 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite |
| 1925 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE].includes(oai_settings.chat_completion_source)) { | 1930 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) { |
| 1926 | await validateReverseProxy(); | 1931 | await validateReverseProxy(); |
| 1927 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; | 1932 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; |
| 1928 | generate_data['proxy_password'] = oai_settings.proxy_password; | 1933 | generate_data['proxy_password'] = oai_settings.proxy_password; |
| @@ -2030,17 +2035,25 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2030 | // https://api-docs.deepseek.com/api/create-chat-completion | 2035 | // https://api-docs.deepseek.com/api/create-chat-completion |
| 2031 | if (isDeepSeek) { | 2036 | if (isDeepSeek) { |
| 2032 | generate_data.top_p = generate_data.top_p || Number.EPSILON; | 2037 | generate_data.top_p = generate_data.top_p || Number.EPSILON; |
| 2038 | |||
| 2039 | if (generate_data.model.endsWith('-reasoner')) { | ||
| 2040 | delete generate_data.top_p; | ||
| 2041 | delete generate_data.temperature; | ||
| 2042 | delete generate_data.frequency_penalty; | ||
| 2043 | delete generate_data.presence_penalty; | ||
| 2044 | delete generate_data.top_logprobs; | ||
| 2045 | delete generate_data.logprobs; | ||
| 2046 | delete generate_data.logit_bias; | ||
| 2047 | delete generate_data.tools; | ||
| 2048 | delete generate_data.tool_choice; | ||
| 2049 | } | ||
| 2033 | } | 2050 | } |
| 2034 | 2051 | ||
| 2035 | if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) { | 2052 | if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) { |
| 2036 | generate_data['seed'] = oai_settings.seed; | 2053 | generate_data['seed'] = oai_settings.seed; |
| 2037 | } | 2054 | } |
| 2038 | 2055 | ||
| 2039 | if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) { | 2056 | if (isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) { |
| 2040 | await ToolManager.registerFunctionToolsOpenAI(generate_data); | ||
| 2041 | } | ||
| 2042 | |||
| 2043 | if (isOAI && oai_settings.openai_model.startsWith('o1-')) { | ||
| 2044 | generate_data.messages.forEach((msg) => { | 2057 | generate_data.messages.forEach((msg) => { |
| 2045 | if (msg.role === 'system') { | 2058 | if (msg.role === 'system') { |
| 2046 | msg.role = 'user'; | 2059 | msg.role = 'user'; |
| @@ -2048,7 +2061,6 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2048 | }); | 2061 | }); |
| 2049 | generate_data.max_completion_tokens = generate_data.max_tokens; | 2062 | generate_data.max_completion_tokens = generate_data.max_tokens; |
| 2050 | delete generate_data.max_tokens; | 2063 | delete generate_data.max_tokens; |
| 2051 | delete generate_data.stream; | ||
| 2052 | delete generate_data.logprobs; | 2064 | delete generate_data.logprobs; |
| 2053 | delete generate_data.top_logprobs; | 2065 | delete generate_data.top_logprobs; |
| 2054 | delete generate_data.n; | 2066 | delete generate_data.n; |
| @@ -2059,8 +2071,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2059 | delete generate_data.tools; | 2071 | delete generate_data.tools; |
| 2060 | delete generate_data.tool_choice; | 2072 | delete generate_data.tool_choice; |
| 2061 | delete generate_data.stop; | 2073 | delete generate_data.stop; |
| 2062 | // It does support logit_bias, but the tokenizer used and its effect is yet unknown. | 2074 | delete generate_data.logit_bias; |
| 2063 | // delete generate_data.logit_bias; | ||
| 2064 | } | 2075 | } |
| 2065 | 2076 | ||
| 2066 | await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data); | 2077 | await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data); |
| @@ -2085,6 +2096,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2085 | let text = ''; | 2096 | let text = ''; |
| 2086 | const swipes = []; | 2097 | const swipes = []; |
| 2087 | const toolCalls = []; | 2098 | const toolCalls = []; |
| 2099 | const state = { reasoning: '' }; | ||
| 2088 | while (true) { | 2100 | while (true) { |
| 2089 | const { done, value } = await reader.read(); | 2101 | const { done, value } = await reader.read(); |
| 2090 | if (done) return; | 2102 | if (done) return; |
| @@ -2095,14 +2107,14 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2095 | 2107 | ||
| 2096 | if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { | 2108 | if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { |
| 2097 | const swipeIndex = parsed.choices[0].index - 1; | 2109 | const swipeIndex = parsed.choices[0].index - 1; |
| 2098 | swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed); | 2110 | swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state); |
| 2099 | } else { | 2111 | } else { |
| 2100 | text += getStreamingReply(parsed); | 2112 | text += getStreamingReply(parsed, state); |
| 2101 | } | 2113 | } |
| 2102 | 2114 | ||
| 2103 | ToolManager.parseToolCalls(toolCalls, parsed); | 2115 | ToolManager.parseToolCalls(toolCalls, parsed); |
| 2104 | 2116 | ||
| 2105 | yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls }; | 2117 | yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls, state: state }; |
| 2106 | } | 2118 | } |
| 2107 | }; | 2119 | }; |
| 2108 | } | 2120 | } |
| @@ -2129,13 +2141,32 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2129 | } | 2141 | } |
| 2130 | } | 2142 | } |
| 2131 | 2143 | ||
| 2132 | function getStreamingReply(data) { | 2144 | /** |
| 2145 | * Extracts the reply from the response data from a chat completions-like source | ||
| 2146 | * @param {object} data Response data from the chat completions-like source | ||
| 2147 | * @param {object} state Additional state to keep track of | ||
| 2148 | * @returns {string} The reply extracted from the response data | ||
| 2149 | */ | ||
| 2150 | function getStreamingReply(data, state) { | ||
| 2133 | if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) { | 2151 | if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) { |
| 2134 | return data?.delta?.text || ''; | 2152 | return data?.delta?.text || ''; |
| 2135 | } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) { | 2153 | } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) { |
| 2136 | return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || ''; | 2154 | if (oai_settings.show_thoughts) { |
| 2155 | state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || ''); | ||
| 2156 | } | ||
| 2157 | return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || ''; | ||
| 2137 | } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { | 2158 | } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { |
| 2138 | return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || ''; | 2159 | return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || ''; |
| 2160 | } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { | ||
| 2161 | if (oai_settings.show_thoughts) { | ||
| 2162 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); | ||
| 2163 | } | ||
| 2164 | return data.choices?.[0]?.delta?.content || ''; | ||
| 2165 | } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) { | ||
| 2166 | if (oai_settings.show_thoughts) { | ||
| 2167 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); | ||
| 2168 | } | ||
| 2169 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | ||
| 2139 | } else { | 2170 | } else { |
| 2140 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | 2171 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 2141 | } | 2172 | } |
| @@ -3094,6 +3125,7 @@ function loadOpenAISettings(data, settings) { | |||
| 3094 | oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality; | 3125 | oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality; |
| 3095 | oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check; | 3126 | oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check; |
| 3096 | oai_settings.show_thoughts = settings.show_thoughts ?? default_settings.show_thoughts; | 3127 | oai_settings.show_thoughts = settings.show_thoughts ?? default_settings.show_thoughts; |
| 3128 | oai_settings.reasoning_effort = settings.reasoning_effort ?? default_settings.reasoning_effort; | ||
| 3097 | oai_settings.seed = settings.seed ?? default_settings.seed; | 3129 | oai_settings.seed = settings.seed ?? default_settings.seed; |
| 3098 | oai_settings.n = settings.n ?? default_settings.n; | 3130 | oai_settings.n = settings.n ?? default_settings.n; |
| 3099 | 3131 | ||
| @@ -3223,6 +3255,9 @@ function loadOpenAISettings(data, settings) { | |||
| 3223 | $('#n_openai').val(oai_settings.n); | 3255 | $('#n_openai').val(oai_settings.n); |
| 3224 | $('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts); | 3256 | $('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts); |
| 3225 | 3257 | ||
| 3258 | $('#openai_reasoning_effort').val(oai_settings.reasoning_effort); | ||
| 3259 | $(`#openai_reasoning_effort option[value="${oai_settings.reasoning_effort}"]`).prop('selected', true); | ||
| 3260 | |||
| 3226 | if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy; | 3261 | if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy; |
| 3227 | $('#openai_reverse_proxy').val(oai_settings.reverse_proxy); | 3262 | $('#openai_reverse_proxy').val(oai_settings.reverse_proxy); |
| 3228 | 3263 | ||
| @@ -3346,7 +3381,7 @@ async function getStatusOpen() { | |||
| 3346 | chat_completion_source: oai_settings.chat_completion_source, | 3381 | chat_completion_source: oai_settings.chat_completion_source, |
| 3347 | }; | 3382 | }; |
| 3348 | 3383 | ||
| 3349 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE].includes(oai_settings.chat_completion_source)) { | 3384 | if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) { |
| 3350 | await validateReverseProxy(); | 3385 | await validateReverseProxy(); |
| 3351 | } | 3386 | } |
| 3352 | 3387 | ||
| @@ -3483,6 +3518,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { | |||
| 3483 | continue_postfix: settings.continue_postfix, | 3518 | continue_postfix: settings.continue_postfix, |
| 3484 | function_calling: settings.function_calling, | 3519 | function_calling: settings.function_calling, |
| 3485 | show_thoughts: settings.show_thoughts, | 3520 | show_thoughts: settings.show_thoughts, |
| 3521 | reasoning_effort: settings.reasoning_effort, | ||
| 3486 | seed: settings.seed, | 3522 | seed: settings.seed, |
| 3487 | n: settings.n, | 3523 | n: settings.n, |
| 3488 | }; | 3524 | }; |
| @@ -3941,6 +3977,7 @@ function onSettingsPresetChange() { | |||
| 3941 | continue_postfix: ['#continue_postfix', 'continue_postfix', false], | 3977 | continue_postfix: ['#continue_postfix', 'continue_postfix', false], |
| 3942 | function_calling: ['#openai_function_calling', 'function_calling', true], | 3978 | function_calling: ['#openai_function_calling', 'function_calling', true], |
| 3943 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true], | 3979 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true], |
| 3980 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false], | ||
| 3944 | seed: ['#seed_openai', 'seed', false], | 3981 | seed: ['#seed_openai', 'seed', false], |
| 3945 | n: ['#n_openai', 'n', false], | 3982 | n: ['#n_openai', 'n', false], |
| 3946 | }; | 3983 | }; |
| @@ -3997,7 +4034,7 @@ function getMaxContextOpenAI(value) { | |||
| 3997 | if (oai_settings.max_context_unlocked) { | 4034 | if (oai_settings.max_context_unlocked) { |
| 3998 | return unlocked_max; | 4035 | return unlocked_max; |
| 3999 | } | 4036 | } |
| 4000 | else if (value.startsWith('o1-')) { | 4037 | else if (value.startsWith('o1') || value.startsWith('o3')) { |
| 4001 | return max_128k; | 4038 | return max_128k; |
| 4002 | } | 4039 | } |
| 4003 | 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')) { | 4040 | 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')) { |
| @@ -4202,9 +4239,9 @@ async function onModelChange() { | |||
| 4202 | $('#openai_max_context').attr('max', max_2mil); | 4239 | $('#openai_max_context').attr('max', max_2mil); |
| 4203 | } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121') || value.includes('gemini-2.0-flash-thinking-exp-1219')) { | 4240 | } else if (value.includes('gemini-exp-1114') || value.includes('gemini-exp-1121') || value.includes('gemini-2.0-flash-thinking-exp-1219')) { |
| 4204 | $('#openai_max_context').attr('max', max_32k); | 4241 | $('#openai_max_context').attr('max', max_32k); |
| 4205 | } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) { | 4242 | } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) { |
| 4206 | $('#openai_max_context').attr('max', max_2mil); | 4243 | $('#openai_max_context').attr('max', max_2mil); |
| 4207 | } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash-exp')) { | 4244 | } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash')) { |
| 4208 | $('#openai_max_context').attr('max', max_1mil); | 4245 | $('#openai_max_context').attr('max', max_1mil); |
| 4209 | } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') { | 4246 | } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') { |
| 4210 | $('#openai_max_context').attr('max', max_32k); | 4247 | $('#openai_max_context').attr('max', max_32k); |
| @@ -4350,28 +4387,19 @@ async function onModelChange() { | |||
| 4350 | if (oai_settings.max_context_unlocked) { | 4387 | if (oai_settings.max_context_unlocked) { |
| 4351 | $('#openai_max_context').attr('max', unlocked_max); | 4388 | $('#openai_max_context').attr('max', unlocked_max); |
| 4352 | } | 4389 | } |
| 4390 | else if (['sonar', 'sonar-reasoning'].includes(oai_settings.perplexity_model)) { | ||
| 4391 | $('#openai_max_context').attr('max', 127000); | ||
| 4392 | } | ||
| 4393 | else if (['sonar-pro'].includes(oai_settings.perplexity_model)) { | ||
| 4394 | $('#openai_max_context').attr('max', 200000); | ||
| 4395 | } | ||
| 4353 | else if (oai_settings.perplexity_model.includes('llama-3.1')) { | 4396 | else if (oai_settings.perplexity_model.includes('llama-3.1')) { |
| 4354 | const isOnline = oai_settings.perplexity_model.includes('online'); | 4397 | const isOnline = oai_settings.perplexity_model.includes('online'); |
| 4355 | const contextSize = isOnline ? 128 * 1024 - 4000 : 128 * 1024; | 4398 | const contextSize = isOnline ? 128 * 1024 - 4000 : 128 * 1024; |
| 4356 | $('#openai_max_context').attr('max', contextSize); | 4399 | $('#openai_max_context').attr('max', contextSize); |
| 4357 | } | 4400 | } |
| 4358 | else if (['llama-3-sonar-small-32k-chat', 'llama-3-sonar-large-32k-chat'].includes(oai_settings.perplexity_model)) { | ||
| 4359 | $('#openai_max_context').attr('max', max_32k); | ||
| 4360 | } | ||
| 4361 | else if (['llama-3-sonar-small-32k-online', 'llama-3-sonar-large-32k-online'].includes(oai_settings.perplexity_model)) { | ||
| 4362 | $('#openai_max_context').attr('max', 28000); | ||
| 4363 | } | ||
| 4364 | else if (['sonar-small-chat', 'sonar-medium-chat', 'codellama-70b-instruct', 'mistral-7b-instruct', 'mixtral-8x7b-instruct', 'mixtral-8x22b-instruct'].includes(oai_settings.perplexity_model)) { | ||
| 4365 | $('#openai_max_context').attr('max', max_16k); | ||
| 4366 | } | ||
| 4367 | else if (['llama-3-8b-instruct', 'llama-3-70b-instruct'].includes(oai_settings.perplexity_model)) { | ||
| 4368 | $('#openai_max_context').attr('max', max_8k); | ||
| 4369 | } | ||
| 4370 | else if (['sonar-small-online', 'sonar-medium-online'].includes(oai_settings.perplexity_model)) { | ||
| 4371 | $('#openai_max_context').attr('max', 12000); | ||
| 4372 | } | ||
| 4373 | else { | 4401 | else { |
| 4374 | $('#openai_max_context').attr('max', max_4k); | 4402 | $('#openai_max_context').attr('max', max_128k); |
| 4375 | } | 4403 | } |
| 4376 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | 4404 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 4377 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | 4405 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| @@ -4382,24 +4410,30 @@ async function onModelChange() { | |||
| 4382 | if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) { | 4410 | if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) { |
| 4383 | if (oai_settings.max_context_unlocked) { | 4411 | if (oai_settings.max_context_unlocked) { |
| 4384 | $('#openai_max_context').attr('max', unlocked_max); | 4412 | $('#openai_max_context').attr('max', unlocked_max); |
| 4385 | } | 4413 | } else if (oai_settings.groq_model.includes('gemma2-9b-it')) { |
| 4386 | else if (oai_settings.groq_model.includes('llama-3.2') && oai_settings.groq_model.includes('-preview')) { | ||
| 4387 | $('#openai_max_context').attr('max', max_8k); | 4414 | $('#openai_max_context').attr('max', max_8k); |
| 4388 | } | 4415 | } else if (oai_settings.groq_model.includes('llama-3.3-70b-versatile')) { |
| 4389 | else if (oai_settings.groq_model.includes('llama-3.3') || oai_settings.groq_model.includes('llama-3.2') || oai_settings.groq_model.includes('llama-3.1')) { | ||
| 4390 | $('#openai_max_context').attr('max', max_128k); | 4416 | $('#openai_max_context').attr('max', max_128k); |
| 4391 | } | 4417 | } else if (oai_settings.groq_model.includes('llama-3.1-8b-instant')) { |
| 4392 | else if (oai_settings.groq_model.includes('llama3-groq')) { | 4418 | $('#openai_max_context').attr('max', max_128k); |
| 4419 | } else if (oai_settings.groq_model.includes('llama3-70b-8192')) { | ||
| 4393 | $('#openai_max_context').attr('max', max_8k); | 4420 | $('#openai_max_context').attr('max', max_8k); |
| 4394 | } | 4421 | } else if (oai_settings.groq_model.includes('llama3-8b-8192')) { |
| 4395 | else if (['llama3-8b-8192', 'llama3-70b-8192', 'gemma-7b-it', 'gemma2-9b-it'].includes(oai_settings.groq_model)) { | ||
| 4396 | $('#openai_max_context').attr('max', max_8k); | 4422 | $('#openai_max_context').attr('max', max_8k); |
| 4397 | } | 4423 | } else if (oai_settings.groq_model.includes('mixtral-8x7b-32768')) { |
| 4398 | else if (['mixtral-8x7b-32768'].includes(oai_settings.groq_model)) { | ||
| 4399 | $('#openai_max_context').attr('max', max_32k); | 4424 | $('#openai_max_context').attr('max', max_32k); |
| 4400 | } | 4425 | } else if (oai_settings.groq_model.includes('deepseek-r1-distill-llama-70b')) { |
| 4401 | else { | 4426 | $('#openai_max_context').attr('max', max_128k); |
| 4402 | $('#openai_max_context').attr('max', max_4k); | 4427 | } else if (oai_settings.groq_model.includes('llama-3.3-70b-specdec')) { |
| 4428 | $('#openai_max_context').attr('max', max_8k); | ||
| 4429 | } else if (oai_settings.groq_model.includes('llama-3.2-1b-preview')) { | ||
| 4430 | $('#openai_max_context').attr('max', max_128k); | ||
| 4431 | } else if (oai_settings.groq_model.includes('llama-3.2-3b-preview')) { | ||
| 4432 | $('#openai_max_context').attr('max', max_128k); | ||
| 4433 | } else if (oai_settings.groq_model.includes('llama-3.2-11b-vision-preview')) { | ||
| 4434 | $('#openai_max_context').attr('max', max_128k); | ||
| 4435 | } else if (oai_settings.groq_model.includes('llama-3.2-90b-vision-preview')) { | ||
| 4436 | $('#openai_max_context').attr('max', max_128k); | ||
| 4403 | } | 4437 | } |
| 4404 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); | 4438 | oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context); |
| 4405 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); | 4439 | $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input'); |
| @@ -4488,7 +4522,7 @@ async function onModelChange() { | |||
| 4488 | if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { | 4522 | if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { |
| 4489 | if (oai_settings.max_context_unlocked) { | 4523 | if (oai_settings.max_context_unlocked) { |
| 4490 | $('#openai_max_context').attr('max', unlocked_max); | 4524 | $('#openai_max_context').attr('max', unlocked_max); |
| 4491 | } else if (oai_settings.deepseek_model == 'deepseek-chat') { | 4525 | } else if (['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) { |
| 4492 | $('#openai_max_context').attr('max', max_64k); | 4526 | $('#openai_max_context').attr('max', max_64k); |
| 4493 | } else if (oai_settings.deepseek_model == 'deepseek-coder') { | 4527 | } else if (oai_settings.deepseek_model == 'deepseek-coder') { |
| 4494 | $('#openai_max_context').attr('max', max_16k); | 4528 | $('#openai_max_context').attr('max', max_16k); |
| @@ -4725,7 +4759,7 @@ async function onConnectButtonClick(e) { | |||
| 4725 | await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek); | 4759 | await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek); |
| 4726 | } | 4760 | } |
| 4727 | 4761 | ||
| 4728 | if (!secret_state[SECRET_KEYS.DEEPSEEK]) { | 4762 | if (!secret_state[SECRET_KEYS.DEEPSEEK] && !oai_settings.reverse_proxy) { |
| 4729 | console.log('No secret key saved for DeepSeek'); | 4763 | console.log('No secret key saved for DeepSeek'); |
| 4730 | return; | 4764 | return; |
| 4731 | } | 4765 | } |
| @@ -4900,7 +4934,15 @@ export function isImageInliningSupported() { | |||
| 4900 | // gultra just isn't being offered as multimodal, thanks google. | 4934 | // gultra just isn't being offered as multimodal, thanks google. |
| 4901 | const visionSupportedModels = [ | 4935 | const visionSupportedModels = [ |
| 4902 | 'gpt-4-vision', | 4936 | 'gpt-4-vision', |
| 4937 | 'gemini-2.0-pro-exp', | ||
| 4938 | 'gemini-2.0-pro-exp-02-05', | ||
| 4939 | 'gemini-2.0-flash-lite-preview', | ||
| 4940 | 'gemini-2.0-flash-lite-preview-02-05', | ||
| 4941 | 'gemini-2.0-flash', | ||
| 4942 | 'gemini-2.0-flash-001', | ||
| 4903 | 'gemini-2.0-flash-thinking-exp-1219', | 4943 | 'gemini-2.0-flash-thinking-exp-1219', |
| 4944 | 'gemini-2.0-flash-thinking-exp-01-21', | ||
| 4945 | 'gemini-2.0-flash-thinking-exp', | ||
| 4904 | 'gemini-2.0-flash-exp', | 4946 | 'gemini-2.0-flash-exp', |
| 4905 | 'gemini-1.5-flash', | 4947 | 'gemini-1.5-flash', |
| 4906 | 'gemini-1.5-flash-latest', | 4948 | 'gemini-1.5-flash-latest', |
| @@ -4925,6 +4967,8 @@ export function isImageInliningSupported() { | |||
| 4925 | 'gpt-4-turbo', | 4967 | 'gpt-4-turbo', |
| 4926 | 'gpt-4o', | 4968 | 'gpt-4o', |
| 4927 | 'gpt-4o-mini', | 4969 | 'gpt-4o-mini', |
| 4970 | 'o1', | ||
| 4971 | 'o1-2024-12-17', | ||
| 4928 | 'chatgpt-4o-latest', | 4972 | 'chatgpt-4o-latest', |
| 4929 | 'yi-vision', | 4973 | 'yi-vision', |
| 4930 | 'pixtral-latest', | 4974 | 'pixtral-latest', |
| @@ -5483,6 +5527,11 @@ export function initOpenAI() { | |||
| 5483 | saveSettingsDebounced(); | 5527 | saveSettingsDebounced(); |
| 5484 | }); | 5528 | }); |
| 5485 | 5529 | ||
| 5530 | $('#openai_reasoning_effort').on('input', function () { | ||
| 5531 | oai_settings.reasoning_effort = String($(this).val()); | ||
| 5532 | saveSettingsDebounced(); | ||
| 5533 | }); | ||
| 5534 | |||
| 5486 | if (!CSS.supports('field-sizing', 'content')) { | 5535 | if (!CSS.supports('field-sizing', 'content')) { |
| 5487 | $(document).on('input', '#openai_settings .autoSetHeight', function () { | 5536 | $(document).on('input', '#openai_settings .autoSetHeight', function () { |
| 5488 | resetScrollHeight($(this)); | 5537 | resetScrollHeight($(this)); |
| @@ -25,6 +25,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | |||
| 25 | import { t } from './i18n.js'; | 25 | import { t } from './i18n.js'; |
| 26 | import { openWorldInfoEditor, world_names } from './world-info.js'; | 26 | import { openWorldInfoEditor, world_names } from './world-info.js'; |
| 27 | import { renderTemplateAsync } from './templates.js'; | 27 | import { renderTemplateAsync } from './templates.js'; |
| 28 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 28 | 29 | ||
| 29 | let savePersonasPage = 0; | 30 | let savePersonasPage = 0; |
| 30 | const GRID_STORAGE_KEY = 'Personas_GridView'; | 31 | const GRID_STORAGE_KEY = 'Personas_GridView'; |
| @@ -34,7 +35,7 @@ export let user_avatar = ''; | |||
| 34 | export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick)); | 35 | export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick)); |
| 35 | 36 | ||
| 36 | function switchPersonaGridView() { | 37 | function switchPersonaGridView() { |
| 37 | const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true'; | 38 | const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true'; |
| 38 | $('#user_avatar_block').toggleClass('gridView', state); | 39 | $('#user_avatar_block').toggleClass('gridView', state); |
| 39 | } | 40 | } |
| 40 | 41 | ||
| @@ -182,7 +183,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | |||
| 182 | 183 | ||
| 183 | const storageKey = 'Personas_PerPage'; | 184 | const storageKey = 'Personas_PerPage'; |
| 184 | const listId = '#user_avatar_block'; | 185 | const listId = '#user_avatar_block'; |
| 185 | const perPage = Number(localStorage.getItem(storageKey)) || 5; | 186 | const perPage = Number(accountStorage.getItem(storageKey)) || 5; |
| 186 | 187 | ||
| 187 | $('#persona_pagination_container').pagination({ | 188 | $('#persona_pagination_container').pagination({ |
| 188 | dataSource: entities, | 189 | dataSource: entities, |
| @@ -205,7 +206,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | |||
| 205 | highlightSelectedAvatar(); | 206 | highlightSelectedAvatar(); |
| 206 | }, | 207 | }, |
| 207 | afterSizeSelectorChange: function (e) { | 208 | afterSizeSelectorChange: function (e) { |
| 208 | localStorage.setItem(storageKey, e.target.value); | 209 | accountStorage.setItem(storageKey, e.target.value); |
| 209 | }, | 210 | }, |
| 210 | afterPaging: function (e) { | 211 | afterPaging: function (e) { |
| 211 | savePersonasPage = e; | 212 | savePersonasPage = e; |
| @@ -1132,8 +1133,8 @@ export function initPersonas() { | |||
| 1132 | saveSettingsDebounced(); | 1133 | saveSettingsDebounced(); |
| 1133 | }); | 1134 | }); |
| 1134 | $('#persona_grid_toggle').on('click', () => { | 1135 | $('#persona_grid_toggle').on('click', () => { |
| 1135 | const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true'; | 1136 | const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true'; |
| 1136 | localStorage.setItem(GRID_STORAGE_KEY, String(!state)); | 1137 | accountStorage.setItem(GRID_STORAGE_KEY, String(!state)); |
| 1137 | switchPersonaGridView(); | 1138 | switchPersonaGridView(); |
| 1138 | }); | 1139 | }); |
| 1139 | 1140 | ||
| @@ -24,6 +24,15 @@ export const POPUP_RESULT = { | |||
| 24 | AFFIRMATIVE: 1, | 24 | AFFIRMATIVE: 1, |
| 25 | NEGATIVE: 0, | 25 | NEGATIVE: 0, |
| 26 | CANCELLED: null, | 26 | CANCELLED: null, |
| 27 | CUSTOM1: 1001, | ||
| 28 | CUSTOM2: 1002, | ||
| 29 | CUSTOM3: 1003, | ||
| 30 | CUSTOM4: 1004, | ||
| 31 | CUSTOM5: 1005, | ||
| 32 | CUSTOM6: 1006, | ||
| 33 | CUSTOM7: 1007, | ||
| 34 | CUSTOM8: 1008, | ||
| 35 | CUSTOM9: 1009, | ||
| 27 | }; | 36 | }; |
| 28 | 37 | ||
| 29 | /** | 38 | /** |
| @@ -37,6 +46,7 @@ export const POPUP_RESULT = { | |||
| 37 | * @property {boolean?} [transparent=false] - Whether to display the popup in transparent mode (no background, border, shadow or anything, only its content) | 46 | * @property {boolean?} [transparent=false] - Whether to display the popup in transparent mode (no background, border, shadow or anything, only its content) |
| 38 | * @property {boolean?} [allowHorizontalScrolling=false] - Whether to allow horizontal scrolling in the popup | 47 | * @property {boolean?} [allowHorizontalScrolling=false] - Whether to allow horizontal scrolling in the popup |
| 39 | * @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup | 48 | * @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup |
| 49 | * @property {boolean?} [leftAlign=false] - Whether the popup content should be left-aligned by default | ||
| 40 | * @property {'slow'|'fast'|'none'?} [animation='slow'] - Animation speed for the popup (opening, closing, ...) | 50 | * @property {'slow'|'fast'|'none'?} [animation='slow'] - Animation speed for the popup (opening, closing, ...) |
| 41 | * @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`. | 51 | * @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`. |
| 42 | * @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. | 52 | * @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. |
| @@ -164,7 +174,7 @@ export class Popup { | |||
| 164 | * @param {string} [inputValue=''] - The initial value of the input field | 174 | * @param {string} [inputValue=''] - The initial value of the input field |
| 165 | * @param {PopupOptions} [options={}] - Additional options for the popup | 175 | * @param {PopupOptions} [options={}] - Additional options for the popup |
| 166 | */ | 176 | */ |
| 167 | constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, animation = 'fast', defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, customInputs = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) { | 177 | constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, leftAlign = false, animation = 'fast', defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, customInputs = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) { |
| 168 | Popup.util.popups.push(this); | 178 | Popup.util.popups.push(this); |
| 169 | 179 | ||
| 170 | // Make this popup uniquely identifiable | 180 | // Make this popup uniquely identifiable |
| @@ -209,6 +219,7 @@ export class Popup { | |||
| 209 | if (transparent) this.dlg.classList.add('transparent_dialogue_popup'); | 219 | if (transparent) this.dlg.classList.add('transparent_dialogue_popup'); |
| 210 | if (allowHorizontalScrolling) this.dlg.classList.add('horizontal_scrolling_dialogue_popup'); | 220 | if (allowHorizontalScrolling) this.dlg.classList.add('horizontal_scrolling_dialogue_popup'); |
| 211 | if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup'); | 221 | if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup'); |
| 222 | if (leftAlign) this.dlg.classList.add('left_aligned_dialogue_popup'); | ||
| 212 | if (animation) this.dlg.classList.add('popup--animation-' + animation); | 223 | if (animation) this.dlg.classList.add('popup--animation-' + animation); |
| 213 | 224 | ||
| 214 | // If custom button captions are provided, we set them beforehand | 225 | // If custom button captions are provided, we set them beforehand |
| @@ -54,6 +54,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 54 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 54 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 55 | import { loadSystemPrompts } from './sysprompt.js'; | 55 | import { loadSystemPrompts } from './sysprompt.js'; |
| 56 | import { fuzzySearchCategories } from './filters.js'; | 56 | import { fuzzySearchCategories } from './filters.js'; |
| 57 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 57 | 58 | ||
| 58 | export { | 59 | export { |
| 59 | loadPowerUserSettings, | 60 | loadPowerUserSettings, |
| @@ -253,6 +254,17 @@ let power_user = { | |||
| 253 | content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.', | 254 | content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.', |
| 254 | }, | 255 | }, |
| 255 | 256 | ||
| 257 | reasoning: { | ||
| 258 | auto_parse: false, | ||
| 259 | add_to_prompts: false, | ||
| 260 | auto_expand: false, | ||
| 261 | show_hidden: false, | ||
| 262 | prefix: '<think>\n', | ||
| 263 | suffix: '\n</think>', | ||
| 264 | separator: '\n\n', | ||
| 265 | max_additions: 1, | ||
| 266 | }, | ||
| 267 | |||
| 256 | personas: {}, | 268 | personas: {}, |
| 257 | default_persona: null, | 269 | default_persona: null, |
| 258 | persona_descriptions: {}, | 270 | persona_descriptions: {}, |
| @@ -2009,7 +2021,7 @@ export function renderStoryString(params) { | |||
| 2009 | */ | 2021 | */ |
| 2010 | function validateStoryString(storyString, params) { | 2022 | function validateStoryString(storyString, params) { |
| 2011 | /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ | 2023 | /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ |
| 2012 | const cache = JSON.parse(localStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; | 2024 | const cache = JSON.parse(accountStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; |
| 2013 | 2025 | ||
| 2014 | const hash = getStringHash(storyString); | 2026 | const hash = getStringHash(storyString); |
| 2015 | 2027 | ||
| @@ -2046,7 +2058,7 @@ function validateStoryString(storyString, params) { | |||
| 2046 | toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); | 2058 | toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); |
| 2047 | } | 2059 | } |
| 2048 | 2060 | ||
| 2049 | localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); | 2061 | accountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); |
| 2050 | } | 2062 | } |
| 2051 | 2063 | ||
| 2052 | 2064 | ||
| @@ -2441,7 +2453,7 @@ async function resetMovablePanels(type) { | |||
| 2441 | } | 2453 | } |
| 2442 | 2454 | ||
| 2443 | saveSettingsDebounced(); | 2455 | saveSettingsDebounced(); |
| 2444 | eventSource.emit(event_types.MOVABLE_PANELS_RESET); | 2456 | await eventSource.emit(event_types.MOVABLE_PANELS_RESET); |
| 2445 | 2457 | ||
| 2446 | eventSource.once(event_types.SETTINGS_UPDATED, () => { | 2458 | eventSource.once(event_types.SETTINGS_UPDATED, () => { |
| 2447 | $('.resizing').removeClass('resizing'); | 2459 | $('.resizing').removeClass('resizing'); |
| @@ -2534,7 +2546,7 @@ async function loadUntilMesId(mesId) { | |||
| 2534 | let target; | 2546 | let target; |
| 2535 | 2547 | ||
| 2536 | while (getFirstDisplayedMessageId() > mesId && getFirstDisplayedMessageId() !== 0) { | 2548 | while (getFirstDisplayedMessageId() > mesId && getFirstDisplayedMessageId() !== 0) { |
| 2537 | showMoreMessages(); | 2549 | await showMoreMessages(); |
| 2538 | await delay(1); | 2550 | await delay(1); |
| 2539 | target = $('#chat').find(`.mes[mesid=${mesId}]`); | 2551 | target = $('#chat').find(`.mes[mesid=${mesId}]`); |
| 2540 | 2552 | ||
| @@ -2909,6 +2921,46 @@ export function flushEphemeralStoppingStrings() { | |||
| 2909 | } | 2921 | } |
| 2910 | 2922 | ||
| 2911 | /** | 2923 | /** |
| 2924 | * Checks if the generated text should be filtered based on the auto-swipe settings. | ||
| 2925 | * @param {string} text The text to check | ||
| 2926 | * @returns {boolean} If the generated text should be filtered | ||
| 2927 | */ | ||
| 2928 | export function generatedTextFiltered(text) { | ||
| 2929 | /** | ||
| 2930 | * Checks if the given text contains any of the blacklisted words. | ||
| 2931 | * @param {string} text The text to check | ||
| 2932 | * @param {string[]} blacklist The list of blacklisted words | ||
| 2933 | * @param {number} threshold The number of blacklisted words that need to be present to trigger the check | ||
| 2934 | * @returns {boolean} Whether the text contains blacklisted words | ||
| 2935 | */ | ||
| 2936 | function containsBlacklistedWords(text, blacklist, threshold) { | ||
| 2937 | const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); | ||
| 2938 | const matches = text.match(regex) || []; | ||
| 2939 | return matches.length >= threshold; | ||
| 2940 | } | ||
| 2941 | |||
| 2942 | // Make sure a generated text is non-empty | ||
| 2943 | // Otherwise we might get in a loop with a broken API | ||
| 2944 | text = text.trim(); | ||
| 2945 | if (text.length > 0) { | ||
| 2946 | if (power_user.auto_swipe_minimum_length) { | ||
| 2947 | if (text.length < power_user.auto_swipe_minimum_length) { | ||
| 2948 | console.log('Generated text size too small'); | ||
| 2949 | return true; | ||
| 2950 | } | ||
| 2951 | } | ||
| 2952 | if (power_user.auto_swipe_blacklist.length && power_user.auto_swipe_blacklist_threshold) { | ||
| 2953 | if (containsBlacklistedWords(text, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) { | ||
| 2954 | console.log('Generated text has blacklisted words'); | ||
| 2955 | return true; | ||
| 2956 | } | ||
| 2957 | } | ||
| 2958 | } | ||
| 2959 | |||
| 2960 | return false; | ||
| 2961 | } | ||
| 2962 | |||
| 2963 | /** | ||
| 2912 | * Gets the custom stopping strings from the power user settings. | 2964 | * Gets the custom stopping strings from the power user settings. |
| 2913 | * @param {number | undefined} limit Number of strings to return. If 0 or undefined, returns all strings. | 2965 | * @param {number | undefined} limit Number of strings to return. If 0 or undefined, returns all strings. |
| 2914 | * @returns {string[]} An array of custom stopping strings | 2966 | * @returns {string[]} An array of custom stopping strings |
| @@ -3879,9 +3931,9 @@ $(document).ready(() => { | |||
| 3879 | helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.', | 3931 | helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.', |
| 3880 | })); | 3932 | })); |
| 3881 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 3933 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 3882 | name: 'delmode', | 3934 | name: 'del', |
| 3883 | callback: doDelMode, | 3935 | callback: doDelMode, |
| 3884 | aliases: ['del'], | 3936 | aliases: ['delete', 'delmode'], |
| 3885 | unnamedArgumentList: [ | 3937 | unnamedArgumentList: [ |
| 3886 | new SlashCommandArgument( | 3938 | new SlashCommandArgument( |
| 3887 | 'optional number', [ARGUMENT_TYPE.NUMBER], false, | 3939 | 'optional number', [ARGUMENT_TYPE.NUMBER], false, |
| @@ -4064,4 +4116,45 @@ $(document).ready(() => { | |||
| 4064 | ], | 4116 | ], |
| 4065 | helpString: 'activates a movingUI preset by name', | 4117 | helpString: 'activates a movingUI preset by name', |
| 4066 | })); | 4118 | })); |
| 4119 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 4120 | name: 'stop-strings', | ||
| 4121 | aliases: ['stopping-strings', 'custom-stopping-strings', 'custom-stop-strings'], | ||
| 4122 | helpString: ` | ||
| 4123 | <div> | ||
| 4124 | Sets a list of custom stopping strings. Gets the list if no value is provided. | ||
| 4125 | </div> | ||
| 4126 | <div> | ||
| 4127 | <strong>Examples:</strong> | ||
| 4128 | </div> | ||
| 4129 | <ul> | ||
| 4130 | <li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li> | ||
| 4131 | <li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li> | ||
| 4132 | </ul> | ||
| 4133 | `, | ||
| 4134 | returns: ARGUMENT_TYPE.LIST, | ||
| 4135 | unnamedArgumentList: [ | ||
| 4136 | SlashCommandArgument.fromProps({ | ||
| 4137 | description: 'list of strings', | ||
| 4138 | typeList: [ARGUMENT_TYPE.LIST], | ||
| 4139 | acceptsMultiple: false, | ||
| 4140 | isRequired: false, | ||
| 4141 | }), | ||
| 4142 | ], | ||
| 4143 | callback: (_, value) => { | ||
| 4144 | if (String(value ?? '').trim()) { | ||
| 4145 | const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value); | ||
| 4146 | if (!parsedValue || !Array.isArray(parsedValue)) { | ||
| 4147 | throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.'); | ||
| 4148 | } | ||
| 4149 | parsedValue.forEach((item, index) => { | ||
| 4150 | parsedValue[index] = String(item); | ||
| 4151 | }); | ||
| 4152 | power_user.custom_stopping_strings = JSON.stringify(parsedValue); | ||
| 4153 | $('#custom_stopping_strings').val(power_user.custom_stopping_strings); | ||
| 4154 | saveSettingsDebounced(); | ||
| 4155 | } | ||
| 4156 | |||
| 4157 | return power_user.custom_stopping_strings; | ||
| 4158 | }, | ||
| 4159 | })); | ||
| 4067 | }); | 4160 | }); |
| @@ -586,6 +586,9 @@ class PresetManager { | |||
| 586 | 'tabby_model', | 586 | 'tabby_model', |
| 587 | 'derived', | 587 | 'derived', |
| 588 | 'generic_model', | 588 | 'generic_model', |
| 589 | 'include_reasoning', | ||
| 590 | 'global_banned_tokens', | ||
| 591 | 'send_banned_tokens', | ||
| 589 | ]; | 592 | ]; |
| 590 | const settings = Object.assign({}, getSettingsByApiId(this.apiId)); | 593 | const settings = Object.assign({}, getSettingsByApiId(this.apiId)); |
| 591 | 594 | ||
| @@ -0,0 +1,913 @@ | |||
| 1 | import { | ||
| 2 | moment, | ||
| 3 | } from '../lib.js'; | ||
| 4 | import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; | ||
| 5 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; | ||
| 6 | import { getCurrentLocale, t } from './i18n.js'; | ||
| 7 | import { MacrosParser } from './macros.js'; | ||
| 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; | ||
| 9 | import { Popup } from './popup.js'; | ||
| 10 | import { power_user } from './power-user.js'; | ||
| 11 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | ||
| 12 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | ||
| 13 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | ||
| 14 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | ||
| 15 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | ||
| 16 | import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty } from './utils.js'; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * Gets a message from a jQuery element. | ||
| 20 | * @param {Element} element | ||
| 21 | * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}} | ||
| 22 | */ | ||
| 23 | function getMessageFromJquery(element) { | ||
| 24 | const messageBlock = $(element).closest('.mes'); | ||
| 25 | const messageId = Number(messageBlock.attr('mesid')); | ||
| 26 | const message = chat[messageId]; | ||
| 27 | return { messageId: messageId, message, messageBlock }; | ||
| 28 | } | ||
| 29 | |||
| 30 | /** | ||
| 31 | * Toggles the auto-expand state of reasoning blocks. | ||
| 32 | */ | ||
| 33 | function toggleReasoningAutoExpand() { | ||
| 34 | const reasoningBlocks = document.querySelectorAll('details.mes_reasoning_details'); | ||
| 35 | reasoningBlocks.forEach((block) => { | ||
| 36 | if (block instanceof HTMLDetailsElement) { | ||
| 37 | block.open = power_user.reasoning.auto_expand; | ||
| 38 | } | ||
| 39 | }); | ||
| 40 | } | ||
| 41 | |||
| 42 | /** | ||
| 43 | * Extracts the reasoning from the response data. | ||
| 44 | * @param {object} data Response data | ||
| 45 | * @returns {string} Extracted reasoning | ||
| 46 | */ | ||
| 47 | export function extractReasoningFromData(data) { | ||
| 48 | switch (main_api) { | ||
| 49 | case 'textgenerationwebui': | ||
| 50 | switch (textgenerationwebui_settings.type) { | ||
| 51 | case textgen_types.OPENROUTER: | ||
| 52 | return data?.choices?.[0]?.reasoning ?? ''; | ||
| 53 | } | ||
| 54 | break; | ||
| 55 | |||
| 56 | case 'openai': | ||
| 57 | if (!oai_settings.show_thoughts) break; | ||
| 58 | |||
| 59 | switch (oai_settings.chat_completion_source) { | ||
| 60 | case chat_completion_sources.DEEPSEEK: | ||
| 61 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; | ||
| 62 | case chat_completion_sources.OPENROUTER: | ||
| 63 | return data?.choices?.[0]?.message?.reasoning ?? ''; | ||
| 64 | case chat_completion_sources.MAKERSUITE: | ||
| 65 | return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; | ||
| 66 | } | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | |||
| 70 | return ''; | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Check if the model supports reasoning, but does not send back the reasoning | ||
| 75 | * @returns {boolean} True if the model supports reasoning | ||
| 76 | */ | ||
| 77 | export function isHiddenReasoningModel() { | ||
| 78 | if (main_api !== 'openai') { | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | |||
| 82 | /** @typedef {{ (currentModel: string, supportedModel: string): boolean }} MatchingFunc */ | ||
| 83 | /** @type {Record.<string, MatchingFunc>} */ | ||
| 84 | const FUNCS = { | ||
| 85 | equals: (currentModel, supportedModel) => currentModel === supportedModel, | ||
| 86 | startsWith: (currentModel, supportedModel) => currentModel.startsWith(supportedModel), | ||
| 87 | }; | ||
| 88 | |||
| 89 | /** @type {{ name: string; func: MatchingFunc; }[]} */ | ||
| 90 | const hiddenReasoningModels = [ | ||
| 91 | { name: 'o1', func: FUNCS.startsWith }, | ||
| 92 | { name: 'o3', func: FUNCS.startsWith }, | ||
| 93 | { name: 'gemini-2.0-flash-thinking-exp', func: FUNCS.startsWith }, | ||
| 94 | { name: 'gemini-2.0-pro-exp', func: FUNCS.startsWith }, | ||
| 95 | ]; | ||
| 96 | |||
| 97 | const model = getChatCompletionModel(); | ||
| 98 | |||
| 99 | const isHidden = hiddenReasoningModels.some(({ name, func }) => func(model, name)); | ||
| 100 | return isHidden; | ||
| 101 | } | ||
| 102 | |||
| 103 | /** | ||
| 104 | * Updates the Reasoning UI for a specific message | ||
| 105 | * @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement The message ID or the message element | ||
| 106 | * @param {Object} [options={}] - Optional arguments | ||
| 107 | * @param {boolean} [options.reset=false] - Whether to reset state, and not take the current mess properties (for example when swiping) | ||
| 108 | */ | ||
| 109 | export function updateReasoningUI(messageIdOrElement, { reset = false } = {}) { | ||
| 110 | const handler = new ReasoningHandler(); | ||
| 111 | handler.initHandleMessage(messageIdOrElement, { reset }); | ||
| 112 | } | ||
| 113 | |||
| 114 | |||
| 115 | /** | ||
| 116 | * Enum for representing the state of reasoning | ||
| 117 | * @enum {string} | ||
| 118 | * @readonly | ||
| 119 | */ | ||
| 120 | export const ReasoningState = { | ||
| 121 | None: 'none', | ||
| 122 | Thinking: 'thinking', | ||
| 123 | Done: 'done', | ||
| 124 | Hidden: 'hidden', | ||
| 125 | }; | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Handles reasoning-specific logic and DOM updates for messages. | ||
| 129 | * This class is used inside the {@link StreamingProcessor} to manage reasoning states and UI updates. | ||
| 130 | */ | ||
| 131 | export class ReasoningHandler { | ||
| 132 | #isHiddenReasoningModel; | ||
| 133 | |||
| 134 | /** | ||
| 135 | * @param {Date?} [timeStarted=null] - When the generation started | ||
| 136 | */ | ||
| 137 | constructor(timeStarted = null) { | ||
| 138 | /** @type {ReasoningState} The current state of the reasoning process */ | ||
| 139 | this.state = ReasoningState.None; | ||
| 140 | /** @type {string} The reasoning output */ | ||
| 141 | this.reasoning = ''; | ||
| 142 | /** @type {Date} When the reasoning started */ | ||
| 143 | this.startTime = null; | ||
| 144 | /** @type {Date} When the reasoning ended */ | ||
| 145 | this.endTime = null; | ||
| 146 | |||
| 147 | /** @type {Date} Initial starting time of the generation */ | ||
| 148 | this.initialTime = timeStarted ?? new Date(); | ||
| 149 | |||
| 150 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ | ||
| 151 | this.#isHiddenReasoningModel = isHiddenReasoningModel(); | ||
| 152 | |||
| 153 | // Cached DOM elements for reasoning | ||
| 154 | /** @type {HTMLElement} Main message DOM element `.mes` */ | ||
| 155 | this.messageDom = null; | ||
| 156 | /** @type {HTMLDetailsElement} Reasoning details DOM element `.mes_reasoning_details` */ | ||
| 157 | this.messageReasoningDetailsDom = null; | ||
| 158 | /** @type {HTMLElement} Reasoning content DOM element `.mes_reasoning` */ | ||
| 159 | this.messageReasoningContentDom = null; | ||
| 160 | /** @type {HTMLElement} Reasoning header DOM element `.mes_reasoning_header_title` */ | ||
| 161 | this.messageReasoningHeaderDom = null; | ||
| 162 | } | ||
| 163 | |||
| 164 | /** | ||
| 165 | * Initializes the reasoning handler for a specific message. | ||
| 166 | * | ||
| 167 | * Can be used to update the DOM elements or read other reasoning states. | ||
| 168 | * It will internally take the message-saved data and write the states back into the handler, as if during streaming of the message. | ||
| 169 | * The state will always be either done/hidden or none. | ||
| 170 | * | ||
| 171 | * @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement - The message ID or the message element | ||
| 172 | * @param {Object} [options={}] - Optional arguments | ||
| 173 | * @param {boolean} [options.reset=false] - Whether to reset state of the handler, and not take the current mess properties (for example when swiping) | ||
| 174 | */ | ||
| 175 | initHandleMessage(messageIdOrElement, { reset = false } = {}) { | ||
| 176 | /** @type {HTMLElement} */ | ||
| 177 | const messageElement = typeof messageIdOrElement === 'number' | ||
| 178 | ? document.querySelector(`#chat [mesid="${messageIdOrElement}"]`) | ||
| 179 | : messageIdOrElement instanceof HTMLElement | ||
| 180 | ? messageIdOrElement | ||
| 181 | : $(messageIdOrElement)[0]; | ||
| 182 | const messageId = Number(messageElement.getAttribute('mesid')); | ||
| 183 | |||
| 184 | if (isNaN(messageId) || !chat[messageId]) return; | ||
| 185 | |||
| 186 | if (!chat[messageId].extra) { | ||
| 187 | chat[messageId].extra = {}; | ||
| 188 | } | ||
| 189 | const extra = chat[messageId].extra; | ||
| 190 | |||
| 191 | if (extra.reasoning) { | ||
| 192 | this.state = ReasoningState.Done; | ||
| 193 | } else if (extra.reasoning_duration) { | ||
| 194 | this.state = ReasoningState.Hidden; | ||
| 195 | } | ||
| 196 | |||
| 197 | this.reasoning = extra?.reasoning ?? ''; | ||
| 198 | |||
| 199 | if (this.state !== ReasoningState.None) { | ||
| 200 | this.initialTime = new Date(chat[messageId].gen_started); | ||
| 201 | this.startTime = this.initialTime; | ||
| 202 | this.endTime = new Date(this.startTime.getTime() + (extra?.reasoning_duration ?? 0)); | ||
| 203 | } | ||
| 204 | |||
| 205 | // Prefill main dom element, as message might not have been rendered yet | ||
| 206 | this.messageDom = messageElement; | ||
| 207 | |||
| 208 | // Make sure reset correctly clears all relevant states | ||
| 209 | if (reset) { | ||
| 210 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; | ||
| 211 | this.reasoning = ''; | ||
| 212 | this.initialTime = new Date(); | ||
| 213 | this.startTime = null; | ||
| 214 | this.endTime = null; | ||
| 215 | } | ||
| 216 | |||
| 217 | this.updateDom(messageId); | ||
| 218 | |||
| 219 | if (power_user.reasoning.auto_expand && this.state !== ReasoningState.Hidden) { | ||
| 220 | this.messageReasoningDetailsDom.open = true; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | /** | ||
| 225 | * Gets the duration of the reasoning in milliseconds. | ||
| 226 | * | ||
| 227 | * @returns {number?} The duration in milliseconds, or null if the start or end time is not set | ||
| 228 | */ | ||
| 229 | getDuration() { | ||
| 230 | if (this.startTime && this.endTime) { | ||
| 231 | return this.endTime.getTime() - this.startTime.getTime(); | ||
| 232 | } | ||
| 233 | return null; | ||
| 234 | } | ||
| 235 | |||
| 236 | /** | ||
| 237 | * Updates the reasoning text/string for a message. | ||
| 238 | * | ||
| 239 | * @param {number} messageId - The ID of the message to update | ||
| 240 | * @param {string?} [reasoning=null] - The reasoning text to update - If null, uses the current reasoning | ||
| 241 | * @param {Object} [options={}] - Optional arguments | ||
| 242 | * @param {boolean} [options.persist=false] - Whether to persist the reasoning to the message object | ||
| 243 | * @returns {boolean} - Returns true if the reasoning was changed, otherwise false | ||
| 244 | */ | ||
| 245 | updateReasoning(messageId, reasoning = null, { persist = false } = {}) { | ||
| 246 | if (messageId == -1 || !chat[messageId]) { | ||
| 247 | return false; | ||
| 248 | } | ||
| 249 | |||
| 250 | reasoning = reasoning ?? this.reasoning; | ||
| 251 | reasoning = power_user.trim_spaces ? reasoning.trim() : reasoning; | ||
| 252 | |||
| 253 | // Ensure the chat extra exists | ||
| 254 | if (!chat[messageId].extra) { | ||
| 255 | chat[messageId].extra = {}; | ||
| 256 | } | ||
| 257 | const extra = chat[messageId].extra; | ||
| 258 | |||
| 259 | const reasoningChanged = extra.reasoning !== reasoning; | ||
| 260 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); | ||
| 261 | |||
| 262 | if (persist) { | ||
| 263 | // Build and save the reasoning data to message extras | ||
| 264 | extra.reasoning = this.reasoning; | ||
| 265 | extra.reasoning_duration = this.getDuration(); | ||
| 266 | } | ||
| 267 | |||
| 268 | return reasoningChanged; | ||
| 269 | } | ||
| 270 | |||
| 271 | |||
| 272 | /** | ||
| 273 | * Handles processing of reasoning for a message. | ||
| 274 | * | ||
| 275 | * This is usually called by the message processor when a message is changed. | ||
| 276 | * | ||
| 277 | * @param {number} messageId - The ID of the message to process | ||
| 278 | * @param {boolean} mesChanged - Whether the message has changed | ||
| 279 | * @returns {Promise<void>} | ||
| 280 | */ | ||
| 281 | async process(messageId, mesChanged) { | ||
| 282 | if (!this.reasoning && !this.#isHiddenReasoningModel) return; | ||
| 283 | |||
| 284 | // Ensure reasoning string is updated and regexes are applied correctly | ||
| 285 | const reasoningChanged = this.updateReasoning(messageId, null, { persist: true }); | ||
| 286 | |||
| 287 | if ((this.#isHiddenReasoningModel || reasoningChanged) && this.state === ReasoningState.None) { | ||
| 288 | this.state = ReasoningState.Thinking; | ||
| 289 | this.startTime = this.initialTime; | ||
| 290 | } | ||
| 291 | if ((this.#isHiddenReasoningModel || !reasoningChanged) && mesChanged && this.state === ReasoningState.Thinking) { | ||
| 292 | this.endTime = new Date(); | ||
| 293 | await this.finish(messageId); | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | /** | ||
| 298 | * Completes the reasoning process for a message. | ||
| 299 | * | ||
| 300 | * Records the finish time if it was not set during streaming and updates the reasoning state. | ||
| 301 | * Emits an event to signal the completion of reasoning and updates the DOM elements accordingly. | ||
| 302 | * | ||
| 303 | * @param {number} messageId - The ID of the message to complete reasoning for | ||
| 304 | * @returns {Promise<void>} | ||
| 305 | */ | ||
| 306 | async finish(messageId) { | ||
| 307 | if (this.state === ReasoningState.None) return; | ||
| 308 | |||
| 309 | // Make sure the finish time is recorded if a reasoning was in process and it wasn't ended correctly during streaming | ||
| 310 | if (this.startTime !== null && this.endTime === null) { | ||
| 311 | this.endTime = new Date(); | ||
| 312 | } | ||
| 313 | |||
| 314 | if (this.state === ReasoningState.Thinking) { | ||
| 315 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Hidden : ReasoningState.Done; | ||
| 316 | this.updateReasoning(messageId, null, { persist: true }); | ||
| 317 | await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.getDuration(), messageId, this.state); | ||
| 318 | } | ||
| 319 | |||
| 320 | this.updateDom(messageId); | ||
| 321 | } | ||
| 322 | |||
| 323 | /** | ||
| 324 | * Updates the reasoning UI elements for a message. | ||
| 325 | * | ||
| 326 | * Toggles the CSS class, updates states, reasoning message, and duration. | ||
| 327 | * | ||
| 328 | * @param {number} messageId - The ID of the message to update | ||
| 329 | */ | ||
| 330 | updateDom(messageId) { | ||
| 331 | this.#checkDomElements(messageId); | ||
| 332 | |||
| 333 | // Main CSS class to show this message includes reasoning | ||
| 334 | this.messageDom.classList.toggle('reasoning', this.state !== ReasoningState.None); | ||
| 335 | |||
| 336 | // Update states to the relevant DOM elements | ||
| 337 | setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null); | ||
| 338 | setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state); | ||
| 339 | |||
| 340 | // Update the reasoning message | ||
| 341 | const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | ||
| 342 | const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true); | ||
| 343 | this.messageReasoningContentDom.innerHTML = displayReasoning; | ||
| 344 | |||
| 345 | // Update tooltip for hidden reasoning edit | ||
| 346 | /** @type {HTMLElement} */ | ||
| 347 | const button = this.messageDom.querySelector('.mes_edit_add_reasoning'); | ||
| 348 | button.title = this.state === ReasoningState.Hidden ? t`Hidden reasoning - Add reasoning block` : t`Add reasoning block`; | ||
| 349 | |||
| 350 | // Make sure that hidden reasoning headers are collapsed by default, to not show a useless edit button | ||
| 351 | if (this.state === ReasoningState.Hidden) { | ||
| 352 | this.messageReasoningDetailsDom.open = false; | ||
| 353 | } | ||
| 354 | |||
| 355 | // Update the reasoning duration in the UI | ||
| 356 | this.#updateReasoningTimeUI(); | ||
| 357 | } | ||
| 358 | |||
| 359 | /** | ||
| 360 | * Finds and caches reasoning-related DOM elements for the given message. | ||
| 361 | * | ||
| 362 | * @param {number} messageId - The ID of the message to cache the DOM elements for | ||
| 363 | */ | ||
| 364 | #checkDomElements(messageId) { | ||
| 365 | // Make sure we reset dom elements if we are checking for a different message (shouldn't happen, but be sure) | ||
| 366 | if (this.messageDom !== null && this.messageDom.getAttribute('mesid') !== messageId.toString()) { | ||
| 367 | this.messageDom = null; | ||
| 368 | } | ||
| 369 | |||
| 370 | // Cache the DOM elements once | ||
| 371 | if (this.messageDom === null) { | ||
| 372 | this.messageDom = document.querySelector(`#chat .mes[mesid="${messageId}"]`); | ||
| 373 | if (this.messageDom === null) throw new Error('message dom does not exist'); | ||
| 374 | } | ||
| 375 | if (this.messageReasoningDetailsDom === null) { | ||
| 376 | this.messageReasoningDetailsDom = this.messageDom.querySelector('.mes_reasoning_details'); | ||
| 377 | } | ||
| 378 | if (this.messageReasoningContentDom === null) { | ||
| 379 | this.messageReasoningContentDom = this.messageDom.querySelector('.mes_reasoning'); | ||
| 380 | } | ||
| 381 | if (this.messageReasoningHeaderDom === null) { | ||
| 382 | this.messageReasoningHeaderDom = this.messageDom.querySelector('.mes_reasoning_header_title'); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | /** | ||
| 387 | * Updates the reasoning time display in the UI. | ||
| 388 | * | ||
| 389 | * Shows the duration in a human-readable format with a tooltip for exact seconds. | ||
| 390 | * Displays "Thinking..." if still processing, or a generic message otherwise. | ||
| 391 | */ | ||
| 392 | #updateReasoningTimeUI() { | ||
| 393 | const element = this.messageReasoningHeaderDom; | ||
| 394 | const duration = this.getDuration(); | ||
| 395 | let data = null; | ||
| 396 | if (duration) { | ||
| 397 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); | ||
| 398 | const secondsStr = moment.duration(duration).asSeconds(); | ||
| 399 | |||
| 400 | const span = document.createElement('span'); | ||
| 401 | span.title = t`${secondsStr} seconds`; | ||
| 402 | span.textContent = durationStr; | ||
| 403 | |||
| 404 | element.textContent = t`Thought for `; | ||
| 405 | element.appendChild(span); | ||
| 406 | data = String(secondsStr); | ||
| 407 | } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) { | ||
| 408 | element.textContent = t`Thought for some time`; | ||
| 409 | data = 'unknown'; | ||
| 410 | } else { | ||
| 411 | element.textContent = t`Thinking...`; | ||
| 412 | data = null; | ||
| 413 | } | ||
| 414 | |||
| 415 | setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data); | ||
| 416 | setDatasetProperty(element, 'duration', data); | ||
| 417 | } | ||
| 418 | } | ||
| 419 | |||
| 420 | /** | ||
| 421 | * Helper class for adding reasoning to messages. | ||
| 422 | * Keeps track of the number of reasoning additions. | ||
| 423 | */ | ||
| 424 | export class PromptReasoning { | ||
| 425 | static REASONING_PLACEHOLDER = '\u200B'; | ||
| 426 | |||
| 427 | constructor() { | ||
| 428 | this.counter = 0; | ||
| 429 | } | ||
| 430 | |||
| 431 | /** | ||
| 432 | * Checks if the limit of reasoning additions has been reached. | ||
| 433 | * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise. | ||
| 434 | */ | ||
| 435 | isLimitReached() { | ||
| 436 | if (!power_user.reasoning.add_to_prompts) { | ||
| 437 | return true; | ||
| 438 | } | ||
| 439 | |||
| 440 | return this.counter >= power_user.reasoning.max_additions; | ||
| 441 | } | ||
| 442 | |||
| 443 | /** | ||
| 444 | * Add reasoning to a message according to the power user settings. | ||
| 445 | * @param {string} content Message content | ||
| 446 | * @param {string} reasoning Message reasoning | ||
| 447 | * @param {boolean} isPrefix Whether this is the last message prefix | ||
| 448 | * @returns {string} Message content with reasoning | ||
| 449 | */ | ||
| 450 | addToMessage(content, reasoning, isPrefix) { | ||
| 451 | // Disabled or reached limit of additions | ||
| 452 | if (!isPrefix && (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions)) { | ||
| 453 | return content; | ||
| 454 | } | ||
| 455 | |||
| 456 | // No reasoning provided or a legacy placeholder | ||
| 457 | if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) { | ||
| 458 | return content; | ||
| 459 | } | ||
| 460 | |||
| 461 | // Increment the counter | ||
| 462 | this.counter++; | ||
| 463 | |||
| 464 | // Substitute macros in variable parts | ||
| 465 | const prefix = substituteParams(power_user.reasoning.prefix || ''); | ||
| 466 | const separator = substituteParams(power_user.reasoning.separator || ''); | ||
| 467 | const suffix = substituteParams(power_user.reasoning.suffix || ''); | ||
| 468 | |||
| 469 | // Combine parts with reasoning only | ||
| 470 | if (isPrefix && !content) { | ||
| 471 | return `${prefix}${reasoning}`; | ||
| 472 | } | ||
| 473 | |||
| 474 | // Combine parts with reasoning and content | ||
| 475 | return `${prefix}${reasoning}${suffix}${separator}${content}`; | ||
| 476 | } | ||
| 477 | } | ||
| 478 | |||
| 479 | function loadReasoningSettings() { | ||
| 480 | $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts); | ||
| 481 | $('#reasoning_add_to_prompts').on('change', function () { | ||
| 482 | power_user.reasoning.add_to_prompts = !!$(this).prop('checked'); | ||
| 483 | saveSettingsDebounced(); | ||
| 484 | }); | ||
| 485 | |||
| 486 | $('#reasoning_prefix').val(power_user.reasoning.prefix); | ||
| 487 | $('#reasoning_prefix').on('input', function () { | ||
| 488 | power_user.reasoning.prefix = String($(this).val()); | ||
| 489 | saveSettingsDebounced(); | ||
| 490 | }); | ||
| 491 | |||
| 492 | $('#reasoning_suffix').val(power_user.reasoning.suffix); | ||
| 493 | $('#reasoning_suffix').on('input', function () { | ||
| 494 | power_user.reasoning.suffix = String($(this).val()); | ||
| 495 | saveSettingsDebounced(); | ||
| 496 | }); | ||
| 497 | |||
| 498 | $('#reasoning_separator').val(power_user.reasoning.separator); | ||
| 499 | $('#reasoning_separator').on('input', function () { | ||
| 500 | power_user.reasoning.separator = String($(this).val()); | ||
| 501 | saveSettingsDebounced(); | ||
| 502 | }); | ||
| 503 | |||
| 504 | $('#reasoning_max_additions').val(power_user.reasoning.max_additions); | ||
| 505 | $('#reasoning_max_additions').on('input', function () { | ||
| 506 | power_user.reasoning.max_additions = Number($(this).val()); | ||
| 507 | saveSettingsDebounced(); | ||
| 508 | }); | ||
| 509 | |||
| 510 | $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse); | ||
| 511 | $('#reasoning_auto_parse').on('change', function () { | ||
| 512 | power_user.reasoning.auto_parse = !!$(this).prop('checked'); | ||
| 513 | saveSettingsDebounced(); | ||
| 514 | }); | ||
| 515 | |||
| 516 | $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand); | ||
| 517 | $('#reasoning_auto_expand').on('change', function () { | ||
| 518 | power_user.reasoning.auto_expand = !!$(this).prop('checked'); | ||
| 519 | toggleReasoningAutoExpand(); | ||
| 520 | saveSettingsDebounced(); | ||
| 521 | }); | ||
| 522 | toggleReasoningAutoExpand(); | ||
| 523 | |||
| 524 | $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden); | ||
| 525 | $('#reasoning_show_hidden').on('change', function () { | ||
| 526 | power_user.reasoning.show_hidden = !!$(this).prop('checked'); | ||
| 527 | $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null); | ||
| 528 | saveSettingsDebounced(); | ||
| 529 | }); | ||
| 530 | $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null); | ||
| 531 | } | ||
| 532 | |||
| 533 | function registerReasoningSlashCommands() { | ||
| 534 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 535 | name: 'reasoning-get', | ||
| 536 | aliases: ['get-reasoning'], | ||
| 537 | returns: ARGUMENT_TYPE.STRING, | ||
| 538 | helpString: t`Get the contents of a reasoning block of a message. Returns an empty string if the message does not have a reasoning block.`, | ||
| 539 | unnamedArgumentList: [ | ||
| 540 | SlashCommandArgument.fromProps({ | ||
| 541 | description: 'Message ID. If not provided, the message ID of the last message is used.', | ||
| 542 | typeList: ARGUMENT_TYPE.NUMBER, | ||
| 543 | enumProvider: commonEnumProviders.messages(), | ||
| 544 | }), | ||
| 545 | ], | ||
| 546 | callback: (_args, value) => { | ||
| 547 | const messageId = !isNaN(parseInt(value.toString())) ? parseInt(value.toString()) : chat.length - 1; | ||
| 548 | const message = chat[messageId]; | ||
| 549 | const reasoning = String(message?.extra?.reasoning ?? ''); | ||
| 550 | return reasoning; | ||
| 551 | }, | ||
| 552 | })); | ||
| 553 | |||
| 554 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 555 | name: 'reasoning-set', | ||
| 556 | aliases: ['set-reasoning'], | ||
| 557 | returns: ARGUMENT_TYPE.STRING, | ||
| 558 | helpString: t`Set the reasoning block of a message. Returns the reasoning block content.`, | ||
| 559 | namedArgumentList: [ | ||
| 560 | SlashCommandNamedArgument.fromProps({ | ||
| 561 | name: 'at', | ||
| 562 | description: 'Message ID. If not provided, the message ID of the last message is used.', | ||
| 563 | typeList: ARGUMENT_TYPE.NUMBER, | ||
| 564 | enumProvider: commonEnumProviders.messages(), | ||
| 565 | }), | ||
| 566 | ], | ||
| 567 | unnamedArgumentList: [ | ||
| 568 | SlashCommandArgument.fromProps({ | ||
| 569 | description: 'Reasoning block content.', | ||
| 570 | typeList: ARGUMENT_TYPE.STRING, | ||
| 571 | }), | ||
| 572 | ], | ||
| 573 | callback: async (args, value) => { | ||
| 574 | const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1; | ||
| 575 | const message = chat[messageId]; | ||
| 576 | if (!message?.extra) { | ||
| 577 | return ''; | ||
| 578 | } | ||
| 579 | |||
| 580 | message.extra.reasoning = String(value ?? ''); | ||
| 581 | await saveChatConditional(); | ||
| 582 | |||
| 583 | closeMessageEditor('reasoning'); | ||
| 584 | updateMessageBlock(messageId, message); | ||
| 585 | return message.extra.reasoning; | ||
| 586 | }, | ||
| 587 | })); | ||
| 588 | |||
| 589 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 590 | name: 'reasoning-parse', | ||
| 591 | aliases: ['parse-reasoning'], | ||
| 592 | returns: 'reasoning string', | ||
| 593 | helpString: t`Extracts the reasoning block from a string using the Reasoning Formatting settings.`, | ||
| 594 | namedArgumentList: [ | ||
| 595 | SlashCommandNamedArgument.fromProps({ | ||
| 596 | name: 'regex', | ||
| 597 | description: 'Whether to apply regex scripts to the reasoning content.', | ||
| 598 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 599 | defaultValue: 'true', | ||
| 600 | isRequired: false, | ||
| 601 | enumProvider: commonEnumProviders.boolean('trueFalse'), | ||
| 602 | }), | ||
| 603 | ], | ||
| 604 | unnamedArgumentList: [ | ||
| 605 | SlashCommandArgument.fromProps({ | ||
| 606 | description: 'input string', | ||
| 607 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 608 | }), | ||
| 609 | ], | ||
| 610 | callback: (args, value) => { | ||
| 611 | if (!value) { | ||
| 612 | return ''; | ||
| 613 | } | ||
| 614 | |||
| 615 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) { | ||
| 616 | toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`); | ||
| 617 | return String(value); | ||
| 618 | } | ||
| 619 | |||
| 620 | const parsedReasoning = parseReasoningFromString(String(value)); | ||
| 621 | |||
| 622 | if (!parsedReasoning) { | ||
| 623 | return ''; | ||
| 624 | } | ||
| 625 | |||
| 626 | const applyRegex = !isFalseBoolean(String(args.regex ?? '')); | ||
| 627 | return applyRegex | ||
| 628 | ? getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING) | ||
| 629 | : parsedReasoning.reasoning; | ||
| 630 | }, | ||
| 631 | })); | ||
| 632 | } | ||
| 633 | |||
| 634 | function registerReasoningMacros() { | ||
| 635 | MacrosParser.registerMacro('reasoningPrefix', () => power_user.reasoning.prefix, t`Reasoning Prefix`); | ||
| 636 | MacrosParser.registerMacro('reasoningSuffix', () => power_user.reasoning.suffix, t`Reasoning Suffix`); | ||
| 637 | MacrosParser.registerMacro('reasoningSeparator', () => power_user.reasoning.separator, t`Reasoning Separator`); | ||
| 638 | } | ||
| 639 | |||
| 640 | function setReasoningEventHandlers() { | ||
| 641 | $(document).on('click', '.mes_reasoning_details', function (e) { | ||
| 642 | if (!e.target.closest('.mes_reasoning_actions') && !e.target.closest('.mes_reasoning_header')) { | ||
| 643 | e.preventDefault(); | ||
| 644 | } | ||
| 645 | }); | ||
| 646 | |||
| 647 | $(document).on('click', '.mes_reasoning_header', function (e) { | ||
| 648 | const details = $(this).closest('.mes_reasoning_details'); | ||
| 649 | // Along with the CSS rules to mark blocks not toggle-able when they are empty, prevent them from actually being toggled, or being edited | ||
| 650 | if (details.find('.mes_reasoning').is(':empty')) { | ||
| 651 | e.preventDefault(); | ||
| 652 | return; | ||
| 653 | } | ||
| 654 | |||
| 655 | // If we are in message edit mode and reasoning area is closed, a click opens and edits it | ||
| 656 | const mes = $(this).closest('.mes'); | ||
| 657 | const mesEditArea = mes.find('#curEditTextarea'); | ||
| 658 | if (mesEditArea.length) { | ||
| 659 | const summary = $(mes).find('.mes_reasoning_summary'); | ||
| 660 | if (!summary.attr('open')) { | ||
| 661 | summary.find('.mes_reasoning_edit').trigger('click'); | ||
| 662 | } | ||
| 663 | } | ||
| 664 | }); | ||
| 665 | |||
| 666 | $(document).on('click', '.mes_reasoning_copy', (e) => { | ||
| 667 | e.stopPropagation(); | ||
| 668 | e.preventDefault(); | ||
| 669 | }); | ||
| 670 | |||
| 671 | $(document).on('click', '.mes_reasoning_edit', function (e) { | ||
| 672 | e.stopPropagation(); | ||
| 673 | e.preventDefault(); | ||
| 674 | const { message, messageBlock } = getMessageFromJquery(this); | ||
| 675 | if (!message?.extra) { | ||
| 676 | return; | ||
| 677 | } | ||
| 678 | |||
| 679 | const reasoning = String(message?.extra?.reasoning ?? ''); | ||
| 680 | const chatElement = document.getElementById('chat'); | ||
| 681 | const textarea = document.createElement('textarea'); | ||
| 682 | const reasoningBlock = messageBlock.find('.mes_reasoning'); | ||
| 683 | textarea.classList.add('reasoning_edit_textarea'); | ||
| 684 | textarea.value = reasoning; | ||
| 685 | $(textarea).insertBefore(reasoningBlock); | ||
| 686 | |||
| 687 | if (!CSS.supports('field-sizing', 'content')) { | ||
| 688 | const resetHeight = function () { | ||
| 689 | const scrollTop = chatElement.scrollTop; | ||
| 690 | textarea.style.height = '0px'; | ||
| 691 | textarea.style.height = `${textarea.scrollHeight}px`; | ||
| 692 | chatElement.scrollTop = scrollTop; | ||
| 693 | }; | ||
| 694 | |||
| 695 | textarea.addEventListener('input', resetHeight); | ||
| 696 | resetHeight(); | ||
| 697 | } | ||
| 698 | |||
| 699 | textarea.focus(); | ||
| 700 | textarea.setSelectionRange(textarea.value.length, textarea.value.length); | ||
| 701 | |||
| 702 | const textareaRect = textarea.getBoundingClientRect(); | ||
| 703 | const chatRect = chatElement.getBoundingClientRect(); | ||
| 704 | |||
| 705 | // Scroll if textarea bottom is below visible area | ||
| 706 | if (textareaRect.bottom > chatRect.bottom) { | ||
| 707 | const scrollOffset = textareaRect.bottom - chatRect.bottom; | ||
| 708 | chatElement.scrollTop += scrollOffset; | ||
| 709 | } | ||
| 710 | }); | ||
| 711 | |||
| 712 | $(document).on('click', '.mes_reasoning_edit_done', async function (e) { | ||
| 713 | e.stopPropagation(); | ||
| 714 | e.preventDefault(); | ||
| 715 | const { message, messageId, messageBlock } = getMessageFromJquery(this); | ||
| 716 | if (!message?.extra) { | ||
| 717 | return; | ||
| 718 | } | ||
| 719 | |||
| 720 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | ||
| 721 | const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true }); | ||
| 722 | message.extra.reasoning = reasoning; | ||
| 723 | await saveChatConditional(); | ||
| 724 | updateMessageBlock(messageId, message); | ||
| 725 | textarea.remove(); | ||
| 726 | |||
| 727 | messageBlock.find('.mes_edit_done:visible').trigger('click'); | ||
| 728 | }); | ||
| 729 | |||
| 730 | $(document).on('click', '.mes_reasoning_edit_cancel', function (e) { | ||
| 731 | e.stopPropagation(); | ||
| 732 | e.preventDefault(); | ||
| 733 | |||
| 734 | const { messageBlock } = getMessageFromJquery(this); | ||
| 735 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | ||
| 736 | textarea.remove(); | ||
| 737 | |||
| 738 | messageBlock.find('.mes_reasoning_edit_cancel:visible').trigger('click'); | ||
| 739 | |||
| 740 | updateReasoningUI(messageBlock); | ||
| 741 | }); | ||
| 742 | |||
| 743 | $(document).on('click', '.mes_edit_add_reasoning', async function () { | ||
| 744 | const { message, messageBlock } = getMessageFromJquery(this); | ||
| 745 | if (!message?.extra) { | ||
| 746 | return; | ||
| 747 | } | ||
| 748 | |||
| 749 | if (message.extra.reasoning) { | ||
| 750 | toastr.info(t`Reasoning already exists.`, t`Edit Message`); | ||
| 751 | return; | ||
| 752 | } | ||
| 753 | |||
| 754 | messageBlock.addClass('reasoning'); | ||
| 755 | |||
| 756 | // To make hidden reasoning blocks editable, we just set them to "Done" here already. | ||
| 757 | // They will be done on save anyway - and on cancel the reasoning block gets rerendered too. | ||
| 758 | if (messageBlock.attr('data-reasoning-state') === ReasoningState.Hidden) { | ||
| 759 | messageBlock.attr('data-reasoning-state', ReasoningState.Done); | ||
| 760 | } | ||
| 761 | |||
| 762 | // Open the reasoning area so we can actually edit it | ||
| 763 | messageBlock.find('.mes_reasoning_details').attr('open', ''); | ||
| 764 | messageBlock.find('.mes_reasoning_edit').trigger('click'); | ||
| 765 | await saveChatConditional(); | ||
| 766 | }); | ||
| 767 | |||
| 768 | $(document).on('click', '.mes_reasoning_delete', async function (e) { | ||
| 769 | e.stopPropagation(); | ||
| 770 | e.preventDefault(); | ||
| 771 | |||
| 772 | const confirm = await Popup.show.confirm(t`Remove Reasoning`, t`Are you sure you want to clear the reasoning?<br />Visible message contents will stay intact.`); | ||
| 773 | |||
| 774 | if (!confirm) { | ||
| 775 | return; | ||
| 776 | } | ||
| 777 | |||
| 778 | const { message, messageId, messageBlock } = getMessageFromJquery(this); | ||
| 779 | if (!message?.extra) { | ||
| 780 | return; | ||
| 781 | } | ||
| 782 | message.extra.reasoning = ''; | ||
| 783 | await saveChatConditional(); | ||
| 784 | updateMessageBlock(messageId, message); | ||
| 785 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | ||
| 786 | textarea.remove(); | ||
| 787 | }); | ||
| 788 | |||
| 789 | $(document).on('pointerup', '.mes_reasoning_copy', async function () { | ||
| 790 | const { message } = getMessageFromJquery(this); | ||
| 791 | const reasoning = String(message?.extra?.reasoning ?? ''); | ||
| 792 | |||
| 793 | if (!reasoning) { | ||
| 794 | return; | ||
| 795 | } | ||
| 796 | |||
| 797 | await copyText(reasoning); | ||
| 798 | toastr.info(t`Copied!`, '', { timeOut: 2000 }); | ||
| 799 | }); | ||
| 800 | } | ||
| 801 | |||
| 802 | /** | ||
| 803 | * Removes reasoning from a string if auto-parsing is enabled. | ||
| 804 | * @param {string} str Input string | ||
| 805 | * @returns {string} Output string | ||
| 806 | */ | ||
| 807 | export function removeReasoningFromString(str) { | ||
| 808 | if (!power_user.reasoning.auto_parse) { | ||
| 809 | return str; | ||
| 810 | } | ||
| 811 | |||
| 812 | const parsedReasoning = parseReasoningFromString(str); | ||
| 813 | return parsedReasoning?.content ?? str; | ||
| 814 | } | ||
| 815 | |||
| 816 | /** | ||
| 817 | * Parses reasoning from a string using the power user reasoning settings. | ||
| 818 | * @typedef {Object} ParsedReasoning | ||
| 819 | * @property {string} reasoning Reasoning block | ||
| 820 | * @property {string} content Message content | ||
| 821 | * @param {string} str Content of the message | ||
| 822 | * @returns {ParsedReasoning|null} Parsed reasoning block and message content | ||
| 823 | */ | ||
| 824 | function parseReasoningFromString(str) { | ||
| 825 | // Both prefix and suffix must be defined | ||
| 826 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) { | ||
| 827 | return null; | ||
| 828 | } | ||
| 829 | |||
| 830 | try { | ||
| 831 | const regex = new RegExp(`${escapeRegex(power_user.reasoning.prefix)}(.*?)${escapeRegex(power_user.reasoning.suffix)}`, 's'); | ||
| 832 | |||
| 833 | let didReplace = false; | ||
| 834 | let reasoning = ''; | ||
| 835 | let content = String(str).replace(regex, (_match, captureGroup) => { | ||
| 836 | didReplace = true; | ||
| 837 | reasoning = captureGroup; | ||
| 838 | return ''; | ||
| 839 | }); | ||
| 840 | |||
| 841 | if (didReplace && power_user.trim_spaces) { | ||
| 842 | reasoning = reasoning.trim(); | ||
| 843 | content = content.trim(); | ||
| 844 | } | ||
| 845 | |||
| 846 | return { reasoning, content }; | ||
| 847 | } catch (error) { | ||
| 848 | console.error('[Reasoning] Error parsing reasoning block', error); | ||
| 849 | return null; | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | function registerReasoningAppEvents() { | ||
| 854 | eventSource.makeFirst(event_types.MESSAGE_RECEIVED, (/** @type {number} */ idx) => { | ||
| 855 | if (!power_user.reasoning.auto_parse) { | ||
| 856 | return; | ||
| 857 | } | ||
| 858 | |||
| 859 | console.debug('[Reasoning] Auto-parsing reasoning block for message', idx); | ||
| 860 | const message = chat[idx]; | ||
| 861 | |||
| 862 | if (!message) { | ||
| 863 | console.warn('[Reasoning] Message not found', idx); | ||
| 864 | return null; | ||
| 865 | } | ||
| 866 | |||
| 867 | if (!message.mes || message.mes === '...') { | ||
| 868 | console.debug('[Reasoning] Message content is empty or a placeholder', idx); | ||
| 869 | return null; | ||
| 870 | } | ||
| 871 | |||
| 872 | const parsedReasoning = parseReasoningFromString(message.mes); | ||
| 873 | |||
| 874 | // No reasoning block found | ||
| 875 | if (!parsedReasoning) { | ||
| 876 | return; | ||
| 877 | } | ||
| 878 | |||
| 879 | // Make sure the message has an extra object | ||
| 880 | if (!message.extra || typeof message.extra !== 'object') { | ||
| 881 | message.extra = {}; | ||
| 882 | } | ||
| 883 | |||
| 884 | const contentUpdated = !!parsedReasoning.reasoning || parsedReasoning.content !== message.mes; | ||
| 885 | |||
| 886 | // If reasoning was found, add it to the message | ||
| 887 | if (parsedReasoning.reasoning) { | ||
| 888 | message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING); | ||
| 889 | } | ||
| 890 | |||
| 891 | // Update the message text if it was changed | ||
| 892 | if (parsedReasoning.content !== message.mes) { | ||
| 893 | message.mes = parsedReasoning.content; | ||
| 894 | } | ||
| 895 | |||
| 896 | // Find if a message already exists in DOM and must be updated | ||
| 897 | if (contentUpdated) { | ||
| 898 | const messageRendered = document.querySelector(`.mes[mesid="${idx}"]`) !== null; | ||
| 899 | if (messageRendered) { | ||
| 900 | console.debug('[Reasoning] Updating message block', idx); | ||
| 901 | updateMessageBlock(idx, message); | ||
| 902 | } | ||
| 903 | } | ||
| 904 | }); | ||
| 905 | } | ||
| 906 | |||
| 907 | export function initReasoning() { | ||
| 908 | loadReasoningSettings(); | ||
| 909 | setReasoningEventHandlers(); | ||
| 910 | registerReasoningSlashCommands(); | ||
| 911 | registerReasoningMacros(); | ||
| 912 | registerReasoningAppEvents(); | ||
| 913 | } | ||
| @@ -40,6 +40,8 @@ export const SECRET_KEYS = { | |||
| 40 | BFL: 'api_key_bfl', | 40 | BFL: 'api_key_bfl', |
| 41 | GENERIC: 'api_key_generic', | 41 | GENERIC: 'api_key_generic', |
| 42 | DEEPSEEK: 'api_key_deepseek', | 42 | DEEPSEEK: 'api_key_deepseek', |
| 43 | SERPER: 'api_key_serper', | ||
| 44 | FALAI: 'api_key_falai', | ||
| 43 | }; | 45 | }; |
| 44 | 46 | ||
| 45 | const INPUT_MAP = { | 47 | const INPUT_MAP = { |
| @@ -42,6 +42,7 @@ import { | |||
| 42 | showMoreMessages, | 42 | showMoreMessages, |
| 43 | stopGeneration, | 43 | stopGeneration, |
| 44 | substituteParams, | 44 | substituteParams, |
| 45 | syncCurrentSwipeInfoExtras, | ||
| 45 | system_avatar, | 46 | system_avatar, |
| 46 | system_message_types, | 47 | system_message_types, |
| 47 | this_chid, | 48 | this_chid, |
| @@ -58,7 +59,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat | |||
| 58 | import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; | 59 | import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; |
| 59 | import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | 60 | import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 60 | import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js'; | 61 | import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js'; |
| 61 | import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; | 62 | import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, regexFromString, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; |
| 62 | import { registerVariableCommands, resolveVariable } from './variables.js'; | 63 | import { registerVariableCommands, resolveVariable } from './variables.js'; |
| 63 | import { background_settings } from './backgrounds.js'; | 64 | import { background_settings } from './backgrounds.js'; |
| 64 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | 65 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| @@ -74,6 +75,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 74 | import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; | 75 | import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; |
| 75 | import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; | 76 | import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; |
| 76 | import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; | 77 | import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; |
| 78 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 77 | export { | 79 | export { |
| 78 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, | 80 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, |
| 79 | }; | 81 | }; |
| @@ -234,7 +236,6 @@ export function initDefaultSlashCommands() { | |||
| 234 | description: 'Character name - or unique character identifier (avatar key)', | 236 | description: 'Character name - or unique character identifier (avatar key)', |
| 235 | typeList: [ARGUMENT_TYPE.STRING], | 237 | typeList: [ARGUMENT_TYPE.STRING], |
| 236 | enumProvider: commonEnumProviders.characters('character'), | 238 | enumProvider: commonEnumProviders.characters('character'), |
| 237 | forceEnum: false, | ||
| 238 | }), | 239 | }), |
| 239 | ], | 240 | ], |
| 240 | helpString: ` | 241 | helpString: ` |
| @@ -273,7 +274,6 @@ export function initDefaultSlashCommands() { | |||
| 273 | typeList: [ARGUMENT_TYPE.STRING], | 274 | typeList: [ARGUMENT_TYPE.STRING], |
| 274 | isRequired: true, | 275 | isRequired: true, |
| 275 | enumProvider: commonEnumProviders.characters('character'), | 276 | enumProvider: commonEnumProviders.characters('character'), |
| 276 | forceEnum: false, | ||
| 277 | }), | 277 | }), |
| 278 | SlashCommandNamedArgument.fromProps({ | 278 | SlashCommandNamedArgument.fromProps({ |
| 279 | name: 'avatar', | 279 | name: 'avatar', |
| @@ -517,7 +517,6 @@ export function initDefaultSlashCommands() { | |||
| 517 | typeList: [ARGUMENT_TYPE.STRING], | 517 | typeList: [ARGUMENT_TYPE.STRING], |
| 518 | isRequired: true, | 518 | isRequired: true, |
| 519 | enumProvider: commonEnumProviders.characters('all'), | 519 | enumProvider: commonEnumProviders.characters('all'), |
| 520 | forceEnum: true, | ||
| 521 | }), | 520 | }), |
| 522 | ], | 521 | ], |
| 523 | helpString: 'Opens up a chat with the character or group by its name', | 522 | helpString: 'Opens up a chat with the character or group by its name', |
| @@ -733,6 +732,57 @@ export function initDefaultSlashCommands() { | |||
| 733 | helpString: 'Unhides a message from the prompt.', | 732 | helpString: 'Unhides a message from the prompt.', |
| 734 | })); | 733 | })); |
| 735 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 734 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 735 | name: 'member-get', | ||
| 736 | aliases: ['getmember', 'memberget'], | ||
| 737 | callback: (async ({ field = 'name' }, arg) => { | ||
| 738 | if (!selected_group) { | ||
| 739 | toastr.warning('Cannot run /member-get command outside of a group chat.'); | ||
| 740 | return ''; | ||
| 741 | } | ||
| 742 | if (field === '') { | ||
| 743 | toastr.warning('\'/member-get field=\' argument required!'); | ||
| 744 | return ''; | ||
| 745 | } | ||
| 746 | field = field.toString(); | ||
| 747 | arg = arg.toString(); | ||
| 748 | if (!['name', 'index', 'id', 'avatar'].includes(field)) { | ||
| 749 | toastr.warning('\'/member-get field=\' argument required!'); | ||
| 750 | return ''; | ||
| 751 | } | ||
| 752 | const isId = !isNaN(parseInt(arg)); | ||
| 753 | const groupMember = findGroupMemberId(arg, true); | ||
| 754 | if (!groupMember) { | ||
| 755 | toastr.warn(`No group member found using ${isId ? 'id' : 'string'} ${arg}`); | ||
| 756 | return ''; | ||
| 757 | } | ||
| 758 | return groupMember[field]; | ||
| 759 | }), | ||
| 760 | namedArgumentList: [ | ||
| 761 | SlashCommandNamedArgument.fromProps({ | ||
| 762 | name: 'field', | ||
| 763 | description: 'Whether to retrieve the name, index, id, or avatar.', | ||
| 764 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 765 | isRequired: true, | ||
| 766 | defaultValue: 'name', | ||
| 767 | enumList: [ | ||
| 768 | new SlashCommandEnumValue('name', 'Character name'), | ||
| 769 | new SlashCommandEnumValue('index', 'Group member index'), | ||
| 770 | new SlashCommandEnumValue('avatar', 'Character avatar'), | ||
| 771 | new SlashCommandEnumValue('id', 'Character index'), | ||
| 772 | ], | ||
| 773 | }), | ||
| 774 | ], | ||
| 775 | unnamedArgumentList: [ | ||
| 776 | SlashCommandArgument.fromProps({ | ||
| 777 | description: 'member index (starts with 0), name, or avatar', | ||
| 778 | typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], | ||
| 779 | isRequired: true, | ||
| 780 | enumProvider: commonEnumProviders.groupMembers(), | ||
| 781 | }), | ||
| 782 | ], | ||
| 783 | helpString: 'Retrieves a group member\'s name, index, id, or avatar.', | ||
| 784 | })); | ||
| 785 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 736 | name: 'member-disable', | 786 | name: 'member-disable', |
| 737 | callback: disableGroupMemberCallback, | 787 | callback: disableGroupMemberCallback, |
| 738 | aliases: ['disable', 'disablemember', 'memberdisable'], | 788 | aliases: ['disable', 'disablemember', 'memberdisable'], |
| @@ -842,7 +892,8 @@ export function initDefaultSlashCommands() { | |||
| 842 | helpString: 'Moves a group member down in the group chat list.', | 892 | helpString: 'Moves a group member down in the group chat list.', |
| 843 | })); | 893 | })); |
| 844 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 894 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 845 | name: 'peek', | 895 | name: 'member-peek', |
| 896 | aliases: ['peek', 'memberpeek', 'peekmember'], | ||
| 846 | callback: peekCallback, | 897 | callback: peekCallback, |
| 847 | unnamedArgumentList: [ | 898 | unnamedArgumentList: [ |
| 848 | SlashCommandArgument.fromProps({ | 899 | SlashCommandArgument.fromProps({ |
| @@ -1008,7 +1059,6 @@ export function initDefaultSlashCommands() { | |||
| 1008 | typeList: [ARGUMENT_TYPE.STRING], | 1059 | typeList: [ARGUMENT_TYPE.STRING], |
| 1009 | defaultValue: 'System', | 1060 | defaultValue: 'System', |
| 1010 | enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)], | 1061 | enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)], |
| 1011 | forceEnum: false, | ||
| 1012 | }), | 1062 | }), |
| 1013 | new SlashCommandNamedArgument( | 1063 | new SlashCommandNamedArgument( |
| 1014 | 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false, | 1064 | 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false, |
| @@ -1902,7 +1952,7 @@ export function initDefaultSlashCommands() { | |||
| 1902 | returns: 'uppercase string', | 1952 | returns: 'uppercase string', |
| 1903 | unnamedArgumentList: [ | 1953 | unnamedArgumentList: [ |
| 1904 | new SlashCommandArgument( | 1954 | new SlashCommandArgument( |
| 1905 | 'string', [ARGUMENT_TYPE.STRING], true, false, | 1955 | 'text to affect', [ARGUMENT_TYPE.STRING], true, false, |
| 1906 | ), | 1956 | ), |
| 1907 | ], | 1957 | ], |
| 1908 | helpString: 'Converts the provided string to uppercase.', | 1958 | helpString: 'Converts the provided string to uppercase.', |
| @@ -1914,7 +1964,7 @@ export function initDefaultSlashCommands() { | |||
| 1914 | returns: 'lowercase string', | 1964 | returns: 'lowercase string', |
| 1915 | unnamedArgumentList: [ | 1965 | unnamedArgumentList: [ |
| 1916 | new SlashCommandArgument( | 1966 | new SlashCommandArgument( |
| 1917 | 'string', [ARGUMENT_TYPE.STRING], true, false, | 1967 | 'text to affect', [ARGUMENT_TYPE.STRING], true, false, |
| 1918 | ), | 1968 | ), |
| 1919 | ], | 1969 | ], |
| 1920 | helpString: 'Converts the provided string to lowercase.', | 1970 | helpString: 'Converts the provided string to lowercase.', |
| @@ -1934,7 +1984,7 @@ export function initDefaultSlashCommands() { | |||
| 1934 | ], | 1984 | ], |
| 1935 | unnamedArgumentList: [ | 1985 | unnamedArgumentList: [ |
| 1936 | new SlashCommandArgument( | 1986 | new SlashCommandArgument( |
| 1937 | 'string', [ARGUMENT_TYPE.STRING], true, false, | 1987 | 'text to affect', [ARGUMENT_TYPE.STRING], true, false, |
| 1938 | ), | 1988 | ), |
| 1939 | ], | 1989 | ], |
| 1940 | helpString: ` | 1990 | helpString: ` |
| @@ -1968,8 +2018,8 @@ export function initDefaultSlashCommands() { | |||
| 1968 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2018 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1969 | name: 'chat-render', | 2019 | name: 'chat-render', |
| 1970 | helpString: 'Renders a specified number of messages into the chat window. Displays all messages if no argument is provided.', | 2020 | helpString: 'Renders a specified number of messages into the chat window. Displays all messages if no argument is provided.', |
| 1971 | callback: (args, number) => { | 2021 | callback: async (args, number) => { |
| 1972 | showMoreMessages(number && !isNaN(Number(number)) ? Number(number) : Number.MAX_SAFE_INTEGER); | 2022 | await showMoreMessages(number && !isNaN(Number(number)) ? Number(number) : Number.MAX_SAFE_INTEGER); |
| 1973 | if (isTrueBoolean(String(args?.scroll ?? ''))) { | 2023 | if (isTrueBoolean(String(args?.scroll ?? ''))) { |
| 1974 | $('#chat').scrollTop(0); | 2024 | $('#chat').scrollTop(0); |
| 1975 | } | 2025 | } |
| @@ -1998,6 +2048,62 @@ export function initDefaultSlashCommands() { | |||
| 1998 | return ''; | 2048 | return ''; |
| 1999 | }, | 2049 | }, |
| 2000 | })); | 2050 | })); |
| 2051 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2052 | name: 'replace', | ||
| 2053 | aliases: ['re'], | ||
| 2054 | callback: (async ({ mode = 'literal', pattern, replacer = '' }, text) => { | ||
| 2055 | if (pattern === '') | ||
| 2056 | throw new Error('Argument of \'pattern=\' cannot be empty'); | ||
| 2057 | switch (mode) { | ||
| 2058 | case 'literal': | ||
| 2059 | return text.replaceAll(pattern, replacer); | ||
| 2060 | case 'regex': | ||
| 2061 | return text.replace(regexFromString(pattern), replacer); | ||
| 2062 | default: | ||
| 2063 | throw new Error('Invalid \'/replace mode=\' argument specified!'); | ||
| 2064 | } | ||
| 2065 | }), | ||
| 2066 | returns: 'replaced text', | ||
| 2067 | namedArgumentList: [ | ||
| 2068 | SlashCommandNamedArgument.fromProps({ | ||
| 2069 | name: 'mode', | ||
| 2070 | description: 'Replaces occurrence(s) of a pattern', | ||
| 2071 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 2072 | defaultValue: 'literal', | ||
| 2073 | enumList: ['literal', 'regex'], | ||
| 2074 | }), | ||
| 2075 | new SlashCommandNamedArgument( | ||
| 2076 | 'pattern', 'pattern to search with', [ARGUMENT_TYPE.STRING], true, false, | ||
| 2077 | ), | ||
| 2078 | new SlashCommandNamedArgument( | ||
| 2079 | 'replacer', 'replacement text for matches', [ARGUMENT_TYPE.STRING], false, false, '', | ||
| 2080 | ), | ||
| 2081 | ], | ||
| 2082 | unnamedArgumentList: [ | ||
| 2083 | new SlashCommandArgument( | ||
| 2084 | 'text to affect', [ARGUMENT_TYPE.STRING], true, false, | ||
| 2085 | ), | ||
| 2086 | ], | ||
| 2087 | helpString: ` | ||
| 2088 | <div> | ||
| 2089 | Replaces text within the provided string based on the pattern. | ||
| 2090 | </div> | ||
| 2091 | <div> | ||
| 2092 | If <code>mode</code> is <code>literal</code> (or omitted), <code>pattern</code> is a literal search string (case-sensitive).<br /> | ||
| 2093 | If <code>mode</code> is <code>regex</code>, <code>pattern</code> is parsed as an ECMAScript Regular Expression.<br /> | ||
| 2094 | The <code>replacer</code> replaces based on the <code>pattern</code> in the input text.<br /> | ||
| 2095 | If <code>replacer</code> is omitted, the replacement(s) will be an empty string.<br /> | ||
| 2096 | </div> | ||
| 2097 | <div> | ||
| 2098 | <strong>Example:</strong> | ||
| 2099 | <pre>/let x Blue house and blue car || </pre> | ||
| 2100 | <pre>/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</pre> | ||
| 2101 | <pre>/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</pre> | ||
| 2102 | <pre>/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</pre> | ||
| 2103 | <pre>/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</pre> | ||
| 2104 | </div> | ||
| 2105 | `, | ||
| 2106 | })); | ||
| 2001 | 2107 | ||
| 2002 | registerVariableCommands(); | 2108 | registerVariableCommands(); |
| 2003 | } | 2109 | } |
| @@ -2814,8 +2920,11 @@ async function addSwipeCallback(args, value) { | |||
| 2814 | const newSwipeId = lastMessage.swipes.length - 1; | 2920 | const newSwipeId = lastMessage.swipes.length - 1; |
| 2815 | 2921 | ||
| 2816 | if (isTrueBoolean(args.switch)) { | 2922 | if (isTrueBoolean(args.switch)) { |
| 2923 | // Make sure ad-hoc changes to extras are saved before swiping away | ||
| 2924 | syncCurrentSwipeInfoExtras(); | ||
| 2817 | lastMessage.swipe_id = newSwipeId; | 2925 | lastMessage.swipe_id = newSwipeId; |
| 2818 | lastMessage.mes = lastMessage.swipes[newSwipeId]; | 2926 | lastMessage.mes = lastMessage.swipes[newSwipeId]; |
| 2927 | lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {}); | ||
| 2819 | } | 2928 | } |
| 2820 | 2929 | ||
| 2821 | await saveChatConditional(); | 2930 | await saveChatConditional(); |
| @@ -2987,7 +3096,7 @@ function performGroupMemberAction(chid, action) { | |||
| 2987 | 3096 | ||
| 2988 | async function disableGroupMemberCallback(_, arg) { | 3097 | async function disableGroupMemberCallback(_, arg) { |
| 2989 | if (!selected_group) { | 3098 | if (!selected_group) { |
| 2990 | toastr.warning('Cannot run /disable command outside of a group chat.'); | 3099 | toastr.warning('Cannot run /member-disable command outside of a group chat.'); |
| 2991 | return ''; | 3100 | return ''; |
| 2992 | } | 3101 | } |
| 2993 | 3102 | ||
| @@ -3004,7 +3113,7 @@ async function disableGroupMemberCallback(_, arg) { | |||
| 3004 | 3113 | ||
| 3005 | async function enableGroupMemberCallback(_, arg) { | 3114 | async function enableGroupMemberCallback(_, arg) { |
| 3006 | if (!selected_group) { | 3115 | if (!selected_group) { |
| 3007 | toastr.warning('Cannot run /enable command outside of a group chat.'); | 3116 | toastr.warning('Cannot run /member-enable command outside of a group chat.'); |
| 3008 | return ''; | 3117 | return ''; |
| 3009 | } | 3118 | } |
| 3010 | 3119 | ||
| @@ -3021,7 +3130,7 @@ async function enableGroupMemberCallback(_, arg) { | |||
| 3021 | 3130 | ||
| 3022 | async function moveGroupMemberUpCallback(_, arg) { | 3131 | async function moveGroupMemberUpCallback(_, arg) { |
| 3023 | if (!selected_group) { | 3132 | if (!selected_group) { |
| 3024 | toastr.warning('Cannot run /memberup command outside of a group chat.'); | 3133 | toastr.warning('Cannot run /member-up command outside of a group chat.'); |
| 3025 | return ''; | 3134 | return ''; |
| 3026 | } | 3135 | } |
| 3027 | 3136 | ||
| @@ -3038,7 +3147,7 @@ async function moveGroupMemberUpCallback(_, arg) { | |||
| 3038 | 3147 | ||
| 3039 | async function moveGroupMemberDownCallback(_, arg) { | 3148 | async function moveGroupMemberDownCallback(_, arg) { |
| 3040 | if (!selected_group) { | 3149 | if (!selected_group) { |
| 3041 | toastr.warning('Cannot run /memberdown command outside of a group chat.'); | 3150 | toastr.warning('Cannot run /member-down command outside of a group chat.'); |
| 3042 | return ''; | 3151 | return ''; |
| 3043 | } | 3152 | } |
| 3044 | 3153 | ||
| @@ -3055,12 +3164,12 @@ async function moveGroupMemberDownCallback(_, arg) { | |||
| 3055 | 3164 | ||
| 3056 | async function peekCallback(_, arg) { | 3165 | async function peekCallback(_, arg) { |
| 3057 | if (!selected_group) { | 3166 | if (!selected_group) { |
| 3058 | toastr.warning('Cannot run /peek command outside of a group chat.'); | 3167 | toastr.warning('Cannot run /member-peek command outside of a group chat.'); |
| 3059 | return ''; | 3168 | return ''; |
| 3060 | } | 3169 | } |
| 3061 | 3170 | ||
| 3062 | if (is_group_generating) { | 3171 | if (is_group_generating) { |
| 3063 | toastr.warning('Cannot run /peek command while the group reply is generating.'); | 3172 | toastr.warning('Cannot run /member-peek command while the group reply is generating.'); |
| 3064 | return ''; | 3173 | return ''; |
| 3065 | } | 3174 | } |
| 3066 | 3175 | ||
| @@ -3077,12 +3186,7 @@ async function peekCallback(_, arg) { | |||
| 3077 | 3186 | ||
| 3078 | async function removeGroupMemberCallback(_, arg) { | 3187 | async function removeGroupMemberCallback(_, arg) { |
| 3079 | if (!selected_group) { | 3188 | if (!selected_group) { |
| 3080 | toastr.warning('Cannot run /memberremove command outside of a group chat.'); | 3189 | toastr.warning('Cannot run /member-remove command outside of a group chat.'); |
| 3081 | return ''; | ||
| 3082 | } | ||
| 3083 | |||
| 3084 | if (is_group_generating) { | ||
| 3085 | toastr.warning('Cannot run /memberremove command while the group reply is generating.'); | ||
| 3086 | return ''; | 3190 | return ''; |
| 3087 | } | 3191 | } |
| 3088 | 3192 | ||
| @@ -3190,12 +3294,7 @@ function findPersonaByName(name) { | |||
| 3190 | } | 3294 | } |
| 3191 | 3295 | ||
| 3192 | async function sendUserMessageCallback(args, text) { | 3296 | async function sendUserMessageCallback(args, text) { |
| 3193 | if (!text) { | 3297 | text = String(text ?? '').trim(); |
| 3194 | toastr.warning('You must specify text to send'); | ||
| 3195 | return; | ||
| 3196 | } | ||
| 3197 | |||
| 3198 | text = text.trim(); | ||
| 3199 | const compact = isTrueBoolean(args?.compact); | 3298 | const compact = isTrueBoolean(args?.compact); |
| 3200 | const bias = extractMessageBias(text); | 3299 | const bias = extractMessageBias(text); |
| 3201 | 3300 | ||
| @@ -3504,24 +3603,18 @@ export function getNameAndAvatarForMessage(character, name = null) { | |||
| 3504 | } | 3603 | } |
| 3505 | 3604 | ||
| 3506 | export async function sendMessageAs(args, text) { | 3605 | export async function sendMessageAs(args, text) { |
| 3507 | if (!text) { | ||
| 3508 | toastr.warning('You must specify text to send as'); | ||
| 3509 | return ''; | ||
| 3510 | } | ||
| 3511 | |||
| 3512 | let name = args.name?.trim(); | 3606 | let name = args.name?.trim(); |
| 3513 | let mesText; | ||
| 3514 | 3607 | ||
| 3515 | if (!name) { | 3608 | if (!name) { |
| 3516 | const namelessWarningKey = 'sendAsNamelessWarningShown'; | 3609 | const namelessWarningKey = 'sendAsNamelessWarningShown'; |
| 3517 | if (localStorage.getItem(namelessWarningKey) !== 'true') { | 3610 | if (accountStorage.getItem(namelessWarningKey) !== 'true') { |
| 3518 | toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); | 3611 | toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); |
| 3519 | localStorage.setItem(namelessWarningKey, 'true'); | 3612 | accountStorage.setItem(namelessWarningKey, 'true'); |
| 3520 | } | 3613 | } |
| 3521 | name = name2; | 3614 | name = name2; |
| 3522 | } | 3615 | } |
| 3523 | 3616 | ||
| 3524 | mesText = text.trim(); | 3617 | let mesText = String(text ?? '').trim(); |
| 3525 | 3618 | ||
| 3526 | // Requires a regex check after the slash command is pushed to output | 3619 | // Requires a regex check after the slash command is pushed to output |
| 3527 | mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name }); | 3620 | mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name }); |
| @@ -3599,11 +3692,7 @@ export async function sendMessageAs(args, text) { | |||
| 3599 | } | 3692 | } |
| 3600 | 3693 | ||
| 3601 | export async function sendNarratorMessage(args, text) { | 3694 | export async function sendNarratorMessage(args, text) { |
| 3602 | if (!text) { | 3695 | text = String(text ?? ''); |
| 3603 | toastr.warning('You must specify text to send'); | ||
| 3604 | return ''; | ||
| 3605 | } | ||
| 3606 | |||
| 3607 | const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT; | 3696 | const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT; |
| 3608 | // Messages that do nothing but set bias will be hidden from the context | 3697 | // Messages that do nothing but set bias will be hidden from the context |
| 3609 | const bias = extractMessageBias(text); | 3698 | const bias = extractMessageBias(text); |
| @@ -3694,18 +3783,13 @@ export async function promptQuietForLoudResponse(who, text) { | |||
| 3694 | } | 3783 | } |
| 3695 | 3784 | ||
| 3696 | async function sendCommentMessage(args, text) { | 3785 | async function sendCommentMessage(args, text) { |
| 3697 | if (!text) { | ||
| 3698 | toastr.warning('You must specify text to send'); | ||
| 3699 | return ''; | ||
| 3700 | } | ||
| 3701 | |||
| 3702 | const compact = isTrueBoolean(args?.compact); | 3786 | const compact = isTrueBoolean(args?.compact); |
| 3703 | const message = { | 3787 | const message = { |
| 3704 | name: COMMENT_NAME_DEFAULT, | 3788 | name: COMMENT_NAME_DEFAULT, |
| 3705 | is_user: false, | 3789 | is_user: false, |
| 3706 | is_system: true, | 3790 | is_system: true, |
| 3707 | send_date: getMessageTimeStamp(), | 3791 | send_date: getMessageTimeStamp(), |
| 3708 | mes: substituteParams(text.trim()), | 3792 | mes: substituteParams(String(text ?? '').trim()), |
| 3709 | force_avatar: comment_avatar, | 3793 | force_avatar: comment_avatar, |
| 3710 | extra: { | 3794 | extra: { |
| 3711 | type: system_message_types.COMMENT, | 3795 | type: system_message_types.COMMENT, |
| @@ -220,6 +220,36 @@ async function* parseStreamData(json) { | |||
| 220 | } | 220 | } |
| 221 | return; | 221 | return; |
| 222 | } | 222 | } |
| 223 | else if (typeof json.choices[0].delta.reasoning_content === 'string' && json.choices[0].delta.reasoning_content.length > 0) { | ||
| 224 | for (let j = 0; j < json.choices[0].delta.reasoning_content.length; j++) { | ||
| 225 | const str = json.choices[0].delta.reasoning_content[j]; | ||
| 226 | const isLastSymbol = j === json.choices[0].delta.reasoning_content.length - 1; | ||
| 227 | const choiceClone = structuredClone(json.choices[0]); | ||
| 228 | choiceClone.delta.reasoning_content = str; | ||
| 229 | choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : ''; | ||
| 230 | const choices = [choiceClone]; | ||
| 231 | yield { | ||
| 232 | data: { ...json, choices }, | ||
| 233 | chunk: str, | ||
| 234 | }; | ||
| 235 | } | ||
| 236 | return; | ||
| 237 | } | ||
| 238 | else if (typeof json.choices[0].delta.reasoning === 'string' && json.choices[0].delta.reasoning.length > 0) { | ||
| 239 | for (let j = 0; j < json.choices[0].delta.reasoning.length; j++) { | ||
| 240 | const str = json.choices[0].delta.reasoning[j]; | ||
| 241 | const isLastSymbol = j === json.choices[0].delta.reasoning.length - 1; | ||
| 242 | const choiceClone = structuredClone(json.choices[0]); | ||
| 243 | choiceClone.delta.reasoning = str; | ||
| 244 | choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : ''; | ||
| 245 | const choices = [choiceClone]; | ||
| 246 | yield { | ||
| 247 | data: { ...json, choices }, | ||
| 248 | chunk: str, | ||
| 249 | }; | ||
| 250 | } | ||
| 251 | return; | ||
| 252 | } | ||
| 223 | else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { | 253 | else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { |
| 224 | for (let j = 0; j < json.choices[0].delta.content.length; j++) { | 254 | for (let j = 0; j < json.choices[0].delta.content.length; j++) { |
| 225 | const str = json.choices[0].delta.content[j]; | 255 | const str = json.choices[0].delta.content[j]; |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { | 1 | import { |
| 2 | activateSendButtons, | 2 | activateSendButtons, |
| 3 | addOneMessage, | 3 | addOneMessage, |
| 4 | appendMediaToMessage, | ||
| 4 | callPopup, | 5 | callPopup, |
| 5 | characters, | 6 | characters, |
| 6 | chat, | 7 | chat, |
| @@ -12,6 +13,7 @@ import { | |||
| 12 | extension_prompts, | 13 | extension_prompts, |
| 13 | Generate, | 14 | Generate, |
| 14 | generateQuietPrompt, | 15 | generateQuietPrompt, |
| 16 | getCharacters, | ||
| 15 | getCurrentChatId, | 17 | getCurrentChatId, |
| 16 | getRequestHeaders, | 18 | getRequestHeaders, |
| 17 | getThumbnailUrl, | 19 | getThumbnailUrl, |
| @@ -40,6 +42,7 @@ import { | |||
| 40 | substituteParamsExtended, | 42 | substituteParamsExtended, |
| 41 | this_chid, | 43 | this_chid, |
| 42 | updateChatMetadata, | 44 | updateChatMetadata, |
| 45 | updateMessageBlock, | ||
| 43 | } from '../script.js'; | 46 | } from '../script.js'; |
| 44 | import { | 47 | import { |
| 45 | extension_settings, | 48 | extension_settings, |
| @@ -55,7 +58,7 @@ import { MacrosParser } from './macros.js'; | |||
| 55 | import { oai_settings } from './openai.js'; | 58 | import { oai_settings } from './openai.js'; |
| 56 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; | 59 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 57 | import { power_user, registerDebugFunction } from './power-user.js'; | 60 | import { power_user, registerDebugFunction } from './power-user.js'; |
| 58 | import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js'; | 61 | import { humanizedDateTime, isMobile, shouldSendOnEnter } from './RossAscends-mods.js'; |
| 59 | import { ScraperManager } from './scrapers.js'; | 62 | import { ScraperManager } from './scrapers.js'; |
| 60 | import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js'; | 63 | import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js'; |
| 61 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 64 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -65,10 +68,14 @@ import { tag_map, tags } from './tags.js'; | |||
| 65 | import { textgenerationwebui_settings } from './textgen-settings.js'; | 68 | import { textgenerationwebui_settings } from './textgen-settings.js'; |
| 66 | import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js'; | 69 | import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js'; |
| 67 | import { ToolManager } from './tool-calling.js'; | 70 | import { ToolManager } from './tool-calling.js'; |
| 68 | import { timestampToMoment } from './utils.js'; | 71 | import { accountStorage } from './util/AccountStorage.js'; |
| 72 | import { timestampToMoment, uuidv4 } from './utils.js'; | ||
| 73 | import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js'; | ||
| 74 | import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js'; | ||
| 69 | 75 | ||
| 70 | export function getContext() { | 76 | export function getContext() { |
| 71 | return { | 77 | return { |
| 78 | accountStorage, | ||
| 72 | chat, | 79 | chat, |
| 73 | characters, | 80 | characters, |
| 74 | groups, | 81 | groups, |
| @@ -167,6 +174,25 @@ export function getContext() { | |||
| 167 | chatCompletionSettings: oai_settings, | 174 | chatCompletionSettings: oai_settings, |
| 168 | textCompletionSettings: textgenerationwebui_settings, | 175 | textCompletionSettings: textgenerationwebui_settings, |
| 169 | powerUserSettings: power_user, | 176 | powerUserSettings: power_user, |
| 177 | getCharacters, | ||
| 178 | uuidv4, | ||
| 179 | humanizedDateTime, | ||
| 180 | updateMessageBlock, | ||
| 181 | appendMediaToMessage, | ||
| 182 | variables: { | ||
| 183 | local: { | ||
| 184 | get: getLocalVariable, | ||
| 185 | set: setLocalVariable, | ||
| 186 | }, | ||
| 187 | global: { | ||
| 188 | get: getGlobalVariable, | ||
| 189 | set: setGlobalVariable, | ||
| 190 | }, | ||
| 191 | }, | ||
| 192 | loadWorldInfo, | ||
| 193 | saveWorldInfo, | ||
| 194 | updateWorldInfoList, | ||
| 195 | convertCharacterBook, | ||
| 170 | }; | 196 | }; |
| 171 | } | 197 | } |
| 172 | 198 | ||
| @@ -1,3 +1,9 @@ | |||
| 1 | <div data-type="assistant_note"> | ||
| 1 | <div> | 2 | <div> |
| 2 | <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span> | 3 | <b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span> |
| 4 | <span>Click the button to save it as a file.</span> | ||
| 5 | </div> | ||
| 6 | <div class="assistant_note_export menu_button menu_button_icon" title="Export as JSONL"> | ||
| 7 | <i class="fa-solid fa-file-export"></i> | ||
| 8 | </div> | ||
| 3 | </div> | 9 | </div> |
| @@ -7,7 +7,7 @@ | |||
| 7 | <li><span data-i18n="char_import_2">Chub Lorebook (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>lorebooks/bartleby/example-lorebook</tt></li> | 7 | <li><span data-i18n="char_import_2">Chub Lorebook (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>lorebooks/bartleby/example-lorebook</tt></li> |
| 8 | <li><span data-i18n="char_import_3">JanitorAI Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>ddd1498a-a370-4136-b138-a8cd9461fdfe_character-aqua-the-useless-goddess</tt></li> | 8 | <li><span data-i18n="char_import_3">JanitorAI Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>ddd1498a-a370-4136-b138-a8cd9461fdfe_character-aqua-the-useless-goddess</tt></li> |
| 9 | <li><span data-i18n="char_import_4">Pygmalion.chat Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>a7ca95a1-0c88-4e23-91b3-149db1e78ab9</tt></li> | 9 | <li><span data-i18n="char_import_4">Pygmalion.chat Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>a7ca95a1-0c88-4e23-91b3-149db1e78ab9</tt></li> |
| 10 | <li><span data-i18n="char_import_5">AICharacterCard.com Character (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>AICC/aicharcards/the-game-master</tt></li> | 10 | <li><span data-i18n="char_import_5">AICharacterCards.com Character (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>AICC/aicharcards/the-game-master</tt></li> |
| 11 | <li><span data-i18n="char_import_6">Direct PNG Link (refer to</span> <code>config.yaml</code><span data-i18n="char_import_7"> for allowed hosts)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://files.catbox.moe/notarealfile.png</tt></li> | 11 | <li><span data-i18n="char_import_6">Direct PNG Link (refer to</span> <code>config.yaml</code><span data-i18n="char_import_7"> for allowed hosts)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://files.catbox.moe/notarealfile.png</tt></li> |
| 12 | <li><span data-i18n="char_import_8">RisuRealm Character (Direct Link)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://realm.risuai.net/character/3ca54c71-6efe-46a2-b9d0-4f62df23d712</tt></li> | 12 | <li><span data-i18n="char_import_8">RisuRealm Character (Direct Link)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://realm.risuai.net/character/3ca54c71-6efe-46a2-b9d0-4f62df23d712</tt></li> |
| 13 | </ul> | 13 | </ul> |
| @@ -146,5 +146,5 @@ | |||
| 146 | </div> | 146 | </div> |
| 147 | <hr> | 147 | <hr> |
| 148 | <div id="rawPromptPopup" class="list-group"> | 148 | <div id="rawPromptPopup" class="list-group"> |
| 149 | <div id="rawPromptWrapper" class="tokenItemizingSubclass"></div> | 149 | <div id="rawPromptWrapper" class="tokenItemizingMaintext"></div> |
| 150 | </div> | 150 | </div> |
| @@ -6,6 +6,7 @@ import { tokenizers } from './tokenizers.js'; | |||
| 6 | import { renderTemplateAsync } from './templates.js'; | 6 | import { renderTemplateAsync } from './templates.js'; |
| 7 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 7 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 8 | import { t } from './i18n.js'; | 8 | import { t } from './i18n.js'; |
| 9 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 9 | 10 | ||
| 10 | let mancerModels = []; | 11 | let mancerModels = []; |
| 11 | let togetherModels = []; | 12 | let togetherModels = []; |
| @@ -54,6 +55,17 @@ const OPENROUTER_PROVIDERS = [ | |||
| 54 | 'xAI', | 55 | 'xAI', |
| 55 | 'Cloudflare', | 56 | 'Cloudflare', |
| 56 | 'SF Compute', | 57 | 'SF Compute', |
| 58 | 'Minimax', | ||
| 59 | 'Nineteen', | ||
| 60 | 'Liquid', | ||
| 61 | 'InferenceNet', | ||
| 62 | 'Friendli', | ||
| 63 | 'AionLabs', | ||
| 64 | 'Alibaba', | ||
| 65 | 'Nebius', | ||
| 66 | 'Chutes', | ||
| 67 | 'Kluster', | ||
| 68 | 'Targon', | ||
| 57 | '01.AI', | 69 | '01.AI', |
| 58 | 'HuggingFace', | 70 | 'HuggingFace', |
| 59 | 'Mancer', | 71 | 'Mancer', |
| @@ -330,7 +342,7 @@ export async function loadFeatherlessModels(data) { | |||
| 330 | populateClassSelection(data); | 342 | populateClassSelection(data); |
| 331 | 343 | ||
| 332 | // Retrieve the stored number of items per page or default to 10 | 344 | // Retrieve the stored number of items per page or default to 10 |
| 333 | const perPage = Number(localStorage.getItem(storageKey)) || 10; | 345 | const perPage = Number(accountStorage.getItem(storageKey)) || 10; |
| 334 | 346 | ||
| 335 | // Initialize pagination | 347 | // Initialize pagination |
| 336 | applyFiltersAndSort(); | 348 | applyFiltersAndSort(); |
| @@ -406,7 +418,7 @@ export async function loadFeatherlessModels(data) { | |||
| 406 | }, | 418 | }, |
| 407 | afterSizeSelectorChange: function (e) { | 419 | afterSizeSelectorChange: function (e) { |
| 408 | const newPerPage = e.target.value; | 420 | const newPerPage = e.target.value; |
| 409 | localStorage.setItem('Models_PerPage', newPerPage); | 421 | accountStorage.setItem(storageKey, newPerPage); |
| 410 | setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number | 422 | setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number |
| 411 | }, | 423 | }, |
| 412 | }); | 424 | }); |
| @@ -507,7 +519,7 @@ export async function loadFeatherlessModels(data) { | |||
| 507 | const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model); | 519 | const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model); |
| 508 | featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1; | 520 | featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1; |
| 509 | 521 | ||
| 510 | setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); | 522 | setupPagination(filteredModels, Number(accountStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); |
| 511 | } | 523 | } |
| 512 | 524 | ||
| 513 | // Required to keep the /model command function | 525 | // Required to keep the /model command function |
| @@ -10,6 +10,7 @@ import { | |||
| 10 | setOnlineStatus, | 10 | setOnlineStatus, |
| 11 | substituteParams, | 11 | substituteParams, |
| 12 | } from '../script.js'; | 12 | } from '../script.js'; |
| 13 | import { t } from './i18n.js'; | ||
| 13 | import { BIAS_CACHE, createNewLogitBiasEntry, displayLogitBias, getLogitBiasListResult } from './logit-bias.js'; | 14 | import { BIAS_CACHE, createNewLogitBiasEntry, displayLogitBias, getLogitBiasListResult } from './logit-bias.js'; |
| 14 | 15 | ||
| 15 | import { power_user, registerDebugFunction } from './power-user.js'; | 16 | import { power_user, registerDebugFunction } from './power-user.js'; |
| @@ -172,6 +173,7 @@ const settings = { | |||
| 172 | //truncation_length: 2048, | 173 | //truncation_length: 2048, |
| 173 | ban_eos_token: false, | 174 | ban_eos_token: false, |
| 174 | skip_special_tokens: true, | 175 | skip_special_tokens: true, |
| 176 | include_reasoning: true, | ||
| 175 | streaming: false, | 177 | streaming: false, |
| 176 | mirostat_mode: 0, | 178 | mirostat_mode: 0, |
| 177 | mirostat_tau: 5, | 179 | mirostat_tau: 5, |
| @@ -181,6 +183,8 @@ const settings = { | |||
| 181 | grammar_string: '', | 183 | grammar_string: '', |
| 182 | json_schema: {}, | 184 | json_schema: {}, |
| 183 | banned_tokens: '', | 185 | banned_tokens: '', |
| 186 | global_banned_tokens: '', | ||
| 187 | send_banned_tokens: true, | ||
| 184 | sampler_priority: OOBA_DEFAULT_ORDER, | 188 | sampler_priority: OOBA_DEFAULT_ORDER, |
| 185 | samplers: LLAMACPP_DEFAULT_ORDER, | 189 | samplers: LLAMACPP_DEFAULT_ORDER, |
| 186 | samplers_priorities: APHRODITE_DEFAULT_ORDER, | 190 | samplers_priorities: APHRODITE_DEFAULT_ORDER, |
| @@ -263,6 +267,7 @@ export const setting_names = [ | |||
| 263 | 'add_bos_token', | 267 | 'add_bos_token', |
| 264 | 'ban_eos_token', | 268 | 'ban_eos_token', |
| 265 | 'skip_special_tokens', | 269 | 'skip_special_tokens', |
| 270 | 'include_reasoning', | ||
| 266 | 'streaming', | 271 | 'streaming', |
| 267 | 'mirostat_mode', | 272 | 'mirostat_mode', |
| 268 | 'mirostat_tau', | 273 | 'mirostat_tau', |
| @@ -272,6 +277,8 @@ export const setting_names = [ | |||
| 272 | 'grammar_string', | 277 | 'grammar_string', |
| 273 | 'json_schema', | 278 | 'json_schema', |
| 274 | 'banned_tokens', | 279 | 'banned_tokens', |
| 280 | 'global_banned_tokens', | ||
| 281 | 'send_banned_tokens', | ||
| 275 | 'ignore_eos_token', | 282 | 'ignore_eos_token', |
| 276 | 'spaces_between_special_tokens', | 283 | 'spaces_between_special_tokens', |
| 277 | 'speculative_ngram', | 284 | 'speculative_ngram', |
| @@ -392,7 +399,7 @@ function getTokenizerForTokenIds() { | |||
| 392 | * @returns {TokenBanResult} String with comma-separated banned token IDs | 399 | * @returns {TokenBanResult} String with comma-separated banned token IDs |
| 393 | */ | 400 | */ |
| 394 | function getCustomTokenBans() { | 401 | function getCustomTokenBans() { |
| 395 | if (!settings.banned_tokens && !textgenerationwebui_banned_in_macros.length) { | 402 | if (!settings.send_banned_tokens || (!settings.banned_tokens && !settings.global_banned_tokens && !textgenerationwebui_banned_in_macros.length)) { |
| 396 | return { | 403 | return { |
| 397 | banned_tokens: '', | 404 | banned_tokens: '', |
| 398 | banned_strings: [], | 405 | banned_strings: [], |
| @@ -402,8 +409,9 @@ function getCustomTokenBans() { | |||
| 402 | const tokenizer = getTokenizerForTokenIds(); | 409 | const tokenizer = getTokenizerForTokenIds(); |
| 403 | const banned_tokens = []; | 410 | const banned_tokens = []; |
| 404 | const banned_strings = []; | 411 | const banned_strings = []; |
| 405 | const sequences = settings.banned_tokens | 412 | const sequences = [] |
| 406 | .split('\n') | 413 | .concat(settings.banned_tokens.split('\n')) |
| 414 | .concat(settings.global_banned_tokens.split('\n')) | ||
| 407 | .concat(textgenerationwebui_banned_in_macros) | 415 | .concat(textgenerationwebui_banned_in_macros) |
| 408 | .filter(x => x.length > 0) | 416 | .filter(x => x.length > 0) |
| 409 | .filter(onlyUnique); | 417 | .filter(onlyUnique); |
| @@ -451,6 +459,18 @@ function getCustomTokenBans() { | |||
| 451 | } | 459 | } |
| 452 | 460 | ||
| 453 | /** | 461 | /** |
| 462 | * Sets the banned strings kill switch toggle. | ||
| 463 | * @param {boolean} isEnabled Kill switch state | ||
| 464 | * @param {string} title Label title | ||
| 465 | */ | ||
| 466 | function toggleBannedStringsKillSwitch(isEnabled, title) { | ||
| 467 | $('#send_banned_tokens_textgenerationwebui').prop('checked', isEnabled); | ||
| 468 | $('#send_banned_tokens_label').find('.menu_button').toggleClass('toggleEnabled', isEnabled).prop('title', title); | ||
| 469 | settings.send_banned_tokens = isEnabled; | ||
| 470 | saveSettingsDebounced(); | ||
| 471 | } | ||
| 472 | |||
| 473 | /** | ||
| 454 | * Calculates logit bias object from the logit bias list. | 474 | * Calculates logit bias object from the logit bias list. |
| 455 | * @returns {object} Logit bias object | 475 | * @returns {object} Logit bias object |
| 456 | */ | 476 | */ |
| @@ -501,7 +521,7 @@ export function loadTextGenSettings(data, loadedSettings) { | |||
| 501 | for (const [type, selector] of Object.entries(SERVER_INPUTS)) { | 521 | for (const [type, selector] of Object.entries(SERVER_INPUTS)) { |
| 502 | const control = $(selector); | 522 | const control = $(selector); |
| 503 | control.val(settings.server_urls[type] ?? '').on('input', function () { | 523 | control.val(settings.server_urls[type] ?? '').on('input', function () { |
| 504 | settings.server_urls[type] = String($(this).val()); | 524 | settings.server_urls[type] = String($(this).val()).trim(); |
| 505 | saveSettingsDebounced(); | 525 | saveSettingsDebounced(); |
| 506 | }); | 526 | }); |
| 507 | } | 527 | } |
| @@ -592,6 +612,14 @@ function sortAphroditeItemsByOrder(orderArray) { | |||
| 592 | } | 612 | } |
| 593 | 613 | ||
| 594 | jQuery(function () { | 614 | jQuery(function () { |
| 615 | $('#send_banned_tokens_textgenerationwebui').on('change', function () { | ||
| 616 | const checked = !!$(this).prop('checked'); | ||
| 617 | toggleBannedStringsKillSwitch(checked, | ||
| 618 | checked | ||
| 619 | ? t`Banned tokens/strings are being sent in the request.` | ||
| 620 | : t`Banned tokens/strings are NOT being sent in the request.`); | ||
| 621 | }); | ||
| 622 | |||
| 595 | $('#koboldcpp_order').sortable({ | 623 | $('#koboldcpp_order').sortable({ |
| 596 | delay: getSortableDelay(), | 624 | delay: getSortableDelay(), |
| 597 | stop: function () { | 625 | stop: function () { |
| @@ -740,6 +768,7 @@ jQuery(function () { | |||
| 740 | 'add_bos_token_textgenerationwebui': true, | 768 | 'add_bos_token_textgenerationwebui': true, |
| 741 | 'temperature_last_textgenerationwebui': true, | 769 | 'temperature_last_textgenerationwebui': true, |
| 742 | 'skip_special_tokens_textgenerationwebui': true, | 770 | 'skip_special_tokens_textgenerationwebui': true, |
| 771 | 'include_reasoning_textgenerationwebui': true, | ||
| 743 | 'top_a_textgenerationwebui': 0, | 772 | 'top_a_textgenerationwebui': 0, |
| 744 | 'top_a_counter_textgenerationwebui': 0, | 773 | 'top_a_counter_textgenerationwebui': 0, |
| 745 | 'mirostat_mode_textgenerationwebui': 0, | 774 | 'mirostat_mode_textgenerationwebui': 0, |
| @@ -929,6 +958,10 @@ function setSettingByName(setting, value, trigger) { | |||
| 929 | if (isCheckbox) { | 958 | if (isCheckbox) { |
| 930 | const val = Boolean(value); | 959 | const val = Boolean(value); |
| 931 | $(`#${setting}_textgenerationwebui`).prop('checked', val); | 960 | $(`#${setting}_textgenerationwebui`).prop('checked', val); |
| 961 | |||
| 962 | if ('send_banned_tokens' === setting) { | ||
| 963 | $(`#${setting}_textgenerationwebui`).trigger('change'); | ||
| 964 | } | ||
| 932 | } | 965 | } |
| 933 | else if (isText) { | 966 | else if (isText) { |
| 934 | $(`#${setting}_textgenerationwebui`).val(value); | 967 | $(`#${setting}_textgenerationwebui`).val(value); |
| @@ -986,6 +1019,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) { | |||
| 986 | let logprobs = null; | 1019 | let logprobs = null; |
| 987 | const swipes = []; | 1020 | const swipes = []; |
| 988 | const toolCalls = []; | 1021 | const toolCalls = []; |
| 1022 | const state = { reasoning: '' }; | ||
| 989 | while (true) { | 1023 | while (true) { |
| 990 | const { done, value } = await reader.read(); | 1024 | const { done, value } = await reader.read(); |
| 991 | if (done) return; | 1025 | if (done) return; |
| @@ -1002,9 +1036,10 @@ export async function generateTextGenWithStreaming(generate_data, signal) { | |||
| 1002 | const newText = data?.choices?.[0]?.text || data?.content || ''; | 1036 | const newText = data?.choices?.[0]?.text || data?.content || ''; |
| 1003 | text += newText; | 1037 | text += newText; |
| 1004 | logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities); | 1038 | logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities); |
| 1039 | state.reasoning += data?.choices?.[0]?.reasoning ?? ''; | ||
| 1005 | } | 1040 | } |
| 1006 | 1041 | ||
| 1007 | yield { text, swipes, logprobs, toolCalls }; | 1042 | yield { text, swipes, logprobs, toolCalls, state }; |
| 1008 | } | 1043 | } |
| 1009 | }; | 1044 | }; |
| 1010 | } | 1045 | } |
| @@ -1216,7 +1251,7 @@ function replaceMacrosInList(str) { | |||
| 1216 | } | 1251 | } |
| 1217 | } | 1252 | } |
| 1218 | 1253 | ||
| 1219 | export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) { | 1254 | export async function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) { |
| 1220 | const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet'; | 1255 | const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet'; |
| 1221 | const dynatemp = isDynamicTemperatureSupported(); | 1256 | const dynatemp = isDynamicTemperatureSupported(); |
| 1222 | const { banned_tokens, banned_strings } = getCustomTokenBans(); | 1257 | const { banned_tokens, banned_strings } = getCustomTokenBans(); |
| @@ -1231,7 +1266,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | |||
| 1231 | 'top_p': settings.top_p, | 1266 | 'top_p': settings.top_p, |
| 1232 | 'typical_p': settings.typical_p, | 1267 | 'typical_p': settings.typical_p, |
| 1233 | 'typical': settings.typical_p, | 1268 | 'typical': settings.typical_p, |
| 1234 | 'sampler_seed': settings.seed, | 1269 | 'sampler_seed': settings.seed >= 0 ? settings.seed : undefined, |
| 1235 | 'min_p': settings.min_p, | 1270 | 'min_p': settings.min_p, |
| 1236 | 'repetition_penalty': settings.rep_pen, | 1271 | 'repetition_penalty': settings.rep_pen, |
| 1237 | 'frequency_penalty': settings.freq_pen, | 1272 | 'frequency_penalty': settings.freq_pen, |
| @@ -1265,6 +1300,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | |||
| 1265 | 'truncation_length': max_context, | 1300 | 'truncation_length': max_context, |
| 1266 | 'ban_eos_token': settings.ban_eos_token, | 1301 | 'ban_eos_token': settings.ban_eos_token, |
| 1267 | 'skip_special_tokens': settings.skip_special_tokens, | 1302 | 'skip_special_tokens': settings.skip_special_tokens, |
| 1303 | 'include_reasoning': settings.include_reasoning, | ||
| 1268 | 'top_a': settings.top_a, | 1304 | 'top_a': settings.top_a, |
| 1269 | 'tfs': settings.tfs, | 1305 | 'tfs': settings.tfs, |
| 1270 | 'epsilon_cutoff': [OOBA, MANCER].includes(settings.type) ? settings.epsilon_cutoff : undefined, | 1306 | 'epsilon_cutoff': [OOBA, MANCER].includes(settings.type) ? settings.epsilon_cutoff : undefined, |
| @@ -1294,7 +1330,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | |||
| 1294 | 'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined, | 1330 | 'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined, |
| 1295 | 'speculative_ngram': settings.type === TABBY ? settings.speculative_ngram : undefined, | 1331 | 'speculative_ngram': settings.type === TABBY ? settings.speculative_ngram : undefined, |
| 1296 | 'do_sample': settings.type === OOBA ? settings.do_sample : undefined, | 1332 | 'do_sample': settings.type === OOBA ? settings.do_sample : undefined, |
| 1297 | 'seed': settings.seed, | 1333 | 'seed': settings.seed >= 0 ? settings.seed : undefined, |
| 1298 | 'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1, | 1334 | 'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1, |
| 1299 | 'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '', | 1335 | 'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '', |
| 1300 | 'grammar_string': settings.grammar_string, | 1336 | 'grammar_string': settings.grammar_string, |
| @@ -1443,7 +1479,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | |||
| 1443 | } | 1479 | } |
| 1444 | } | 1480 | } |
| 1445 | 1481 | ||
| 1446 | eventSource.emitAndWait(event_types.TEXT_COMPLETION_SETTINGS_READY, params); | 1482 | await eventSource.emit(event_types.TEXT_COMPLETION_SETTINGS_READY, params); |
| 1447 | 1483 | ||
| 1448 | // Grammar conflicts with with json_schema | 1484 | // Grammar conflicts with with json_schema |
| 1449 | if (settings.type === LLAMACPP) { | 1485 | if (settings.type === LLAMACPP) { |
| @@ -679,6 +679,9 @@ export function getTokenizerModel() { | |||
| 679 | } | 679 | } |
| 680 | 680 | ||
| 681 | if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) { | 681 | if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) { |
| 682 | if (oai_settings.perplexity_model.includes('sonar-reasoning')) { | ||
| 683 | return deepseekTokenizer; | ||
| 684 | } | ||
| 682 | if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) { | 685 | if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) { |
| 683 | return llama3Tokenizer; | 686 | return llama3Tokenizer; |
| 684 | } | 687 | } |
| @@ -563,6 +563,7 @@ export class ToolManager { | |||
| 563 | chat_completion_sources.OPENROUTER, | 563 | chat_completion_sources.OPENROUTER, |
| 564 | chat_completion_sources.GROQ, | 564 | chat_completion_sources.GROQ, |
| 565 | chat_completion_sources.COHERE, | 565 | chat_completion_sources.COHERE, |
| 566 | chat_completion_sources.DEEPSEEK, | ||
| 566 | ]; | 567 | ]; |
| 567 | return supportedSources.includes(oai_settings.chat_completion_source); | 568 | return supportedSources.includes(oai_settings.chat_completion_source); |
| 568 | } | 569 | } |
| @@ -44,6 +44,14 @@ export function isAdmin() { | |||
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | /** | 46 | /** |
| 47 | * Gets the handle string of the current user. | ||
| 48 | * @returns {string} User handle | ||
| 49 | */ | ||
| 50 | export function getCurrentUserHandle() { | ||
| 51 | return currentUser?.handle || 'default-user'; | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 47 | * Get the current user. | 55 | * Get the current user. |
| 48 | * @returns {Promise<void>} | 56 | * @returns {Promise<void>} |
| 49 | */ | 57 | */ |
| @@ -0,0 +1,139 @@ | |||
| 1 | import { saveSettingsDebounced } from '../../script.js'; | ||
| 2 | |||
| 3 | const MIGRATED_MARKER = '__migrated'; | ||
| 4 | const MIGRATABLE_KEYS = [ | ||
| 5 | /^AlertRegex_/, | ||
| 6 | /^AlertWI_/, | ||
| 7 | /^Assets_SkipConfirm_/, | ||
| 8 | /^Characters_PerPage$/, | ||
| 9 | /^DataBank_sortField$/, | ||
| 10 | /^DataBank_sortOrder$/, | ||
| 11 | /^extension_update_nag$/, | ||
| 12 | /^extensions_sortByName$/, | ||
| 13 | /^FeatherlessModels_PerPage$/, | ||
| 14 | /^GroupMembers_PerPage$/, | ||
| 15 | /^GroupCandidates_PerPage$/, | ||
| 16 | /^LNavLockOn$/, | ||
| 17 | /^LNavOpened$/, | ||
| 18 | /^mediaWarningShown:/, | ||
| 19 | /^NavLockOn$/, | ||
| 20 | /^NavOpened$/, | ||
| 21 | /^Personas_PerPage$/, | ||
| 22 | /^Personas_GridView$/, | ||
| 23 | /^Proxy_SkipConfirm_/, | ||
| 24 | /^qr--executeShortcut$/, | ||
| 25 | /^qr--syntax$/, | ||
| 26 | /^qr--tabSize$/, | ||
| 27 | /^qr--wrap$/, | ||
| 28 | /^RegenerateWithCtrlEnter$/, | ||
| 29 | /^SelectedNavTab$/, | ||
| 30 | /^sendAsNamelessWarningShown$/, | ||
| 31 | /^StoryStringValidationCache$/, | ||
| 32 | /^WINavOpened$/, | ||
| 33 | /^WI_PerPage$/, | ||
| 34 | /^world_info_sort_order$/, | ||
| 35 | ]; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * Provides access to account storage of arbitrary key-value pairs. | ||
| 39 | */ | ||
| 40 | class AccountStorage { | ||
| 41 | /** | ||
| 42 | * @type {Record<string, string>} Storage state | ||
| 43 | */ | ||
| 44 | #state = {}; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * @type {boolean} If the storage was initialized | ||
| 48 | */ | ||
| 49 | #ready = false; | ||
| 50 | |||
| 51 | #migrateLocalStorage() { | ||
| 52 | const localStorageKeys = []; | ||
| 53 | for (let i = 0; i < globalThis.localStorage.length; i++) { | ||
| 54 | localStorageKeys.push(globalThis.localStorage.key(i)); | ||
| 55 | } | ||
| 56 | for (const key of localStorageKeys) { | ||
| 57 | if (MIGRATABLE_KEYS.some(k => k.test(key))) { | ||
| 58 | const value = globalThis.localStorage.getItem(key); | ||
| 59 | this.#state[key] = value; | ||
| 60 | globalThis.localStorage.removeItem(key); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | /** | ||
| 66 | * Initialize the account storage. | ||
| 67 | * @param {Object} state Initial state | ||
| 68 | */ | ||
| 69 | init(state) { | ||
| 70 | if (state && typeof state === 'object') { | ||
| 71 | this.#state = Object.assign(this.#state, state); | ||
| 72 | } | ||
| 73 | |||
| 74 | if (!Object.hasOwn(this.#state, MIGRATED_MARKER)) { | ||
| 75 | this.#migrateLocalStorage(); | ||
| 76 | this.#state[MIGRATED_MARKER] = '1'; | ||
| 77 | saveSettingsDebounced(); | ||
| 78 | } | ||
| 79 | |||
| 80 | this.#ready = true; | ||
| 81 | } | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Get the value of a key in account storage. | ||
| 85 | * @param {string} key Key to get | ||
| 86 | * @returns {string|null} Value of the key | ||
| 87 | */ | ||
| 88 | getItem(key) { | ||
| 89 | if (!this.#ready) { | ||
| 90 | console.warn(`AccountStorage not ready (trying to read from ${key})`); | ||
| 91 | } | ||
| 92 | |||
| 93 | return Object.hasOwn(this.#state, key) ? String(this.#state[key]) : null; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Set a key in account storage. | ||
| 98 | * @param {string} key Key to set | ||
| 99 | * @param {string} value Value to set | ||
| 100 | */ | ||
| 101 | setItem(key, value) { | ||
| 102 | if (!this.#ready) { | ||
| 103 | console.warn(`AccountStorage not ready (trying to write to ${key})`); | ||
| 104 | } | ||
| 105 | |||
| 106 | this.#state[key] = String(value); | ||
| 107 | saveSettingsDebounced(); | ||
| 108 | } | ||
| 109 | |||
| 110 | /** | ||
| 111 | * Remove a key from account storage. | ||
| 112 | * @param {string} key Key to remove | ||
| 113 | */ | ||
| 114 | removeItem(key) { | ||
| 115 | if (!this.#ready) { | ||
| 116 | console.warn(`AccountStorage not ready (trying to remove ${key})`); | ||
| 117 | } | ||
| 118 | |||
| 119 | if (!Object.hasOwn(this.#state, key)) { | ||
| 120 | return; | ||
| 121 | } | ||
| 122 | |||
| 123 | delete this.#state[key]; | ||
| 124 | saveSettingsDebounced(); | ||
| 125 | } | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Gets a snapshot of the storage state. | ||
| 129 | * @returns {Record<string, string>} A deep clone of the storage state | ||
| 130 | */ | ||
| 131 | getState() { | ||
| 132 | return structuredClone(this.#state); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | /** | ||
| 137 | * Account storage instance. | ||
| 138 | */ | ||
| 139 | export const accountStorage = new AccountStorage(); | ||
| @@ -1733,17 +1733,17 @@ export function hasAnimation(control) { | |||
| 1733 | 1733 | ||
| 1734 | /** | 1734 | /** |
| 1735 | * Run an action once an animation on a control ends. If the control has no animation, the action will be executed immediately. | 1735 | * Run an action once an animation on a control ends. If the control has no animation, the action will be executed immediately. |
| 1736 | * | 1736 | * The action will be executed after the animation ends or after the timeout, whichever comes first. |
| 1737 | * @param {HTMLElement} control - The control element to listen for animation end event | 1737 | * @param {HTMLElement} control - The control element to listen for animation end event |
| 1738 | * @param {(control:*?) => void} callback - The callback function to be executed when the animation ends | 1738 | * @param {(control:*?) => void} callback - The callback function to be executed when the animation ends |
| 1739 | * @param {number} [timeout=500] - The timeout in milliseconds to wait for the animation to end before executing the callback | ||
| 1739 | */ | 1740 | */ |
| 1740 | export function runAfterAnimation(control, callback) { | 1741 | export function runAfterAnimation(control, callback, timeout = 500) { |
| 1741 | if (hasAnimation(control)) { | 1742 | if (hasAnimation(control)) { |
| 1742 | const onAnimationEnd = () => { | 1743 | Promise.race([ |
| 1743 | control.removeEventListener('animationend', onAnimationEnd); | 1744 | new Promise((r) => setTimeout(r, timeout)), // Fallback timeout |
| 1744 | callback(control); | 1745 | new Promise((r) => control.addEventListener('animationend', r, { once: true })), |
| 1745 | }; | 1746 | ]).finally(() => callback(control)); |
| 1746 | control.addEventListener('animationend', onAnimationEnd); | ||
| 1747 | } else { | 1747 | } else { |
| 1748 | callback(control); | 1748 | callback(control); |
| 1749 | } | 1749 | } |
| @@ -2059,6 +2059,23 @@ export function toggleDrawer(drawer, expand = true) { | |||
| 2059 | } | 2059 | } |
| 2060 | } | 2060 | } |
| 2061 | 2061 | ||
| 2062 | /** | ||
| 2063 | * Sets or removes a dataset property on an HTMLElement | ||
| 2064 | * | ||
| 2065 | * Utility function to make it easier to reset dataset properties on null, without them being "null" as value. | ||
| 2066 | * | ||
| 2067 | * @param {HTMLElement} element - The element to modify | ||
| 2068 | * @param {string} name - The name of the dataset property | ||
| 2069 | * @param {string|null} value - The value to set - If null, the dataset property will be removed | ||
| 2070 | */ | ||
| 2071 | export function setDatasetProperty(element, name, value) { | ||
| 2072 | if (value === null) { | ||
| 2073 | delete element.dataset[name]; | ||
| 2074 | } else { | ||
| 2075 | element.dataset[name] = value; | ||
| 2076 | } | ||
| 2077 | } | ||
| 2078 | |||
| 2062 | export async function fetchFaFile(name) { | 2079 | export async function fetchFaFile(name) { |
| 2063 | const style = document.createElement('style'); | 2080 | const style = document.createElement('style'); |
| 2064 | style.innerHTML = await (await fetch(`/css/${name}`)).text(); | 2081 | style.innerHTML = await (await fetch(`/css/${name}`)).text(); |
| @@ -19,7 +19,7 @@ import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js'; | |||
| 19 | 19 | ||
| 20 | const MAX_LOOPS = 100; | 20 | const MAX_LOOPS = 100; |
| 21 | 21 | ||
| 22 | function getLocalVariable(name, args = {}) { | 22 | export function getLocalVariable(name, args = {}) { |
| 23 | if (!chat_metadata.variables) { | 23 | if (!chat_metadata.variables) { |
| 24 | chat_metadata.variables = {}; | 24 | chat_metadata.variables = {}; |
| 25 | } | 25 | } |
| @@ -45,7 +45,7 @@ function getLocalVariable(name, args = {}) { | |||
| 45 | return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable); | 45 | return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable); |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | function setLocalVariable(name, value, args = {}) { | 48 | export function setLocalVariable(name, value, args = {}) { |
| 49 | if (!name) { | 49 | if (!name) { |
| 50 | throw new Error('Variable name cannot be empty or undefined.'); | 50 | throw new Error('Variable name cannot be empty or undefined.'); |
| 51 | } | 51 | } |
| @@ -80,7 +80,7 @@ function setLocalVariable(name, value, args = {}) { | |||
| 80 | return value; | 80 | return value; |
| 81 | } | 81 | } |
| 82 | 82 | ||
| 83 | function getGlobalVariable(name, args = {}) { | 83 | export function getGlobalVariable(name, args = {}) { |
| 84 | let globalVariable = extension_settings.variables.global[args.key ?? name]; | 84 | let globalVariable = extension_settings.variables.global[args.key ?? name]; |
| 85 | if (args.index !== undefined) { | 85 | if (args.index !== undefined) { |
| 86 | try { | 86 | try { |
| @@ -102,7 +102,7 @@ function getGlobalVariable(name, args = {}) { | |||
| 102 | return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable); | 102 | return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable); |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | function setGlobalVariable(name, value, args = {}) { | 105 | export function setGlobalVariable(name, value, args = {}) { |
| 106 | if (!name) { | 106 | if (!name) { |
| 107 | throw new Error('Variable name cannot be empty or undefined.'); | 107 | throw new Error('Variable name cannot be empty or undefined.'); |
| 108 | } | 108 | } |
| @@ -21,6 +21,7 @@ import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; | |||
| 21 | import { StructuredCloneMap } from './util/StructuredCloneMap.js'; | 21 | import { StructuredCloneMap } from './util/StructuredCloneMap.js'; |
| 22 | import { renderTemplateAsync } from './templates.js'; | 22 | import { renderTemplateAsync } from './templates.js'; |
| 23 | import { t } from './i18n.js'; | 23 | import { t } from './i18n.js'; |
| 24 | import { accountStorage } from './util/AccountStorage.js'; | ||
| 24 | 25 | ||
| 25 | export const world_info_insertion_strategy = { | 26 | export const world_info_insertion_strategy = { |
| 26 | evenly: 0, | 27 | evenly: 0, |
| @@ -400,6 +401,12 @@ class WorldInfoTimedEffects { | |||
| 400 | #entries = []; | 401 | #entries = []; |
| 401 | 402 | ||
| 402 | /** | 403 | /** |
| 404 | * Is this a dry run? | ||
| 405 | * @type {boolean} | ||
| 406 | */ | ||
| 407 | #isDryRun = false; | ||
| 408 | |||
| 409 | /** | ||
| 403 | * Buffer for active timed effects. | 410 | * Buffer for active timed effects. |
| 404 | * @type {Record<TimedEffectType, WIScanEntry[]>} | 411 | * @type {Record<TimedEffectType, WIScanEntry[]>} |
| 405 | */ | 412 | */ |
| @@ -448,10 +455,12 @@ class WorldInfoTimedEffects { | |||
| 448 | * Initialize the timed effects with the given messages. | 455 | * Initialize the timed effects with the given messages. |
| 449 | * @param {string[]} chat Array of chat messages | 456 | * @param {string[]} chat Array of chat messages |
| 450 | * @param {WIScanEntry[]} entries Array of entries | 457 | * @param {WIScanEntry[]} entries Array of entries |
| 458 | * @param {boolean} isDryRun Whether the operation is a dry run | ||
| 451 | */ | 459 | */ |
| 452 | constructor(chat, entries) { | 460 | constructor(chat, entries, isDryRun = false) { |
| 453 | this.#chat = chat; | 461 | this.#chat = chat; |
| 454 | this.#entries = entries; | 462 | this.#entries = entries; |
| 463 | this.#isDryRun = isDryRun; | ||
| 455 | this.#ensureChatMetadata(); | 464 | this.#ensureChatMetadata(); |
| 456 | } | 465 | } |
| 457 | 466 | ||
| @@ -583,8 +592,10 @@ class WorldInfoTimedEffects { | |||
| 583 | * Checks for timed effects on chat messages. | 592 | * Checks for timed effects on chat messages. |
| 584 | */ | 593 | */ |
| 585 | checkTimedEffects() { | 594 | checkTimedEffects() { |
| 595 | if (!this.#isDryRun) { | ||
| 586 | this.#checkTimedEffectOfType('sticky', this.#buffer.sticky, this.#onEnded.sticky.bind(this)); | 596 | this.#checkTimedEffectOfType('sticky', this.#buffer.sticky, this.#onEnded.sticky.bind(this)); |
| 587 | this.#checkTimedEffectOfType('cooldown', this.#buffer.cooldown, this.#onEnded.cooldown.bind(this)); | 597 | this.#checkTimedEffectOfType('cooldown', this.#buffer.cooldown, this.#onEnded.cooldown.bind(this)); |
| 598 | } | ||
| 588 | this.#checkDelayEffect(this.#buffer.delay); | 599 | this.#checkDelayEffect(this.#buffer.delay); |
| 589 | } | 600 | } |
| 590 | 601 | ||
| @@ -629,6 +640,7 @@ class WorldInfoTimedEffects { | |||
| 629 | * @param {WIScanEntry[]} activatedEntries Entries that were activated | 640 | * @param {WIScanEntry[]} activatedEntries Entries that were activated |
| 630 | */ | 641 | */ |
| 631 | setTimedEffects(activatedEntries) { | 642 | setTimedEffects(activatedEntries) { |
| 643 | if (this.#isDryRun) return; | ||
| 632 | for (const entry of activatedEntries) { | 644 | for (const entry of activatedEntries) { |
| 633 | this.#setTimedEffectOfType('sticky', entry); | 645 | this.#setTimedEffectOfType('sticky', entry); |
| 634 | this.#setTimedEffectOfType('cooldown', entry); | 646 | this.#setTimedEffectOfType('cooldown', entry); |
| @@ -645,6 +657,9 @@ class WorldInfoTimedEffects { | |||
| 645 | if (!this.isValidEffectType(type)) { | 657 | if (!this.isValidEffectType(type)) { |
| 646 | return; | 658 | return; |
| 647 | } | 659 | } |
| 660 | if (this.#isDryRun && type !== 'delay') { | ||
| 661 | return; | ||
| 662 | } | ||
| 648 | 663 | ||
| 649 | const key = this.#getEntryKey(entry); | 664 | const key = this.#getEntryKey(entry); |
| 650 | delete chat_metadata.timedWorldInfo[type][key]; | 665 | delete chat_metadata.timedWorldInfo[type][key]; |
| @@ -858,7 +873,7 @@ export function setWorldInfoSettings(settings, data) { | |||
| 858 | $('#world_editor_select').append(`<option value='${i}'>${item}</option>`); | 873 | $('#world_editor_select').append(`<option value='${i}'>${item}</option>`); |
| 859 | }); | 874 | }); |
| 860 | 875 | ||
| 861 | $('#world_info_sort_order').val(localStorage.getItem(SORT_ORDER_KEY) || '0'); | 876 | $('#world_info_sort_order').val(accountStorage.getItem(SORT_ORDER_KEY) || '0'); |
| 862 | $('#world_info').trigger('change'); | 877 | $('#world_info').trigger('change'); |
| 863 | $('#world_editor_select').trigger('change'); | 878 | $('#world_editor_select').trigger('change'); |
| 864 | 879 | ||
| @@ -1708,7 +1723,7 @@ export async function loadWorldInfo(name) { | |||
| 1708 | return null; | 1723 | return null; |
| 1709 | } | 1724 | } |
| 1710 | 1725 | ||
| 1711 | async function updateWorldInfoList() { | 1726 | export async function updateWorldInfoList() { |
| 1712 | const result = await fetch('/api/settings/get', { | 1727 | const result = await fetch('/api/settings/get', { |
| 1713 | method: 'POST', | 1728 | method: 'POST', |
| 1714 | headers: getRequestHeaders(), | 1729 | headers: getRequestHeaders(), |
| @@ -1933,13 +1948,13 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl | |||
| 1933 | if (typeof navigation === 'number' && Number(navigation) >= 0) { | 1948 | if (typeof navigation === 'number' && Number(navigation) >= 0) { |
| 1934 | const data = getDataArray(); | 1949 | const data = getDataArray(); |
| 1935 | const uidIndex = data.findIndex(x => x.uid === navigation); | 1950 | const uidIndex = data.findIndex(x => x.uid === navigation); |
| 1936 | const perPage = Number(localStorage.getItem(storageKey)) || perPageDefault; | 1951 | const perPage = Number(accountStorage.getItem(storageKey)) || perPageDefault; |
| 1937 | startPage = Math.floor(uidIndex / perPage) + 1; | 1952 | startPage = Math.floor(uidIndex / perPage) + 1; |
| 1938 | } | 1953 | } |
| 1939 | 1954 | ||
| 1940 | $('#world_info_pagination').pagination({ | 1955 | $('#world_info_pagination').pagination({ |
| 1941 | dataSource: getDataArray, | 1956 | dataSource: getDataArray, |
| 1942 | pageSize: Number(localStorage.getItem(storageKey)) || perPageDefault, | 1957 | pageSize: Number(accountStorage.getItem(storageKey)) || perPageDefault, |
| 1943 | sizeChangerOptions: [10, 25, 50, 100, 500, 1000], | 1958 | sizeChangerOptions: [10, 25, 50, 100, 500, 1000], |
| 1944 | showSizeChanger: true, | 1959 | showSizeChanger: true, |
| 1945 | pageRange: 1, | 1960 | pageRange: 1, |
| @@ -1969,7 +1984,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl | |||
| 1969 | worldEntriesList.append(blocks); | 1984 | worldEntriesList.append(blocks); |
| 1970 | }, | 1985 | }, |
| 1971 | afterSizeSelectorChange: function (e) { | 1986 | afterSizeSelectorChange: function (e) { |
| 1972 | localStorage.setItem(storageKey, e.target.value); | 1987 | accountStorage.setItem(storageKey, e.target.value); |
| 1973 | }, | 1988 | }, |
| 1974 | afterPaging: function () { | 1989 | afterPaging: function () { |
| 1975 | $('#world_popup_entries_list textarea[name="comment"]').each(function () { | 1990 | $('#world_popup_entries_list textarea[name="comment"]').each(function () { |
| @@ -2174,7 +2189,7 @@ function verifyWorldInfoSearchSortRule() { | |||
| 2174 | // If search got cleared, we make sure to hide the option and go back to the one before | 2189 | // If search got cleared, we make sure to hide the option and go back to the one before |
| 2175 | if (!searchTerm && !isHidden) { | 2190 | if (!searchTerm && !isHidden) { |
| 2176 | searchOption.attr('hidden', ''); | 2191 | searchOption.attr('hidden', ''); |
| 2177 | selector.val(localStorage.getItem(SORT_ORDER_KEY) || '0'); | 2192 | selector.val(accountStorage.getItem(SORT_ORDER_KEY) || '0'); |
| 2178 | } | 2193 | } |
| 2179 | } | 2194 | } |
| 2180 | 2195 | ||
| @@ -2423,7 +2438,9 @@ export async function getWorldEntry(name, data, entry) { | |||
| 2423 | setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); | 2438 | setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); |
| 2424 | await saveWorldInfo(name, data); | 2439 | await saveWorldInfo(name, data); |
| 2425 | } | 2440 | } |
| 2441 | $(this).toggleClass('empty', !data.entries[uid][entryPropName].length); | ||
| 2426 | }); | 2442 | }); |
| 2443 | input.toggleClass('empty', !entry[entryPropName].length); | ||
| 2427 | input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data])); | 2444 | input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data])); |
| 2428 | input.on('select2:unselect', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true })); | 2445 | input.on('select2:unselect', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true })); |
| 2429 | 2446 | ||
| @@ -2458,6 +2475,7 @@ export async function getWorldEntry(name, data, entry) { | |||
| 2458 | data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value); | 2475 | data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value); |
| 2459 | setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); | 2476 | setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); |
| 2460 | await saveWorldInfo(name, data); | 2477 | await saveWorldInfo(name, data); |
| 2478 | $(this).toggleClass('empty', !data.entries[uid][entryPropName].length); | ||
| 2461 | } | 2479 | } |
| 2462 | }); | 2480 | }); |
| 2463 | input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true }); | 2481 | input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true }); |
| @@ -3435,7 +3453,7 @@ async function _save(name, data) { | |||
| 3435 | headers: getRequestHeaders(), | 3453 | headers: getRequestHeaders(), |
| 3436 | body: JSON.stringify({ name: name, data: data }), | 3454 | body: JSON.stringify({ name: name, data: data }), |
| 3437 | }); | 3455 | }); |
| 3438 | eventSource.emit(event_types.WORLDINFO_UPDATED, name, data); | 3456 | await eventSource.emit(event_types.WORLDINFO_UPDATED, name, data); |
| 3439 | } | 3457 | } |
| 3440 | 3458 | ||
| 3441 | 3459 | ||
| @@ -3847,7 +3865,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 3847 | const context = getContext(); | 3865 | const context = getContext(); |
| 3848 | const buffer = new WorldInfoBuffer(chat); | 3866 | const buffer = new WorldInfoBuffer(chat); |
| 3849 | 3867 | ||
| 3850 | console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages) ---`); | 3868 | console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`); |
| 3851 | 3869 | ||
| 3852 | // Combine the chat | 3870 | // Combine the chat |
| 3853 | 3871 | ||
| @@ -3879,9 +3897,9 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 3879 | 3897 | ||
| 3880 | console.debug(`[WI] Context size: ${maxContext}; WI budget: ${budget} (max% = ${world_info_budget}%, cap = ${world_info_budget_cap})`); | 3898 | console.debug(`[WI] Context size: ${maxContext}; WI budget: ${budget} (max% = ${world_info_budget}%, cap = ${world_info_budget_cap})`); |
| 3881 | const sortedEntries = await getSortedEntries(); | 3899 | const sortedEntries = await getSortedEntries(); |
| 3882 | const timedEffects = new WorldInfoTimedEffects(chat, sortedEntries); | 3900 | const timedEffects = new WorldInfoTimedEffects(chat, sortedEntries, isDryRun); |
| 3883 | 3901 | ||
| 3884 | !isDryRun && timedEffects.checkTimedEffects(); | 3902 | timedEffects.checkTimedEffects(); |
| 3885 | 3903 | ||
| 3886 | if (sortedEntries.length === 0) { | 3904 | if (sortedEntries.length === 0) { |
| 3887 | return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() }; | 3905 | return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() }; |
| @@ -4324,12 +4342,12 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 4324 | context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]); | 4342 | context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]); |
| 4325 | } | 4343 | } |
| 4326 | 4344 | ||
| 4327 | !isDryRun && timedEffects.setTimedEffects(Array.from(allActivatedEntries.values())); | 4345 | timedEffects.setTimedEffects(Array.from(allActivatedEntries.values())); |
| 4328 | buffer.resetExternalEffects(); | 4346 | buffer.resetExternalEffects(); |
| 4329 | timedEffects.cleanUp(); | 4347 | timedEffects.cleanUp(); |
| 4330 | 4348 | ||
| 4331 | console.log(`[WI] Adding ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values())); | 4349 | console.log(`[WI] ${isDryRun ? 'Hypothetically adding' : 'Adding'} ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values())); |
| 4332 | console.debug('[WI] --- DONE ---'); | 4350 | console.debug(`[WI] --- DONE${isDryRun ? ' (DRY RUN)' : ''} ---`); |
| 4333 | 4351 | ||
| 4334 | return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) }; | 4352 | return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) }; |
| 4335 | } | 4353 | } |
| @@ -4658,7 +4676,7 @@ function convertNovelLorebook(inputObj) { | |||
| 4658 | return outputObj; | 4676 | return outputObj; |
| 4659 | } | 4677 | } |
| 4660 | 4678 | ||
| 4661 | function convertCharacterBook(characterBook) { | 4679 | export function convertCharacterBook(characterBook) { |
| 4662 | const result = { entries: {}, originalData: characterBook }; | 4680 | const result = { entries: {}, originalData: characterBook }; |
| 4663 | 4681 | ||
| 4664 | characterBook.entries.forEach((entry, index) => { | 4682 | characterBook.entries.forEach((entry, index) => { |
| @@ -4736,8 +4754,8 @@ export function checkEmbeddedWorld(chid) { | |||
| 4736 | // Only show the alert once per character | 4754 | // Only show the alert once per character |
| 4737 | const checkKey = `AlertWI_${characters[chid].avatar}`; | 4755 | const checkKey = `AlertWI_${characters[chid].avatar}`; |
| 4738 | const worldName = characters[chid]?.data?.extensions?.world; | 4756 | const worldName = characters[chid]?.data?.extensions?.world; |
| 4739 | if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { | 4757 | if (!accountStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { |
| 4740 | localStorage.setItem(checkKey, 'true'); | 4758 | accountStorage.setItem(checkKey, 'true'); |
| 4741 | 4759 | ||
| 4742 | if (power_user.world_import_dialog) { | 4760 | if (power_user.world_import_dialog) { |
| 4743 | const html = `<h3>This character has an embedded World/Lorebook.</h3> | 4761 | const html = `<h3>This character has an embedded World/Lorebook.</h3> |
| @@ -5181,7 +5199,7 @@ jQuery(() => { | |||
| 5181 | $('#world_info_sort_order').on('change', function () { | 5199 | $('#world_info_sort_order').on('change', function () { |
| 5182 | const value = String($(this).find(':selected').val()); | 5200 | const value = String($(this).find(':selected').val()); |
| 5183 | // Save sort order, but do not save search sorting, as this is a temporary sorting option | 5201 | // Save sort order, but do not save search sorting, as this is a temporary sorting option |
| 5184 | if (value !== 'search') localStorage.setItem(SORT_ORDER_KEY, value); | 5202 | if (value !== 'search') accountStorage.setItem(SORT_ORDER_KEY, value); |
| 5185 | updateEditor(navigation_option.none); | 5203 | updateEditor(navigation_option.none); |
| 5186 | }); | 5204 | }); |
| 5187 | 5205 | ||
| @@ -106,6 +106,8 @@ | |||
| 106 | --tool-cool-color-picker-btn-bg: transparent; | 106 | --tool-cool-color-picker-btn-bg: transparent; |
| 107 | --tool-cool-color-picker-btn-border-color: transparent; | 107 | --tool-cool-color-picker-btn-border-color: transparent; |
| 108 | 108 | ||
| 109 | --mes-right-spacing: 30px; | ||
| 110 | |||
| 109 | --avatar-base-height: 50px; | 111 | --avatar-base-height: 50px; |
| 110 | --avatar-base-width: 50px; | 112 | --avatar-base-width: 50px; |
| 111 | --avatar-base-border-radius: 2px; | 113 | --avatar-base-border-radius: 2px; |
| @@ -260,6 +262,10 @@ input[type='checkbox']:focus-visible { | |||
| 260 | color: var(--SmartThemeEmColor); | 262 | color: var(--SmartThemeEmColor); |
| 261 | } | 263 | } |
| 262 | 264 | ||
| 265 | .tokenItemizingMaintext { | ||
| 266 | font-size: calc(var(--mainFontSize) * 0.8); | ||
| 267 | } | ||
| 268 | |||
| 263 | .tokenGraph { | 269 | .tokenGraph { |
| 264 | border-radius: 10px; | 270 | border-radius: 10px; |
| 265 | border: 1px solid var(--SmartThemeBorderColor); | 271 | border: 1px solid var(--SmartThemeBorderColor); |
| @@ -292,36 +298,44 @@ input[type='checkbox']:focus-visible { | |||
| 292 | filter: grayscale(25%); | 298 | filter: grayscale(25%); |
| 293 | } | 299 | } |
| 294 | 300 | ||
| 295 | .mes_text table { | 301 | .mes_text table, |
| 302 | .mes_reasoning table { | ||
| 296 | border-spacing: 0; | 303 | border-spacing: 0; |
| 297 | border-collapse: collapse; | 304 | border-collapse: collapse; |
| 298 | margin-bottom: 10px; | 305 | margin-bottom: 10px; |
| 299 | } | 306 | } |
| 300 | 307 | ||
| 301 | .mes_text td, | 308 | .mes_text td, |
| 302 | .mes_text th { | 309 | .mes_text th, |
| 310 | .mes_reasoning td, | ||
| 311 | .mes_reasoning th { | ||
| 303 | border: 1px solid; | 312 | border: 1px solid; |
| 304 | border-collapse: collapse; | 313 | border-collapse: collapse; |
| 305 | padding: 0.25em; | 314 | padding: 0.25em; |
| 306 | } | 315 | } |
| 307 | 316 | ||
| 308 | .mes_text p { | 317 | .mes_text p, |
| 318 | .mes_reasoning p { | ||
| 309 | margin-top: 0; | 319 | margin-top: 0; |
| 310 | margin-bottom: 10px; | 320 | margin-bottom: 10px; |
| 311 | } | 321 | } |
| 312 | 322 | ||
| 313 | .mes_text li tt { | 323 | .mes_text li tt, |
| 324 | .mes_reasoning li tt { | ||
| 314 | display: inline-block; | 325 | display: inline-block; |
| 315 | } | 326 | } |
| 316 | 327 | ||
| 317 | .mes_text ol, | 328 | .mes_text ol, |
| 318 | .mes_text ul { | 329 | .mes_text ul, |
| 330 | .mes_reasoning ol, | ||
| 331 | .mes_reasoning ul { | ||
| 319 | margin-top: 5px; | 332 | margin-top: 5px; |
| 320 | margin-bottom: 5px; | 333 | margin-bottom: 5px; |
| 321 | } | 334 | } |
| 322 | 335 | ||
| 323 | .mes_text br, | 336 | .mes_text br, |
| 324 | .mes_bias br { | 337 | .mes_bias br, |
| 338 | .mes_reasoning br { | ||
| 325 | content: ' '; | 339 | content: ' '; |
| 326 | } | 340 | } |
| 327 | 341 | ||
| @@ -332,25 +346,150 @@ input[type='checkbox']:focus-visible { | |||
| 332 | color: var(--SmartThemeQuoteColor); | 346 | color: var(--SmartThemeQuoteColor); |
| 333 | } | 347 | } |
| 334 | 348 | ||
| 349 | .mes_reasoning { | ||
| 350 | display: block; | ||
| 351 | border-left: 2px solid var(--SmartThemeEmColor); | ||
| 352 | border-radius: 2px; | ||
| 353 | padding: 5px; | ||
| 354 | padding-left: 14px; | ||
| 355 | margin-bottom: 0.5em; | ||
| 356 | overflow-y: auto; | ||
| 357 | color: var(--SmartThemeEmColor); | ||
| 358 | } | ||
| 359 | |||
| 360 | .mes_reasoning_details { | ||
| 361 | margin-right: var(--mes-right-spacing); | ||
| 362 | } | ||
| 363 | |||
| 364 | .mes_reasoning_details .mes_reasoning_summary { | ||
| 365 | list-style: none; | ||
| 366 | margin-right: calc(var(--mes-right-spacing) * -1); | ||
| 367 | } | ||
| 368 | |||
| 369 | .mes_reasoning_details summary::-webkit-details-marker { | ||
| 370 | display: none; | ||
| 371 | } | ||
| 372 | |||
| 373 | .mes_reasoning *:last-child { | ||
| 374 | margin-bottom: 0; | ||
| 375 | } | ||
| 376 | |||
| 377 | .mes_reasoning em, | ||
| 378 | .mes_reasoning i, | ||
| 379 | .mes_reasoning u, | ||
| 380 | .mes_reasoning q, | ||
| 381 | .mes_reasoning blockquote { | ||
| 382 | filter: saturate(0.5); | ||
| 383 | } | ||
| 384 | |||
| 385 | .mes_reasoning_details .mes_reasoning em { | ||
| 386 | color: color-mix(in srgb, var(--SmartThemeEmColor) 67%, var(--SmartThemeBlurTintColor) 33%); | ||
| 387 | } | ||
| 388 | |||
| 389 | .mes_reasoning_header_block { | ||
| 390 | flex-grow: 1; | ||
| 391 | } | ||
| 392 | |||
| 393 | .mes_reasoning_header { | ||
| 394 | cursor: pointer; | ||
| 395 | position: relative; | ||
| 396 | user-select: none; | ||
| 397 | margin: 0.5em 2px; | ||
| 398 | padding: 7px 14px; | ||
| 399 | padding-right: calc(0.7em + 14px); | ||
| 400 | border-radius: 5px; | ||
| 401 | background-color: var(--grey30); | ||
| 402 | font-size: calc(var(--mainFontSize) * 0.9); | ||
| 403 | align-items: baseline; | ||
| 404 | } | ||
| 405 | |||
| 406 | .mes:has(.mes_reasoning:empty) .mes_reasoning_header { | ||
| 407 | cursor: default; | ||
| 408 | } | ||
| 409 | |||
| 410 | /* TWIMC: Remove with custom CSS to show the icon */ | ||
| 411 | .mes_reasoning_header>.icon-svg { | ||
| 412 | display: none; | ||
| 413 | } | ||
| 414 | |||
| 415 | @supports not selector(:has(*)) { | ||
| 416 | .mes_reasoning_details { | ||
| 417 | display: none !important; | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | .mes_bias:empty, | ||
| 422 | .mes:not(.reasoning) .mes_reasoning_details, | ||
| 423 | .mes_reasoning_details:not([open]) .mes_reasoning_actions, | ||
| 424 | .mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning, | ||
| 425 | .mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_header, | ||
| 426 | .mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.edit_button), | ||
| 427 | .mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .edit_button, | ||
| 428 | .mes_block:has(.edit_textarea):has(.reasoning_edit_textarea) .mes_reasoning_actions, | ||
| 429 | .mes.reasoning:not([data-reasoning-state="hidden"]) .mes_edit_add_reasoning, | ||
| 430 | .mes:has(.mes_reasoning:empty) .mes_reasoning_arrow, | ||
| 431 | .mes:has(.mes_reasoning:empty) .mes_reasoning, | ||
| 432 | .mes:has(.mes_reasoning:empty) .mes_reasoning_copy { | ||
| 433 | display: none; | ||
| 434 | } | ||
| 435 | |||
| 436 | .mes[data-reasoning-state="hidden"] .mes_edit_add_reasoning { | ||
| 437 | background-color: color-mix(in srgb, var(--SmartThemeQuoteColor) 33%, var(--SmartThemeBlurTintColor) 66%); | ||
| 438 | } | ||
| 439 | |||
| 440 | /** If hidden reasoning should not be shown, we hide all blocks that don't have content */ | ||
| 441 | #chat:not([data-show-hidden-reasoning="true"]) .mes:has(.mes_reasoning:empty) .mes_reasoning_details { | ||
| 442 | display: none; | ||
| 443 | } | ||
| 444 | |||
| 445 | .mes_reasoning_details .mes_reasoning_arrow { | ||
| 446 | position: absolute; | ||
| 447 | top: 50%; | ||
| 448 | right: 7px; | ||
| 449 | transform: translateY(-50%); | ||
| 450 | font-size: calc(var(--mainFontSize) * 0.7); | ||
| 451 | width: calc(var(--mainFontSize) * 0.7); | ||
| 452 | height: calc(var(--mainFontSize) * 0.7); | ||
| 453 | } | ||
| 454 | |||
| 455 | .mes_reasoning_details:not([open]) .mes_reasoning_arrow { | ||
| 456 | transform: translateY(-50%) rotate(180deg); | ||
| 457 | } | ||
| 458 | |||
| 459 | .mes_reasoning_summary>span { | ||
| 460 | margin-left: 0.5em; | ||
| 461 | } | ||
| 462 | |||
| 335 | .mes_text i, | 463 | .mes_text i, |
| 336 | .mes_text em { | 464 | .mes_text em, |
| 465 | .mes_reasoning i, | ||
| 466 | .mes_reasoning em { | ||
| 337 | color: var(--SmartThemeEmColor); | 467 | color: var(--SmartThemeEmColor); |
| 338 | } | 468 | } |
| 339 | 469 | ||
| 340 | .mes_text u { | 470 | .mes_text q i, |
| 471 | .mes_text q em { | ||
| 472 | color: inherit; | ||
| 473 | } | ||
| 474 | |||
| 475 | .mes_text u, | ||
| 476 | .mes_reasoning u { | ||
| 341 | color: var(--SmartThemeUnderlineColor); | 477 | color: var(--SmartThemeUnderlineColor); |
| 342 | } | 478 | } |
| 343 | 479 | ||
| 344 | .mes_text q { | 480 | .mes_text q, |
| 481 | .mes_reasoning q { | ||
| 345 | color: var(--SmartThemeQuoteColor); | 482 | color: var(--SmartThemeQuoteColor); |
| 346 | } | 483 | } |
| 347 | 484 | ||
| 348 | .mes_text font[color] em, | 485 | .mes_text font[color] em, |
| 349 | .mes_text font[color] i { | 486 | .mes_text font[color] i, |
| 350 | color: inherit; | 487 | .mes_text font[color] u, |
| 351 | } | 488 | .mes_text font[color] q, |
| 352 | 489 | .mes_reasoning font[color] em, | |
| 353 | .mes_text font[color] q { | 490 | .mes_reasoning font[color] i, |
| 491 | .mes_reasoning font[color] u, | ||
| 492 | .mes_reasoning font[color] q { | ||
| 354 | color: inherit; | 493 | color: inherit; |
| 355 | } | 494 | } |
| 356 | 495 | ||
| @@ -358,7 +497,8 @@ input[type='checkbox']:focus-visible { | |||
| 358 | display: block; | 497 | display: block; |
| 359 | } | 498 | } |
| 360 | 499 | ||
| 361 | .mes_text blockquote { | 500 | .mes_text blockquote, |
| 501 | .mes_reasoning blockquote { | ||
| 362 | border-left: 3px solid var(--SmartThemeQuoteColor); | 502 | border-left: 3px solid var(--SmartThemeQuoteColor); |
| 363 | padding-left: 10px; | 503 | padding-left: 10px; |
| 364 | background-color: var(--black30a); | 504 | background-color: var(--black30a); |
| @@ -368,18 +508,24 @@ input[type='checkbox']:focus-visible { | |||
| 368 | .mes_text strong em, | 508 | .mes_text strong em, |
| 369 | .mes_text strong, | 509 | .mes_text strong, |
| 370 | .mes_text h2, | 510 | .mes_text h2, |
| 371 | .mes_text h1 { | 511 | .mes_text h1, |
| 512 | .mes_reasoning strong em, | ||
| 513 | .mes_reasoning strong, | ||
| 514 | .mes_reasoning h2, | ||
| 515 | .mes_reasoning h1 { | ||
| 372 | font-weight: bold; | 516 | font-weight: bold; |
| 373 | } | 517 | } |
| 374 | 518 | ||
| 375 | .mes_text pre code { | 519 | .mes_text pre code, |
| 520 | .mes_reasoning pre code { | ||
| 376 | position: relative; | 521 | position: relative; |
| 377 | display: block; | 522 | display: block; |
| 378 | overflow-x: auto; | 523 | overflow-x: auto; |
| 379 | padding: 1em; | 524 | padding: 1em; |
| 380 | } | 525 | } |
| 381 | 526 | ||
| 382 | .mes_text img:not(.mes_img) { | 527 | .mes_text img:not(.mes_img), |
| 528 | .mes_reasoning img:not(.mes_img) { | ||
| 383 | max-width: 100%; | 529 | max-width: 100%; |
| 384 | max-height: var(--doc-height); | 530 | max-height: var(--doc-height); |
| 385 | } | 531 | } |
| @@ -1022,8 +1168,8 @@ body .panelControlBar { | |||
| 1022 | /*only affects bubblechat to make it sit nicely at the bottom*/ | 1168 | /*only affects bubblechat to make it sit nicely at the bottom*/ |
| 1023 | } | 1169 | } |
| 1024 | 1170 | ||
| 1025 | .last_mes .mes_text { | 1171 | .last_mes:has(.mes_text:empty):has(.mes_reasoning_details) .mes_reasoning:not(:empty) { |
| 1026 | padding-right: 30px; | 1172 | margin-bottom: var(--mes-right-spacing); |
| 1027 | } | 1173 | } |
| 1028 | 1174 | ||
| 1029 | /* SWIPE RELATED STYLES*/ | 1175 | /* SWIPE RELATED STYLES*/ |
| @@ -1235,14 +1381,19 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter { | |||
| 1235 | overflow-y: clip; | 1381 | overflow-y: clip; |
| 1236 | } | 1382 | } |
| 1237 | 1383 | ||
| 1238 | .mes_text { | 1384 | .mes_text, |
| 1385 | .mes_reasoning { | ||
| 1239 | font-weight: 500; | 1386 | font-weight: 500; |
| 1240 | line-height: calc(var(--mainFontSize) + .5rem); | 1387 | line-height: calc(var(--mainFontSize) + .5rem); |
| 1388 | max-width: 100%; | ||
| 1389 | overflow-wrap: anywhere; | ||
| 1390 | } | ||
| 1391 | |||
| 1392 | .mes_text { | ||
| 1241 | padding-left: 0; | 1393 | padding-left: 0; |
| 1242 | padding-top: 5px; | 1394 | padding-top: 5px; |
| 1243 | padding-bottom: 5px; | 1395 | padding-bottom: 5px; |
| 1244 | max-width: 100%; | 1396 | padding-right: var(--mes-right-spacing); |
| 1245 | overflow-wrap: anywhere; | ||
| 1246 | } | 1397 | } |
| 1247 | 1398 | ||
| 1248 | br { | 1399 | br { |
| @@ -2728,9 +2879,8 @@ select option:not(:checked) { | |||
| 2728 | color: var(--active) !important; | 2879 | color: var(--active) !important; |
| 2729 | } | 2880 | } |
| 2730 | 2881 | ||
| 2731 | #instruct_enabled_label .menu_button:not(.toggleEnabled), | 2882 | .menu_button.togglable:not(.toggleEnabled) { |
| 2732 | #sysprompt_enabled_label .menu_button:not(.toggleEnabled) { | 2883 | color: red; |
| 2733 | color: Red; | ||
| 2734 | } | 2884 | } |
| 2735 | 2885 | ||
| 2736 | .displayBlock { | 2886 | .displayBlock { |
| @@ -2913,6 +3063,7 @@ input[type=search]:focus::-webkit-search-cancel-button { | |||
| 2913 | .mes_block .ch_name { | 3063 | .mes_block .ch_name { |
| 2914 | max-width: 100%; | 3064 | max-width: 100%; |
| 2915 | min-height: 22px; | 3065 | min-height: 22px; |
| 3066 | align-items: flex-start; | ||
| 2916 | } | 3067 | } |
| 2917 | 3068 | ||
| 2918 | /*applies to both groups and solos chars in the char list*/ | 3069 | /*applies to both groups and solos chars in the char list*/ |
| @@ -2921,7 +3072,7 @@ input[type=search]:focus::-webkit-search-cancel-button { | |||
| 2921 | position: relative; | 3072 | position: relative; |
| 2922 | } | 3073 | } |
| 2923 | 3074 | ||
| 2924 | #rm_print_characters_block .ch_name, | 3075 | .character_name_block .ch_name, |
| 2925 | .avatar-container .ch_name { | 3076 | .avatar-container .ch_name { |
| 2926 | flex: 1 1 auto; | 3077 | flex: 1 1 auto; |
| 2927 | white-space: nowrap; | 3078 | white-space: nowrap; |
| @@ -2931,6 +3082,13 @@ input[type=search]:focus::-webkit-search-cancel-button { | |||
| 2931 | display: block; | 3082 | display: block; |
| 2932 | } | 3083 | } |
| 2933 | 3084 | ||
| 3085 | .character_name_block .character_version { | ||
| 3086 | text-overflow: ellipsis; | ||
| 3087 | overflow: hidden; | ||
| 3088 | text-wrap: nowrap; | ||
| 3089 | max-width: 50%; | ||
| 3090 | } | ||
| 3091 | |||
| 2934 | #rm_print_characters_block .character_name_block> :last-child { | 3092 | #rm_print_characters_block .character_name_block> :last-child { |
| 2935 | flex: 0 100000 auto; | 3093 | flex: 0 100000 auto; |
| 2936 | /* Force shrinking first */ | 3094 | /* Force shrinking first */ |
| @@ -4130,7 +4288,13 @@ input[type="range"]::-webkit-slider-thumb { | |||
| 4130 | transition: 0.3s ease-in-out; | 4288 | transition: 0.3s ease-in-out; |
| 4131 | } | 4289 | } |
| 4132 | 4290 | ||
| 4133 | .mes_edit_buttons .menu_button { | 4291 | .mes_reasoning_actions { |
| 4292 | margin: 0; | ||
| 4293 | margin-top: 0.5em; | ||
| 4294 | } | ||
| 4295 | |||
| 4296 | .mes_edit_buttons .menu_button, | ||
| 4297 | .mes_reasoning_actions .edit_button { | ||
| 4134 | opacity: 0.5; | 4298 | opacity: 0.5; |
| 4135 | padding: 0px; | 4299 | padding: 0px; |
| 4136 | font-size: 1rem; | 4300 | font-size: 1rem; |
| @@ -4143,10 +4307,18 @@ input[type="range"]::-webkit-slider-thumb { | |||
| 4143 | align-items: center; | 4307 | align-items: center; |
| 4144 | } | 4308 | } |
| 4145 | 4309 | ||
| 4310 | .mes_reasoning_actions .edit_button { | ||
| 4311 | margin-bottom: 0.5em; | ||
| 4312 | opacity: 1; | ||
| 4313 | filter: brightness(0.7); | ||
| 4314 | } | ||
| 4315 | |||
| 4316 | .mes_reasoning_edit_cancel, | ||
| 4146 | .mes_edit_cancel.menu_button { | 4317 | .mes_edit_cancel.menu_button { |
| 4147 | background-color: var(--crimson70a); | 4318 | background-color: var(--crimson70a); |
| 4148 | } | 4319 | } |
| 4149 | 4320 | ||
| 4321 | .mes_reasoning_edit_done, | ||
| 4150 | .mes_edit_done.menu_button { | 4322 | .mes_edit_done.menu_button { |
| 4151 | background-color: var(--okGreen70a); | 4323 | background-color: var(--okGreen70a); |
| 4152 | } | 4324 | } |
| @@ -4155,6 +4327,7 @@ input[type="range"]::-webkit-slider-thumb { | |||
| 4155 | opacity: 1; | 4327 | opacity: 1; |
| 4156 | } | 4328 | } |
| 4157 | 4329 | ||
| 4330 | .reasoning_edit_textarea, | ||
| 4158 | .edit_textarea { | 4331 | .edit_textarea { |
| 4159 | padding: 5px; | 4332 | padding: 5px; |
| 4160 | margin: 0; | 4333 | margin: 0; |
| @@ -4166,6 +4339,14 @@ input[type="range"]::-webkit-slider-thumb { | |||
| 4166 | field-sizing: content; | 4339 | field-sizing: content; |
| 4167 | } | 4340 | } |
| 4168 | 4341 | ||
| 4342 | body[data-generating="true"] #send_but, | ||
| 4343 | body[data-generating="true"] #mes_continue, | ||
| 4344 | body[data-generating="true"] #mes_impersonate, | ||
| 4345 | body[data-generating="true"] #chat .last_mes .mes_buttons, | ||
| 4346 | body[data-generating="true"] #chat .last_mes .mes_reasoning_actions { | ||
| 4347 | display: none; | ||
| 4348 | } | ||
| 4349 | |||
| 4169 | #anchor_order { | 4350 | #anchor_order { |
| 4170 | margin-bottom: 15px; | 4351 | margin-bottom: 15px; |
| 4171 | } | 4352 | } |
| @@ -4505,23 +4686,6 @@ body .ui-widget-content li:hover { | |||
| 4505 | opacity: 1; | 4686 | opacity: 1; |
| 4506 | } | 4687 | } |
| 4507 | 4688 | ||
| 4508 | .typing_indicator { | ||
| 4509 | position: sticky; | ||
| 4510 | bottom: 10px; | ||
| 4511 | margin: 10px; | ||
| 4512 | opacity: 0.85; | ||
| 4513 | text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor); | ||
| 4514 | order: 9999; | ||
| 4515 | } | ||
| 4516 | |||
| 4517 | .typing_indicator:after { | ||
| 4518 | display: inline-block; | ||
| 4519 | vertical-align: bottom; | ||
| 4520 | animation: ellipsis steps(4, end) 1500ms infinite; | ||
| 4521 | content: ""; | ||
| 4522 | width: 0px; | ||
| 4523 | } | ||
| 4524 | |||
| 4525 | #group_avatar_preview .missing-avatar { | 4689 | #group_avatar_preview .missing-avatar { |
| 4526 | display: inline; | 4690 | display: inline; |
| 4527 | vertical-align: middle; | 4691 | vertical-align: middle; |
| @@ -5610,11 +5774,13 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5610 | overflow-wrap: anywhere; | 5774 | overflow-wrap: anywhere; |
| 5611 | } | 5775 | } |
| 5612 | 5776 | ||
| 5777 | #SystemPromptColumn summary, | ||
| 5613 | #InstructSequencesColumn summary { | 5778 | #InstructSequencesColumn summary { |
| 5614 | font-size: 0.95em; | 5779 | font-size: 0.95em; |
| 5615 | cursor: pointer; | 5780 | cursor: pointer; |
| 5616 | } | 5781 | } |
| 5617 | 5782 | ||
| 5783 | #SystemPromptColumn details, | ||
| 5618 | #InstructSequencesColumn details:not(:last-of-type) { | 5784 | #InstructSequencesColumn details:not(:last-of-type) { |
| 5619 | margin-bottom: 5px; | 5785 | margin-bottom: 5px; |
| 5620 | } | 5786 | } |
| @@ -5643,6 +5809,7 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5643 | 5809 | ||
| 5644 | .model-card .details-container { | 5810 | .model-card .details-container { |
| 5645 | text-align: right; | 5811 | text-align: right; |
| 5812 | line-height: 0.9; | ||
| 5646 | } | 5813 | } |
| 5647 | 5814 | ||
| 5648 | .model-card:hover { | 5815 | .model-card:hover { |
| @@ -5665,7 +5832,7 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5665 | } | 5832 | } |
| 5666 | 5833 | ||
| 5667 | .model-title { | 5834 | .model-title { |
| 5668 | font-size: 13px; | 5835 | font-size: calc(var(--mainFontSize) * 0.95); |
| 5669 | font-weight: bold; | 5836 | font-weight: bold; |
| 5670 | overflow: hidden; | 5837 | overflow: hidden; |
| 5671 | } | 5838 | } |
| @@ -5681,7 +5848,7 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5681 | .model-class, | 5848 | .model-class, |
| 5682 | .model-context-length, | 5849 | .model-context-length, |
| 5683 | .model-date-added { | 5850 | .model-date-added { |
| 5684 | font-size: 10px; | 5851 | font-size: calc(var(--mainFontSize) * 0.75); |
| 5685 | } | 5852 | } |
| 5686 | 5853 | ||
| 5687 | .model-class, | 5854 | .model-class, |
| @@ -5763,3 +5930,29 @@ body:not(.movingUI) .drawer-content.maximized { | |||
| 5763 | .alternate_greetings_list { | 5930 | .alternate_greetings_list { |
| 5764 | overflow-y: scroll; | 5931 | overflow-y: scroll; |
| 5765 | } | 5932 | } |
| 5933 | |||
| 5934 | .mes_text div[data-type="assistant_note"]:has(.assistant_note_export) { | ||
| 5935 | display: flex; | ||
| 5936 | flex-direction: row; | ||
| 5937 | flex-wrap: nowrap; | ||
| 5938 | justify-content: space-between; | ||
| 5939 | align-items: center; | ||
| 5940 | gap: 10px; | ||
| 5941 | padding: 0 2px; | ||
| 5942 | } | ||
| 5943 | |||
| 5944 | .mes_text div[data-type="assistant_note"]:has(.assistant_note_export)>div:not(.assistant_note_export) { | ||
| 5945 | flex: 1; | ||
| 5946 | } | ||
| 5947 | |||
| 5948 | .oneline-dropdown label { | ||
| 5949 | margin-top: 3px; | ||
| 5950 | margin-bottom: 5px; | ||
| 5951 | flex-grow: 1; | ||
| 5952 | text-align: left; | ||
| 5953 | } | ||
| 5954 | |||
| 5955 | .oneline-dropdown select { | ||
| 5956 | min-width: fit-content; | ||
| 5957 | width: 40%; | ||
| 5958 | } | ||
| @@ -4,6 +4,7 @@ | |||
| 4 | import fs from 'node:fs'; | 4 | import fs from 'node:fs'; |
| 5 | import http from 'node:http'; | 5 | import http from 'node:http'; |
| 6 | import https from 'node:https'; | 6 | import https from 'node:https'; |
| 7 | import os from 'os'; | ||
| 7 | import path from 'node:path'; | 8 | import path from 'node:path'; |
| 8 | import util from 'node:util'; | 9 | import util from 'node:util'; |
| 9 | import net from 'node:net'; | 10 | import net from 'node:net'; |
| @@ -18,10 +19,9 @@ import { hideBin } from 'yargs/helpers'; | |||
| 18 | 19 | ||
| 19 | // express/server related library imports | 20 | // express/server related library imports |
| 20 | import cors from 'cors'; | 21 | import cors from 'cors'; |
| 21 | import { doubleCsrf } from 'csrf-csrf'; | 22 | import { csrfSync } from 'csrf-sync'; |
| 22 | import express from 'express'; | 23 | import express from 'express'; |
| 23 | import compression from 'compression'; | 24 | import compression from 'compression'; |
| 24 | import cookieParser from 'cookie-parser'; | ||
| 25 | import cookieSession from 'cookie-session'; | 25 | import cookieSession from 'cookie-session'; |
| 26 | import multer from 'multer'; | 26 | import multer from 'multer'; |
| 27 | import responseTime from 'response-time'; | 27 | import responseTime from 'response-time'; |
| @@ -30,6 +30,7 @@ import bodyParser from 'body-parser'; | |||
| 30 | 30 | ||
| 31 | // net related library imports | 31 | // net related library imports |
| 32 | import fetch from 'node-fetch'; | 32 | import fetch from 'node-fetch'; |
| 33 | import ipRegex from 'ip-regex'; | ||
| 33 | 34 | ||
| 34 | // Unrestrict console logs display limit | 35 | // Unrestrict console logs display limit |
| 35 | util.inspect.defaultOptions.maxArrayLength = null; | 36 | util.inspect.defaultOptions.maxArrayLength = null; |
| @@ -40,7 +41,6 @@ util.inspect.defaultOptions.depth = 4; | |||
| 40 | import { loadPlugins } from './src/plugin-loader.js'; | 41 | import { loadPlugins } from './src/plugin-loader.js'; |
| 41 | import { | 42 | import { |
| 42 | initUserStorage, | 43 | initUserStorage, |
| 43 | getCsrfSecret, | ||
| 44 | getCookieSecret, | 44 | getCookieSecret, |
| 45 | getCookieSessionName, | 45 | getCookieSessionName, |
| 46 | getAllEnabledUsers, | 46 | getAllEnabledUsers, |
| @@ -60,6 +60,7 @@ import basicAuthMiddleware from './src/middleware/basicAuth.js'; | |||
| 60 | import whitelistMiddleware from './src/middleware/whitelist.js'; | 60 | import whitelistMiddleware from './src/middleware/whitelist.js'; |
| 61 | import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js'; | 61 | import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js'; |
| 62 | import initRequestProxy from './src/request-proxy.js'; | 62 | import initRequestProxy from './src/request-proxy.js'; |
| 63 | import getCacheBusterMiddleware from './src/middleware/cacheBuster.js'; | ||
| 63 | import { | 64 | import { |
| 64 | getVersion, | 65 | getVersion, |
| 65 | getConfigValue, | 66 | getConfigValue, |
| @@ -67,6 +68,11 @@ import { | |||
| 67 | forwardFetchResponse, | 68 | forwardFetchResponse, |
| 68 | removeColorFormatting, | 69 | removeColorFormatting, |
| 69 | getSeparator, | 70 | getSeparator, |
| 71 | stringToBool, | ||
| 72 | urlHostnameToIPv6, | ||
| 73 | canResolve, | ||
| 74 | safeReadFileSync, | ||
| 75 | setupLogLevel, | ||
| 70 | } from './src/util.js'; | 76 | } from './src/util.js'; |
| 71 | import { UPLOADS_DIRECTORY } from './src/constants.js'; | 77 | import { UPLOADS_DIRECTORY } from './src/constants.js'; |
| 72 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; | 78 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; |
| @@ -126,6 +132,8 @@ if (process.versions && process.versions.node && process.versions.node.match(/20 | |||
| 126 | const DEFAULT_PORT = 8000; | 132 | const DEFAULT_PORT = 8000; |
| 127 | const DEFAULT_AUTORUN = false; | 133 | const DEFAULT_AUTORUN = false; |
| 128 | const DEFAULT_LISTEN = false; | 134 | const DEFAULT_LISTEN = false; |
| 135 | const DEFAULT_LISTEN_ADDRESS_IPV6 = '[::]'; | ||
| 136 | const DEFAULT_LISTEN_ADDRESS_IPV4 = '0.0.0.0'; | ||
| 129 | const DEFAULT_CORS_PROXY = false; | 137 | const DEFAULT_CORS_PROXY = false; |
| 130 | const DEFAULT_WHITELIST = true; | 138 | const DEFAULT_WHITELIST = true; |
| 131 | const DEFAULT_ACCOUNTS = false; | 139 | const DEFAULT_ACCOUNTS = false; |
| @@ -150,11 +158,11 @@ const DEFAULT_PROXY_BYPASS = []; | |||
| 150 | const cliArguments = yargs(hideBin(process.argv)) | 158 | const cliArguments = yargs(hideBin(process.argv)) |
| 151 | .usage('Usage: <your-start-script> <command> [options]') | 159 | .usage('Usage: <your-start-script> <command> [options]') |
| 152 | .option('enableIPv6', { | 160 | .option('enableIPv6', { |
| 153 | type: 'boolean', | 161 | type: 'string', |
| 154 | default: null, | 162 | default: null, |
| 155 | describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`, | 163 | describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`, |
| 156 | }).option('enableIPv4', { | 164 | }).option('enableIPv4', { |
| 157 | type: 'boolean', | 165 | type: 'string', |
| 158 | default: null, | 166 | default: null, |
| 159 | describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`, | 167 | describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`, |
| 160 | }).option('port', { | 168 | }).option('port', { |
| @@ -181,6 +189,14 @@ const cliArguments = yargs(hideBin(process.argv)) | |||
| 181 | type: 'boolean', | 189 | type: 'boolean', |
| 182 | default: null, | 190 | default: null, |
| 183 | describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`, | 191 | describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`, |
| 192 | }).option('listenAddressIPv6', { | ||
| 193 | type: 'string', | ||
| 194 | default: null, | ||
| 195 | describe: 'Set SillyTavern to listen to a specific IPv6 address. If not set, it will fallback to listen to all.\n[config default: [::] ]', | ||
| 196 | }).option('listenAddressIPv4', { | ||
| 197 | type: 'string', | ||
| 198 | default: null, | ||
| 199 | describe: 'Set SillyTavern to listen to a specific IPv4 address. If not set, it will fallback to listen to all.\n[config default: 0.0.0.0 ]', | ||
| 184 | }).option('corsProxy', { | 200 | }).option('corsProxy', { |
| 185 | type: 'boolean', | 201 | type: 'boolean', |
| 186 | default: null, | 202 | default: null, |
| @@ -243,27 +259,46 @@ app.use(helmet({ | |||
| 243 | app.use(compression()); | 259 | app.use(compression()); |
| 244 | app.use(responseTime()); | 260 | app.use(responseTime()); |
| 245 | 261 | ||
| 262 | |||
| 263 | /** @type {number} */ | ||
| 246 | const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT); | 264 | const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT); |
| 265 | /** @type {boolean} */ | ||
| 247 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; | 266 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; |
| 267 | /** @type {boolean} */ | ||
| 248 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); | 268 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); |
| 269 | /** @type {string} */ | ||
| 270 | const listenAddressIPv6 = cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', DEFAULT_LISTEN_ADDRESS_IPV6); | ||
| 271 | /** @type {string} */ | ||
| 272 | const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', DEFAULT_LISTEN_ADDRESS_IPV4); | ||
| 273 | /** @type {boolean} */ | ||
| 249 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); | 274 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); |
| 250 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); | 275 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); |
| 276 | /** @type {string} */ | ||
| 251 | const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data'); | 277 | const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data'); |
| 278 | /** @type {boolean} */ | ||
| 252 | const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED); | 279 | const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED); |
| 253 | const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH); | 280 | const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH); |
| 254 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); | 281 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); |
| 282 | /** @type {boolean} */ | ||
| 255 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); | 283 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); |
| 256 | 284 | ||
| 257 | const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY); | 285 | const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY); |
| 258 | 286 | ||
| 259 | const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); | ||
| 260 | const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); | ||
| 261 | 287 | ||
| 288 | /** @type {boolean | "auto"} */ | ||
| 289 | let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); | ||
| 290 | /** @type {boolean | "auto"} */ | ||
| 291 | let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); | ||
| 292 | |||
| 293 | /** @type {string} */ | ||
| 262 | const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME); | 294 | const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME); |
| 295 | /** @type {number} */ | ||
| 263 | const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT); | 296 | const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT); |
| 264 | 297 | ||
| 298 | /** @type {boolean} */ | ||
| 265 | const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6); | 299 | const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6); |
| 266 | 300 | ||
| 301 | /** @type {boolean} */ | ||
| 267 | const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST); | 302 | const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST); |
| 268 | 303 | ||
| 269 | const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED); | 304 | const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED); |
| @@ -280,7 +315,19 @@ if (dnsPreferIPv6) { | |||
| 280 | console.log('Preferring IPv4 for DNS resolution'); | 315 | console.log('Preferring IPv4 for DNS resolution'); |
| 281 | } | 316 | } |
| 282 | 317 | ||
| 283 | if (!enableIPv6 && !enableIPv4) { | 318 | |
| 319 | const ipOptions = [true, 'auto', false]; | ||
| 320 | |||
| 321 | if (!ipOptions.includes(enableIPv6)) { | ||
| 322 | console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV6); | ||
| 323 | enableIPv6 = DEFAULT_ENABLE_IPV6; | ||
| 324 | } | ||
| 325 | if (!ipOptions.includes(enableIPv4)) { | ||
| 326 | console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV4); | ||
| 327 | enableIPv4 = DEFAULT_ENABLE_IPV4; | ||
| 328 | } | ||
| 329 | |||
| 330 | if (enableIPv6 === false && enableIPv4 === false) { | ||
| 284 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); | 331 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); |
| 285 | process.exit(1); | 332 | process.exit(1); |
| 286 | } | 333 | } |
| @@ -347,8 +394,8 @@ if (enableCorsProxy) { | |||
| 347 | } | 394 | } |
| 348 | 395 | ||
| 349 | function getSessionCookieAge() { | 396 | function getSessionCookieAge() { |
| 350 | // Defaults to 24 hours in seconds if not set | 397 | // Defaults to "no expiration" if not set |
| 351 | const configValue = getConfigValue('sessionTimeout', 24 * 60 * 60); | 398 | const configValue = getConfigValue('sessionTimeout', -1); |
| 352 | 399 | ||
| 353 | // Convert to milliseconds | 400 | // Convert to milliseconds |
| 354 | if (configValue > 0) { | 401 | if (configValue > 0) { |
| @@ -365,6 +412,55 @@ function getSessionCookieAge() { | |||
| 365 | return undefined; | 412 | return undefined; |
| 366 | } | 413 | } |
| 367 | 414 | ||
| 415 | /** | ||
| 416 | * Checks the network interfaces to determine the presence of IPv6 and IPv4 addresses. | ||
| 417 | * | ||
| 418 | * @returns {Promise<[boolean, boolean, boolean, boolean]>} A promise that resolves to an array containing: | ||
| 419 | * - [0]: `hasIPv6` (boolean) - Whether the computer has any IPv6 address, including (`::1`). | ||
| 420 | * - [1]: `hasIPv4` (boolean) - Whether the computer has any IPv4 address, including (`127.0.0.1`). | ||
| 421 | * - [2]: `hasIPv6Local` (boolean) - Whether the computer has local IPv6 address (`::1`). | ||
| 422 | * - [3]: `hasIPv4Local` (boolean) - Whether the computer has local IPv4 address (`127.0.0.1`). | ||
| 423 | */ | ||
| 424 | async function getHasIP() { | ||
| 425 | let hasIPv6 = false; | ||
| 426 | let hasIPv6Local = false; | ||
| 427 | |||
| 428 | let hasIPv4 = false; | ||
| 429 | let hasIPv4Local = false; | ||
| 430 | |||
| 431 | const interfaces = os.networkInterfaces(); | ||
| 432 | |||
| 433 | for (const iface of Object.values(interfaces)) { | ||
| 434 | if (iface === undefined) { | ||
| 435 | continue; | ||
| 436 | } | ||
| 437 | |||
| 438 | for (const info of iface) { | ||
| 439 | if (info.family === 'IPv6') { | ||
| 440 | hasIPv6 = true; | ||
| 441 | if (info.address === '::1') { | ||
| 442 | hasIPv6Local = true; | ||
| 443 | } | ||
| 444 | } | ||
| 445 | |||
| 446 | if (info.family === 'IPv4') { | ||
| 447 | hasIPv4 = true; | ||
| 448 | if (info.address === '127.0.0.1') { | ||
| 449 | hasIPv4Local = true; | ||
| 450 | } | ||
| 451 | } | ||
| 452 | if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break; | ||
| 453 | } | ||
| 454 | if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break; | ||
| 455 | } | ||
| 456 | return [ | ||
| 457 | hasIPv6, | ||
| 458 | hasIPv4, | ||
| 459 | hasIPv6Local, | ||
| 460 | hasIPv4Local, | ||
| 461 | ]; | ||
| 462 | } | ||
| 463 | |||
| 368 | app.use(cookieSession({ | 464 | app.use(cookieSession({ |
| 369 | name: getCookieSessionName(), | 465 | name: getCookieSessionName(), |
| 370 | sameSite: 'strict', | 466 | sameSite: 'strict', |
| @@ -377,27 +473,38 @@ app.use(setUserDataMiddleware); | |||
| 377 | 473 | ||
| 378 | // CSRF Protection // | 474 | // CSRF Protection // |
| 379 | if (!disableCsrf) { | 475 | if (!disableCsrf) { |
| 380 | const COOKIES_SECRET = getCookieSecret(); | 476 | const csrfSyncProtection = csrfSync({ |
| 381 | 477 | getTokenFromState: (req) => { | |
| 382 | const { generateToken, doubleCsrfProtection } = doubleCsrf({ | 478 | if (!req.session) { |
| 383 | getSecret: getCsrfSecret, | 479 | console.error('(CSRF error) getTokenFromState: Session object not initialized'); |
| 384 | cookieName: 'X-CSRF-Token', | 480 | return; |
| 385 | cookieOptions: { | 481 | } |
| 386 | sameSite: 'strict', | 482 | return req.session.csrfToken; |
| 387 | secure: false, | 483 | }, |
| 484 | getTokenFromRequest: (req) => { | ||
| 485 | return req.headers['x-csrf-token']?.toString(); | ||
| 486 | }, | ||
| 487 | storeTokenInState: (req, token) => { | ||
| 488 | if (!req.session) { | ||
| 489 | console.error('(CSRF error) storeTokenInState: Session object not initialized'); | ||
| 490 | return; | ||
| 491 | } | ||
| 492 | req.session.csrfToken = token; | ||
| 388 | }, | 493 | }, |
| 389 | size: 64, | 494 | size: 32, |
| 390 | getTokenFromRequest: (req) => req.headers['x-csrf-token'], | ||
| 391 | }); | 495 | }); |
| 392 | 496 | ||
| 393 | app.get('/csrf-token', (req, res) => { | 497 | app.get('/csrf-token', (req, res) => { |
| 394 | res.json({ | 498 | res.json({ |
| 395 | 'token': generateToken(res, req), | 499 | 'token': csrfSyncProtection.generateToken(req), |
| 396 | }); | 500 | }); |
| 397 | }); | 501 | }); |
| 398 | 502 | ||
| 399 | app.use(cookieParser(COOKIES_SECRET)); | 503 | // Customize the error message |
| 400 | app.use(doubleCsrfProtection); | 504 | csrfSyncProtection.invalidCsrfTokenError.message = color.red('Invalid CSRF token. Please refresh the page and try again.'); |
| 505 | csrfSyncProtection.invalidCsrfTokenError.stack = undefined; | ||
| 506 | |||
| 507 | app.use(csrfSyncProtection.csrfSynchronisedProtection); | ||
| 401 | } else { | 508 | } else { |
| 402 | console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); | 509 | console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); |
| 403 | app.get('/csrf-token', (req, res) => { | 510 | app.get('/csrf-token', (req, res) => { |
| @@ -409,7 +516,7 @@ if (!disableCsrf) { | |||
| 409 | 516 | ||
| 410 | // Static files | 517 | // Static files |
| 411 | // Host index page | 518 | // Host index page |
| 412 | app.get('/', (request, response) => { | 519 | app.get('/', getCacheBusterMiddleware(), (request, response) => { |
| 413 | if (shouldRedirectToLogin(request)) { | 520 | if (shouldRedirectToLogin(request)) { |
| 414 | const query = request.url.split('?')[1]; | 521 | const query = request.url.split('?')[1]; |
| 415 | const redirectUrl = query ? `/login?${query}` : '/login'; | 522 | const redirectUrl = query ? `/login?${query}` : '/login'; |
| @@ -617,13 +724,13 @@ app.use('/api/azure', azureRouter); | |||
| 617 | 724 | ||
| 618 | const tavernUrlV6 = new URL( | 725 | const tavernUrlV6 = new URL( |
| 619 | (cliArguments.ssl ? 'https://' : 'http://') + | 726 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 620 | (listen ? '[::]' : '[::1]') + | 727 | (listen ? (ipRegex.v6({ exact: true }).test(listenAddressIPv6) ? listenAddressIPv6 : '[::]') : '[::1]') + |
| 621 | (':' + server_port), | 728 | (':' + server_port), |
| 622 | ); | 729 | ); |
| 623 | 730 | ||
| 624 | const tavernUrl = new URL( | 731 | const tavernUrl = new URL( |
| 625 | (cliArguments.ssl ? 'https://' : 'http://') + | 732 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 626 | (listen ? '0.0.0.0' : '127.0.0.1') + | 733 | (listen ? (ipRegex.v4({ exact: true }).test(listenAddressIPv4) ? listenAddressIPv4 : '0.0.0.0') : '127.0.0.1') + |
| 627 | (':' + server_port), | 734 | (':' + server_port), |
| 628 | ); | 735 | ); |
| 629 | 736 | ||
| @@ -683,20 +790,23 @@ const preSetupTasks = async function () { | |||
| 683 | 790 | ||
| 684 | /** | 791 | /** |
| 685 | * Gets the hostname to use for autorun in the browser. | 792 | * Gets the hostname to use for autorun in the browser. |
| 686 | * @returns {string} The hostname to use for autorun | 793 | * @param {boolean} useIPv6 If use IPv6 |
| 794 | * @param {boolean} useIPv4 If use IPv4 | ||
| 795 | * @returns Promise<string> The hostname to use for autorun | ||
| 687 | */ | 796 | */ |
| 688 | function getAutorunHostname() { | 797 | async function getAutorunHostname(useIPv6, useIPv4) { |
| 689 | if (autorunHostname === 'auto') { | 798 | if (autorunHostname === 'auto') { |
| 690 | if (enableIPv6 && enableIPv4) { | 799 | let localhostResolve = await canResolve('localhost', useIPv6, useIPv4); |
| 691 | if (avoidLocalhost) return '[::1]'; | 800 | |
| 692 | return 'localhost'; | 801 | if (useIPv6 && useIPv4) { |
| 802 | return (avoidLocalhost || !localhostResolve) ? '[::1]' : 'localhost'; | ||
| 693 | } | 803 | } |
| 694 | 804 | ||
| 695 | if (enableIPv6) { | 805 | if (useIPv6) { |
| 696 | return '[::1]'; | 806 | return '[::1]'; |
| 697 | } | 807 | } |
| 698 | 808 | ||
| 699 | if (enableIPv4) { | 809 | if (useIPv4) { |
| 700 | return '127.0.0.1'; | 810 | return '127.0.0.1'; |
| 701 | } | 811 | } |
| 702 | } | 812 | } |
| @@ -708,11 +818,13 @@ function getAutorunHostname() { | |||
| 708 | * Tasks that need to be run after the server starts listening. | 818 | * Tasks that need to be run after the server starts listening. |
| 709 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 819 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 710 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 820 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 821 | * @param {boolean} useIPv6 If the server is using IPv6 | ||
| 822 | * @param {boolean} useIPv4 If the server is using IPv4 | ||
| 711 | */ | 823 | */ |
| 712 | const postSetupTasks = async function (v6Failed, v4Failed) { | 824 | const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { |
| 713 | const autorunUrl = new URL( | 825 | const autorunUrl = new URL( |
| 714 | (cliArguments.ssl ? 'https://' : 'http://') + | 826 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 715 | (getAutorunHostname()) + | 827 | (await getAutorunHostname(useIPv6, useIPv4)) + |
| 716 | (':') + | 828 | (':') + |
| 717 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), | 829 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), |
| 718 | ); | 830 | ); |
| @@ -725,36 +837,48 @@ const postSetupTasks = async function (v6Failed, v4Failed) { | |||
| 725 | 837 | ||
| 726 | let logListen = 'SillyTavern is listening on'; | 838 | let logListen = 'SillyTavern is listening on'; |
| 727 | 839 | ||
| 728 | if (enableIPv6 && !v6Failed) { | 840 | if (useIPv6 && !v6Failed) { |
| 729 | logListen += color.green(' IPv6: ' + tavernUrlV6.host); | 841 | logListen += color.green( |
| 842 | ' IPv6: ' + tavernUrlV6.host, | ||
| 843 | ); | ||
| 730 | } | 844 | } |
| 731 | 845 | ||
| 732 | if (enableIPv4 && !v4Failed) { | 846 | if (useIPv4 && !v4Failed) { |
| 733 | logListen += color.green(' IPv4: ' + tavernUrl.host); | 847 | logListen += color.green( |
| 848 | ' IPv4: ' + tavernUrl.host, | ||
| 849 | ); | ||
| 734 | } | 850 | } |
| 735 | 851 | ||
| 736 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; | 852 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; |
| 737 | const plainGoToLog = removeColorFormatting(goToLog); | 853 | const plainGoToLog = removeColorFormatting(goToLog); |
| 738 | 854 | ||
| 739 | console.log(logListen); | 855 | console.log(logListen); |
| 856 | if (listen) { | ||
| 857 | console.log(); | ||
| 858 | console.log('To limit connections to internal localhost only ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false".'); | ||
| 859 | console.log('Check the "access.log" file in the SillyTavern directory to inspect incoming connections.'); | ||
| 860 | } | ||
| 740 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | 861 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); |
| 741 | console.log(goToLog); | 862 | console.log(goToLog); |
| 742 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | 863 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); |
| 743 | 864 | ||
| 744 | if (listen) { | ||
| 745 | console.log('[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n'); | ||
| 746 | } | ||
| 747 | 865 | ||
| 748 | if (basicAuthMode) { | 866 | if (basicAuthMode) { |
| 749 | if (perUserBasicAuth && !enableAccounts) { | 867 | if (perUserBasicAuth && !enableAccounts) { |
| 750 | console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.')); | 868 | console.error(color.red( |
| 869 | 'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.', | ||
| 870 | )); | ||
| 751 | } else if (!perUserBasicAuth) { | 871 | } else if (!perUserBasicAuth) { |
| 752 | const basicAuthUser = getConfigValue('basicAuthUser', {}); | 872 | const basicAuthUser = getConfigValue('basicAuthUser', {}); |
| 753 | if (!basicAuthUser?.username || !basicAuthUser?.password) { | 873 | if (!basicAuthUser?.username || !basicAuthUser?.password) { |
| 754 | console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!')); | 874 | console.warn(color.yellow( |
| 875 | 'Basic Authentication is enabled, but username or password is not set or empty!', | ||
| 876 | )); | ||
| 755 | } | 877 | } |
| 756 | } | 878 | } |
| 757 | } | 879 | } |
| 880 | |||
| 881 | setupLogLevel(); | ||
| 758 | }; | 882 | }; |
| 759 | 883 | ||
| 760 | /** | 884 | /** |
| @@ -804,14 +928,16 @@ function logSecurityAlert(message) { | |||
| 804 | * Handles the case where the server failed to start on one or both protocols. | 928 | * Handles the case where the server failed to start on one or both protocols. |
| 805 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 929 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 806 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 930 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 931 | * @param {boolean} useIPv6 If use IPv6 | ||
| 932 | * @param {boolean} useIPv4 If use IPv4 | ||
| 807 | */ | 933 | */ |
| 808 | function handleServerListenFail(v6Failed, v4Failed) { | 934 | function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) { |
| 809 | if (v6Failed && !enableIPv4) { | 935 | if (v6Failed && !useIPv4) { |
| 810 | console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled')); | 936 | console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled')); |
| 811 | process.exit(1); | 937 | process.exit(1); |
| 812 | } | 938 | } |
| 813 | 939 | ||
| 814 | if (v4Failed && !enableIPv6) { | 940 | if (v4Failed && !useIPv6) { |
| 815 | console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled')); | 941 | console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled')); |
| 816 | process.exit(1); | 942 | process.exit(1); |
| 817 | } | 943 | } |
| @@ -825,10 +951,11 @@ function handleServerListenFail(v6Failed, v4Failed) { | |||
| 825 | /** | 951 | /** |
| 826 | * Creates an HTTPS server. | 952 | * Creates an HTTPS server. |
| 827 | * @param {URL} url The URL to listen on | 953 | * @param {URL} url The URL to listen on |
| 954 | * @param {number} ipVersion the ip version to use | ||
| 828 | * @returns {Promise<void>} A promise that resolves when the server is listening | 955 | * @returns {Promise<void>} A promise that resolves when the server is listening |
| 829 | * @throws {Error} If the server fails to start | 956 | * @throws {Error} If the server fails to start |
| 830 | */ | 957 | */ |
| 831 | function createHttpsServer(url) { | 958 | function createHttpsServer(url, ipVersion) { |
| 832 | return new Promise((resolve, reject) => { | 959 | return new Promise((resolve, reject) => { |
| 833 | const server = https.createServer( | 960 | const server = https.createServer( |
| 834 | { | 961 | { |
| @@ -837,34 +964,56 @@ function createHttpsServer(url) { | |||
| 837 | }, app); | 964 | }, app); |
| 838 | server.on('error', reject); | 965 | server.on('error', reject); |
| 839 | server.on('listening', resolve); | 966 | server.on('listening', resolve); |
| 840 | server.listen(Number(url.port || 443), url.hostname); | 967 | |
| 968 | let host = url.hostname; | ||
| 969 | if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname); | ||
| 970 | server.listen({ | ||
| 971 | host: host, | ||
| 972 | port: Number(url.port || 443), | ||
| 973 | // see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used | ||
| 974 | ipv6Only: true, | ||
| 975 | }); | ||
| 841 | }); | 976 | }); |
| 842 | } | 977 | } |
| 843 | 978 | ||
| 844 | /** | 979 | /** |
| 845 | * Creates an HTTP server. | 980 | * Creates an HTTP server. |
| 846 | * @param {URL} url The URL to listen on | 981 | * @param {URL} url The URL to listen on |
| 982 | * @param {number} ipVersion the ip version to use | ||
| 847 | * @returns {Promise<void>} A promise that resolves when the server is listening | 983 | * @returns {Promise<void>} A promise that resolves when the server is listening |
| 848 | * @throws {Error} If the server fails to start | 984 | * @throws {Error} If the server fails to start |
| 849 | */ | 985 | */ |
| 850 | function createHttpServer(url) { | 986 | function createHttpServer(url, ipVersion) { |
| 851 | return new Promise((resolve, reject) => { | 987 | return new Promise((resolve, reject) => { |
| 852 | const server = http.createServer(app); | 988 | const server = http.createServer(app); |
| 853 | server.on('error', reject); | 989 | server.on('error', reject); |
| 854 | server.on('listening', resolve); | 990 | server.on('listening', resolve); |
| 855 | server.listen(Number(url.port || 80), url.hostname); | 991 | |
| 992 | let host = url.hostname; | ||
| 993 | if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname); | ||
| 994 | server.listen({ | ||
| 995 | host: host, | ||
| 996 | port: Number(url.port || 80), | ||
| 997 | // see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used | ||
| 998 | ipv6Only: true, | ||
| 999 | }); | ||
| 856 | }); | 1000 | }); |
| 857 | } | 1001 | } |
| 858 | 1002 | ||
| 859 | async function startHTTPorHTTPS() { | 1003 | /** |
| 1004 | * Starts the server using http or https depending on config | ||
| 1005 | * @param {boolean} useIPv6 If use IPv6 | ||
| 1006 | * @param {boolean} useIPv4 If use IPv4 | ||
| 1007 | */ | ||
| 1008 | async function startHTTPorHTTPS(useIPv6, useIPv4) { | ||
| 860 | let v6Failed = false; | 1009 | let v6Failed = false; |
| 861 | let v4Failed = false; | 1010 | let v4Failed = false; |
| 862 | 1011 | ||
| 863 | const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer; | 1012 | const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer; |
| 864 | 1013 | ||
| 865 | if (enableIPv6) { | 1014 | if (useIPv6) { |
| 866 | try { | 1015 | try { |
| 867 | await createFunc(tavernUrlV6); | 1016 | await createFunc(tavernUrlV6, 6); |
| 868 | } catch (error) { | 1017 | } catch (error) { |
| 869 | console.error('non-fatal error: failed to start server on IPv6'); | 1018 | console.error('non-fatal error: failed to start server on IPv6'); |
| 870 | console.error(error); | 1019 | console.error(error); |
| @@ -873,9 +1022,9 @@ async function startHTTPorHTTPS() { | |||
| 873 | } | 1022 | } |
| 874 | } | 1023 | } |
| 875 | 1024 | ||
| 876 | if (enableIPv4) { | 1025 | if (useIPv4) { |
| 877 | try { | 1026 | try { |
| 878 | await createFunc(tavernUrl); | 1027 | await createFunc(tavernUrl, 4); |
| 879 | } catch (error) { | 1028 | } catch (error) { |
| 880 | console.error('non-fatal error: failed to start server on IPv4'); | 1029 | console.error('non-fatal error: failed to start server on IPv4'); |
| 881 | console.error(error); | 1030 | console.error(error); |
| @@ -888,10 +1037,59 @@ async function startHTTPorHTTPS() { | |||
| 888 | } | 1037 | } |
| 889 | 1038 | ||
| 890 | async function startServer() { | 1039 | async function startServer() { |
| 891 | const [v6Failed, v4Failed] = await startHTTPorHTTPS(); | 1040 | let useIPv6 = (enableIPv6 === true); |
| 1041 | let useIPv4 = (enableIPv4 === true); | ||
| 1042 | |||
| 1043 | let hasIPv6 = false, | ||
| 1044 | hasIPv4 = false, | ||
| 1045 | hasIPv6Local = false, | ||
| 1046 | hasIPv4Local = false, | ||
| 1047 | hasIPv6Any = false, | ||
| 1048 | hasIPv4Any = false; | ||
| 1049 | |||
| 1050 | if (enableIPv6 === 'auto' || enableIPv4 === 'auto') { | ||
| 1051 | [hasIPv6Any, hasIPv4Any, hasIPv6Local, hasIPv4Local] = await getHasIP(); | ||
| 1052 | |||
| 1053 | hasIPv6 = listen ? hasIPv6Any : hasIPv6Local; | ||
| 1054 | if (enableIPv6 === 'auto') { | ||
| 1055 | useIPv6 = hasIPv6; | ||
| 1056 | } | ||
| 1057 | if (hasIPv6) { | ||
| 1058 | if (useIPv6) { | ||
| 1059 | console.log(color.green('IPv6 support detected')); | ||
| 1060 | } else { | ||
| 1061 | console.log('IPv6 support detected (but disabled)'); | ||
| 1062 | } | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | hasIPv4 = listen ? hasIPv4Any : hasIPv4Local; | ||
| 1066 | if (enableIPv4 === 'auto') { | ||
| 1067 | useIPv4 = hasIPv4; | ||
| 1068 | } | ||
| 1069 | if (hasIPv4) { | ||
| 1070 | if (useIPv4) { | ||
| 1071 | console.log(color.green('IPv4 support detected')); | ||
| 1072 | } else { | ||
| 1073 | console.log('IPv4 support detected (but disabled)'); | ||
| 1074 | } | ||
| 1075 | } | ||
| 892 | 1076 | ||
| 893 | handleServerListenFail(v6Failed, v4Failed); | 1077 | if (enableIPv6 === 'auto' && enableIPv4 === 'auto') { |
| 894 | postSetupTasks(v6Failed, v4Failed); | 1078 | if (!hasIPv6 && !hasIPv4) { |
| 1079 | console.error('Both IPv6 and IPv4 are not detected'); | ||
| 1080 | process.exit(1); | ||
| 1081 | } | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | if (!useIPv6 && !useIPv4) { | ||
| 1086 | console.error('Both IPv6 and IPv4 are disabled,\nP.S. you should never see this error, at least at one point it was checked for before this, with the rest of the config options'); | ||
| 1087 | process.exit(1); | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4); | ||
| 1091 | handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4); | ||
| 1092 | postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4); | ||
| 895 | } | 1093 | } |
| 896 | 1094 | ||
| 897 | async function verifySecuritySettings() { | 1095 | async function verifySecuritySettings() { |
| @@ -901,7 +1099,7 @@ async function verifySecuritySettings() { | |||
| 901 | } | 1099 | } |
| 902 | 1100 | ||
| 903 | if (!enableAccounts) { | 1101 | if (!enableAccounts) { |
| 904 | logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.'); | 1102 | logSecurityAlert('Your current SillyTavern configuration is insecure (listening to non-localhost). Enable whitelisting, basic authentication or user accounts.'); |
| 905 | } | 1103 | } |
| 906 | 1104 | ||
| 907 | const users = await getAllEnabledUsers(); | 1105 | const users = await getAllEnabledUsers(); |
| @@ -921,6 +1119,16 @@ async function verifySecuritySettings() { | |||
| 921 | } | 1119 | } |
| 922 | } | 1120 | } |
| 923 | 1121 | ||
| 1122 | /** | ||
| 1123 | * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered. | ||
| 1124 | */ | ||
| 1125 | function apply404Middleware() { | ||
| 1126 | const notFoundWebpage = safeReadFileSync('./public/error/url-not-found.html') ?? ''; | ||
| 1127 | app.use((req, res) => { | ||
| 1128 | res.status(404).send(notFoundWebpage); | ||
| 1129 | }); | ||
| 1130 | } | ||
| 1131 | |||
| 924 | // User storage module needs to be initialized before starting the server | 1132 | // User storage module needs to be initialized before starting the server |
| 925 | initUserStorage(dataRoot) | 1133 | initUserStorage(dataRoot) |
| 926 | .then(ensurePublicDirectoriesExist) | 1134 | .then(ensurePublicDirectoriesExist) |
| @@ -928,4 +1136,5 @@ initUserStorage(dataRoot) | |||
| 928 | .then(migrateSystemPrompts) | 1136 | .then(migrateSystemPrompts) |
| 929 | .then(verifySecuritySettings) | 1137 | .then(verifySecuritySettings) |
| 930 | .then(preSetupTasks) | 1138 | .then(preSetupTasks) |
| 1139 | .then(apply404Middleware) | ||
| 931 | .finally(startServer); | 1140 | .finally(startServer); |
| @@ -139,19 +139,19 @@ export const UNSAFE_EXTENSIONS = [ | |||
| 139 | export const GEMINI_SAFETY = [ | 139 | export const GEMINI_SAFETY = [ |
| 140 | { | 140 | { |
| 141 | category: 'HARM_CATEGORY_HARASSMENT', | 141 | category: 'HARM_CATEGORY_HARASSMENT', |
| 142 | threshold: 'BLOCK_NONE', | 142 | threshold: 'OFF', |
| 143 | }, | 143 | }, |
| 144 | { | 144 | { |
| 145 | category: 'HARM_CATEGORY_HATE_SPEECH', | 145 | category: 'HARM_CATEGORY_HATE_SPEECH', |
| 146 | threshold: 'BLOCK_NONE', | 146 | threshold: 'OFF', |
| 147 | }, | 147 | }, |
| 148 | { | 148 | { |
| 149 | category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', | 149 | category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', |
| 150 | threshold: 'BLOCK_NONE', | 150 | threshold: 'OFF', |
| 151 | }, | 151 | }, |
| 152 | { | 152 | { |
| 153 | category: 'HARM_CATEGORY_DANGEROUS_CONTENT', | 153 | category: 'HARM_CATEGORY_DANGEROUS_CONTENT', |
| 154 | threshold: 'BLOCK_NONE', | 154 | threshold: 'OFF', |
| 155 | }, | 155 | }, |
| 156 | { | 156 | { |
| 157 | category: 'HARM_CATEGORY_CIVIC_INTEGRITY', | 157 | category: 'HARM_CATEGORY_CIVIC_INTEGRITY', |
| @@ -304,6 +304,7 @@ export const TOGETHERAI_KEYS = [ | |||
| 304 | export const OLLAMA_KEYS = [ | 304 | export const OLLAMA_KEYS = [ |
| 305 | 'num_predict', | 305 | 'num_predict', |
| 306 | 'num_ctx', | 306 | 'num_ctx', |
| 307 | 'num_batch', | ||
| 307 | 'stop', | 308 | 'stop', |
| 308 | 'temperature', | 309 | 'temperature', |
| 309 | 'repeat_penalty', | 310 | 'repeat_penalty', |
| @@ -369,6 +370,7 @@ export const OPENROUTER_KEYS = [ | |||
| 369 | 'prompt', | 370 | 'prompt', |
| 370 | 'stop', | 371 | 'stop', |
| 371 | 'provider', | 372 | 'provider', |
| 373 | 'include_reasoning', | ||
| 372 | ]; | 374 | ]; |
| 373 | 375 | ||
| 374 | // https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220 | 376 | // https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220 |
| @@ -413,3 +415,10 @@ export const VLLM_KEYS = [ | |||
| 413 | 'guided_decoding_backend', | 415 | 'guided_decoding_backend', |
| 414 | 'guided_whitespace_pattern', | 416 | 'guided_whitespace_pattern', |
| 415 | ]; | 417 | ]; |
| 418 | |||
| 419 | export const LOG_LEVELS = { | ||
| 420 | DEBUG: 0, | ||
| 421 | INFO: 1, | ||
| 422 | WARN: 2, | ||
| 423 | ERROR: 3, | ||
| 424 | }; | ||
| @@ -32,7 +32,7 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 32 | max_tokens: 4096, | 32 | max_tokens: 4096, |
| 33 | }; | 33 | }; |
| 34 | 34 | ||
| 35 | console.log('Multimodal captioning request', body); | 35 | console.debug('Multimodal captioning request', body); |
| 36 | 36 | ||
| 37 | const result = await fetch(url, { | 37 | const result = await fetch(url, { |
| 38 | body: JSON.stringify(body), | 38 | body: JSON.stringify(body), |
| @@ -46,14 +46,14 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 46 | 46 | ||
| 47 | if (!result.ok) { | 47 | if (!result.ok) { |
| 48 | const text = await result.text(); | 48 | const text = await result.text(); |
| 49 | console.log(`Claude API returned error: ${result.status} ${result.statusText}`, text); | 49 | console.warn(`Claude API returned error: ${result.status} ${result.statusText}`, text); |
| 50 | return response.status(result.status).send({ error: true }); | 50 | return response.status(result.status).send({ error: true }); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | /** @type {any} */ | 53 | /** @type {any} */ |
| 54 | const generateResponseJson = await result.json(); | 54 | const generateResponseJson = await result.json(); |
| 55 | const caption = generateResponseJson.content[0].text; | 55 | const caption = generateResponseJson.content[0].text; |
| 56 | console.log('Claude response:', generateResponseJson); | 56 | console.debug('Claude response:', generateResponseJson); |
| 57 | 57 | ||
| 58 | if (!caption) { | 58 | if (!caption) { |
| 59 | return response.status(500).send('No caption found'); | 59 | return response.status(500).send('No caption found'); |
| @@ -176,7 +176,7 @@ router.post('/get', jsonParser, async (request, response) => { | |||
| 176 | } | 176 | } |
| 177 | } | 177 | } |
| 178 | catch (err) { | 178 | catch (err) { |
| 179 | console.log(err); | 179 | console.error(err); |
| 180 | } | 180 | } |
| 181 | return response.send(output); | 181 | return response.send(output); |
| 182 | }); | 182 | }); |
| @@ -200,7 +200,7 @@ router.post('/download', jsonParser, async (request, response) => { | |||
| 200 | category = i; | 200 | category = i; |
| 201 | 201 | ||
| 202 | if (category === null) { | 202 | if (category === null) { |
| 203 | console.debug('Bad request: unsupported asset category.'); | 203 | console.error('Bad request: unsupported asset category.'); |
| 204 | return response.sendStatus(400); | 204 | return response.sendStatus(400); |
| 205 | } | 205 | } |
| 206 | 206 | ||
| @@ -212,7 +212,7 @@ router.post('/download', jsonParser, async (request, response) => { | |||
| 212 | 212 | ||
| 213 | const temp_path = path.join(request.user.directories.assets, 'temp', request.body.filename); | 213 | const temp_path = path.join(request.user.directories.assets, 'temp', request.body.filename); |
| 214 | const file_path = path.join(request.user.directories.assets, category, request.body.filename); | 214 | const file_path = path.join(request.user.directories.assets, category, request.body.filename); |
| 215 | console.debug('Request received to download', url, 'to', file_path); | 215 | console.info('Request received to download', url, 'to', file_path); |
| 216 | 216 | ||
| 217 | try { | 217 | try { |
| 218 | // Download to temp | 218 | // Download to temp |
| @@ -241,13 +241,13 @@ router.post('/download', jsonParser, async (request, response) => { | |||
| 241 | } | 241 | } |
| 242 | 242 | ||
| 243 | // Move into asset place | 243 | // Move into asset place |
| 244 | console.debug('Download finished, moving file from', temp_path, 'to', file_path); | 244 | console.info('Download finished, moving file from', temp_path, 'to', file_path); |
| 245 | fs.copyFileSync(temp_path, file_path); | 245 | fs.copyFileSync(temp_path, file_path); |
| 246 | fs.rmSync(temp_path); | 246 | fs.rmSync(temp_path); |
| 247 | response.sendStatus(200); | 247 | response.sendStatus(200); |
| 248 | } | 248 | } |
| 249 | catch (error) { | 249 | catch (error) { |
| 250 | console.log(error); | 250 | console.error(error); |
| 251 | response.sendStatus(500); | 251 | response.sendStatus(500); |
| 252 | } | 252 | } |
| 253 | }); | 253 | }); |
| @@ -270,7 +270,7 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 270 | category = i; | 270 | category = i; |
| 271 | 271 | ||
| 272 | if (category === null) { | 272 | if (category === null) { |
| 273 | console.debug('Bad request: unsupported asset category.'); | 273 | console.error('Bad request: unsupported asset category.'); |
| 274 | return response.sendStatus(400); | 274 | return response.sendStatus(400); |
| 275 | } | 275 | } |
| 276 | 276 | ||
| @@ -280,7 +280,7 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 280 | return response.status(400).send(validation.message); | 280 | return response.status(400).send(validation.message); |
| 281 | 281 | ||
| 282 | const file_path = path.join(request.user.directories.assets, category, request.body.filename); | 282 | const file_path = path.join(request.user.directories.assets, category, request.body.filename); |
| 283 | console.debug('Request received to delete', category, file_path); | 283 | console.info('Request received to delete', category, file_path); |
| 284 | 284 | ||
| 285 | try { | 285 | try { |
| 286 | // Delete if previous download failed | 286 | // Delete if previous download failed |
| @@ -288,17 +288,17 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 288 | fs.unlink(file_path, (err) => { | 288 | fs.unlink(file_path, (err) => { |
| 289 | if (err) throw err; | 289 | if (err) throw err; |
| 290 | }); | 290 | }); |
| 291 | console.debug('Asset deleted.'); | 291 | console.info('Asset deleted.'); |
| 292 | } | 292 | } |
| 293 | else { | 293 | else { |
| 294 | console.debug('Asset not found.'); | 294 | console.error('Asset not found.'); |
| 295 | response.sendStatus(400); | 295 | response.sendStatus(400); |
| 296 | } | 296 | } |
| 297 | // Move into asset place | 297 | // Move into asset place |
| 298 | response.sendStatus(200); | 298 | response.sendStatus(200); |
| 299 | } | 299 | } |
| 300 | catch (error) { | 300 | catch (error) { |
| 301 | console.log(error); | 301 | console.error(error); |
| 302 | response.sendStatus(500); | 302 | response.sendStatus(500); |
| 303 | } | 303 | } |
| 304 | }); | 304 | }); |
| @@ -314,6 +314,7 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 314 | */ | 314 | */ |
| 315 | router.post('/character', jsonParser, async (request, response) => { | 315 | router.post('/character', jsonParser, async (request, response) => { |
| 316 | if (request.query.name === undefined) return response.sendStatus(400); | 316 | if (request.query.name === undefined) return response.sendStatus(400); |
| 317 | |||
| 317 | // For backwards compatibility, don't reject invalid character names, just sanitize them | 318 | // For backwards compatibility, don't reject invalid character names, just sanitize them |
| 318 | const name = sanitize(request.query.name.toString()); | 319 | const name = sanitize(request.query.name.toString()); |
| 319 | const inputCategory = request.query.category; | 320 | const inputCategory = request.query.category; |
| @@ -325,7 +326,7 @@ router.post('/character', jsonParser, async (request, response) => { | |||
| 325 | category = i; | 326 | category = i; |
| 326 | 327 | ||
| 327 | if (category === null) { | 328 | if (category === null) { |
| 328 | console.debug('Bad request: unsupported asset category.'); | 329 | console.error('Bad request: unsupported asset category.'); |
| 329 | return response.sendStatus(400); | 330 | return response.sendStatus(400); |
| 330 | } | 331 | } |
| 331 | 332 | ||
| @@ -364,7 +365,7 @@ router.post('/character', jsonParser, async (request, response) => { | |||
| 364 | return response.send(output); | 365 | return response.send(output); |
| 365 | } | 366 | } |
| 366 | catch (err) { | 367 | catch (err) { |
| 367 | console.log(err); | 368 | console.error(err); |
| 368 | return response.sendStatus(500); | 369 | return response.sendStatus(500); |
| 369 | } | 370 | } |
| 370 | }); | 371 | }); |
| @@ -9,6 +9,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; | |||
| 9 | import { jsonParser, urlencodedParser } from '../express-common.js'; | 9 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 10 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; | 10 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; |
| 11 | import { getImages, tryParse } from '../util.js'; | 11 | import { getImages, tryParse } from '../util.js'; |
| 12 | import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; | ||
| 12 | 13 | ||
| 13 | export const router = express.Router(); | 14 | export const router = express.Router(); |
| 14 | 15 | ||
| @@ -17,7 +18,7 @@ router.post('/get', jsonParser, function (request, response) { | |||
| 17 | response.send(JSON.stringify(images)); | 18 | response.send(JSON.stringify(images)); |
| 18 | }); | 19 | }); |
| 19 | 20 | ||
| 20 | router.post('/delete', jsonParser, function (request, response) { | 21 | router.post('/delete', jsonParser, getFileNameValidationFunction('avatar'), function (request, response) { |
| 21 | if (!request.body) return response.sendStatus(400); | 22 | if (!request.body) return response.sendStatus(400); |
| 22 | 23 | ||
| 23 | if (request.body.avatar !== sanitize(request.body.avatar)) { | 24 | if (request.body.avatar !== sanitize(request.body.avatar)) { |
| @@ -11,14 +11,14 @@ router.post('/list', jsonParser, async (req, res) => { | |||
| 11 | const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS); | 11 | const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS); |
| 12 | 12 | ||
| 13 | if (!key) { | 13 | if (!key) { |
| 14 | console.error('Azure TTS API Key not set'); | 14 | console.warn('Azure TTS API Key not set'); |
| 15 | return res.sendStatus(403); | 15 | return res.sendStatus(403); |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | const region = req.body.region; | 18 | const region = req.body.region; |
| 19 | 19 | ||
| 20 | if (!region) { | 20 | if (!region) { |
| 21 | console.error('Azure TTS region not set'); | 21 | console.warn('Azure TTS region not set'); |
| 22 | return res.sendStatus(400); | 22 | return res.sendStatus(400); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| @@ -32,7 +32,7 @@ router.post('/list', jsonParser, async (req, res) => { | |||
| 32 | }); | 32 | }); |
| 33 | 33 | ||
| 34 | if (!response.ok) { | 34 | if (!response.ok) { |
| 35 | console.error('Azure Request failed', response.status, response.statusText); | 35 | console.warn('Azure Request failed', response.status, response.statusText); |
| 36 | return res.sendStatus(500); | 36 | return res.sendStatus(500); |
| 37 | } | 37 | } |
| 38 | 38 | ||
| @@ -49,13 +49,13 @@ router.post('/generate', jsonParser, async (req, res) => { | |||
| 49 | const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS); | 49 | const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS); |
| 50 | 50 | ||
| 51 | if (!key) { | 51 | if (!key) { |
| 52 | console.error('Azure TTS API Key not set'); | 52 | console.warn('Azure TTS API Key not set'); |
| 53 | return res.sendStatus(403); | 53 | return res.sendStatus(403); |
| 54 | } | 54 | } |
| 55 | 55 | ||
| 56 | const { text, voice, region } = req.body; | 56 | const { text, voice, region } = req.body; |
| 57 | if (!text || !voice || !region) { | 57 | if (!text || !voice || !region) { |
| 58 | console.error('Missing required parameters'); | 58 | console.warn('Missing required parameters'); |
| 59 | return res.sendStatus(400); | 59 | return res.sendStatus(400); |
| 60 | } | 60 | } |
| 61 | 61 | ||
| @@ -75,7 +75,7 @@ router.post('/generate', jsonParser, async (req, res) => { | |||
| 75 | }); | 75 | }); |
| 76 | 76 | ||
| 77 | if (!response.ok) { | 77 | if (!response.ok) { |
| 78 | console.error('Azure Request failed', response.status, response.statusText); | 78 | console.warn('Azure Request failed', response.status, response.statusText); |
| 79 | return res.sendStatus(500); | 79 | return res.sendStatus(500); |
| 80 | } | 80 | } |
| 81 | 81 | ||
| @@ -37,6 +37,8 @@ import { | |||
| 37 | getTiktokenTokenizer, | 37 | getTiktokenTokenizer, |
| 38 | sentencepieceTokenizers, | 38 | sentencepieceTokenizers, |
| 39 | TEXT_COMPLETION_MODELS, | 39 | TEXT_COMPLETION_MODELS, |
| 40 | webTokenizers, | ||
| 41 | getWebTokenizer, | ||
| 40 | } from '../tokenizers.js'; | 42 | } from '../tokenizers.js'; |
| 41 | 43 | ||
| 42 | const API_OPENAI = 'https://api.openai.com/v1'; | 44 | const API_OPENAI = 'https://api.openai.com/v1'; |
| @@ -61,6 +63,7 @@ const API_DEEPSEEK = 'https://api.deepseek.com/beta'; | |||
| 61 | * @returns | 63 | * @returns |
| 62 | */ | 64 | */ |
| 63 | function postProcessPrompt(messages, type, names) { | 65 | function postProcessPrompt(messages, type, names) { |
| 66 | const addAssistantPrefix = x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x; | ||
| 64 | switch (type) { | 67 | switch (type) { |
| 65 | case 'merge': | 68 | case 'merge': |
| 66 | case 'claude': | 69 | case 'claude': |
| @@ -70,7 +73,9 @@ function postProcessPrompt(messages, type, names) { | |||
| 70 | case 'strict': | 73 | case 'strict': |
| 71 | return mergeMessages(messages, names, true, true); | 74 | return mergeMessages(messages, names, true, true); |
| 72 | case 'deepseek': | 75 | case 'deepseek': |
| 73 | return (x => x.length && (x[x.length - 1].role !== 'assistant' || (x[x.length - 1].prefix = true)) ? x : x)(mergeMessages(messages, names, true, false)); | 76 | return addAssistantPrefix(mergeMessages(messages, names, true, false)); |
| 77 | case 'deepseek-reasoner': | ||
| 78 | return addAssistantPrefix(mergeMessages(messages, names, true, true)); | ||
| 74 | default: | 79 | default: |
| 75 | return messages; | 80 | return messages; |
| 76 | } | 81 | } |
| @@ -109,7 +114,7 @@ async function sendClaudeRequest(request, response) { | |||
| 109 | } | 114 | } |
| 110 | 115 | ||
| 111 | if (!apiKey) { | 116 | if (!apiKey) { |
| 112 | console.log(color.red(`Claude API key is missing.\n${divider}`)); | 117 | console.warn(color.red(`Claude API key is missing.\n${divider}`)); |
| 113 | return response.status(400).send({ error: true }); | 118 | return response.status(400).send({ error: true }); |
| 114 | } | 119 | } |
| 115 | 120 | ||
| @@ -174,7 +179,7 @@ async function sendClaudeRequest(request, response) { | |||
| 174 | additionalHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31'; | 179 | additionalHeaders['anthropic-beta'] = 'prompt-caching-2024-07-31'; |
| 175 | } | 180 | } |
| 176 | 181 | ||
| 177 | console.log('Claude request:', requestBody); | 182 | console.debug('Claude request:', requestBody); |
| 178 | 183 | ||
| 179 | const generateResponse = await fetch(apiUrl + '/messages', { | 184 | const generateResponse = await fetch(apiUrl + '/messages', { |
| 180 | method: 'POST', | 185 | method: 'POST', |
| @@ -194,21 +199,21 @@ async function sendClaudeRequest(request, response) { | |||
| 194 | } else { | 199 | } else { |
| 195 | if (!generateResponse.ok) { | 200 | if (!generateResponse.ok) { |
| 196 | const generateResponseText = await generateResponse.text(); | 201 | const generateResponseText = await generateResponse.text(); |
| 197 | console.log(color.red(`Claude API returned error: ${generateResponse.status} ${generateResponse.statusText}\n${generateResponseText}\n${divider}`)); | 202 | console.warn(color.red(`Claude API returned error: ${generateResponse.status} ${generateResponse.statusText}\n${generateResponseText}\n${divider}`)); |
| 198 | return response.status(generateResponse.status).send({ error: true }); | 203 | return response.status(generateResponse.status).send({ error: true }); |
| 199 | } | 204 | } |
| 200 | 205 | ||
| 201 | /** @type {any} */ | 206 | /** @type {any} */ |
| 202 | const generateResponseJson = await generateResponse.json(); | 207 | const generateResponseJson = await generateResponse.json(); |
| 203 | const responseText = generateResponseJson?.content?.[0]?.text || ''; | 208 | const responseText = generateResponseJson?.content?.[0]?.text || ''; |
| 204 | console.log('Claude response:', generateResponseJson); | 209 | console.debug('Claude response:', generateResponseJson); |
| 205 | 210 | ||
| 206 | // Wrap it back to OAI format + save the original content | 211 | // Wrap it back to OAI format + save the original content |
| 207 | const reply = { choices: [{ 'message': { 'content': responseText } }], content: generateResponseJson.content }; | 212 | const reply = { choices: [{ 'message': { 'content': responseText } }], content: generateResponseJson.content }; |
| 208 | return response.send(reply); | 213 | return response.send(reply); |
| 209 | } | 214 | } |
| 210 | } catch (error) { | 215 | } catch (error) { |
| 211 | console.log(color.red(`Error communicating with Claude: ${error}\n${divider}`)); | 216 | console.error(color.red(`Error communicating with Claude: ${error}\n${divider}`)); |
| 212 | if (!response.headersSent) { | 217 | if (!response.headersSent) { |
| 213 | return response.status(500).send({ error: true }); | 218 | return response.status(500).send({ error: true }); |
| 214 | } | 219 | } |
| @@ -225,12 +230,12 @@ async function sendScaleRequest(request, response) { | |||
| 225 | const apiKey = readSecret(request.user.directories, SECRET_KEYS.SCALE); | 230 | const apiKey = readSecret(request.user.directories, SECRET_KEYS.SCALE); |
| 226 | 231 | ||
| 227 | if (!apiKey) { | 232 | if (!apiKey) { |
| 228 | console.log('Scale API key is missing.'); | 233 | console.warn('Scale API key is missing.'); |
| 229 | return response.status(400).send({ error: true }); | 234 | return response.status(400).send({ error: true }); |
| 230 | } | 235 | } |
| 231 | 236 | ||
| 232 | const requestPrompt = convertTextCompletionPrompt(request.body.messages); | 237 | const requestPrompt = convertTextCompletionPrompt(request.body.messages); |
| 233 | console.log('Scale request:', requestPrompt); | 238 | console.debug('Scale request:', requestPrompt); |
| 234 | 239 | ||
| 235 | try { | 240 | try { |
| 236 | const controller = new AbortController(); | 241 | const controller = new AbortController(); |
| @@ -249,18 +254,18 @@ async function sendScaleRequest(request, response) { | |||
| 249 | }); | 254 | }); |
| 250 | 255 | ||
| 251 | if (!generateResponse.ok) { | 256 | if (!generateResponse.ok) { |
| 252 | console.log(`Scale API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); | 257 | console.warn(`Scale API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); |
| 253 | return response.status(500).send({ error: true }); | 258 | return response.status(500).send({ error: true }); |
| 254 | } | 259 | } |
| 255 | 260 | ||
| 256 | /** @type {any} */ | 261 | /** @type {any} */ |
| 257 | const generateResponseJson = await generateResponse.json(); | 262 | const generateResponseJson = await generateResponse.json(); |
| 258 | console.log('Scale response:', generateResponseJson); | 263 | console.debug('Scale response:', generateResponseJson); |
| 259 | 264 | ||
| 260 | const reply = { choices: [{ 'message': { 'content': generateResponseJson.output } }] }; | 265 | const reply = { choices: [{ 'message': { 'content': generateResponseJson.output } }] }; |
| 261 | return response.send(reply); | 266 | return response.send(reply); |
| 262 | } catch (error) { | 267 | } catch (error) { |
| 263 | console.log(error); | 268 | console.error(error); |
| 264 | if (!response.headersSent) { | 269 | if (!response.headersSent) { |
| 265 | return response.status(500).send({ error: true }); | 270 | return response.status(500).send({ error: true }); |
| 266 | } | 271 | } |
| @@ -277,13 +282,13 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 277 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); | 282 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE); |
| 278 | 283 | ||
| 279 | if (!request.body.reverse_proxy && !apiKey) { | 284 | if (!request.body.reverse_proxy && !apiKey) { |
| 280 | console.log('Google AI Studio API key is missing.'); | 285 | console.warn('Google AI Studio API key is missing.'); |
| 281 | return response.status(400).send({ error: true }); | 286 | return response.status(400).send({ error: true }); |
| 282 | } | 287 | } |
| 283 | 288 | ||
| 284 | const model = String(request.body.model); | 289 | const model = String(request.body.model); |
| 285 | const stream = Boolean(request.body.stream); | 290 | const stream = Boolean(request.body.stream); |
| 286 | const showThoughts = Boolean(request.body.show_thoughts); | 291 | const isThinking = model.includes('thinking'); |
| 287 | 292 | ||
| 288 | const generationConfig = { | 293 | const generationConfig = { |
| 289 | stopSequences: request.body.stop, | 294 | stopSequences: request.body.stop, |
| @@ -300,8 +305,9 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 300 | } | 305 | } |
| 301 | 306 | ||
| 302 | const should_use_system_prompt = ( | 307 | const should_use_system_prompt = ( |
| 308 | model.includes('gemini-2.0-pro') || | ||
| 309 | model.includes('gemini-2.0-flash') || | ||
| 303 | model.includes('gemini-2.0-flash-thinking-exp') || | 310 | model.includes('gemini-2.0-flash-thinking-exp') || |
| 304 | model.includes('gemini-2.0-flash-exp') || | ||
| 305 | model.includes('gemini-1.5-flash') || | 311 | model.includes('gemini-1.5-flash') || |
| 306 | model.includes('gemini-1.5-pro') || | 312 | model.includes('gemini-1.5-pro') || |
| 307 | model.startsWith('gemini-exp') | 313 | model.startsWith('gemini-exp') |
| @@ -310,9 +316,15 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 310 | const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request)); | 316 | const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request)); |
| 311 | let safetySettings = GEMINI_SAFETY; | 317 | let safetySettings = GEMINI_SAFETY; |
| 312 | 318 | ||
| 313 | if (model.includes('gemini-2.0-flash-exp')) { | 319 | // These old models do not support setting the threshold to OFF at all. |
| 320 | if (['gemini-1.5-pro-001', 'gemini-1.5-flash-001', 'gemini-1.5-flash-8b-exp-0827', 'gemini-1.5-flash-8b-exp-0924', 'gemini-pro', 'gemini-1.0-pro', 'gemini-1.0-pro-001'].includes(model)) { | ||
| 321 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'BLOCK_NONE' })); | ||
| 322 | } | ||
| 323 | // Interestingly, Gemini 2.0 Flash does support setting the threshold for HARM_CATEGORY_CIVIC_INTEGRITY to OFF. | ||
| 324 | else if (['gemini-2.0-flash', 'gemini-2.0-flash-001', 'gemini-2.0-flash-exp'].includes(model)) { | ||
| 314 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'OFF' })); | 325 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'OFF' })); |
| 315 | } | 326 | } |
| 327 | // Most of the other models allow for setting the threshold of filters, except for HARM_CATEGORY_CIVIC_INTEGRITY, to OFF. | ||
| 316 | 328 | ||
| 317 | let body = { | 329 | let body = { |
| 318 | contents: prompt.contents, | 330 | contents: prompt.contents, |
| @@ -328,7 +340,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 328 | } | 340 | } |
| 329 | 341 | ||
| 330 | const body = getGeminiBody(); | 342 | const body = getGeminiBody(); |
| 331 | console.log('Google AI Studio request:', body); | 343 | console.debug('Google AI Studio request:', body); |
| 332 | 344 | ||
| 333 | try { | 345 | try { |
| 334 | const controller = new AbortController(); | 346 | const controller = new AbortController(); |
| @@ -337,7 +349,6 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 337 | controller.abort(); | 349 | controller.abort(); |
| 338 | }); | 350 | }); |
| 339 | 351 | ||
| 340 | const isThinking = model.includes('thinking'); | ||
| 341 | const apiVersion = isThinking ? 'v1alpha' : 'v1beta'; | 352 | const apiVersion = isThinking ? 'v1alpha' : 'v1beta'; |
| 342 | const responseType = (stream ? 'streamGenerateContent' : 'generateContent'); | 353 | const responseType = (stream ? 'streamGenerateContent' : 'generateContent'); |
| 343 | 354 | ||
| @@ -355,14 +366,14 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 355 | // Pipe remote SSE stream to Express response | 366 | // Pipe remote SSE stream to Express response |
| 356 | forwardFetchResponse(generateResponse, response); | 367 | forwardFetchResponse(generateResponse, response); |
| 357 | } catch (error) { | 368 | } catch (error) { |
| 358 | console.log('Error forwarding streaming response:', error); | 369 | console.error('Error forwarding streaming response:', error); |
| 359 | if (!response.headersSent) { | 370 | if (!response.headersSent) { |
| 360 | return response.status(500).send({ error: true }); | 371 | return response.status(500).send({ error: true }); |
| 361 | } | 372 | } |
| 362 | } | 373 | } |
| 363 | } else { | 374 | } else { |
| 364 | if (!generateResponse.ok) { | 375 | if (!generateResponse.ok) { |
| 365 | console.log(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); | 376 | console.warn(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`); |
| 366 | return response.status(generateResponse.status).send({ error: true }); | 377 | return response.status(generateResponse.status).send({ error: true }); |
| 367 | } | 378 | } |
| 368 | 379 | ||
| @@ -372,7 +383,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 372 | const candidates = generateResponseJson?.candidates; | 383 | const candidates = generateResponseJson?.candidates; |
| 373 | if (!candidates || candidates.length === 0) { | 384 | if (!candidates || candidates.length === 0) { |
| 374 | let message = 'Google AI Studio API returned no candidate'; | 385 | let message = 'Google AI Studio API returned no candidate'; |
| 375 | console.log(message, generateResponseJson); | 386 | console.warn(message, generateResponseJson); |
| 376 | if (generateResponseJson?.promptFeedback?.blockReason) { | 387 | if (generateResponseJson?.promptFeedback?.blockReason) { |
| 377 | message += `\nPrompt was blocked due to : ${generateResponseJson.promptFeedback.blockReason}`; | 388 | message += `\nPrompt was blocked due to : ${generateResponseJson.promptFeedback.blockReason}`; |
| 378 | } | 389 | } |
| @@ -380,25 +391,21 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 380 | } | 391 | } |
| 381 | 392 | ||
| 382 | const responseContent = candidates[0].content ?? candidates[0].output; | 393 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 383 | console.log('Google AI Studio response:', responseContent); | 394 | console.warn('Google AI Studio response:', responseContent); |
| 384 | 395 | ||
| 385 | if (Array.isArray(responseContent?.parts) && isThinking && !showThoughts) { | 396 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); |
| 386 | responseContent.parts = responseContent.parts.filter(part => !part.thought); | ||
| 387 | } | ||
| 388 | |||
| 389 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join('\n\n'); | ||
| 390 | if (!responseText) { | 397 | if (!responseText) { |
| 391 | let message = 'Google AI Studio Candidate text empty'; | 398 | let message = 'Google AI Studio Candidate text empty'; |
| 392 | console.log(message, generateResponseJson); | 399 | console.warn(message, generateResponseJson); |
| 393 | return response.send({ error: { message } }); | 400 | return response.send({ error: { message } }); |
| 394 | } | 401 | } |
| 395 | 402 | ||
| 396 | // Wrap it back to OAI format | 403 | // Wrap it back to OAI format |
| 397 | const reply = { choices: [{ 'message': { 'content': responseText } }] }; | 404 | const reply = { choices: [{ 'message': { 'content': responseText } }], responseContent }; |
| 398 | return response.send(reply); | 405 | return response.send(reply); |
| 399 | } | 406 | } |
| 400 | } catch (error) { | 407 | } catch (error) { |
| 401 | console.log('Error communicating with Google AI Studio API: ', error); | 408 | console.error('Error communicating with Google AI Studio API: ', error); |
| 402 | if (!response.headersSent) { | 409 | if (!response.headersSent) { |
| 403 | return response.status(500).send({ error: true }); | 410 | return response.status(500).send({ error: true }); |
| 404 | } | 411 | } |
| @@ -412,8 +419,9 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 412 | */ | 419 | */ |
| 413 | async function sendAI21Request(request, response) { | 420 | async function sendAI21Request(request, response) { |
| 414 | if (!request.body) return response.sendStatus(400); | 421 | if (!request.body) return response.sendStatus(400); |
| 422 | |||
| 415 | const controller = new AbortController(); | 423 | const controller = new AbortController(); |
| 416 | console.log(request.body.messages); | 424 | console.debug(request.body.messages); |
| 417 | request.socket.removeAllListeners('close'); | 425 | request.socket.removeAllListeners('close'); |
| 418 | request.socket.on('close', function () { | 426 | request.socket.on('close', function () { |
| 419 | controller.abort(); | 427 | controller.abort(); |
| @@ -439,7 +447,7 @@ async function sendAI21Request(request, response) { | |||
| 439 | signal: controller.signal, | 447 | signal: controller.signal, |
| 440 | }; | 448 | }; |
| 441 | 449 | ||
| 442 | console.log('AI21 request:', body); | 450 | console.debug('AI21 request:', body); |
| 443 | 451 | ||
| 444 | try { | 452 | try { |
| 445 | const generateResponse = await fetch(API_AI21 + '/chat/completions', options); | 453 | const generateResponse = await fetch(API_AI21 + '/chat/completions', options); |
| @@ -448,16 +456,16 @@ async function sendAI21Request(request, response) { | |||
| 448 | } else { | 456 | } else { |
| 449 | if (!generateResponse.ok) { | 457 | if (!generateResponse.ok) { |
| 450 | const errorText = await generateResponse.text(); | 458 | const errorText = await generateResponse.text(); |
| 451 | console.log(`AI21 API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | 459 | console.warn(`AI21 API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); |
| 452 | const errorJson = tryParse(errorText) ?? { error: true }; | 460 | const errorJson = tryParse(errorText) ?? { error: true }; |
| 453 | return response.status(500).send(errorJson); | 461 | return response.status(500).send(errorJson); |
| 454 | } | 462 | } |
| 455 | const generateResponseJson = await generateResponse.json(); | 463 | const generateResponseJson = await generateResponse.json(); |
| 456 | console.log('AI21 response:', generateResponseJson); | 464 | console.debug('AI21 response:', generateResponseJson); |
| 457 | return response.send(generateResponseJson); | 465 | return response.send(generateResponseJson); |
| 458 | } | 466 | } |
| 459 | } catch (error) { | 467 | } catch (error) { |
| 460 | console.log('Error communicating with AI21 API: ', error); | 468 | console.error('Error communicating with AI21 API: ', error); |
| 461 | if (!response.headersSent) { | 469 | if (!response.headersSent) { |
| 462 | response.send({ error: true }); | 470 | response.send({ error: true }); |
| 463 | } else { | 471 | } else { |
| @@ -476,7 +484,7 @@ async function sendMistralAIRequest(request, response) { | |||
| 476 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI); | 484 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI); |
| 477 | 485 | ||
| 478 | if (!apiKey) { | 486 | if (!apiKey) { |
| 479 | console.log('MistralAI API key is missing.'); | 487 | console.warn('MistralAI API key is missing.'); |
| 480 | return response.status(400).send({ error: true }); | 488 | return response.status(400).send({ error: true }); |
| 481 | } | 489 | } |
| 482 | 490 | ||
| @@ -517,7 +525,7 @@ async function sendMistralAIRequest(request, response) { | |||
| 517 | timeout: 0, | 525 | timeout: 0, |
| 518 | }; | 526 | }; |
| 519 | 527 | ||
| 520 | console.log('MisralAI request:', requestBody); | 528 | console.debug('MisralAI request:', requestBody); |
| 521 | 529 | ||
| 522 | const generateResponse = await fetch(apiUrl + '/chat/completions', config); | 530 | const generateResponse = await fetch(apiUrl + '/chat/completions', config); |
| 523 | if (request.body.stream) { | 531 | if (request.body.stream) { |
| @@ -525,16 +533,16 @@ async function sendMistralAIRequest(request, response) { | |||
| 525 | } else { | 533 | } else { |
| 526 | if (!generateResponse.ok) { | 534 | if (!generateResponse.ok) { |
| 527 | const errorText = await generateResponse.text(); | 535 | const errorText = await generateResponse.text(); |
| 528 | console.log(`MistralAI API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | 536 | console.warn(`MistralAI API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); |
| 529 | const errorJson = tryParse(errorText) ?? { error: true }; | 537 | const errorJson = tryParse(errorText) ?? { error: true }; |
| 530 | return response.status(500).send(errorJson); | 538 | return response.status(500).send(errorJson); |
| 531 | } | 539 | } |
| 532 | const generateResponseJson = await generateResponse.json(); | 540 | const generateResponseJson = await generateResponse.json(); |
| 533 | console.log('MistralAI response:', generateResponseJson); | 541 | console.debug('MistralAI response:', generateResponseJson); |
| 534 | return response.send(generateResponseJson); | 542 | return response.send(generateResponseJson); |
| 535 | } | 543 | } |
| 536 | } catch (error) { | 544 | } catch (error) { |
| 537 | console.log('Error communicating with MistralAI API: ', error); | 545 | console.error('Error communicating with MistralAI API: ', error); |
| 538 | if (!response.headersSent) { | 546 | if (!response.headersSent) { |
| 539 | response.send({ error: true }); | 547 | response.send({ error: true }); |
| 540 | } else { | 548 | } else { |
| @@ -557,7 +565,7 @@ async function sendCohereRequest(request, response) { | |||
| 557 | }); | 565 | }); |
| 558 | 566 | ||
| 559 | if (!apiKey) { | 567 | if (!apiKey) { |
| 560 | console.log('Cohere API key is missing.'); | 568 | console.warn('Cohere API key is missing.'); |
| 561 | return response.status(400).send({ error: true }); | 569 | return response.status(400).send({ error: true }); |
| 562 | } | 570 | } |
| 563 | 571 | ||
| @@ -596,7 +604,7 @@ async function sendCohereRequest(request, response) { | |||
| 596 | requestBody.safety_mode = 'OFF'; | 604 | requestBody.safety_mode = 'OFF'; |
| 597 | } | 605 | } |
| 598 | 606 | ||
| 599 | console.log('Cohere request:', requestBody); | 607 | console.debug('Cohere request:', requestBody); |
| 600 | 608 | ||
| 601 | const config = { | 609 | const config = { |
| 602 | method: 'POST', | 610 | method: 'POST', |
| @@ -618,16 +626,16 @@ async function sendCohereRequest(request, response) { | |||
| 618 | const generateResponse = await fetch(apiUrl, config); | 626 | const generateResponse = await fetch(apiUrl, config); |
| 619 | if (!generateResponse.ok) { | 627 | if (!generateResponse.ok) { |
| 620 | const errorText = await generateResponse.text(); | 628 | const errorText = await generateResponse.text(); |
| 621 | console.log(`Cohere API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | 629 | console.warn(`Cohere API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); |
| 622 | const errorJson = tryParse(errorText) ?? { error: true }; | 630 | const errorJson = tryParse(errorText) ?? { error: true }; |
| 623 | return response.status(500).send(errorJson); | 631 | return response.status(500).send(errorJson); |
| 624 | } | 632 | } |
| 625 | const generateResponseJson = await generateResponse.json(); | 633 | const generateResponseJson = await generateResponse.json(); |
| 626 | console.log('Cohere response:', generateResponseJson); | 634 | console.debug('Cohere response:', generateResponseJson); |
| 627 | return response.send(generateResponseJson); | 635 | return response.send(generateResponseJson); |
| 628 | } | 636 | } |
| 629 | } catch (error) { | 637 | } catch (error) { |
| 630 | console.log('Error communicating with Cohere API: ', error); | 638 | console.error('Error communicating with Cohere API: ', error); |
| 631 | if (!response.headersSent) { | 639 | if (!response.headersSent) { |
| 632 | response.send({ error: true }); | 640 | response.send({ error: true }); |
| 633 | } else { | 641 | } else { |
| @@ -636,6 +644,94 @@ async function sendCohereRequest(request, response) { | |||
| 636 | } | 644 | } |
| 637 | } | 645 | } |
| 638 | 646 | ||
| 647 | /** | ||
| 648 | * Sends a request to DeepSeek API. | ||
| 649 | * @param {express.Request} request Express request | ||
| 650 | * @param {express.Response} response Express response | ||
| 651 | */ | ||
| 652 | async function sendDeepSeekRequest(request, response) { | ||
| 653 | const apiUrl = new URL(request.body.reverse_proxy || API_DEEPSEEK).toString(); | ||
| 654 | const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | ||
| 655 | |||
| 656 | if (!apiKey && !request.body.reverse_proxy) { | ||
| 657 | console.warn('DeepSeek API key is missing.'); | ||
| 658 | return response.status(400).send({ error: true }); | ||
| 659 | } | ||
| 660 | |||
| 661 | const controller = new AbortController(); | ||
| 662 | request.socket.removeAllListeners('close'); | ||
| 663 | request.socket.on('close', function () { | ||
| 664 | controller.abort(); | ||
| 665 | }); | ||
| 666 | |||
| 667 | try { | ||
| 668 | let bodyParams = {}; | ||
| 669 | |||
| 670 | if (request.body.logprobs > 0) { | ||
| 671 | bodyParams['top_logprobs'] = request.body.logprobs; | ||
| 672 | bodyParams['logprobs'] = true; | ||
| 673 | } | ||
| 674 | |||
| 675 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0) { | ||
| 676 | bodyParams['tools'] = request.body.tools; | ||
| 677 | bodyParams['tool_choice'] = request.body.tool_choice; | ||
| 678 | } | ||
| 679 | |||
| 680 | const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; | ||
| 681 | const processedMessages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); | ||
| 682 | |||
| 683 | const requestBody = { | ||
| 684 | 'messages': processedMessages, | ||
| 685 | 'model': request.body.model, | ||
| 686 | 'temperature': request.body.temperature, | ||
| 687 | 'max_tokens': request.body.max_tokens, | ||
| 688 | 'stream': request.body.stream, | ||
| 689 | 'presence_penalty': request.body.presence_penalty, | ||
| 690 | 'frequency_penalty': request.body.frequency_penalty, | ||
| 691 | 'top_p': request.body.top_p, | ||
| 692 | 'stop': request.body.stop, | ||
| 693 | 'seed': request.body.seed, | ||
| 694 | ...bodyParams, | ||
| 695 | }; | ||
| 696 | |||
| 697 | const config = { | ||
| 698 | method: 'POST', | ||
| 699 | headers: { | ||
| 700 | 'Content-Type': 'application/json', | ||
| 701 | 'Authorization': 'Bearer ' + apiKey, | ||
| 702 | }, | ||
| 703 | body: JSON.stringify(requestBody), | ||
| 704 | signal: controller.signal, | ||
| 705 | }; | ||
| 706 | |||
| 707 | console.debug('DeepSeek request:', requestBody); | ||
| 708 | |||
| 709 | const generateResponse = await fetch(apiUrl + '/chat/completions', config); | ||
| 710 | |||
| 711 | if (request.body.stream) { | ||
| 712 | forwardFetchResponse(generateResponse, response); | ||
| 713 | } else { | ||
| 714 | if (!generateResponse.ok) { | ||
| 715 | const errorText = await generateResponse.text(); | ||
| 716 | console.warn(`DeepSeek API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | ||
| 717 | const errorJson = tryParse(errorText) ?? { error: true }; | ||
| 718 | return response.status(500).send(errorJson); | ||
| 719 | } | ||
| 720 | const generateResponseJson = await generateResponse.json(); | ||
| 721 | console.debug('DeepSeek response:', generateResponseJson); | ||
| 722 | return response.send(generateResponseJson); | ||
| 723 | } | ||
| 724 | } catch (error) { | ||
| 725 | console.error('Error communicating with DeepSeek API: ', error); | ||
| 726 | if (!response.headersSent) { | ||
| 727 | response.send({ error: true }); | ||
| 728 | } else { | ||
| 729 | response.end(); | ||
| 730 | } | ||
| 731 | } | ||
| 732 | } | ||
| 733 | |||
| 734 | |||
| 639 | export const router = express.Router(); | 735 | export const router = express.Router(); |
| 640 | 736 | ||
| 641 | router.post('/status', jsonParser, async function (request, response_getstatus_openai) { | 737 | router.post('/status', jsonParser, async function (request, response_getstatus_openai) { |
| @@ -680,16 +776,16 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o | |||
| 680 | api_key_openai = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); | 776 | api_key_openai = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); |
| 681 | headers = {}; | 777 | headers = {}; |
| 682 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { | 778 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { |
| 683 | api_url = API_DEEPSEEK.replace('/beta', ''); | 779 | api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')); |
| 684 | api_key_openai = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | 780 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); |
| 685 | headers = {}; | 781 | headers = {}; |
| 686 | } else { | 782 | } else { |
| 687 | console.log('This chat completion source is not supported yet.'); | 783 | console.warn('This chat completion source is not supported yet.'); |
| 688 | return response_getstatus_openai.status(400).send({ error: true }); | 784 | return response_getstatus_openai.status(400).send({ error: true }); |
| 689 | } | 785 | } |
| 690 | 786 | ||
| 691 | if (!api_key_openai && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { | 787 | if (!api_key_openai && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { |
| 692 | console.log('Chat Completion API key is missing.'); | 788 | console.warn('Chat Completion API key is missing.'); |
| 693 | return response_getstatus_openai.status(400).send({ error: true }); | 789 | return response_getstatus_openai.status(400).send({ error: true }); |
| 694 | } | 790 | } |
| 695 | 791 | ||
| @@ -724,23 +820,23 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o | |||
| 724 | }; | 820 | }; |
| 725 | }); | 821 | }); |
| 726 | 822 | ||
| 727 | console.log('Available OpenRouter models:', models); | 823 | console.info('Available OpenRouter models:', models); |
| 728 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) { | 824 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) { |
| 729 | const models = data?.data; | 825 | const models = data?.data; |
| 730 | console.log(models); | 826 | console.info(models); |
| 731 | } else { | 827 | } else { |
| 732 | const models = data?.data; | 828 | const models = data?.data; |
| 733 | 829 | ||
| 734 | if (Array.isArray(models)) { | 830 | if (Array.isArray(models)) { |
| 735 | const modelIds = models.filter(x => x && typeof x === 'object').map(x => x.id).sort(); | 831 | const modelIds = models.filter(x => x && typeof x === 'object').map(x => x.id).sort(); |
| 736 | console.log('Available models:', modelIds); | 832 | console.info('Available models:', modelIds); |
| 737 | } else { | 833 | } else { |
| 738 | console.log('Chat Completion endpoint did not return a list of models.'); | 834 | console.warn('Chat Completion endpoint did not return a list of models.'); |
| 739 | } | 835 | } |
| 740 | } | 836 | } |
| 741 | } | 837 | } |
| 742 | else { | 838 | else { |
| 743 | console.log('Chat Completion status check failed. Either Access Token is incorrect or API endpoint is down.'); | 839 | console.error('Chat Completion status check failed. Either Access Token is incorrect or API endpoint is down.'); |
| 744 | response_getstatus_openai.send({ error: true, can_bypass: true, data: { data: [] } }); | 840 | response_getstatus_openai.send({ error: true, can_bypass: true, data: { data: [] } }); |
| 745 | } | 841 | } |
| 746 | } catch (e) { | 842 | } catch (e) { |
| @@ -773,10 +869,18 @@ router.post('/bias', jsonParser, async function (request, response) { | |||
| 773 | const tokenizer = getSentencepiceTokenizer(model); | 869 | const tokenizer = getSentencepiceTokenizer(model); |
| 774 | const instance = await tokenizer?.get(); | 870 | const instance = await tokenizer?.get(); |
| 775 | if (!instance) { | 871 | if (!instance) { |
| 776 | console.warn('Tokenizer not initialized:', model); | 872 | console.error('Tokenizer not initialized:', model); |
| 777 | return response.send({}); | 873 | return response.send({}); |
| 778 | } | 874 | } |
| 779 | encodeFunction = (text) => new Uint32Array(instance.encodeIds(text)); | 875 | encodeFunction = (text) => new Uint32Array(instance.encodeIds(text)); |
| 876 | } else if (webTokenizers.includes(model)) { | ||
| 877 | const tokenizer = getWebTokenizer(model); | ||
| 878 | const instance = await tokenizer?.get(); | ||
| 879 | if (!instance) { | ||
| 880 | console.warn('Tokenizer not initialized:', model); | ||
| 881 | return response.send({}); | ||
| 882 | } | ||
| 883 | encodeFunction = (text) => new Uint32Array(instance.encode(text)); | ||
| 780 | } else { | 884 | } else { |
| 781 | const tokenizer = getTiktokenTokenizer(model); | 885 | const tokenizer = getTiktokenTokenizer(model); |
| 782 | encodeFunction = (tokenizer.encode.bind(tokenizer)); | 886 | encodeFunction = (tokenizer.encode.bind(tokenizer)); |
| @@ -841,6 +945,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 841 | case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response); | 945 | case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response); |
| 842 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); | 946 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); |
| 843 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); | 947 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); |
| 948 | case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response); | ||
| 844 | } | 949 | } |
| 845 | 950 | ||
| 846 | let apiUrl; | 951 | let apiUrl; |
| @@ -899,6 +1004,10 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 899 | bodyParams['route'] = 'fallback'; | 1004 | bodyParams['route'] = 'fallback'; |
| 900 | } | 1005 | } |
| 901 | 1006 | ||
| 1007 | if (request.body.include_reasoning) { | ||
| 1008 | bodyParams['include_reasoning'] = true; | ||
| 1009 | } | ||
| 1010 | |||
| 902 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1); | 1011 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1); |
| 903 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { | 1012 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { |
| 904 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); | 1013 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); |
| @@ -922,7 +1031,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 922 | mergeObjectWithYaml(headers, request.body.custom_include_headers); | 1031 | mergeObjectWithYaml(headers, request.body.custom_include_headers); |
| 923 | 1032 | ||
| 924 | if (request.body.custom_prompt_post_processing) { | 1033 | if (request.body.custom_prompt_post_processing) { |
| 925 | console.log('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing); | 1034 | console.info('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing); |
| 926 | request.body.messages = postProcessPrompt( | 1035 | request.body.messages = postProcessPrompt( |
| 927 | request.body.messages, | 1036 | request.body.messages, |
| 928 | request.body.custom_prompt_post_processing, | 1037 | request.body.custom_prompt_post_processing, |
| @@ -954,25 +1063,20 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 954 | apiKey = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY); | 1063 | apiKey = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY); |
| 955 | headers = {}; | 1064 | headers = {}; |
| 956 | bodyParams = {}; | 1065 | bodyParams = {}; |
| 957 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { | ||
| 958 | apiUrl = API_DEEPSEEK; | ||
| 959 | apiKey = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | ||
| 960 | headers = {}; | ||
| 961 | bodyParams = {}; | ||
| 962 | |||
| 963 | if (request.body.logprobs > 0) { | ||
| 964 | bodyParams['top_logprobs'] = request.body.logprobs; | ||
| 965 | bodyParams['logprobs'] = true; | ||
| 966 | } | ||
| 967 | |||
| 968 | request.body.messages = postProcessPrompt(request.body.messages, 'deepseek', getPromptNames(request)); | ||
| 969 | } else { | 1066 | } else { |
| 970 | console.log('This chat completion source is not supported yet.'); | 1067 | console.warn('This chat completion source is not supported yet.'); |
| 971 | return response.status(400).send({ error: true }); | 1068 | return response.status(400).send({ error: true }); |
| 972 | } | 1069 | } |
| 973 | 1070 | ||
| 1071 | // A few of OpenAIs reasoning models support reasoning effort | ||
| 1072 | if ([CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) { | ||
| 1073 | if (['o1', 'o3-mini', 'o3-mini-2025-01-31'].includes(request.body.model)) { | ||
| 1074 | bodyParams['reasoning_effort'] = request.body.reasoning_effort; | ||
| 1075 | } | ||
| 1076 | } | ||
| 1077 | |||
| 974 | if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { | 1078 | if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) { |
| 975 | console.log('OpenAI API key is missing.'); | 1079 | console.warn('OpenAI API key is missing.'); |
| 976 | return response.status(400).send({ error: true }); | 1080 | return response.status(400).send({ error: true }); |
| 977 | } | 1081 | } |
| 978 | 1082 | ||
| @@ -1032,7 +1136,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1032 | signal: controller.signal, | 1136 | signal: controller.signal, |
| 1033 | }; | 1137 | }; |
| 1034 | 1138 | ||
| 1035 | console.log(requestBody); | 1139 | console.debug(requestBody); |
| 1036 | 1140 | ||
| 1037 | makeRequest(config, response, request); | 1141 | makeRequest(config, response, request); |
| 1038 | 1142 | ||
| @@ -1049,7 +1153,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1049 | const fetchResponse = await fetch(endpointUrl, config); | 1153 | const fetchResponse = await fetch(endpointUrl, config); |
| 1050 | 1154 | ||
| 1051 | if (request.body.stream) { | 1155 | if (request.body.stream) { |
| 1052 | console.log('Streaming request in progress'); | 1156 | console.info('Streaming request in progress'); |
| 1053 | forwardFetchResponse(fetchResponse, response); | 1157 | forwardFetchResponse(fetchResponse, response); |
| 1054 | return; | 1158 | return; |
| 1055 | } | 1159 | } |
| @@ -1058,10 +1162,10 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1058 | /** @type {any} */ | 1162 | /** @type {any} */ |
| 1059 | let json = await fetchResponse.json(); | 1163 | let json = await fetchResponse.json(); |
| 1060 | response.send(json); | 1164 | response.send(json); |
| 1061 | console.log(json); | 1165 | console.debug(json); |
| 1062 | console.log(json?.choices?.[0]?.message); | 1166 | console.debug(json?.choices?.[0]?.message); |
| 1063 | } else if (fetchResponse.status === 429 && retries > 0) { | 1167 | } else if (fetchResponse.status === 429 && retries > 0) { |
| 1064 | console.log(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`); | 1168 | console.warn(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`); |
| 1065 | setTimeout(() => { | 1169 | setTimeout(() => { |
| 1066 | timeout *= 2; | 1170 | timeout *= 2; |
| 1067 | makeRequest(config, response, request, retries - 1, timeout); | 1171 | makeRequest(config, response, request, retries - 1, timeout); |
| @@ -1070,7 +1174,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1070 | await handleErrorResponse(fetchResponse); | 1174 | await handleErrorResponse(fetchResponse); |
| 1071 | } | 1175 | } |
| 1072 | } catch (error) { | 1176 | } catch (error) { |
| 1073 | console.log('Generation failed', error); | 1177 | console.error('Generation failed', error); |
| 1074 | const message = error.code === 'ECONNREFUSED' | 1178 | const message = error.code === 'ECONNREFUSED' |
| 1075 | ? `Connection refused: ${error.message}` | 1179 | ? `Connection refused: ${error.message}` |
| 1076 | : error.message || 'Unknown error occurred'; | 1180 | : error.message || 'Unknown error occurred'; |
| @@ -1092,7 +1196,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1092 | 1196 | ||
| 1093 | const message = errorResponse.statusText || 'Unknown error occurred'; | 1197 | const message = errorResponse.statusText || 'Unknown error occurred'; |
| 1094 | const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota'; | 1198 | const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota'; |
| 1095 | console.log('Chat completion request error: ', message, responseText); | 1199 | console.error('Chat completion request error: ', message, responseText); |
| 1096 | 1200 | ||
| 1097 | if (!response.headersSent) { | 1201 | if (!response.headersSent) { |
| 1098 | response.send({ error: { message }, quota_error: quota_error }); | 1202 | response.send({ error: { message }, quota_error: quota_error }); |
| @@ -1103,4 +1207,3 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1103 | } | 1207 | } |
| 1104 | } | 1208 | } |
| 1105 | }); | 1209 | }); |
| 1106 | |||
| @@ -22,17 +22,17 @@ router.post('/generate', jsonParser, async function (request, response_generate) | |||
| 22 | request.socket.on('close', async function () { | 22 | request.socket.on('close', async function () { |
| 23 | if (request.body.can_abort && !response_generate.writableEnded) { | 23 | if (request.body.can_abort && !response_generate.writableEnded) { |
| 24 | try { | 24 | try { |
| 25 | console.log('Aborting Kobold generation...'); | 25 | console.info('Aborting Kobold generation...'); |
| 26 | // send abort signal to koboldcpp | 26 | // send abort signal to koboldcpp |
| 27 | const abortResponse = await fetch(`${request.body.api_server}/extra/abort`, { | 27 | const abortResponse = await fetch(`${request.body.api_server}/extra/abort`, { |
| 28 | method: 'POST', | 28 | method: 'POST', |
| 29 | }); | 29 | }); |
| 30 | 30 | ||
| 31 | if (!abortResponse.ok) { | 31 | if (!abortResponse.ok) { |
| 32 | console.log('Error sending abort request to Kobold:', abortResponse.status); | 32 | console.error('Error sending abort request to Kobold:', abortResponse.status); |
| 33 | } | 33 | } |
| 34 | } catch (error) { | 34 | } catch (error) { |
| 35 | console.log(error); | 35 | console.error(error); |
| 36 | } | 36 | } |
| 37 | } | 37 | } |
| 38 | controller.abort(); | 38 | controller.abort(); |
| @@ -81,7 +81,7 @@ router.post('/generate', jsonParser, async function (request, response_generate) | |||
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | console.log(this_settings); | 84 | console.debug(this_settings); |
| 85 | const args = { | 85 | const args = { |
| 86 | body: JSON.stringify(this_settings), | 86 | body: JSON.stringify(this_settings), |
| 87 | headers: Object.assign( | 87 | headers: Object.assign( |
| @@ -105,7 +105,7 @@ router.post('/generate', jsonParser, async function (request, response_generate) | |||
| 105 | } else { | 105 | } else { |
| 106 | if (!response.ok) { | 106 | if (!response.ok) { |
| 107 | const errorText = await response.text(); | 107 | const errorText = await response.text(); |
| 108 | console.log(`Kobold returned error: ${response.status} ${response.statusText} ${errorText}`); | 108 | console.warn(`Kobold returned error: ${response.status} ${response.statusText} ${errorText}`); |
| 109 | 109 | ||
| 110 | try { | 110 | try { |
| 111 | const errorJson = JSON.parse(errorText); | 111 | const errorJson = JSON.parse(errorText); |
| @@ -117,7 +117,7 @@ router.post('/generate', jsonParser, async function (request, response_generate) | |||
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | const data = await response.json(); | 119 | const data = await response.json(); |
| 120 | console.log('Endpoint response:', data); | 120 | console.debug('Endpoint response:', data); |
| 121 | return response_generate.send(data); | 121 | return response_generate.send(data); |
| 122 | } | 122 | } |
| 123 | } catch (error) { | 123 | } catch (error) { |
| @@ -125,19 +125,19 @@ router.post('/generate', jsonParser, async function (request, response_generate) | |||
| 125 | switch (error?.status) { | 125 | switch (error?.status) { |
| 126 | case 403: | 126 | case 403: |
| 127 | case 503: // retry in case of temporary service issue, possibly caused by a queue failure? | 127 | case 503: // retry in case of temporary service issue, possibly caused by a queue failure? |
| 128 | console.debug(`KoboldAI is busy. Retry attempt ${i + 1} of ${MAX_RETRIES}...`); | 128 | console.warn(`KoboldAI is busy. Retry attempt ${i + 1} of ${MAX_RETRIES}...`); |
| 129 | await delay(delayAmount); | 129 | await delay(delayAmount); |
| 130 | break; | 130 | break; |
| 131 | default: | 131 | default: |
| 132 | if ('status' in error) { | 132 | if ('status' in error) { |
| 133 | console.log('Status Code from Kobold:', error.status); | 133 | console.error('Status Code from Kobold:', error.status); |
| 134 | } | 134 | } |
| 135 | return response_generate.send({ error: true }); | 135 | return response_generate.send({ error: true }); |
| 136 | } | 136 | } |
| 137 | } | 137 | } |
| 138 | } | 138 | } |
| 139 | 139 | ||
| 140 | console.log('Max retries exceeded. Giving up.'); | 140 | console.error('Max retries exceeded. Giving up.'); |
| 141 | return response_generate.send({ error: true }); | 141 | return response_generate.send({ error: true }); |
| 142 | }); | 142 | }); |
| 143 | 143 | ||
| @@ -193,16 +193,16 @@ router.post('/transcribe-audio', urlencodedParser, async function (request, resp | |||
| 193 | const server = request.body.server; | 193 | const server = request.body.server; |
| 194 | 194 | ||
| 195 | if (!server) { | 195 | if (!server) { |
| 196 | console.log('Server is not set'); | 196 | console.error('Server is not set'); |
| 197 | return response.sendStatus(400); | 197 | return response.sendStatus(400); |
| 198 | } | 198 | } |
| 199 | 199 | ||
| 200 | if (!request.file) { | 200 | if (!request.file) { |
| 201 | console.log('No audio file found'); | 201 | console.error('No audio file found'); |
| 202 | return response.sendStatus(400); | 202 | return response.sendStatus(400); |
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | console.log('Transcribing audio with KoboldCpp', server); | 205 | console.debug('Transcribing audio with KoboldCpp', server); |
| 206 | 206 | ||
| 207 | const fileBase64 = fs.readFileSync(request.file.path).toString('base64'); | 207 | const fileBase64 = fs.readFileSync(request.file.path).toString('base64'); |
| 208 | fs.rmSync(request.file.path); | 208 | fs.rmSync(request.file.path); |
| @@ -226,12 +226,12 @@ router.post('/transcribe-audio', urlencodedParser, async function (request, resp | |||
| 226 | 226 | ||
| 227 | if (!result.ok) { | 227 | if (!result.ok) { |
| 228 | const text = await result.text(); | 228 | const text = await result.text(); |
| 229 | console.log('KoboldCpp request failed', result.statusText, text); | 229 | console.error('KoboldCpp request failed', result.statusText, text); |
| 230 | return response.status(500).send(text); | 230 | return response.status(500).send(text); |
| 231 | } | 231 | } |
| 232 | 232 | ||
| 233 | const data = await result.json(); | 233 | const data = await result.json(); |
| 234 | console.log('KoboldCpp transcription response', data); | 234 | console.debug('KoboldCpp transcription response', data); |
| 235 | return response.json(data); | 235 | return response.json(data); |
| 236 | } catch (error) { | 236 | } catch (error) { |
| 237 | console.error('KoboldCpp transcription failed', error); | 237 | console.error('KoboldCpp transcription failed', error); |
| @@ -13,7 +13,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 13 | const cookie = readSecret(request.user.directories, SECRET_KEYS.SCALE_COOKIE); | 13 | const cookie = readSecret(request.user.directories, SECRET_KEYS.SCALE_COOKIE); |
| 14 | 14 | ||
| 15 | if (!cookie) { | 15 | if (!cookie) { |
| 16 | console.log('No Scale cookie found'); | 16 | console.error('No Scale cookie found'); |
| 17 | return response.sendStatus(400); | 17 | return response.sendStatus(400); |
| 18 | } | 18 | } |
| 19 | 19 | ||
| @@ -62,7 +62,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 62 | }, | 62 | }, |
| 63 | }; | 63 | }; |
| 64 | 64 | ||
| 65 | console.log('Scale request:', body); | 65 | console.debug('Scale request:', body); |
| 66 | 66 | ||
| 67 | const result = await fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', { | 67 | const result = await fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', { |
| 68 | method: 'POST', | 68 | method: 'POST', |
| @@ -75,7 +75,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 75 | 75 | ||
| 76 | if (!result.ok) { | 76 | if (!result.ok) { |
| 77 | const text = await result.text(); | 77 | const text = await result.text(); |
| 78 | console.log('Scale request failed', result.statusText, text); | 78 | console.error('Scale request failed', result.statusText, text); |
| 79 | return response.status(500).send({ error: { message: result.statusText } }); | 79 | return response.status(500).send({ error: { message: result.statusText } }); |
| 80 | } | 80 | } |
| 81 | 81 | ||
| @@ -83,7 +83,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 83 | const data = await result.json(); | 83 | const data = await result.json(); |
| 84 | const output = data?.result?.data?.json?.outputs?.[0] || ''; | 84 | const output = data?.result?.data?.json?.outputs?.[0] || ''; |
| 85 | 85 | ||
| 86 | console.log('Scale response:', data); | 86 | console.debug('Scale response:', data); |
| 87 | 87 | ||
| 88 | if (!output) { | 88 | if (!output) { |
| 89 | console.warn('Scale response is empty'); | 89 | console.warn('Scale response is empty'); |
| @@ -92,7 +92,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 92 | 92 | ||
| 93 | return response.json({ output }); | 93 | return response.json({ output }); |
| 94 | } catch (error) { | 94 | } catch (error) { |
| 95 | console.log(error); | 95 | console.error(error); |
| 96 | return response.sendStatus(500); | 96 | return response.sendStatus(500); |
| 97 | } | 97 | } |
| 98 | }); | 98 | }); |
| @@ -58,12 +58,12 @@ async function parseOllamaStream(jsonStream, request, response) { | |||
| 58 | }); | 58 | }); |
| 59 | 59 | ||
| 60 | jsonStream.body.on('end', () => { | 60 | jsonStream.body.on('end', () => { |
| 61 | console.log('Streaming request finished'); | 61 | console.info('Streaming request finished'); |
| 62 | response.write('data: [DONE]\n\n'); | 62 | response.write('data: [DONE]\n\n'); |
| 63 | response.end(); | 63 | response.end(); |
| 64 | }); | 64 | }); |
| 65 | } catch (error) { | 65 | } catch (error) { |
| 66 | console.log('Error forwarding streaming response:', error); | 66 | console.error('Error forwarding streaming response:', error); |
| 67 | if (!response.headersSent) { | 67 | if (!response.headersSent) { |
| 68 | return response.status(500).send({ error: true }); | 68 | return response.status(500).send({ error: true }); |
| 69 | } else { | 69 | } else { |
| @@ -79,16 +79,16 @@ async function parseOllamaStream(jsonStream, request, response) { | |||
| 79 | */ | 79 | */ |
| 80 | async function abortKoboldCppRequest(url) { | 80 | async function abortKoboldCppRequest(url) { |
| 81 | try { | 81 | try { |
| 82 | console.log('Aborting Kobold generation...'); | 82 | console.info('Aborting Kobold generation...'); |
| 83 | const abortResponse = await fetch(`${url}/api/extra/abort`, { | 83 | const abortResponse = await fetch(`${url}/api/extra/abort`, { |
| 84 | method: 'POST', | 84 | method: 'POST', |
| 85 | }); | 85 | }); |
| 86 | 86 | ||
| 87 | if (!abortResponse.ok) { | 87 | if (!abortResponse.ok) { |
| 88 | console.log('Error sending abort request to Kobold:', abortResponse.status, abortResponse.statusText); | 88 | console.error('Error sending abort request to Kobold:', abortResponse.status, abortResponse.statusText); |
| 89 | } | 89 | } |
| 90 | } catch (error) { | 90 | } catch (error) { |
| 91 | console.log(error); | 91 | console.error(error); |
| 92 | } | 92 | } |
| 93 | } | 93 | } |
| 94 | 94 | ||
| @@ -101,7 +101,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 101 | request.body.api_server = request.body.api_server.replace('localhost', '127.0.0.1'); | 101 | request.body.api_server = request.body.api_server.replace('localhost', '127.0.0.1'); |
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | console.log('Trying to connect to API:', request.body); | 104 | console.debug('Trying to connect to API', request.body); |
| 105 | const baseUrl = trimV1(request.body.api_server); | 105 | const baseUrl = trimV1(request.body.api_server); |
| 106 | 106 | ||
| 107 | const args = { | 107 | const args = { |
| @@ -123,6 +123,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 123 | case TEXTGEN_TYPES.LLAMACPP: | 123 | case TEXTGEN_TYPES.LLAMACPP: |
| 124 | case TEXTGEN_TYPES.INFERMATICAI: | 124 | case TEXTGEN_TYPES.INFERMATICAI: |
| 125 | case TEXTGEN_TYPES.OPENROUTER: | 125 | case TEXTGEN_TYPES.OPENROUTER: |
| 126 | case TEXTGEN_TYPES.FEATHERLESS: | ||
| 126 | url += '/v1/models'; | 127 | url += '/v1/models'; |
| 127 | break; | 128 | break; |
| 128 | case TEXTGEN_TYPES.DREAMGEN: | 129 | case TEXTGEN_TYPES.DREAMGEN: |
| @@ -140,9 +141,6 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 140 | case TEXTGEN_TYPES.OLLAMA: | 141 | case TEXTGEN_TYPES.OLLAMA: |
| 141 | url += '/api/tags'; | 142 | url += '/api/tags'; |
| 142 | break; | 143 | break; |
| 143 | case TEXTGEN_TYPES.FEATHERLESS: | ||
| 144 | url += '/v1/models'; | ||
| 145 | break; | ||
| 146 | case TEXTGEN_TYPES.HUGGINGFACE: | 144 | case TEXTGEN_TYPES.HUGGINGFACE: |
| 147 | url += '/info'; | 145 | url += '/info'; |
| 148 | break; | 146 | break; |
| @@ -152,7 +150,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 152 | const isPossiblyLmStudio = modelsReply.headers.get('x-powered-by') === 'Express'; | 150 | const isPossiblyLmStudio = modelsReply.headers.get('x-powered-by') === 'Express'; |
| 153 | 151 | ||
| 154 | if (!modelsReply.ok) { | 152 | if (!modelsReply.ok) { |
| 155 | console.log('Models endpoint is offline.'); | 153 | console.error('Models endpoint is offline.'); |
| 156 | return response.sendStatus(400); | 154 | return response.sendStatus(400); |
| 157 | } | 155 | } |
| 158 | 156 | ||
| @@ -173,12 +171,12 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 173 | } | 171 | } |
| 174 | 172 | ||
| 175 | if (!Array.isArray(data.data)) { | 173 | if (!Array.isArray(data.data)) { |
| 176 | console.log('Models response is not an array.'); | 174 | console.error('Models response is not an array.'); |
| 177 | return response.sendStatus(400); | 175 | return response.sendStatus(400); |
| 178 | } | 176 | } |
| 179 | 177 | ||
| 180 | const modelIds = data.data.map(x => x.id); | 178 | const modelIds = data.data.map(x => x.id); |
| 181 | console.log('Models available:', modelIds); | 179 | console.info('Models available:', modelIds); |
| 182 | 180 | ||
| 183 | // Set result to the first model ID | 181 | // Set result to the first model ID |
| 184 | result = modelIds[0] || 'Valid'; | 182 | result = modelIds[0] || 'Valid'; |
| @@ -191,7 +189,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 191 | if (modelInfoReply.ok) { | 189 | if (modelInfoReply.ok) { |
| 192 | /** @type {any} */ | 190 | /** @type {any} */ |
| 193 | const modelInfo = await modelInfoReply.json(); | 191 | const modelInfo = await modelInfoReply.json(); |
| 194 | console.log('Ooba model info:', modelInfo); | 192 | console.debug('Ooba model info:', modelInfo); |
| 195 | 193 | ||
| 196 | const modelName = modelInfo?.model_name; | 194 | const modelName = modelInfo?.model_name; |
| 197 | result = modelName || result; | 195 | result = modelName || result; |
| @@ -208,7 +206,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 208 | if (modelInfoReply.ok) { | 206 | if (modelInfoReply.ok) { |
| 209 | /** @type {any} */ | 207 | /** @type {any} */ |
| 210 | const modelInfo = await modelInfoReply.json(); | 208 | const modelInfo = await modelInfoReply.json(); |
| 211 | console.log('Tabby model info:', modelInfo); | 209 | console.debug('Tabby model info:', modelInfo); |
| 212 | 210 | ||
| 213 | const modelName = modelInfo?.id; | 211 | const modelName = modelInfo?.id; |
| 214 | result = modelName || result; | 212 | result = modelName || result; |
| @@ -255,7 +253,7 @@ router.post('/props', jsonParser, async function (request, response) { | |||
| 255 | props['chat_template'] = props['chat_template'].slice(0, -1) + '\n'; | 253 | props['chat_template'] = props['chat_template'].slice(0, -1) + '\n'; |
| 256 | } | 254 | } |
| 257 | props['chat_template_hash'] = createHash('sha256').update(props['chat_template']).digest('hex'); | 255 | props['chat_template_hash'] = createHash('sha256').update(props['chat_template']).digest('hex'); |
| 258 | console.log(`Model properties: ${JSON.stringify(props)}`); | 256 | console.debug(`Model properties: ${JSON.stringify(props)}`); |
| 259 | return response.send(props); | 257 | return response.send(props); |
| 260 | } catch (error) { | 258 | } catch (error) { |
| 261 | console.error(error); | 259 | console.error(error); |
| @@ -273,7 +271,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 273 | 271 | ||
| 274 | const apiType = request.body.api_type; | 272 | const apiType = request.body.api_type; |
| 275 | const baseUrl = request.body.api_server; | 273 | const baseUrl = request.body.api_server; |
| 276 | console.log(request.body); | 274 | console.debug(request.body); |
| 277 | 275 | ||
| 278 | const controller = new AbortController(); | 276 | const controller = new AbortController(); |
| 279 | request.socket.removeAllListeners('close'); | 277 | request.socket.removeAllListeners('close'); |
| @@ -375,6 +373,10 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 375 | 373 | ||
| 376 | if (request.body.api_type === TEXTGEN_TYPES.OLLAMA) { | 374 | if (request.body.api_type === TEXTGEN_TYPES.OLLAMA) { |
| 377 | const keepAlive = getConfigValue('ollama.keepAlive', -1); | 375 | const keepAlive = getConfigValue('ollama.keepAlive', -1); |
| 376 | const numBatch = getConfigValue('ollama.batchSize', -1); | ||
| 377 | if (numBatch > 0) { | ||
| 378 | request.body['num_batch'] = numBatch; | ||
| 379 | } | ||
| 378 | args.body = JSON.stringify({ | 380 | args.body = JSON.stringify({ |
| 379 | model: request.body.model, | 381 | model: request.body.model, |
| 380 | prompt: request.body.prompt, | 382 | prompt: request.body.prompt, |
| @@ -399,7 +401,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 399 | if (completionsReply.ok) { | 401 | if (completionsReply.ok) { |
| 400 | /** @type {any} */ | 402 | /** @type {any} */ |
| 401 | const data = await completionsReply.json(); | 403 | const data = await completionsReply.json(); |
| 402 | console.log('Endpoint response:', data); | 404 | console.debug('Endpoint response:', data); |
| 403 | 405 | ||
| 404 | // Map InfermaticAI response to OAI completions format | 406 | // Map InfermaticAI response to OAI completions format |
| 405 | if (apiType === TEXTGEN_TYPES.INFERMATICAI) { | 407 | if (apiType === TEXTGEN_TYPES.INFERMATICAI) { |
| @@ -411,24 +413,20 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 411 | const text = await completionsReply.text(); | 413 | const text = await completionsReply.text(); |
| 412 | const errorBody = { error: true, status: completionsReply.status, response: text }; | 414 | const errorBody = { error: true, status: completionsReply.status, response: text }; |
| 413 | 415 | ||
| 414 | if (!response.headersSent) { | 416 | return !response.headersSent |
| 415 | return response.send(errorBody); | 417 | ? response.send(errorBody) |
| 416 | } | 418 | : response.end(); |
| 417 | |||
| 418 | return response.end(); | ||
| 419 | } | 419 | } |
| 420 | } | 420 | } |
| 421 | } catch (error) { | 421 | } catch (error) { |
| 422 | const status = error?.status ?? error?.code ?? 'UNKNOWN'; | 422 | const status = error?.status ?? error?.code ?? 'UNKNOWN'; |
| 423 | const text = error?.error ?? error?.statusText ?? error?.message ?? 'Unknown error on /generate endpoint'; | 423 | const text = error?.error ?? error?.statusText ?? error?.message ?? 'Unknown error on /generate endpoint'; |
| 424 | let value = { error: true, status: status, response: text }; | 424 | let value = { error: true, status: status, response: text }; |
| 425 | console.log('Endpoint error:', error); | 425 | console.error('Endpoint error:', error); |
| 426 | 426 | ||
| 427 | if (!response.headersSent) { | 427 | return !response.headersSent |
| 428 | return response.send(value); | 428 | ? response.send(value) |
| 429 | } | 429 | : response.end(); |
| 430 | |||
| 431 | return response.end(); | ||
| 432 | } | 430 | } |
| 433 | }); | 431 | }); |
| 434 | 432 | ||
| @@ -451,7 +449,7 @@ ollama.post('/download', jsonParser, async function (request, response) { | |||
| 451 | }); | 449 | }); |
| 452 | 450 | ||
| 453 | if (!fetchResponse.ok) { | 451 | if (!fetchResponse.ok) { |
| 454 | console.log('Download error:', fetchResponse.status, fetchResponse.statusText); | 452 | console.error('Download error:', fetchResponse.status, fetchResponse.statusText); |
| 455 | return response.status(fetchResponse.status).send({ error: true }); | 453 | return response.status(fetchResponse.status).send({ error: true }); |
| 456 | } | 454 | } |
| 457 | 455 | ||
| @@ -468,7 +466,7 @@ ollama.post('/caption-image', jsonParser, async function (request, response) { | |||
| 468 | return response.sendStatus(400); | 466 | return response.sendStatus(400); |
| 469 | } | 467 | } |
| 470 | 468 | ||
| 471 | console.log('Ollama caption request:', request.body); | 469 | console.debug('Ollama caption request:', request.body); |
| 472 | const baseUrl = trimV1(request.body.server_url); | 470 | const baseUrl = trimV1(request.body.server_url); |
| 473 | 471 | ||
| 474 | const fetchResponse = await fetch(`${baseUrl}/api/generate`, { | 472 | const fetchResponse = await fetch(`${baseUrl}/api/generate`, { |
| @@ -483,18 +481,18 @@ ollama.post('/caption-image', jsonParser, async function (request, response) { | |||
| 483 | }); | 481 | }); |
| 484 | 482 | ||
| 485 | if (!fetchResponse.ok) { | 483 | if (!fetchResponse.ok) { |
| 486 | console.log('Ollama caption error:', fetchResponse.status, fetchResponse.statusText); | 484 | console.error('Ollama caption error:', fetchResponse.status, fetchResponse.statusText); |
| 487 | return response.status(500).send({ error: true }); | 485 | return response.status(500).send({ error: true }); |
| 488 | } | 486 | } |
| 489 | 487 | ||
| 490 | /** @type {any} */ | 488 | /** @type {any} */ |
| 491 | const data = await fetchResponse.json(); | 489 | const data = await fetchResponse.json(); |
| 492 | console.log('Ollama caption response:', data); | 490 | console.debug('Ollama caption response:', data); |
| 493 | 491 | ||
| 494 | const caption = data?.response || ''; | 492 | const caption = data?.response || ''; |
| 495 | 493 | ||
| 496 | if (!caption) { | 494 | if (!caption) { |
| 497 | console.log('Ollama caption is empty.'); | 495 | console.error('Ollama caption is empty.'); |
| 498 | return response.status(500).send({ error: true }); | 496 | return response.status(500).send({ error: true }); |
| 499 | } | 497 | } |
| 500 | 498 | ||
| @@ -513,7 +511,7 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) { | |||
| 513 | return response.sendStatus(400); | 511 | return response.sendStatus(400); |
| 514 | } | 512 | } |
| 515 | 513 | ||
| 516 | console.log('LlamaCpp caption request:', request.body); | 514 | console.debug('LlamaCpp caption request:', request.body); |
| 517 | const baseUrl = trimV1(request.body.server_url); | 515 | const baseUrl = trimV1(request.body.server_url); |
| 518 | 516 | ||
| 519 | const fetchResponse = await fetch(`${baseUrl}/completion`, { | 517 | const fetchResponse = await fetch(`${baseUrl}/completion`, { |
| @@ -529,18 +527,18 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) { | |||
| 529 | }); | 527 | }); |
| 530 | 528 | ||
| 531 | if (!fetchResponse.ok) { | 529 | if (!fetchResponse.ok) { |
| 532 | console.log('LlamaCpp caption error:', fetchResponse.status, fetchResponse.statusText); | 530 | console.error('LlamaCpp caption error:', fetchResponse.status, fetchResponse.statusText); |
| 533 | return response.status(500).send({ error: true }); | 531 | return response.status(500).send({ error: true }); |
| 534 | } | 532 | } |
| 535 | 533 | ||
| 536 | /** @type {any} */ | 534 | /** @type {any} */ |
| 537 | const data = await fetchResponse.json(); | 535 | const data = await fetchResponse.json(); |
| 538 | console.log('LlamaCpp caption response:', data); | 536 | console.debug('LlamaCpp caption response:', data); |
| 539 | 537 | ||
| 540 | const caption = data?.content || ''; | 538 | const caption = data?.content || ''; |
| 541 | 539 | ||
| 542 | if (!caption) { | 540 | if (!caption) { |
| 543 | console.log('LlamaCpp caption is empty.'); | 541 | console.error('LlamaCpp caption is empty.'); |
| 544 | return response.status(500).send({ error: true }); | 542 | return response.status(500).send({ error: true }); |
| 545 | } | 543 | } |
| 546 | 544 | ||
| @@ -558,7 +556,7 @@ llamacpp.post('/props', jsonParser, async function (request, response) { | |||
| 558 | return response.sendStatus(400); | 556 | return response.sendStatus(400); |
| 559 | } | 557 | } |
| 560 | 558 | ||
| 561 | console.log('LlamaCpp props request:', request.body); | 559 | console.debug('LlamaCpp props request:', request.body); |
| 562 | const baseUrl = trimV1(request.body.server_url); | 560 | const baseUrl = trimV1(request.body.server_url); |
| 563 | 561 | ||
| 564 | const fetchResponse = await fetch(`${baseUrl}/props`, { | 562 | const fetchResponse = await fetch(`${baseUrl}/props`, { |
| @@ -566,12 +564,12 @@ llamacpp.post('/props', jsonParser, async function (request, response) { | |||
| 566 | }); | 564 | }); |
| 567 | 565 | ||
| 568 | if (!fetchResponse.ok) { | 566 | if (!fetchResponse.ok) { |
| 569 | console.log('LlamaCpp props error:', fetchResponse.status, fetchResponse.statusText); | 567 | console.error('LlamaCpp props error:', fetchResponse.status, fetchResponse.statusText); |
| 570 | return response.status(500).send({ error: true }); | 568 | return response.status(500).send({ error: true }); |
| 571 | } | 569 | } |
| 572 | 570 | ||
| 573 | const data = await fetchResponse.json(); | 571 | const data = await fetchResponse.json(); |
| 574 | console.log('LlamaCpp props response:', data); | 572 | console.debug('LlamaCpp props response:', data); |
| 575 | 573 | ||
| 576 | return response.send(data); | 574 | return response.send(data); |
| 577 | 575 | ||
| @@ -590,7 +588,7 @@ llamacpp.post('/slots', jsonParser, async function (request, response) { | |||
| 590 | return response.sendStatus(400); | 588 | return response.sendStatus(400); |
| 591 | } | 589 | } |
| 592 | 590 | ||
| 593 | console.log('LlamaCpp slots request:', request.body); | 591 | console.debug('LlamaCpp slots request:', request.body); |
| 594 | const baseUrl = trimV1(request.body.server_url); | 592 | const baseUrl = trimV1(request.body.server_url); |
| 595 | 593 | ||
| 596 | let fetchResponse; | 594 | let fetchResponse; |
| @@ -616,12 +614,12 @@ llamacpp.post('/slots', jsonParser, async function (request, response) { | |||
| 616 | } | 614 | } |
| 617 | 615 | ||
| 618 | if (!fetchResponse.ok) { | 616 | if (!fetchResponse.ok) { |
| 619 | console.log('LlamaCpp slots error:', fetchResponse.status, fetchResponse.statusText); | 617 | console.error('LlamaCpp slots error:', fetchResponse.status, fetchResponse.statusText); |
| 620 | return response.status(500).send({ error: true }); | 618 | return response.status(500).send({ error: true }); |
| 621 | } | 619 | } |
| 622 | 620 | ||
| 623 | const data = await fetchResponse.json(); | 621 | const data = await fetchResponse.json(); |
| 624 | console.log('LlamaCpp slots response:', data); | 622 | console.debug('LlamaCpp slots response:', data); |
| 625 | 623 | ||
| 626 | return response.send(data); | 624 | return response.send(data); |
| 627 | 625 | ||
| @@ -659,14 +657,14 @@ tabby.post('/download', jsonParser, async function (request, response) { | |||
| 659 | return response.status(403).send({ error: true }); | 657 | return response.status(403).send({ error: true }); |
| 660 | } | 658 | } |
| 661 | } else { | 659 | } else { |
| 662 | console.log('API Permission error:', permissionResponse.status, permissionResponse.statusText); | 660 | console.error('API Permission error:', permissionResponse.status, permissionResponse.statusText); |
| 663 | return response.status(permissionResponse.status).send({ error: true }); | 661 | return response.status(permissionResponse.status).send({ error: true }); |
| 664 | } | 662 | } |
| 665 | 663 | ||
| 666 | const fetchResponse = await fetch(`${baseUrl}/v1/download`, args); | 664 | const fetchResponse = await fetch(`${baseUrl}/v1/download`, args); |
| 667 | 665 | ||
| 668 | if (!fetchResponse.ok) { | 666 | if (!fetchResponse.ok) { |
| 669 | console.log('Download error:', fetchResponse.status, fetchResponse.statusText); | 667 | console.error('Download error:', fetchResponse.status, fetchResponse.statusText); |
| 670 | return response.status(fetchResponse.status).send({ error: true }); | 668 | return response.status(fetchResponse.status).send({ error: true }); |
| 671 | } | 669 | } |
| 672 | 670 | ||
| @@ -7,6 +7,7 @@ import sanitize from 'sanitize-filename'; | |||
| 7 | import { jsonParser, urlencodedParser } from '../express-common.js'; | 7 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 8 | import { invalidateThumbnail } from './thumbnails.js'; | 8 | import { invalidateThumbnail } from './thumbnails.js'; |
| 9 | import { getImages } from '../util.js'; | 9 | import { getImages } from '../util.js'; |
| 10 | import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; | ||
| 10 | 11 | ||
| 11 | export const router = express.Router(); | 12 | export const router = express.Router(); |
| 12 | 13 | ||
| @@ -15,7 +16,7 @@ router.post('/all', jsonParser, function (request, response) { | |||
| 15 | response.send(JSON.stringify(images)); | 16 | response.send(JSON.stringify(images)); |
| 16 | }); | 17 | }); |
| 17 | 18 | ||
| 18 | router.post('/delete', jsonParser, function (request, response) { | 19 | router.post('/delete', jsonParser, getFileNameValidationFunction('bg'), function (request, response) { |
| 19 | if (!request.body) return response.sendStatus(400); | 20 | if (!request.body) return response.sendStatus(400); |
| 20 | 21 | ||
| 21 | if (request.body.bg !== sanitize(request.body.bg)) { | 22 | if (request.body.bg !== sanitize(request.body.bg)) { |
| @@ -26,7 +27,7 @@ router.post('/delete', jsonParser, function (request, response) { | |||
| 26 | const fileName = path.join(request.user.directories.backgrounds, sanitize(request.body.bg)); | 27 | const fileName = path.join(request.user.directories.backgrounds, sanitize(request.body.bg)); |
| 27 | 28 | ||
| 28 | if (!fs.existsSync(fileName)) { | 29 | if (!fs.existsSync(fileName)) { |
| 29 | console.log('BG file not found'); | 30 | console.error('BG file not found'); |
| 30 | return response.sendStatus(400); | 31 | return response.sendStatus(400); |
| 31 | } | 32 | } |
| 32 | 33 | ||
| @@ -42,12 +43,12 @@ router.post('/rename', jsonParser, function (request, response) { | |||
| 42 | const newFileName = path.join(request.user.directories.backgrounds, sanitize(request.body.new_bg)); | 43 | const newFileName = path.join(request.user.directories.backgrounds, sanitize(request.body.new_bg)); |
| 43 | 44 | ||
| 44 | if (!fs.existsSync(oldFileName)) { | 45 | if (!fs.existsSync(oldFileName)) { |
| 45 | console.log('BG file not found'); | 46 | console.error('BG file not found'); |
| 46 | return response.sendStatus(400); | 47 | return response.sendStatus(400); |
| 47 | } | 48 | } |
| 48 | 49 | ||
| 49 | if (fs.existsSync(newFileName)) { | 50 | if (fs.existsSync(newFileName)) { |
| 50 | console.log('New BG file already exists'); | 51 | console.error('New BG file already exists'); |
| 51 | return response.sendStatus(400); | 52 | return response.sendStatus(400); |
| 52 | } | 53 | } |
| 53 | 54 | ||
| @@ -2,10 +2,10 @@ import express from 'express'; | |||
| 2 | import { jsonParser } from '../express-common.js'; | 2 | import { jsonParser } from '../express-common.js'; |
| 3 | import { getPipeline, getRawImage } from '../transformers.js'; | 3 | import { getPipeline, getRawImage } from '../transformers.js'; |
| 4 | 4 | ||
| 5 | const TASK = 'image-to-text'; | ||
| 6 | |||
| 7 | export const router = express.Router(); | 5 | export const router = express.Router(); |
| 8 | 6 | ||
| 7 | const TASK = 'image-to-text'; | ||
| 8 | |||
| 9 | router.post('/', jsonParser, async (req, res) => { | 9 | router.post('/', jsonParser, async (req, res) => { |
| 10 | try { | 10 | try { |
| 11 | const { image } = req.body; | 11 | const { image } = req.body; |
| @@ -13,14 +13,14 @@ router.post('/', jsonParser, async (req, res) => { | |||
| 13 | const rawImage = await getRawImage(image); | 13 | const rawImage = await getRawImage(image); |
| 14 | 14 | ||
| 15 | if (!rawImage) { | 15 | if (!rawImage) { |
| 16 | console.log('Failed to parse captioned image'); | 16 | console.warn('Failed to parse captioned image'); |
| 17 | return res.sendStatus(400); | 17 | return res.sendStatus(400); |
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | const pipe = await getPipeline(TASK); | 20 | const pipe = await getPipeline(TASK); |
| 21 | const result = await pipe(rawImage); | 21 | const result = await pipe(rawImage); |
| 22 | const text = result[0].generated_text; | 22 | const text = result[0].generated_text; |
| 23 | console.log('Image caption:', text); | 23 | console.info('Image caption:', text); |
| 24 | 24 | ||
| 25 | return res.json({ caption: text }); | 25 | return res.json({ caption: text }); |
| 26 | } catch (error) { | 26 | } catch (error) { |
| @@ -14,6 +14,7 @@ import jimp from 'jimp'; | |||
| 14 | 14 | ||
| 15 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; | 15 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; |
| 16 | import { jsonParser, urlencodedParser } from '../express-common.js'; | 16 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 17 | import { default as validateAvatarUrlMiddleware, getFileNameValidationFunction } from '../middleware/validateFileName.js'; | ||
| 17 | import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js'; | 18 | import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js'; |
| 18 | import { TavernCardValidator } from '../validator/TavernCardValidator.js'; | 19 | import { TavernCardValidator } from '../validator/TavernCardValidator.js'; |
| 19 | import { parse, write } from '../character-card-parser.js'; | 20 | import { parse, write } from '../character-card-parser.js'; |
| @@ -73,12 +74,18 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u | |||
| 73 | * Read the image, resize, and save it as a PNG into the buffer. | 74 | * Read the image, resize, and save it as a PNG into the buffer. |
| 74 | * @returns {Promise<Buffer>} Image buffer | 75 | * @returns {Promise<Buffer>} Image buffer |
| 75 | */ | 76 | */ |
| 76 | function getInputImage() { | 77 | async function getInputImage() { |
| 78 | try { | ||
| 77 | if (Buffer.isBuffer(inputFile)) { | 79 | if (Buffer.isBuffer(inputFile)) { |
| 78 | return parseImageBuffer(inputFile, crop); | 80 | return await parseImageBuffer(inputFile, crop); |
| 79 | } | 81 | } |
| 80 | 82 | ||
| 81 | return tryReadImage(inputFile, crop); | 83 | return await tryReadImage(inputFile, crop); |
| 84 | } catch (error) { | ||
| 85 | const message = Buffer.isBuffer(inputFile) ? 'Failed to read image buffer.' : `Failed to read image: ${inputFile}.`; | ||
| 86 | console.warn(message, 'Using a fallback image.', error); | ||
| 87 | return await fs.promises.readFile(defaultAvatarPath); | ||
| 88 | } | ||
| 82 | } | 89 | } |
| 83 | 90 | ||
| 84 | const inputImage = await getInputImage(); | 91 | const inputImage = await getInputImage(); |
| @@ -90,7 +97,7 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u | |||
| 90 | writeFileAtomicSync(outputImagePath, outputImage); | 97 | writeFileAtomicSync(outputImagePath, outputImage); |
| 91 | return true; | 98 | return true; |
| 92 | } catch (err) { | 99 | } catch (err) { |
| 93 | console.log(err); | 100 | console.error(err); |
| 94 | return false; | 101 | return false; |
| 95 | } | 102 | } |
| 96 | } | 103 | } |
| @@ -159,7 +166,7 @@ async function tryReadImage(imgPath, crop) { | |||
| 159 | } | 166 | } |
| 160 | // If it's an unsupported type of image (APNG) - just read the file as buffer | 167 | // If it's an unsupported type of image (APNG) - just read the file as buffer |
| 161 | catch (error) { | 168 | catch (error) { |
| 162 | console.log(`Failed to read image: ${imgPath}`, error); | 169 | console.error(`Failed to read image: ${imgPath}`, error); |
| 163 | return fs.readFileSync(imgPath); | 170 | return fs.readFileSync(imgPath); |
| 164 | } | 171 | } |
| 165 | } | 172 | } |
| @@ -222,12 +229,12 @@ const processCharacter = async (item, directories) => { | |||
| 222 | return character; | 229 | return character; |
| 223 | } | 230 | } |
| 224 | catch (err) { | 231 | catch (err) { |
| 225 | console.log(`Could not process character: ${item}`); | 232 | console.error(`Could not process character: ${item}`); |
| 226 | 233 | ||
| 227 | if (err instanceof SyntaxError) { | 234 | if (err instanceof SyntaxError) { |
| 228 | console.log(`${item} does not contain a valid JSON object.`); | 235 | console.error(`${item} does not contain a valid JSON object.`); |
| 229 | } else { | 236 | } else { |
| 230 | console.log('An unexpected error occurred: ', err); | 237 | console.error('An unexpected error occurred: ', err); |
| 231 | } | 238 | } |
| 232 | 239 | ||
| 233 | return { | 240 | return { |
| @@ -315,7 +322,7 @@ function readFromV2(char) { | |||
| 315 | }; | 322 | }; |
| 316 | 323 | ||
| 317 | _.forEach(fieldMappings, (v2Path, charField) => { | 324 | _.forEach(fieldMappings, (v2Path, charField) => { |
| 318 | //console.log(`Migrating field: ${charField} from ${v2Path}`); | 325 | //console.info(`Migrating field: ${charField} from ${v2Path}`); |
| 319 | const v2Value = _.get(char.data, v2Path); | 326 | const v2Value = _.get(char.data, v2Path); |
| 320 | if (_.isUndefined(v2Value)) { | 327 | if (_.isUndefined(v2Value)) { |
| 321 | let defaultValue = undefined; | 328 | let defaultValue = undefined; |
| @@ -330,15 +337,15 @@ function readFromV2(char) { | |||
| 330 | } | 337 | } |
| 331 | 338 | ||
| 332 | if (!_.isUndefined(defaultValue)) { | 339 | if (!_.isUndefined(defaultValue)) { |
| 333 | //console.debug(`Spec v2 extension data missing for field: ${charField}, using default value: ${defaultValue}`); | 340 | //console.warn(`Spec v2 extension data missing for field: ${charField}, using default value: ${defaultValue}`); |
| 334 | char[charField] = defaultValue; | 341 | char[charField] = defaultValue; |
| 335 | } else { | 342 | } else { |
| 336 | console.debug(`Char ${char['name']} has Spec v2 data missing for unknown field: ${charField}`); | 343 | console.warn(`Char ${char['name']} has Spec v2 data missing for unknown field: ${charField}`); |
| 337 | return; | 344 | return; |
| 338 | } | 345 | } |
| 339 | } | 346 | } |
| 340 | if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) { | 347 | if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) { |
| 341 | console.debug(`Char ${char['name']} has Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value); | 348 | console.warn(`Char ${char['name']} has Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value); |
| 342 | } | 349 | } |
| 343 | char[charField] = v2Value; | 350 | char[charField] = v2Value; |
| 344 | }); | 351 | }); |
| @@ -435,7 +442,7 @@ function charaFormatData(data, directories) { | |||
| 435 | } | 442 | } |
| 436 | 443 | ||
| 437 | } catch { | 444 | } catch { |
| 438 | console.debug(`Failed to read world info file: ${data.world}. Character book will not be available.`); | 445 | console.warn(`Failed to read world info file: ${data.world}. Character book will not be available.`); |
| 439 | } | 446 | } |
| 440 | } | 447 | } |
| 441 | 448 | ||
| @@ -445,7 +452,7 @@ function charaFormatData(data, directories) { | |||
| 445 | // Deep merge the extensions object | 452 | // Deep merge the extensions object |
| 446 | _.set(char, 'data.extensions', deepMerge(char.data.extensions, extensions)); | 453 | _.set(char, 'data.extensions', deepMerge(char.data.extensions, extensions)); |
| 447 | } catch { | 454 | } catch { |
| 448 | console.debug(`Failed to parse extensions JSON: ${data.extensions}`); | 455 | console.warn(`Failed to parse extensions JSON: ${data.extensions}`); |
| 449 | } | 456 | } |
| 450 | } | 457 | } |
| 451 | 458 | ||
| @@ -519,7 +526,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) { | |||
| 519 | const fileText = fs.readFileSync(uploadPath, 'utf8'); | 526 | const fileText = fs.readFileSync(uploadPath, 'utf8'); |
| 520 | fs.rmSync(uploadPath); | 527 | fs.rmSync(uploadPath); |
| 521 | const yamlData = yaml.parse(fileText); | 528 | const yamlData = yaml.parse(fileText); |
| 522 | console.log('Importing from YAML'); | 529 | console.info('Importing from YAML'); |
| 523 | yamlData.name = sanitize(yamlData.name); | 530 | yamlData.name = sanitize(yamlData.name); |
| 524 | const fileName = preservedFileName || getPngName(yamlData.name, context.request.user.directories); | 531 | const fileName = preservedFileName || getPngName(yamlData.name, context.request.user.directories); |
| 525 | let char = convertToV2({ | 532 | let char = convertToV2({ |
| @@ -552,7 +559,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) { | |||
| 552 | async function importFromCharX(uploadPath, { request }, preservedFileName) { | 559 | async function importFromCharX(uploadPath, { request }, preservedFileName) { |
| 553 | const data = fs.readFileSync(uploadPath).buffer; | 560 | const data = fs.readFileSync(uploadPath).buffer; |
| 554 | fs.rmSync(uploadPath); | 561 | fs.rmSync(uploadPath); |
| 555 | console.log('Importing from CharX'); | 562 | console.info('Importing from CharX'); |
| 556 | const cardBuffer = await extractFileFromZipBuffer(data, 'card.json'); | 563 | const cardBuffer = await extractFileFromZipBuffer(data, 'card.json'); |
| 557 | 564 | ||
| 558 | if (!cardBuffer) { | 565 | if (!cardBuffer) { |
| @@ -601,7 +608,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) { | |||
| 601 | let jsonData = JSON.parse(data); | 608 | let jsonData = JSON.parse(data); |
| 602 | 609 | ||
| 603 | if (jsonData.spec !== undefined) { | 610 | if (jsonData.spec !== undefined) { |
| 604 | console.log(`Importing from ${jsonData.spec} json`); | 611 | console.info(`Importing from ${jsonData.spec} json`); |
| 605 | importRisuSprites(request.user.directories, jsonData); | 612 | importRisuSprites(request.user.directories, jsonData); |
| 606 | unsetFavFlag(jsonData); | 613 | unsetFavFlag(jsonData); |
| 607 | jsonData = readFromV2(jsonData); | 614 | jsonData = readFromV2(jsonData); |
| @@ -611,7 +618,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) { | |||
| 611 | const result = await writeCharacterData(defaultAvatarPath, char, pngName, request); | 618 | const result = await writeCharacterData(defaultAvatarPath, char, pngName, request); |
| 612 | return result ? pngName : ''; | 619 | return result ? pngName : ''; |
| 613 | } else if (jsonData.name !== undefined) { | 620 | } else if (jsonData.name !== undefined) { |
| 614 | console.log('Importing from v1 json'); | 621 | console.info('Importing from v1 json'); |
| 615 | jsonData.name = sanitize(jsonData.name); | 622 | jsonData.name = sanitize(jsonData.name); |
| 616 | if (jsonData.creator_notes) { | 623 | if (jsonData.creator_notes) { |
| 617 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); | 624 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); |
| @@ -637,7 +644,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) { | |||
| 637 | const result = await writeCharacterData(defaultAvatarPath, charJSON, pngName, request); | 644 | const result = await writeCharacterData(defaultAvatarPath, charJSON, pngName, request); |
| 638 | return result ? pngName : ''; | 645 | return result ? pngName : ''; |
| 639 | } else if (jsonData.char_name !== undefined) {//json Pygmalion notepad | 646 | } else if (jsonData.char_name !== undefined) {//json Pygmalion notepad |
| 640 | console.log('Importing from gradio json'); | 647 | console.info('Importing from gradio json'); |
| 641 | jsonData.char_name = sanitize(jsonData.char_name); | 648 | jsonData.char_name = sanitize(jsonData.char_name); |
| 642 | if (jsonData.creator_notes) { | 649 | if (jsonData.creator_notes) { |
| 643 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); | 650 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); |
| @@ -684,7 +691,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) { | |||
| 684 | const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories); | 691 | const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories); |
| 685 | 692 | ||
| 686 | if (jsonData.spec !== undefined) { | 693 | if (jsonData.spec !== undefined) { |
| 687 | console.log(`Found a ${jsonData.spec} character file.`); | 694 | console.info(`Found a ${jsonData.spec} character file.`); |
| 688 | importRisuSprites(request.user.directories, jsonData); | 695 | importRisuSprites(request.user.directories, jsonData); |
| 689 | unsetFavFlag(jsonData); | 696 | unsetFavFlag(jsonData); |
| 690 | jsonData = readFromV2(jsonData); | 697 | jsonData = readFromV2(jsonData); |
| @@ -694,7 +701,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) { | |||
| 694 | fs.unlinkSync(uploadPath); | 701 | fs.unlinkSync(uploadPath); |
| 695 | return result ? pngName : ''; | 702 | return result ? pngName : ''; |
| 696 | } else if (jsonData.name !== undefined) { | 703 | } else if (jsonData.name !== undefined) { |
| 697 | console.log('Found a v1 character file.'); | 704 | console.info('Found a v1 character file.'); |
| 698 | 705 | ||
| 699 | if (jsonData.creator_notes) { | 706 | if (jsonData.creator_notes) { |
| 700 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); | 707 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); |
| @@ -756,7 +763,7 @@ router.post('/create', urlencodedParser, async function (request, response) { | |||
| 756 | } | 763 | } |
| 757 | }); | 764 | }); |
| 758 | 765 | ||
| 759 | router.post('/rename', jsonParser, async function (request, response) { | 766 | router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 760 | if (!request.body.avatar_url || !request.body.new_name) { | 767 | if (!request.body.avatar_url || !request.body.new_name) { |
| 761 | return response.sendStatus(400); | 768 | return response.sendStatus(400); |
| 762 | } | 769 | } |
| @@ -803,15 +810,15 @@ router.post('/rename', jsonParser, async function (request, response) { | |||
| 803 | } | 810 | } |
| 804 | }); | 811 | }); |
| 805 | 812 | ||
| 806 | router.post('/edit', urlencodedParser, async function (request, response) { | 813 | router.post('/edit', urlencodedParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 807 | if (!request.body) { | 814 | if (!request.body) { |
| 808 | console.error('Error: no response body detected'); | 815 | console.warn('Error: no response body detected'); |
| 809 | response.status(400).send('Error: no response body detected'); | 816 | response.status(400).send('Error: no response body detected'); |
| 810 | return; | 817 | return; |
| 811 | } | 818 | } |
| 812 | 819 | ||
| 813 | if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') { | 820 | if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') { |
| 814 | console.error('Error: invalid name.'); | 821 | console.warn('Error: invalid name.'); |
| 815 | response.status(400).send('Error: invalid name.'); | 822 | response.status(400).send('Error: invalid name.'); |
| 816 | return; | 823 | return; |
| 817 | } | 824 | } |
| @@ -832,6 +839,9 @@ router.post('/edit', urlencodedParser, async function (request, response) { | |||
| 832 | invalidateThumbnail(request.user.directories, 'avatar', request.body.avatar_url); | 839 | invalidateThumbnail(request.user.directories, 'avatar', request.body.avatar_url); |
| 833 | await writeCharacterData(newAvatarPath, char, targetFile, request, crop); | 840 | await writeCharacterData(newAvatarPath, char, targetFile, request, crop); |
| 834 | fs.unlinkSync(newAvatarPath); | 841 | fs.unlinkSync(newAvatarPath); |
| 842 | |||
| 843 | // Bust cache to reload the new avatar | ||
| 844 | response.setHeader('Clear-Site-Data', '"cache"'); | ||
| 835 | } | 845 | } |
| 836 | 846 | ||
| 837 | return response.sendStatus(200); | 847 | return response.sendStatus(200); |
| @@ -852,15 +862,15 @@ router.post('/edit', urlencodedParser, async function (request, response) { | |||
| 852 | * @param {Object} response - The HTTP response object. | 862 | * @param {Object} response - The HTTP response object. |
| 853 | * @returns {void} | 863 | * @returns {void} |
| 854 | */ | 864 | */ |
| 855 | router.post('/edit-attribute', jsonParser, async function (request, response) { | 865 | router.post('/edit-attribute', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 856 | console.log(request.body); | 866 | console.debug(request.body); |
| 857 | if (!request.body) { | 867 | if (!request.body) { |
| 858 | console.error('Error: no response body detected'); | 868 | console.warn('Error: no response body detected'); |
| 859 | return response.status(400).send('Error: no response body detected'); | 869 | return response.status(400).send('Error: no response body detected'); |
| 860 | } | 870 | } |
| 861 | 871 | ||
| 862 | if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') { | 872 | if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') { |
| 863 | console.error('Error: invalid name.'); | 873 | console.warn('Error: invalid name.'); |
| 864 | return response.status(400).send('Error: invalid name.'); | 874 | return response.status(400).send('Error: invalid name.'); |
| 865 | } | 875 | } |
| 866 | 876 | ||
| @@ -872,7 +882,7 @@ router.post('/edit-attribute', jsonParser, async function (request, response) { | |||
| 872 | const char = JSON.parse(charJSON); | 882 | const char = JSON.parse(charJSON); |
| 873 | //check if the field exists | 883 | //check if the field exists |
| 874 | if (char[request.body.field] === undefined && char.data[request.body.field] === undefined) { | 884 | if (char[request.body.field] === undefined && char.data[request.body.field] === undefined) { |
| 875 | console.error('Error: invalid field.'); | 885 | console.warn('Error: invalid field.'); |
| 876 | response.status(400).send('Error: invalid field.'); | 886 | response.status(400).send('Error: invalid field.'); |
| 877 | return; | 887 | return; |
| 878 | } | 888 | } |
| @@ -898,7 +908,7 @@ router.post('/edit-attribute', jsonParser, async function (request, response) { | |||
| 898 | * | 908 | * |
| 899 | * @returns {void} | 909 | * @returns {void} |
| 900 | * */ | 910 | * */ |
| 901 | router.post('/merge-attributes', jsonParser, async function (request, response) { | 911 | router.post('/merge-attributes', jsonParser, getFileNameValidationFunction('avatar'), async function (request, response) { |
| 902 | try { | 912 | try { |
| 903 | const update = request.body; | 913 | const update = request.body; |
| 904 | const avatarPath = path.join(request.user.directories.characters, update.avatar); | 914 | const avatarPath = path.join(request.user.directories.characters, update.avatar); |
| @@ -921,7 +931,7 @@ router.post('/merge-attributes', jsonParser, async function (request, response) | |||
| 921 | await writeCharacterData(avatarPath, JSON.stringify(character), targetImg, request); | 931 | await writeCharacterData(avatarPath, JSON.stringify(character), targetImg, request); |
| 922 | response.sendStatus(200); | 932 | response.sendStatus(200); |
| 923 | } else { | 933 | } else { |
| 924 | console.log(validator.lastValidationError); | 934 | console.warn(validator.lastValidationError); |
| 925 | response.status(400).send({ message: `Validation failed for ${character.name}`, error: validator.lastValidationError }); | 935 | response.status(400).send({ message: `Validation failed for ${character.name}`, error: validator.lastValidationError }); |
| 926 | } | 936 | } |
| 927 | } catch (exception) { | 937 | } catch (exception) { |
| @@ -929,7 +939,7 @@ router.post('/merge-attributes', jsonParser, async function (request, response) | |||
| 929 | } | 939 | } |
| 930 | }); | 940 | }); |
| 931 | 941 | ||
| 932 | router.post('/delete', jsonParser, async function (request, response) { | 942 | router.post('/delete', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 933 | if (!request.body || !request.body.avatar_url) { | 943 | if (!request.body || !request.body.avatar_url) { |
| 934 | return response.sendStatus(400); | 944 | return response.sendStatus(400); |
| 935 | } | 945 | } |
| @@ -992,7 +1002,7 @@ router.post('/all', jsonParser, async function (request, response) { | |||
| 992 | } | 1002 | } |
| 993 | }); | 1003 | }); |
| 994 | 1004 | ||
| 995 | router.post('/get', jsonParser, async function (request, response) { | 1005 | router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 996 | try { | 1006 | try { |
| 997 | if (!request.body) return response.sendStatus(400); | 1007 | if (!request.body) return response.sendStatus(400); |
| 998 | const item = request.body.avatar_url; | 1008 | const item = request.body.avatar_url; |
| @@ -1011,7 +1021,7 @@ router.post('/get', jsonParser, async function (request, response) { | |||
| 1011 | } | 1021 | } |
| 1012 | }); | 1022 | }); |
| 1013 | 1023 | ||
| 1014 | router.post('/chats', jsonParser, async function (request, response) { | 1024 | router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1015 | if (!request.body) return response.sendStatus(400); | 1025 | if (!request.body) return response.sendStatus(400); |
| 1016 | 1026 | ||
| 1017 | const characterDirectory = (request.body.avatar_url).replace('.png', ''); | 1027 | const characterDirectory = (request.body.avatar_url).replace('.png', ''); |
| @@ -1043,7 +1053,7 @@ router.post('/chats', jsonParser, async function (request, response) { | |||
| 1043 | const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`; | 1053 | const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`; |
| 1044 | 1054 | ||
| 1045 | if (stats.size === 0) { | 1055 | if (stats.size === 0) { |
| 1046 | console.log(`Found an empty chat file: ${pathToFile}`); | 1056 | console.warn(`Found an empty chat file: ${pathToFile}`); |
| 1047 | res({}); | 1057 | res({}); |
| 1048 | return; | 1058 | return; |
| 1049 | } | 1059 | } |
| @@ -1075,7 +1085,7 @@ router.post('/chats', jsonParser, async function (request, response) { | |||
| 1075 | 1085 | ||
| 1076 | res(chatData); | 1086 | res(chatData); |
| 1077 | } else { | 1087 | } else { |
| 1078 | console.log('Found an invalid or corrupted chat file:', pathToFile); | 1088 | console.warn('Found an invalid or corrupted chat file:', pathToFile); |
| 1079 | res({}); | 1089 | res({}); |
| 1080 | } | 1090 | } |
| 1081 | } | 1091 | } |
| @@ -1088,7 +1098,7 @@ router.post('/chats', jsonParser, async function (request, response) { | |||
| 1088 | 1098 | ||
| 1089 | return response.send(validFiles); | 1099 | return response.send(validFiles); |
| 1090 | } catch (error) { | 1100 | } catch (error) { |
| 1091 | console.log(error); | 1101 | console.error(error); |
| 1092 | return response.send({ error: true }); | 1102 | return response.send({ error: true }); |
| 1093 | } | 1103 | } |
| 1094 | }); | 1104 | }); |
| @@ -1145,7 +1155,7 @@ router.post('/import', urlencodedParser, async function (request, response) { | |||
| 1145 | const fileName = await importFunction(uploadPath, { request, response }, preservedFileName); | 1155 | const fileName = await importFunction(uploadPath, { request, response }, preservedFileName); |
| 1146 | 1156 | ||
| 1147 | if (!fileName) { | 1157 | if (!fileName) { |
| 1148 | console.error('Failed to import character'); | 1158 | console.warn('Failed to import character'); |
| 1149 | return response.sendStatus(400); | 1159 | return response.sendStatus(400); |
| 1150 | } | 1160 | } |
| 1151 | 1161 | ||
| @@ -1155,22 +1165,21 @@ router.post('/import', urlencodedParser, async function (request, response) { | |||
| 1155 | 1165 | ||
| 1156 | response.send({ file_name: fileName }); | 1166 | response.send({ file_name: fileName }); |
| 1157 | } catch (err) { | 1167 | } catch (err) { |
| 1158 | console.log(err); | 1168 | console.error(err); |
| 1159 | response.send({ error: true }); | 1169 | response.send({ error: true }); |
| 1160 | } | 1170 | } |
| 1161 | }); | 1171 | }); |
| 1162 | 1172 | ||
| 1163 | router.post('/duplicate', jsonParser, async function (request, response) { | 1173 | router.post('/duplicate', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1164 | try { | 1174 | try { |
| 1165 | if (!request.body.avatar_url) { | 1175 | if (!request.body.avatar_url) { |
| 1166 | console.log('avatar URL not found in request body'); | 1176 | console.warn('avatar URL not found in request body'); |
| 1167 | console.log(request.body); | 1177 | console.debug(request.body); |
| 1168 | return response.sendStatus(400); | 1178 | return response.sendStatus(400); |
| 1169 | } | 1179 | } |
| 1170 | let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url)); | 1180 | let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url)); |
| 1171 | if (!fs.existsSync(filename)) { | 1181 | if (!fs.existsSync(filename)) { |
| 1172 | console.log('file for dupe not found'); | 1182 | console.error('file for dupe not found', filename); |
| 1173 | console.log(filename); | ||
| 1174 | return response.sendStatus(404); | 1183 | return response.sendStatus(404); |
| 1175 | } | 1184 | } |
| 1176 | let suffix = 1; | 1185 | let suffix = 1; |
| @@ -1198,7 +1207,7 @@ router.post('/duplicate', jsonParser, async function (request, response) { | |||
| 1198 | } | 1207 | } |
| 1199 | 1208 | ||
| 1200 | fs.copyFileSync(filename, newFilename); | 1209 | fs.copyFileSync(filename, newFilename); |
| 1201 | console.log(`${filename} was copied to ${newFilename}`); | 1210 | console.info(`${filename} was copied to ${newFilename}`); |
| 1202 | response.send({ path: path.parse(newFilename).base }); | 1211 | response.send({ path: path.parse(newFilename).base }); |
| 1203 | } | 1212 | } |
| 1204 | catch (error) { | 1213 | catch (error) { |
| @@ -1207,7 +1216,7 @@ router.post('/duplicate', jsonParser, async function (request, response) { | |||
| 1207 | } | 1216 | } |
| 1208 | }); | 1217 | }); |
| 1209 | 1218 | ||
| 1210 | router.post('/export', jsonParser, async function (request, response) { | 1219 | router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1211 | try { | 1220 | try { |
| 1212 | if (!request.body.format || !request.body.avatar_url) { | 1221 | if (!request.body.format || !request.body.avatar_url) { |
| 1213 | return response.sendStatus(400); | 1222 | return response.sendStatus(400); |
| @@ -9,6 +9,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; | |||
| 9 | import _ from 'lodash'; | 9 | import _ from 'lodash'; |
| 10 | 10 | ||
| 11 | import { jsonParser, urlencodedParser } from '../express-common.js'; | 11 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 12 | import validateAvatarUrlMiddleware from '../middleware/validateFileName.js'; | ||
| 12 | import { | 13 | import { |
| 13 | getConfigValue, | 14 | getConfigValue, |
| 14 | humanizedISO8601DateTime, | 15 | humanizedISO8601DateTime, |
| @@ -49,7 +50,7 @@ function backupChat(directory, name, chat) { | |||
| 49 | 50 | ||
| 50 | removeOldBackups(directory, 'chat_', maxTotalChatBackups); | 51 | removeOldBackups(directory, 'chat_', maxTotalChatBackups); |
| 51 | } catch (err) { | 52 | } catch (err) { |
| 52 | console.log(`Could not backup chat for ${name}`, err); | 53 | console.error(`Could not backup chat for ${name}`, err); |
| 53 | } | 54 | } |
| 54 | } | 55 | } |
| 55 | 56 | ||
| @@ -294,7 +295,7 @@ function importRisuChat(userName, characterName, jsonData) { | |||
| 294 | 295 | ||
| 295 | export const router = express.Router(); | 296 | export const router = express.Router(); |
| 296 | 297 | ||
| 297 | router.post('/save', jsonParser, function (request, response) { | 298 | router.post('/save', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 298 | try { | 299 | try { |
| 299 | const directoryName = String(request.body.avatar_url).replace('.png', ''); | 300 | const directoryName = String(request.body.avatar_url).replace('.png', ''); |
| 300 | const chatData = request.body.chat; | 301 | const chatData = request.body.chat; |
| @@ -305,12 +306,12 @@ router.post('/save', jsonParser, function (request, response) { | |||
| 305 | getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData); | 306 | getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData); |
| 306 | return response.send({ result: 'ok' }); | 307 | return response.send({ result: 'ok' }); |
| 307 | } catch (error) { | 308 | } catch (error) { |
| 308 | response.send(error); | 309 | console.error(error); |
| 309 | return console.log(error); | 310 | return response.send(error); |
| 310 | } | 311 | } |
| 311 | }); | 312 | }); |
| 312 | 313 | ||
| 313 | router.post('/get', jsonParser, function (request, response) { | 314 | router.post('/get', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 314 | try { | 315 | try { |
| 315 | const dirName = String(request.body.avatar_url).replace('.png', ''); | 316 | const dirName = String(request.body.avatar_url).replace('.png', ''); |
| 316 | const directoryPath = path.join(request.user.directories.chats, dirName); | 317 | const directoryPath = path.join(request.user.directories.chats, dirName); |
| @@ -347,7 +348,7 @@ router.post('/get', jsonParser, function (request, response) { | |||
| 347 | }); | 348 | }); |
| 348 | 349 | ||
| 349 | 350 | ||
| 350 | router.post('/rename', jsonParser, async function (request, response) { | 351 | router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 351 | if (!request.body || !request.body.original_file || !request.body.renamed_file) { | 352 | if (!request.body || !request.body.original_file || !request.body.renamed_file) { |
| 352 | return response.sendStatus(400); | 353 | return response.sendStatus(400); |
| 353 | } | 354 | } |
| @@ -358,37 +359,37 @@ router.post('/rename', jsonParser, async function (request, response) { | |||
| 358 | const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file)); | 359 | const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file)); |
| 359 | const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file)); | 360 | const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file)); |
| 360 | const sanitizedFileName = path.parse(pathToRenamedFile).name; | 361 | const sanitizedFileName = path.parse(pathToRenamedFile).name; |
| 361 | console.log('Old chat name', pathToOriginalFile); | 362 | console.info('Old chat name', pathToOriginalFile); |
| 362 | console.log('New chat name', pathToRenamedFile); | 363 | console.info('New chat name', pathToRenamedFile); |
| 363 | 364 | ||
| 364 | if (!fs.existsSync(pathToOriginalFile) || fs.existsSync(pathToRenamedFile)) { | 365 | if (!fs.existsSync(pathToOriginalFile) || fs.existsSync(pathToRenamedFile)) { |
| 365 | console.log('Either Source or Destination files are not available'); | 366 | console.error('Either Source or Destination files are not available'); |
| 366 | return response.status(400).send({ error: true }); | 367 | return response.status(400).send({ error: true }); |
| 367 | } | 368 | } |
| 368 | 369 | ||
| 369 | fs.copyFileSync(pathToOriginalFile, pathToRenamedFile); | 370 | fs.copyFileSync(pathToOriginalFile, pathToRenamedFile); |
| 370 | fs.rmSync(pathToOriginalFile); | 371 | fs.rmSync(pathToOriginalFile); |
| 371 | console.log('Successfully renamed.'); | 372 | console.info('Successfully renamed.'); |
| 372 | return response.send({ ok: true, sanitizedFileName }); | 373 | return response.send({ ok: true, sanitizedFileName }); |
| 373 | }); | 374 | }); |
| 374 | 375 | ||
| 375 | router.post('/delete', jsonParser, function (request, response) { | 376 | router.post('/delete', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 376 | const dirName = String(request.body.avatar_url).replace('.png', ''); | 377 | const dirName = String(request.body.avatar_url).replace('.png', ''); |
| 377 | const fileName = String(request.body.chatfile); | 378 | const fileName = String(request.body.chatfile); |
| 378 | const filePath = path.join(request.user.directories.chats, dirName, sanitize(fileName)); | 379 | const filePath = path.join(request.user.directories.chats, dirName, sanitize(fileName)); |
| 379 | const chatFileExists = fs.existsSync(filePath); | 380 | const chatFileExists = fs.existsSync(filePath); |
| 380 | 381 | ||
| 381 | if (!chatFileExists) { | 382 | if (!chatFileExists) { |
| 382 | console.log(`Chat file not found '${filePath}'`); | 383 | console.error(`Chat file not found '${filePath}'`); |
| 383 | return response.sendStatus(400); | 384 | return response.sendStatus(400); |
| 384 | } | 385 | } |
| 385 | 386 | ||
| 386 | fs.rmSync(filePath); | 387 | fs.rmSync(filePath); |
| 387 | console.log('Deleted chat file: ' + filePath); | 388 | console.info(`Deleted chat file: ${filePath}`); |
| 388 | return response.send('ok'); | 389 | return response.send('ok'); |
| 389 | }); | 390 | }); |
| 390 | 391 | ||
| 391 | router.post('/export', jsonParser, async function (request, response) { | 392 | router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 392 | if (!request.body.file || (!request.body.avatar_url && request.body.is_group === false)) { | 393 | if (!request.body.file || (!request.body.avatar_url && request.body.is_group === false)) { |
| 393 | return response.sendStatus(400); | 394 | return response.sendStatus(400); |
| 394 | } | 395 | } |
| @@ -401,7 +402,7 @@ router.post('/export', jsonParser, async function (request, response) { | |||
| 401 | const errorMessage = { | 402 | const errorMessage = { |
| 402 | message: `Could not find JSONL file to export. Source chat file: ${filename}.`, | 403 | message: `Could not find JSONL file to export. Source chat file: ${filename}.`, |
| 403 | }; | 404 | }; |
| 404 | console.log(errorMessage.message); | 405 | console.error(errorMessage.message); |
| 405 | return response.status(404).json(errorMessage); | 406 | return response.status(404).json(errorMessage); |
| 406 | } | 407 | } |
| 407 | try { | 408 | try { |
| @@ -414,14 +415,14 @@ router.post('/export', jsonParser, async function (request, response) { | |||
| 414 | result: rawFile, | 415 | result: rawFile, |
| 415 | }; | 416 | }; |
| 416 | 417 | ||
| 417 | console.log(`Chat exported as ${exportfilename}`); | 418 | console.info(`Chat exported as ${exportfilename}`); |
| 418 | return response.status(200).json(successMessage); | 419 | return response.status(200).json(successMessage); |
| 419 | } catch (err) { | 420 | } catch (err) { |
| 420 | console.error(err); | 421 | console.error(err); |
| 421 | const errorMessage = { | 422 | const errorMessage = { |
| 422 | message: `Could not read JSONL file to export. Source chat file: ${filename}.`, | 423 | message: `Could not read JSONL file to export. Source chat file: ${filename}.`, |
| 423 | }; | 424 | }; |
| 424 | console.log(errorMessage.message); | 425 | console.error(errorMessage.message); |
| 425 | return response.status(500).json(errorMessage); | 426 | return response.status(500).json(errorMessage); |
| 426 | } | 427 | } |
| 427 | } | 428 | } |
| @@ -448,12 +449,11 @@ router.post('/export', jsonParser, async function (request, response) { | |||
| 448 | message: `Chat saved to ${exportfilename}`, | 449 | message: `Chat saved to ${exportfilename}`, |
| 449 | result: buffer, | 450 | result: buffer, |
| 450 | }; | 451 | }; |
| 451 | console.log(`Chat exported as ${exportfilename}`); | 452 | console.info(`Chat exported as ${exportfilename}`); |
| 452 | return response.status(200).json(successMessage); | 453 | return response.status(200).json(successMessage); |
| 453 | }); | 454 | }); |
| 454 | } catch (err) { | 455 | } catch (err) { |
| 455 | console.log('chat export failed.'); | 456 | console.error('chat export failed.', err); |
| 456 | console.log(err); | ||
| 457 | return response.sendStatus(400); | 457 | return response.sendStatus(400); |
| 458 | } | 458 | } |
| 459 | }); | 459 | }); |
| @@ -478,7 +478,7 @@ router.post('/group/import', urlencodedParser, function (request, response) { | |||
| 478 | } | 478 | } |
| 479 | }); | 479 | }); |
| 480 | 480 | ||
| 481 | router.post('/import', urlencodedParser, function (request, response) { | 481 | router.post('/import', urlencodedParser, validateAvatarUrlMiddleware, function (request, response) { |
| 482 | if (!request.body) return response.sendStatus(400); | 482 | if (!request.body) return response.sendStatus(400); |
| 483 | 483 | ||
| 484 | const format = request.body.file_type; | 484 | const format = request.body.file_type; |
| @@ -512,7 +512,7 @@ router.post('/import', urlencodedParser, function (request, response) { | |||
| 512 | } else if (jsonData.type === 'risuChat') { // RisuAI format | 512 | } else if (jsonData.type === 'risuChat') { // RisuAI format |
| 513 | importFunc = importRisuChat; | 513 | importFunc = importRisuChat; |
| 514 | } else { // Unknown format | 514 | } else { // Unknown format |
| 515 | console.log('Incorrect chat format .json'); | 515 | console.error('Incorrect chat format .json'); |
| 516 | return response.send({ error: true }); | 516 | return response.send({ error: true }); |
| 517 | } | 517 | } |
| 518 | 518 | ||
| @@ -540,7 +540,7 @@ router.post('/import', urlencodedParser, function (request, response) { | |||
| 540 | const jsonData = JSON.parse(header); | 540 | const jsonData = JSON.parse(header); |
| 541 | 541 | ||
| 542 | if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) { | 542 | if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) { |
| 543 | console.log('Incorrect chat format .jsonl'); | 543 | console.error('Incorrect chat format .jsonl'); |
| 544 | return response.send({ error: true }); | 544 | return response.send({ error: true }); |
| 545 | } | 545 | } |
| 546 | 546 | ||
| @@ -626,7 +626,7 @@ router.post('/group/save', jsonParser, (request, response) => { | |||
| 626 | return response.send({ ok: true }); | 626 | return response.send({ ok: true }); |
| 627 | }); | 627 | }); |
| 628 | 628 | ||
| 629 | router.post('/search', jsonParser, function (request, response) { | 629 | router.post('/search', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 630 | try { | 630 | try { |
| 631 | const { query, avatar_url, group_id } = request.body; | 631 | const { query, avatar_url, group_id } = request.body; |
| 632 | let chatFiles = []; | 632 | let chatFiles = []; |
| @@ -646,7 +646,7 @@ router.post('/search', jsonParser, function (request, response) { | |||
| 646 | break; | 646 | break; |
| 647 | } | 647 | } |
| 648 | } catch (error) { | 648 | } catch (error) { |
| 649 | console.error(groupFile, 'group file is corrupted:', error); | 649 | console.warn(groupFile, 'group file is corrupted:', error); |
| 650 | } | 650 | } |
| 651 | } | 651 | } |
| 652 | 652 | ||
| @@ -44,9 +44,9 @@ router.post('/', jsonParser, async (req, res) => { | |||
| 44 | } | 44 | } |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | console.log('Classify input:', text); | 47 | console.debug('Classify input:', text); |
| 48 | const result = await getResult(text); | 48 | const result = await getResult(text); |
| 49 | console.log('Classify output:', result); | 49 | console.debug('Classify output:', result); |
| 50 | 50 | ||
| 51 | return res.json({ classification: result }); | 51 | return res.json({ classification: result }); |
| 52 | } catch (error) { | 52 | } catch (error) { |
| @@ -71,7 +71,7 @@ export function getDefaultPresets(directories) { | |||
| 71 | 71 | ||
| 72 | return presets; | 72 | return presets; |
| 73 | } catch (err) { | 73 | } catch (err) { |
| 74 | console.log('Failed to get default presets', err); | 74 | console.warn('Failed to get default presets', err); |
| 75 | return []; | 75 | return []; |
| 76 | } | 76 | } |
| 77 | } | 77 | } |
| @@ -92,7 +92,7 @@ export function getDefaultPresetFile(filename) { | |||
| 92 | const fileContent = fs.readFileSync(contentPath, 'utf8'); | 92 | const fileContent = fs.readFileSync(contentPath, 'utf8'); |
| 93 | return JSON.parse(fileContent); | 93 | return JSON.parse(fileContent); |
| 94 | } catch (err) { | 94 | } catch (err) { |
| 95 | console.log(`Failed to get default file ${filename}`, err); | 95 | console.warn(`Failed to get default file ${filename}`, err); |
| 96 | return null; | 96 | return null; |
| 97 | } | 97 | } |
| 98 | } | 98 | } |
| @@ -121,21 +121,21 @@ async function seedContentForUser(contentIndex, directories, forceCategories) { | |||
| 121 | } | 121 | } |
| 122 | 122 | ||
| 123 | if (!contentItem.folder) { | 123 | if (!contentItem.folder) { |
| 124 | console.log(`Content file ${contentItem.filename} has no parent folder`); | 124 | console.warn(`Content file ${contentItem.filename} has no parent folder`); |
| 125 | continue; | 125 | continue; |
| 126 | } | 126 | } |
| 127 | 127 | ||
| 128 | const contentPath = path.join(contentItem.folder, contentItem.filename); | 128 | const contentPath = path.join(contentItem.folder, contentItem.filename); |
| 129 | 129 | ||
| 130 | if (!fs.existsSync(contentPath)) { | 130 | if (!fs.existsSync(contentPath)) { |
| 131 | console.log(`Content file ${contentItem.filename} is missing`); | 131 | console.warn(`Content file ${contentItem.filename} is missing`); |
| 132 | continue; | 132 | continue; |
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | const contentTarget = getTargetByType(contentItem.type, directories); | 135 | const contentTarget = getTargetByType(contentItem.type, directories); |
| 136 | 136 | ||
| 137 | if (!contentTarget) { | 137 | if (!contentTarget) { |
| 138 | console.log(`Content file ${contentItem.filename} has unknown type ${contentItem.type}`); | 138 | console.warn(`Content file ${contentItem.filename} has unknown type ${contentItem.type}`); |
| 139 | continue; | 139 | continue; |
| 140 | } | 140 | } |
| 141 | 141 | ||
| @@ -144,12 +144,12 @@ async function seedContentForUser(contentIndex, directories, forceCategories) { | |||
| 144 | contentLog.push(contentItem.filename); | 144 | contentLog.push(contentItem.filename); |
| 145 | 145 | ||
| 146 | if (fs.existsSync(targetPath)) { | 146 | if (fs.existsSync(targetPath)) { |
| 147 | console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`); | 147 | console.warn(`Content file ${contentItem.filename} already exists in ${contentTarget}`); |
| 148 | continue; | 148 | continue; |
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | fs.cpSync(contentPath, targetPath, { recursive: true, force: false }); | 151 | fs.cpSync(contentPath, targetPath, { recursive: true, force: false }); |
| 152 | console.log(`Content file ${contentItem.filename} copied to ${contentTarget}`); | 152 | console.info(`Content file ${contentItem.filename} copied to ${contentTarget}`); |
| 153 | anyContentAdded = true; | 153 | anyContentAdded = true; |
| 154 | } | 154 | } |
| 155 | 155 | ||
| @@ -182,12 +182,12 @@ export async function checkForNewContent(directoriesList, forceCategories = []) | |||
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | if (anyContentAdded && !contentCheckSkip && forceCategories?.length === 0) { | 184 | if (anyContentAdded && !contentCheckSkip && forceCategories?.length === 0) { |
| 185 | console.log(); | 185 | console.info(); |
| 186 | console.log(`${color.blue('If you don\'t want to receive content updates in the future, set')} ${color.yellow('skipContentCheck')} ${color.blue('to true in the config.yaml file.')}`); | 186 | console.info(`${color.blue('If you don\'t want to receive content updates in the future, set')} ${color.yellow('skipContentCheck')} ${color.blue('to true in the config.yaml file.')}`); |
| 187 | console.log(); | 187 | console.info(); |
| 188 | } | 188 | } |
| 189 | } catch (err) { | 189 | } catch (err) { |
| 190 | console.log('Content check failed', err); | 190 | console.error('Content check failed', err); |
| 191 | } | 191 | } |
| 192 | } | 192 | } |
| 193 | 193 | ||
| @@ -331,7 +331,7 @@ async function downloadChubLorebook(id) { | |||
| 331 | 331 | ||
| 332 | if (!result.ok) { | 332 | if (!result.ok) { |
| 333 | const text = await result.text(); | 333 | const text = await result.text(); |
| 334 | console.log('Chub returned error', result.statusText, text); | 334 | console.error('Chub returned error', result.statusText, text); |
| 335 | throw new Error('Failed to download lorebook'); | 335 | throw new Error('Failed to download lorebook'); |
| 336 | } | 336 | } |
| 337 | 337 | ||
| @@ -355,7 +355,7 @@ async function downloadChubCharacter(id) { | |||
| 355 | 355 | ||
| 356 | if (!result.ok) { | 356 | if (!result.ok) { |
| 357 | const text = await result.text(); | 357 | const text = await result.text(); |
| 358 | console.log('Chub returned error', result.statusText, text); | 358 | console.error('Chub returned error', result.statusText, text); |
| 359 | throw new Error('Failed to download character'); | 359 | throw new Error('Failed to download character'); |
| 360 | } | 360 | } |
| 361 | 361 | ||
| @@ -376,7 +376,7 @@ async function downloadPygmalionCharacter(id) { | |||
| 376 | 376 | ||
| 377 | if (!result.ok) { | 377 | if (!result.ok) { |
| 378 | const text = await result.text(); | 378 | const text = await result.text(); |
| 379 | console.log('Pygsite returned error', result.status, text); | 379 | console.error('Pygsite returned error', result.status, text); |
| 380 | throw new Error('Failed to download character'); | 380 | throw new Error('Failed to download character'); |
| 381 | } | 381 | } |
| 382 | 382 | ||
| @@ -485,7 +485,7 @@ async function downloadJannyCharacter(uuid) { | |||
| 485 | } | 485 | } |
| 486 | } | 486 | } |
| 487 | 487 | ||
| 488 | console.log('Janny returned error', result.statusText, await result.text()); | 488 | console.error('Janny returned error', result.statusText, await result.text()); |
| 489 | throw new Error('Failed to download character'); | 489 | throw new Error('Failed to download character'); |
| 490 | } | 490 | } |
| 491 | 491 | ||
| @@ -577,7 +577,7 @@ async function downloadRisuCharacter(uuid) { | |||
| 577 | 577 | ||
| 578 | if (!result.ok) { | 578 | if (!result.ok) { |
| 579 | const text = await result.text(); | 579 | const text = await result.text(); |
| 580 | console.log('RisuAI returned error', result.statusText, text); | 580 | console.error('RisuAI returned error', result.statusText, text); |
| 581 | throw new Error('Failed to download character'); | 581 | throw new Error('Failed to download character'); |
| 582 | } | 582 | } |
| 583 | 583 | ||
| @@ -673,11 +673,11 @@ router.post('/importURL', jsonParser, async (request, response) => { | |||
| 673 | type = chubParsed?.type; | 673 | type = chubParsed?.type; |
| 674 | 674 | ||
| 675 | if (chubParsed?.type === 'character') { | 675 | if (chubParsed?.type === 'character') { |
| 676 | console.log('Downloading chub character:', chubParsed.id); | 676 | console.info('Downloading chub character:', chubParsed.id); |
| 677 | result = await downloadChubCharacter(chubParsed.id); | 677 | result = await downloadChubCharacter(chubParsed.id); |
| 678 | } | 678 | } |
| 679 | else if (chubParsed?.type === 'lorebook') { | 679 | else if (chubParsed?.type === 'lorebook') { |
| 680 | console.log('Downloading chub lorebook:', chubParsed.id); | 680 | console.info('Downloading chub lorebook:', chubParsed.id); |
| 681 | result = await downloadChubLorebook(chubParsed.id); | 681 | result = await downloadChubLorebook(chubParsed.id); |
| 682 | } | 682 | } |
| 683 | else { | 683 | else { |
| @@ -692,7 +692,7 @@ router.post('/importURL', jsonParser, async (request, response) => { | |||
| 692 | type = 'character'; | 692 | type = 'character'; |
| 693 | result = await downloadRisuCharacter(uuid); | 693 | result = await downloadRisuCharacter(uuid); |
| 694 | } else if (isGeneric) { | 694 | } else if (isGeneric) { |
| 695 | console.log('Downloading from generic url.'); | 695 | console.info('Downloading from generic url.'); |
| 696 | type = 'character'; | 696 | type = 'character'; |
| 697 | result = await downloadGenericPng(url); | 697 | result = await downloadGenericPng(url); |
| 698 | } else { | 698 | } else { |
| @@ -708,7 +708,7 @@ router.post('/importURL', jsonParser, async (request, response) => { | |||
| 708 | response.set('X-Custom-Content-Type', type); | 708 | response.set('X-Custom-Content-Type', type); |
| 709 | return response.send(result.buffer); | 709 | return response.send(result.buffer); |
| 710 | } catch (error) { | 710 | } catch (error) { |
| 711 | console.log('Importing custom content failed', error); | 711 | console.error('Importing custom content failed', error); |
| 712 | return response.sendStatus(500); | 712 | return response.sendStatus(500); |
| 713 | } | 713 | } |
| 714 | }); | 714 | }); |
| @@ -728,22 +728,22 @@ router.post('/importUUID', jsonParser, async (request, response) => { | |||
| 728 | const uuidType = uuid.includes('lorebook') ? 'lorebook' : 'character'; | 728 | const uuidType = uuid.includes('lorebook') ? 'lorebook' : 'character'; |
| 729 | 729 | ||
| 730 | if (isPygmalion) { | 730 | if (isPygmalion) { |
| 731 | console.log('Downloading Pygmalion character:', uuid); | 731 | console.info('Downloading Pygmalion character:', uuid); |
| 732 | result = await downloadPygmalionCharacter(uuid); | 732 | result = await downloadPygmalionCharacter(uuid); |
| 733 | } else if (isJannny) { | 733 | } else if (isJannny) { |
| 734 | console.log('Downloading Janitor character:', uuid.split('_')[0]); | 734 | console.info('Downloading Janitor character:', uuid.split('_')[0]); |
| 735 | result = await downloadJannyCharacter(uuid.split('_')[0]); | 735 | result = await downloadJannyCharacter(uuid.split('_')[0]); |
| 736 | } else if (isAICC) { | 736 | } else if (isAICC) { |
| 737 | const [, author, card] = uuid.split('/'); | 737 | const [, author, card] = uuid.split('/'); |
| 738 | console.log('Downloading AICC character:', `${author}/${card}`); | 738 | console.info('Downloading AICC character:', `${author}/${card}`); |
| 739 | result = await downloadAICCCharacter(`${author}/${card}`); | 739 | result = await downloadAICCCharacter(`${author}/${card}`); |
| 740 | } else { | 740 | } else { |
| 741 | if (uuidType === 'character') { | 741 | if (uuidType === 'character') { |
| 742 | console.log('Downloading chub character:', uuid); | 742 | console.info('Downloading chub character:', uuid); |
| 743 | result = await downloadChubCharacter(uuid); | 743 | result = await downloadChubCharacter(uuid); |
| 744 | } | 744 | } |
| 745 | else if (uuidType === 'lorebook') { | 745 | else if (uuidType === 'lorebook') { |
| 746 | console.log('Downloading chub lorebook:', uuid); | 746 | console.info('Downloading chub lorebook:', uuid); |
| 747 | result = await downloadChubLorebook(uuid); | 747 | result = await downloadChubLorebook(uuid); |
| 748 | } | 748 | } |
| 749 | else { | 749 | else { |
| @@ -756,7 +756,7 @@ router.post('/importUUID', jsonParser, async (request, response) => { | |||
| 756 | response.set('X-Custom-Content-Type', uuidType); | 756 | response.set('X-Custom-Content-Type', uuidType); |
| 757 | return response.send(result.buffer); | 757 | return response.send(result.buffer); |
| 758 | } catch (error) { | 758 | } catch (error) { |
| 759 | console.log('Importing custom content failed', error); | 759 | console.error('Importing custom content failed', error); |
| 760 | return response.sendStatus(500); | 760 | return response.sendStatus(500); |
| 761 | } | 761 | } |
| 762 | }); | 762 | }); |
| @@ -80,7 +80,7 @@ router.post('/install', jsonParser, async (request, response) => { | |||
| 80 | const { url, global } = request.body; | 80 | const { url, global } = request.body; |
| 81 | 81 | ||
| 82 | if (global && !request.user.profile.admin) { | 82 | if (global && !request.user.profile.admin) { |
| 83 | console.warn(`User ${request.user.profile.handle} does not have permission to install global extensions.`); | 83 | console.error(`User ${request.user.profile.handle} does not have permission to install global extensions.`); |
| 84 | return response.status(403).send('Forbidden: No permission to install global extensions.'); | 84 | return response.status(403).send('Forbidden: No permission to install global extensions.'); |
| 85 | } | 85 | } |
| 86 | 86 | ||
| @@ -92,13 +92,13 @@ router.post('/install', jsonParser, async (request, response) => { | |||
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | await git.clone(url, extensionPath, { '--depth': 1 }); | 94 | await git.clone(url, extensionPath, { '--depth': 1 }); |
| 95 | console.log(`Extension has been cloned at ${extensionPath}`); | 95 | console.info(`Extension has been cloned at ${extensionPath}`); |
| 96 | 96 | ||
| 97 | const { version, author, display_name } = await getManifest(extensionPath); | 97 | const { version, author, display_name } = await getManifest(extensionPath); |
| 98 | 98 | ||
| 99 | return response.send({ version, author, display_name, extensionPath }); | 99 | return response.send({ version, author, display_name, extensionPath }); |
| 100 | } catch (error) { | 100 | } catch (error) { |
| 101 | console.log('Importing custom content failed', error); | 101 | console.error('Importing custom content failed', error); |
| 102 | return response.status(500).send(`Server Error: ${error.message}`); | 102 | return response.status(500).send(`Server Error: ${error.message}`); |
| 103 | } | 103 | } |
| 104 | }); | 104 | }); |
| @@ -124,7 +124,7 @@ router.post('/update', jsonParser, async (request, response) => { | |||
| 124 | const { extensionName, global } = request.body; | 124 | const { extensionName, global } = request.body; |
| 125 | 125 | ||
| 126 | if (global && !request.user.profile.admin) { | 126 | if (global && !request.user.profile.admin) { |
| 127 | console.warn(`User ${request.user.profile.handle} does not have permission to update global extensions.`); | 127 | console.error(`User ${request.user.profile.handle} does not have permission to update global extensions.`); |
| 128 | return response.status(403).send('Forbidden: No permission to update global extensions.'); | 128 | return response.status(403).send('Forbidden: No permission to update global extensions.'); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| @@ -139,9 +139,9 @@ router.post('/update', jsonParser, async (request, response) => { | |||
| 139 | const currentBranch = await git.cwd(extensionPath).branch(); | 139 | const currentBranch = await git.cwd(extensionPath).branch(); |
| 140 | if (!isUpToDate) { | 140 | if (!isUpToDate) { |
| 141 | await git.cwd(extensionPath).pull('origin', currentBranch.current); | 141 | await git.cwd(extensionPath).pull('origin', currentBranch.current); |
| 142 | console.log(`Extension has been updated at ${extensionPath}`); | 142 | console.info(`Extension has been updated at ${extensionPath}`); |
| 143 | } else { | 143 | } else { |
| 144 | console.log(`Extension is up to date at ${extensionPath}`); | 144 | console.info(`Extension is up to date at ${extensionPath}`); |
| 145 | } | 145 | } |
| 146 | await git.cwd(extensionPath).fetch('origin'); | 146 | await git.cwd(extensionPath).fetch('origin'); |
| 147 | const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); | 147 | const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']); |
| @@ -150,7 +150,7 @@ router.post('/update', jsonParser, async (request, response) => { | |||
| 150 | return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl }); | 150 | return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl }); |
| 151 | 151 | ||
| 152 | } catch (error) { | 152 | } catch (error) { |
| 153 | console.log('Updating custom content failed', error); | 153 | console.error('Updating custom content failed', error); |
| 154 | return response.status(500).send(`Server Error: ${error.message}`); | 154 | return response.status(500).send(`Server Error: ${error.message}`); |
| 155 | } | 155 | } |
| 156 | }); | 156 | }); |
| @@ -164,7 +164,7 @@ router.post('/move', jsonParser, async (request, response) => { | |||
| 164 | } | 164 | } |
| 165 | 165 | ||
| 166 | if (!request.user.profile.admin) { | 166 | if (!request.user.profile.admin) { |
| 167 | console.warn(`User ${request.user.profile.handle} does not have permission to move extensions.`); | 167 | console.error(`User ${request.user.profile.handle} does not have permission to move extensions.`); |
| 168 | return response.status(403).send('Forbidden: No permission to move extensions.'); | 168 | return response.status(403).send('Forbidden: No permission to move extensions.'); |
| 169 | } | 169 | } |
| 170 | 170 | ||
| @@ -190,11 +190,11 @@ router.post('/move', jsonParser, async (request, response) => { | |||
| 190 | 190 | ||
| 191 | fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true }); | 191 | fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true }); |
| 192 | fs.rmSync(sourcePath, { recursive: true, force: true }); | 192 | fs.rmSync(sourcePath, { recursive: true, force: true }); |
| 193 | console.log(`Extension has been moved from ${sourcePath} to ${destinationPath}`); | 193 | console.info(`Extension has been moved from ${sourcePath} to ${destinationPath}`); |
| 194 | 194 | ||
| 195 | return response.sendStatus(204); | 195 | return response.sendStatus(204); |
| 196 | } catch (error) { | 196 | } catch (error) { |
| 197 | console.log('Moving extension failed', error); | 197 | console.error('Moving extension failed', error); |
| 198 | return response.status(500).send('Internal Server Error. Try again later.'); | 198 | return response.status(500).send('Internal Server Error. Try again later.'); |
| 199 | } | 199 | } |
| 200 | }); | 200 | }); |
| @@ -237,13 +237,13 @@ router.post('/version', jsonParser, async (request, response) => { | |||
| 237 | // get only the working branch | 237 | // get only the working branch |
| 238 | const currentBranchName = currentBranch.current; | 238 | const currentBranchName = currentBranch.current; |
| 239 | await git.cwd(extensionPath).fetch('origin'); | 239 | await git.cwd(extensionPath).fetch('origin'); |
| 240 | console.log(extensionName, currentBranchName, currentCommitHash); | 240 | console.debug(extensionName, currentBranchName, currentCommitHash); |
| 241 | const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); | 241 | const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath); |
| 242 | 242 | ||
| 243 | return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl }); | 243 | return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl }); |
| 244 | 244 | ||
| 245 | } catch (error) { | 245 | } catch (error) { |
| 246 | console.log('Getting extension version failed', error); | 246 | console.error('Getting extension version failed', error); |
| 247 | return response.status(500).send(`Server Error: ${error.message}`); | 247 | return response.status(500).send(`Server Error: ${error.message}`); |
| 248 | } | 248 | } |
| 249 | }); | 249 | }); |
| @@ -265,7 +265,7 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 265 | const { extensionName, global } = request.body; | 265 | const { extensionName, global } = request.body; |
| 266 | 266 | ||
| 267 | if (global && !request.user.profile.admin) { | 267 | if (global && !request.user.profile.admin) { |
| 268 | console.warn(`User ${request.user.profile.handle} does not have permission to delete global extensions.`); | 268 | console.error(`User ${request.user.profile.handle} does not have permission to delete global extensions.`); |
| 269 | return response.status(403).send('Forbidden: No permission to delete global extensions.'); | 269 | return response.status(403).send('Forbidden: No permission to delete global extensions.'); |
| 270 | } | 270 | } |
| 271 | 271 | ||
| @@ -277,12 +277,12 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 277 | } | 277 | } |
| 278 | 278 | ||
| 279 | await fs.promises.rm(extensionPath, { recursive: true }); | 279 | await fs.promises.rm(extensionPath, { recursive: true }); |
| 280 | console.log(`Extension has been deleted at ${extensionPath}`); | 280 | console.info(`Extension has been deleted at ${extensionPath}`); |
| 281 | 281 | ||
| 282 | return response.send(`Extension has been deleted at ${extensionPath}`); | 282 | return response.send(`Extension has been deleted at ${extensionPath}`); |
| 283 | 283 | ||
| 284 | } catch (error) { | 284 | } catch (error) { |
| 285 | console.log('Deleting custom content failed', error); | 285 | console.error('Deleting custom content failed', error); |
| 286 | return response.status(500).send(`Server Error: ${error.message}`); | 286 | return response.status(500).send(`Server Error: ${error.message}`); |
| 287 | } | 287 | } |
| 288 | }); | 288 | }); |
| @@ -323,7 +323,7 @@ router.get('/discover', jsonParser, function (request, response) { | |||
| 323 | 323 | ||
| 324 | // Combine all extensions | 324 | // Combine all extensions |
| 325 | const allExtensions = [...builtInExtensions, ...userExtensions, ...globalExtensions]; | 325 | const allExtensions = [...builtInExtensions, ...userExtensions, ...globalExtensions]; |
| 326 | console.log('Extensions available for', request.user.profile.handle, allExtensions); | 326 | console.info('Extensions available for', request.user.profile.handle, allExtensions); |
| 327 | 327 | ||
| 328 | return response.send(allExtensions); | 328 | return response.send(allExtensions); |
| 329 | }); | 329 | }); |
| @@ -21,7 +21,7 @@ router.post('/sanitize-filename', jsonParser, async (request, response) => { | |||
| 21 | const sanitizedFilename = sanitize(fileName); | 21 | const sanitizedFilename = sanitize(fileName); |
| 22 | return response.send({ fileName: sanitizedFilename }); | 22 | return response.send({ fileName: sanitizedFilename }); |
| 23 | } catch (error) { | 23 | } catch (error) { |
| 24 | console.log(error); | 24 | console.error(error); |
| 25 | return response.sendStatus(500); | 25 | return response.sendStatus(500); |
| 26 | } | 26 | } |
| 27 | }); | 27 | }); |
| @@ -44,10 +44,10 @@ router.post('/upload', jsonParser, async (request, response) => { | |||
| 44 | const pathToUpload = path.join(request.user.directories.files, request.body.name); | 44 | const pathToUpload = path.join(request.user.directories.files, request.body.name); |
| 45 | writeFileSyncAtomic(pathToUpload, request.body.data, 'base64'); | 45 | writeFileSyncAtomic(pathToUpload, request.body.data, 'base64'); |
| 46 | const url = clientRelativePath(request.user.directories.root, pathToUpload); | 46 | const url = clientRelativePath(request.user.directories.root, pathToUpload); |
| 47 | console.log(`Uploaded file: ${url} from ${request.user.profile.handle}`); | 47 | console.info(`Uploaded file: ${url} from ${request.user.profile.handle}`); |
| 48 | return response.send({ path: url }); | 48 | return response.send({ path: url }); |
| 49 | } catch (error) { | 49 | } catch (error) { |
| 50 | console.log(error); | 50 | console.error(error); |
| 51 | return response.sendStatus(500); | 51 | return response.sendStatus(500); |
| 52 | } | 52 | } |
| 53 | }); | 53 | }); |
| @@ -68,10 +68,10 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 68 | } | 68 | } |
| 69 | 69 | ||
| 70 | fs.rmSync(pathToDelete); | 70 | fs.rmSync(pathToDelete); |
| 71 | console.log(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`); | 71 | console.info(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`); |
| 72 | return response.sendStatus(200); | 72 | return response.sendStatus(200); |
| 73 | } catch (error) { | 73 | } catch (error) { |
| 74 | console.log(error); | 74 | console.error(error); |
| 75 | return response.sendStatus(500); | 75 | return response.sendStatus(500); |
| 76 | } | 76 | } |
| 77 | }); | 77 | }); |
| @@ -87,7 +87,7 @@ router.post('/verify', jsonParser, async (request, response) => { | |||
| 87 | for (const url of request.body.urls) { | 87 | for (const url of request.body.urls) { |
| 88 | const pathToVerify = path.join(request.user.directories.root, url); | 88 | const pathToVerify = path.join(request.user.directories.root, url); |
| 89 | if (!pathToVerify.startsWith(request.user.directories.files)) { | 89 | if (!pathToVerify.startsWith(request.user.directories.files)) { |
| 90 | console.debug(`File verification: Invalid path: ${pathToVerify}`); | 90 | console.warn(`File verification: Invalid path: ${pathToVerify}`); |
| 91 | continue; | 91 | continue; |
| 92 | } | 92 | } |
| 93 | const fileExists = fs.existsSync(pathToVerify); | 93 | const fileExists = fs.existsSync(pathToVerify); |
| @@ -96,7 +96,7 @@ router.post('/verify', jsonParser, async (request, response) => { | |||
| 96 | 96 | ||
| 97 | return response.send(verified); | 97 | return response.send(verified); |
| 98 | } catch (error) { | 98 | } catch (error) { |
| 99 | console.log(error); | 99 | console.error(error); |
| 100 | return response.sendStatus(500); | 100 | return response.sendStatus(500); |
| 101 | } | 101 | } |
| 102 | }); | 102 | }); |
| @@ -34,7 +34,7 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 34 | generationConfig: { maxOutputTokens: 1000 }, | 34 | generationConfig: { maxOutputTokens: 1000 }, |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | console.log('Multimodal captioning request', model, body); | 37 | console.debug('Multimodal captioning request', model, body); |
| 38 | 38 | ||
| 39 | const result = await fetch(url, { | 39 | const result = await fetch(url, { |
| 40 | body: JSON.stringify(body), | 40 | body: JSON.stringify(body), |
| @@ -46,13 +46,13 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 46 | 46 | ||
| 47 | if (!result.ok) { | 47 | if (!result.ok) { |
| 48 | const error = await result.json(); | 48 | const error = await result.json(); |
| 49 | console.log(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error); | 49 | console.error(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error); |
| 50 | return response.status(result.status).send({ error: true }); | 50 | return response.status(result.status).send({ error: true }); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | /** @type {any} */ | 53 | /** @type {any} */ |
| 54 | const data = await result.json(); | 54 | const data = await result.json(); |
| 55 | console.log('Multimodal captioning response', data); | 55 | console.info('Multimodal captioning response', data); |
| 56 | 56 | ||
| 57 | const candidates = data?.candidates; | 57 | const candidates = data?.candidates; |
| 58 | if (!candidates) { | 58 | if (!candidates) { |
| @@ -114,7 +114,7 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 114 | 114 | ||
| 115 | if (group && Array.isArray(group.chats)) { | 115 | if (group && Array.isArray(group.chats)) { |
| 116 | for (const chat of group.chats) { | 116 | for (const chat of group.chats) { |
| 117 | console.log('Deleting group chat', chat); | 117 | console.info('Deleting group chat', chat); |
| 118 | const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`); | 118 | const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`); |
| 119 | 119 | ||
| 120 | if (fs.existsSync(pathToFile)) { | 120 | if (fs.existsSync(pathToFile)) { |
| @@ -155,7 +155,7 @@ router.post('/cancel-task', jsonParser, async (request, response) => { | |||
| 155 | }); | 155 | }); |
| 156 | 156 | ||
| 157 | const data = await fetchResult.json(); | 157 | const data = await fetchResult.json(); |
| 158 | console.log(`Cancelled Horde task ${taskId}`); | 158 | console.info(`Cancelled Horde task ${taskId}`); |
| 159 | return response.send(data); | 159 | return response.send(data); |
| 160 | } catch (error) { | 160 | } catch (error) { |
| 161 | console.error(error); | 161 | console.error(error); |
| @@ -174,7 +174,7 @@ router.post('/task-status', jsonParser, async (request, response) => { | |||
| 174 | }); | 174 | }); |
| 175 | 175 | ||
| 176 | const data = await fetchResult.json(); | 176 | const data = await fetchResult.json(); |
| 177 | console.log(`Horde task ${taskId} status:`, data); | 177 | console.info(`Horde task ${taskId} status:`, data); |
| 178 | return response.send(data); | 178 | return response.send(data); |
| 179 | } catch (error) { | 179 | } catch (error) { |
| 180 | console.error(error); | 180 | console.error(error); |
| @@ -187,7 +187,7 @@ router.post('/generate-text', jsonParser, async (request, response) => { | |||
| 187 | const url = 'https://aihorde.net/api/v2/generate/text/async'; | 187 | const url = 'https://aihorde.net/api/v2/generate/text/async'; |
| 188 | const agent = await getClientAgent(); | 188 | const agent = await getClientAgent(); |
| 189 | 189 | ||
| 190 | console.log(request.body); | 190 | console.debug(request.body); |
| 191 | try { | 191 | try { |
| 192 | const result = await fetch(url, { | 192 | const result = await fetch(url, { |
Diff truncated