Merge branch 'staging' into patch-1
Signed| @@ -274,7 +274,7 @@ You will need two mandatory directory mappings and a port mapping to allow Silly | ||
| 274 | 274 | 1. Open your Command Line |
| 275 | 275 | 2. Run the following command |
| 276 | 276 | |
| 277 | 277 | `docker createrun --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 | 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 | 45 | /vectors/ |
| 46 | 46 | /cache/ |
| 47 | 47 | public/css/user.css |
| 48 | +public/error/ | |
| 48 | 49 | /plugins/ |
| 49 | 50 | /data |
| 50 | 51 | /default/scaffold |
| @@ -52,3 +53,5 @@ public/scripts/extensions/third-party | ||
| 52 | 53 | /certs |
| 53 | 54 | .aider* |
| 54 | 55 | .env |
| 56 | +/StartDev.bat | |
| 57 | + | |
| @@ -70,7 +70,7 @@ perUserBasicAuth: false | ||
| 70 | 70 | ## Set to a positive number to expire session after a certain time of inactivity |
| 71 | 71 | ## Set to 0 to expire session when the browser is closed |
| 72 | 72 | ## Set to a negative number to disable session expiration |
| 73 | 73 | sessionTimeout: 86400-1 |
| 74 | 74 | # Used to sign session cookies. Will be auto-generated if not set |
| 75 | 75 | cookieSecret: '' |
| 76 | 76 | # Disable CSRF protection - NOT RECOMMENDED |
| @@ -133,24 +133,26 @@ whitelistImportDomains: | ||
| 133 | 133 | ## headers: |
| 134 | 134 | ## User-Agent: "Googlebot/2.1 (+http://www.google.com/bot.html)" |
| 135 | 135 | requestOverrides: [] |
| 136 | -# -- EXTENSIONS CONFIGURATION -- | |
| 136 | + | |
| 137 | 137 | # Enable UIEXTENSIONS extensionsCONFIGURATION |
| 138 | -enableExtensions: true | |
| 138 | +extensions: | |
| 139 | -# Automatically update extensions when a release version changes | |
| 139 | + # Enable UI extensions | |
| 140 | 140 | enableExtensionsAutoUpdate enabled: true |
| 141 | + # Automatically update extensions when a release version changes | |
| 142 | + autoUpdate: true | |
| 143 | + models: | |
| 144 | + # Enables automatic model download from HuggingFace | |
| 145 | + autoDownload: true | |
| 146 | + # Additional models for extensions. Expects model IDs from HuggingFace model hub in ONNX format | |
| 147 | + classification: Cohee/distilbert-base-uncased-go-emotions-onnx | |
| 148 | + captioning: Xenova/vit-gpt2-image-captioning | |
| 149 | + embedding: Cohee/jina-embeddings-v2-base-en | |
| 150 | + speechToText: Xenova/whisper-small | |
| 151 | + textToSpeech: Xenova/speecht5_tts | |
| 152 | + | |
| 141 | 153 | # Additional model tokenizers can be downloaded on demand. |
| 142 | 154 | # Disabling will fallback to another locally available tokenizer. |
| 143 | 155 | 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 | 156 | # -- OPENAI CONFIGURATION -- |
| 155 | 157 | # A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message |
| 156 | 158 | promptPlaceholder: "[Start a new chat]" |
| @@ -782,5 +782,13 @@ | ||
| 782 | 782 | { |
| 783 | 783 | "filename": "presets/context/Mistral V7.json", |
| 784 | 784 | "type": "context" |
| 785 | + }, | |
| 786 | + { | |
| 787 | + "filename": "presets/instruct/DeepSeek-V2.5.json", | |
| 788 | + "type": "instruct" | |
| 789 | + }, | |
| 790 | + { | |
| 791 | + "filename": "presets/context/DeepSeek-V2.5.json", | |
| 792 | + "type": "context" | |
| 785 | 793 | } |
| 786 | 794 | ] |
| @@ -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 | +} | |
| @@ -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 | 1 | import { UserDirectoryList, User } from "./src/users"; |
| 2 | +import { CsrfSyncedToken } from "csrf-sync"; | |
| 2 | 3 | |
| 3 | 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 | 22 | namespace Express { |
| 5 | 23 | export interface Request { |
| 6 | 24 | user: { |
| @@ -15,11 +33,3 @@ declare global { | ||
| 15 | 33 | */ |
| 16 | 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 | 15 | "**/node_modules/**", |
| 16 | 16 | "**/dist/**", |
| 17 | 17 | "**/.git/**", |
| 18 | 18 | "public/lib/**", |
| 19 | 19 | "backups/**", |
| 20 | 20 | "data/**", |
| 21 | 21 | "cache/**", |
| @@ -26,7 +26,7 @@ | ||
| 26 | 26 | "cookie-parser": "^1.4.6", |
| 27 | 27 | "cookie-session": "^2.1.0", |
| 28 | 28 | "cors": "^2.8.5", |
| 29 | 29 | "csrf-csrfsync": "^24.20.3", |
| 30 | 30 | "diff-match-patch": "^1.0.5", |
| 31 | 31 | "dompurify": "^3.1.7", |
| 32 | 32 | "droll": "^0.2.1", |
| @@ -2987,10 +2987,10 @@ | ||
| 2987 | 2987 | "node": "*" |
| 2988 | 2988 | } |
| 2989 | 2989 | }, |
| 2990 | 2990 | "node_modules/csrf-csrfsync": { |
| 2991 | 2991 | "version": "24.20.43", |
| 2992 | 2992 | "resolved": "https://registry.npmjs.org/csrf-csrfsync/-/csrf-csrfsync-24.20.43.tgz", |
| 2993 | 2993 | "integrity": "sha512-LuhBmy5RfRmEfeqeYqgaAuS1eDpVtKZB/Eiec9xiKQLBynJxrGVRdM2yRTwXzltBBzt/YMl1Njo7imzDt6ZT7G/yKh2L9AYsIwSlTPnx2AaxQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==", |
| 2994 | 2994 | "license": "ISC", |
| 2995 | 2995 | "dependencies": { |
| 2996 | 2996 | "http-errors": "^2.0.0" |
| @@ -16,7 +16,7 @@ | ||
| 16 | 16 | "cookie-parser": "^1.4.6", |
| 17 | 17 | "cookie-session": "^2.1.0", |
| 18 | 18 | "cors": "^2.8.5", |
| 19 | 19 | "csrf-csrfsync": "^24.20.3", |
| 20 | 20 | "diff-match-patch": "^1.0.5", |
| 21 | 21 | "dompurify": "^3.1.7", |
| 22 | 22 | "droll": "^0.2.1", |
| @@ -64,6 +64,46 @@ const keyMigrationMap = [ | ||
| 64 | 64 | newKey: 'backups.chat.throttleInterval', |
| 65 | 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 | 113 | * @returns {string[]} Array of all keys in the object |
| 74 | 114 | */ |
| 75 | 115 | function getAllKeys(obj, prefix = '') { |
| 76 | 116 | if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) { |
| 77 | 117 | return []; |
| 78 | 118 | } |
| 79 | 119 | |
| @@ -173,20 +213,60 @@ function addMissingConfigValues() { | ||
| 173 | 213 | * Creates the default config files if they don't exist yet. |
| 174 | 214 | */ |
| 175 | 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 | 239 | try { |
| 183 | 240 | if (!fsdefaultItem.existsSync(type === 'file)') { |
| 184 | - const defaultFilePath = path.join('./default', path.parse(file).base); | |
| 241 | + if (!fs.existsSync(defaultItem.productionPath)) { | |
| 185 | 242 | fs.copyFileSync(defaultFilePath, file); |
| 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 | 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 | } |
| @@ -1977,12 +1977,12 @@ | ||
| 1977 | 1977 | </span> |
| 1978 | 1978 | </div> |
| 1979 | 1979 | </div> |
| 1980 | 1980 | <div class="range-block" data-source="makersuite,deepseek,openrouter"> |
| 1981 | 1981 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 1982 | 1982 | <input id="openai_show_thoughts" type="checkbox" /> |
| 1983 | 1983 | <span> |
| 1984 | 1984 | <span data-i18n="Show model thoughtsreasoning">Show model thoughtsreasoning</span> |
| 1985 | 1985 | <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i> |
| 1986 | 1986 | </span> |
| 1987 | 1987 | </label> |
| 1988 | 1988 | <div class="toggle-description justifyLeft marginBot5"> |
| @@ -2692,7 +2692,7 @@ | ||
| 2692 | 2692 | <option value="windowai">Window AI</option> |
| 2693 | 2693 | </optgroup> |
| 2694 | 2694 | </select> |
| 2695 | 2695 | <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| 2696 | 2696 | <div class="inline-drawer-toggle inline-drawer-header"> |
| 2697 | 2697 | <b data-i18n="Reverse Proxy">Reverse Proxy</b> |
| 2698 | 2698 | <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> |
| @@ -2755,7 +2755,7 @@ | ||
| 2755 | 2755 | </div> |
| 2756 | 2756 | </div> |
| 2757 | 2757 | </div> |
| 2758 | 2758 | <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek"> |
| 2759 | 2759 | <div class="reverse_proxy_warning"> |
| 2760 | 2760 | <b> |
| 2761 | 2761 | <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy."> |
| @@ -3062,7 +3062,9 @@ | ||
| 3062 | 3062 | <option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option> |
| 3063 | 3063 | </optgroup> |
| 3064 | 3064 | <optgroup label="Subversions"> |
| 3065 | 3065 | <option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental</option> |
| 3066 | + <option value="gemini-2.0-flash-thinking-exp-01-21">Gemini 2.0 Flash Thinking Experimental 2025-01-21</option> | |
| 3067 | + <option value="gemini-2.0-flash-thinking-exp-1219">Gemini 2.0 Flash Thinking Experimental 2024-12-19</option> | |
| 3066 | 3068 | <option value="gemini-2.0-flash-exp">Gemini 2.0 Flash Experimental</option> |
| 3067 | 3069 | <option value="gemini-exp-1114">Gemini Experimental 2024-11-14</option> |
| 3068 | 3070 | <option value="gemini-exp-1121">Gemini Experimental 2024-11-21</option> |
| @@ -3209,6 +3211,7 @@ | ||
| 3209 | 3211 | <select id="model_deepseek_select"> |
| 3210 | 3212 | <option value="deepseek-chat">deepseek-chat</option> |
| 3211 | 3213 | <option value="deepseek-coder">deepseek-coder</option> |
| 3214 | + <option value="deepseek-reasoner">deepseek-reasoner</option> | |
| 3212 | 3215 | </select> |
| 3213 | 3216 | </div> |
| 3214 | 3217 | </div> |
| @@ -3797,6 +3800,39 @@ | ||
| 3797 | 3800 | </div> |
| 3798 | 3801 | </div> |
| 3799 | 3802 | <div> |
| 3803 | + <h4 class="standoutHeader"> | |
| 3804 | + <span data-i18n="Reasoning">Reasoning</span> | |
| 3805 | + </h4> | |
| 3806 | + <div> | |
| 3807 | + <label class="checkbox_label" 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"> | |
| 3808 | + <input id="reasoning_add_to_prompts" type="checkbox" /> | |
| 3809 | + <small data-i18n="Add Reasoning to Prompts"> | |
| 3810 | + Add Reasoning to Prompts | |
| 3811 | + </small> | |
| 3812 | + </label> | |
| 3813 | + <div class="flex-container"> | |
| 3814 | + <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix"> | |
| 3815 | + <small data-i18n="Prefix">Prefix</small> | |
| 3816 | + <textarea id="reasoning_prefix" class="text_pole textarea_compact autoSetHeight"></textarea> | |
| 3817 | + </div> | |
| 3818 | + <div class="flex1" title="Inserted after the reasoning content." data-i18n="[title]reasoning_suffix"> | |
| 3819 | + <small data-i18n="Suffix">Suffix</small> | |
| 3820 | + <textarea id="reasoning_suffix" class="text_pole textarea_compact autoSetHeight"></textarea> | |
| 3821 | + </div> | |
| 3822 | + </div> | |
| 3823 | + <div class="flex-container"> | |
| 3824 | + <div class="flex1" title="Inserted between the reasoning and the message content." data-i18n="[title]reasoning_separator"> | |
| 3825 | + <small data-i18n="Separator">Separator</small> | |
| 3826 | + <textarea id="reasoning_separator" class="text_pole textarea_compact autoSetHeight"></textarea> | |
| 3827 | + </div> | |
| 3828 | + <div class="flex1" title="Maximum number of reasoning blocks to be added per prompt, counting from the last message." data-i18n="[title]reasoning_max_additions"> | |
| 3829 | + <small data-i18n="Max Additions">Max Additions</small> | |
| 3830 | + <input id="reasoning_max_additions" class="text_pole textarea_compact" type="number" min="0" max="999"></textarea> | |
| 3831 | + </div> | |
| 3832 | + </div> | |
| 3833 | + </div> | |
| 3834 | + </div> | |
| 3835 | + <div> | |
| 3800 | 3836 | <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4> |
| 3801 | 3837 | <div> |
| 3802 | 3838 | <small> |
| @@ -6218,14 +6254,26 @@ | ||
| 6218 | 6254 | <div class="mes_edit_buttons"> |
| 6219 | 6255 | <div class="mes_edit_done menu_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirm"></div> |
| 6220 | 6256 | <div class="mes_edit_copy menu_button fa-solid fa-copy" title="Copy this message" data-i18n="[title]Copy this message"></div> |
| 6221 | 6257 | <div class="mes_edit_deletemes_edit_add_reasoning menu_button fa-solid fa-trash-canlightbulb" title="DeleteAdd thisa messagereasoning block" data-i18n="[title]DeleteAdd thisa messagereasoning block"></div> |
| 6222 | - </div> | |
| 6258 | + <div class="mes_edit_delete menu_button fa-solid fa-trash-can" title="Delete this message" data-i18n="[title]Delete this message"></div> | |
| 6223 | 6259 | <div class="mes_edit_up menu_button fa-solid fa-chevron-up " title="Move message up" data-i18n="[title]Move message up"></div> |
| 6224 | 6260 | <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 | 6261 | <div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div> |
| 6227 | 6262 | </div> |
| 6228 | 6263 | </div> |
| 6264 | + <details class="mes_reasoning_details"> | |
| 6265 | + <summary class="mes_reasoning_summary"> | |
| 6266 | + <span data-i18n="Reasoning">Reasoning</span> | |
| 6267 | + <div class="mes_reasoning_actions"> | |
| 6268 | + <div class="mes_reasoning_edit_done mes_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div> | |
| 6269 | + <div class="mes_reasoning_edit_cancel mes_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div> | |
| 6270 | + <div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div> | |
| 6271 | + <div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div> | |
| 6272 | + <div class="mes_reasoning_delete mes_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div> | |
| 6273 | + </div> | |
| 6274 | + </summary> | |
| 6275 | + <div class="mes_reasoning"></div> | |
| 6276 | + </details> | |
| 6229 | 6277 | <div class="mes_text"></div> |
| 6230 | 6278 | <div class="mes_img_container"> |
| 6231 | 6279 | <div class="mes_img_controls"> |
| @@ -6325,7 +6373,10 @@ | ||
| 6325 | 6373 | <img alt="Avatar" src="" /> |
| 6326 | 6374 | </div> |
| 6327 | 6375 | <div class="group_member_name"> |
| 6328 | 6376 | <div class="ch_namecharacter_name_block"></div> |
| 6377 | + <span class="ch_name"></span> | |
| 6378 | + <small class="ch_additional_info character_version"></small> | |
| 6379 | + </div> | |
| 6329 | 6380 | <div class="tags tags_inline"></div> |
| 6330 | 6381 | </div> |
| 6331 | 6382 | <input class="ch_fav" value="" hidden /> |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (رابط مباشر أو معرف)", |
| 1377 | 1377 | "char_import_3": "حرف JanitorAI (رابط مباشر أو UUID)", |
| 1378 | 1378 | "char_import_4": "حرف Pygmalion.chat (رابط مباشر أو UUID)", |
| 1379 | 1379 | "char_import_5": "حرف AICharacterCardAICharacterCards.com (رابط مباشر أو معرف)", |
| 1380 | 1380 | "char_import_6": "رابط PNG المباشر (راجع", |
| 1381 | 1381 | "char_import_7": "للمضيفين المسموح بهم)", |
| 1382 | 1382 | "char_import_8": "شخصية RisuRealm (رابط مباشر)", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (Direktlink oder ID)", |
| 1377 | 1377 | "char_import_3": "JanitorAI-Charakter (Direktlink oder UUID)", |
| 1378 | 1378 | "char_import_4": "Pygmalion.chat-Charakter (Direktlink oder UUID)", |
| 1379 | 1379 | "char_import_5": "AICharacterCardAICharacterCards.com-Charakter (Direktlink oder ID)", |
| 1380 | 1380 | "char_import_6": "Direkter PNG-Link (siehe", |
| 1381 | 1381 | "char_import_7": "für erlaubte Hosts)", |
| 1382 | 1382 | "char_import_8": "RisuRealm-Charakter (Direktlink)", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (enlace directo o ID)", |
| 1377 | 1377 | "char_import_3": "Carácter de JanitorAI (enlace directo o UUID)", |
| 1378 | 1378 | "char_import_4": "Carácter Pygmalion.chat (enlace directo o UUID)", |
| 1379 | 1379 | "char_import_5": "Carácter AICharacterCardAICharacterCards.com (enlace directo o ID)", |
| 1380 | 1380 | "char_import_6": "Enlace PNG directo (consulte", |
| 1381 | 1381 | "char_import_7": "para hosts permitidos)", |
| 1382 | 1382 | "char_import_8": "Personaje RisuRealm (Enlace directo)", |
| @@ -1297,7 +1297,7 @@ | ||
| 1297 | 1297 | "char_import_2": "Lorebook de Chub (lien direct ou ID)", |
| 1298 | 1298 | "char_import_3": "Personnage de JanitorAI (lien direct ou UUID)", |
| 1299 | 1299 | "char_import_4": "Personnage de Pygmalion.chat (lien direct ou UUID)", |
| 1300 | 1300 | "char_import_5": "Personnage de AICharacterCardAICharacterCards.com (lien direct ou identifiant)", |
| 1301 | 1301 | "char_import_6": "Lien PNG direct (voir", |
| 1302 | 1302 | "char_import_7": "pour les hôtes autorisés)", |
| 1303 | 1303 | "char_import_8": "Personnage de RisuRealm (lien direct)", |
| @@ -1385,7 +1385,7 @@ | ||
| 1385 | 1385 | "enable_functions_desc_1": "Autorise l'utilisation", |
| 1386 | 1386 | "enable_functions_desc_2": "outils de fonction", |
| 1387 | 1387 | "enable_functions_desc_3": "Peut être utilisé par diverses extensions pour fournir des fonctionnalités supplémentaires.", |
| 1388 | 1388 | "Show model thoughtsreasoning": "Afficher les pensées du modèle", |
| 1389 | 1389 | "Display the model's internal thoughts in the response.": "Afficher les pensées internes du modèle dans la réponse.", |
| 1390 | 1390 | "Confirm token parsing with": "Confirmer l'analyse des tokens avec", |
| 1391 | 1391 | "openai_logit_bias_no_items": "Aucun élément", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (beinn hlekkur eða auðkenni)", |
| 1377 | 1377 | "char_import_3": "JanitorAI karakter (beinn hlekkur eða UUID)", |
| 1378 | 1378 | "char_import_4": "Pygmalion.chat karakter (beinn hlekkur eða UUID)", |
| 1379 | 1379 | "char_import_5": "AICharacterCardAICharacterCards.com Karakter (beinn hlekkur eða auðkenni)", |
| 1380 | 1380 | "char_import_6": "Beinn PNG hlekkur (sjá", |
| 1381 | 1381 | "char_import_7": "fyrir leyfilega gestgjafa)", |
| 1382 | 1382 | "char_import_8": "RisuRealm karakter (beinn hlekkur)", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Lorebook di Chub (collegamento diretto o ID)", |
| 1377 | 1377 | "char_import_3": "Carattere JanitorAI (collegamento diretto o UUID)", |
| 1378 | 1378 | "char_import_4": "Carattere Pygmalion.chat (collegamento diretto o UUID)", |
| 1379 | 1379 | "char_import_5": "Carattere AICharacterCardAICharacterCards.com (Link diretto o ID)", |
| 1380 | 1380 | "char_import_6": "Collegamento PNG diretto (fare riferimento a", |
| 1381 | 1381 | "char_import_7": "per gli host consentiti)", |
| 1382 | 1382 | "char_import_8": "Personaggio RisuRealm (collegamento diretto)", |
| @@ -1378,7 +1378,7 @@ | ||
| 1378 | 1378 | "char_import_2": "Chub ロアブック (直接リンクまたは ID)", |
| 1379 | 1379 | "char_import_3": "JanitorAI キャラクター (直接リンクまたは UUID)", |
| 1380 | 1380 | "char_import_4": "Pygmalion.chat キャラクター (直接リンクまたは UUID)", |
| 1381 | 1381 | "char_import_5": "AICharacterCardAICharacterCards.com キャラクター (直接リンクまたは ID)", |
| 1382 | 1382 | "char_import_6": "直接PNGリンク(参照", |
| 1383 | 1383 | "char_import_7": "許可されたホストの場合)", |
| 1384 | 1384 | "char_import_8": "RisuRealm キャラクター (直接リンク)", |
| @@ -1395,7 +1395,7 @@ | ||
| 1395 | 1395 | "char_import_2": "Chub Lorebook(직접 링크 또는 ID)", |
| 1396 | 1396 | "char_import_3": "JanitorAI 캐릭터(직접 링크 또는 UUID)", |
| 1397 | 1397 | "char_import_4": "Pygmalion.chat 문자(직접 링크 또는 UUID)", |
| 1398 | 1398 | "char_import_5": "AICharacterCardAICharacterCards.com 캐릭터(직접 링크 또는 ID)", |
| 1399 | 1399 | "char_import_6": "직접 PNG 링크(참조", |
| 1400 | 1400 | "char_import_7": "허용된 호스트의 경우)", |
| 1401 | 1401 | "char_import_8": "RisuRealm 캐릭터 (직접링크)", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (directe link of ID)", |
| 1377 | 1377 | "char_import_3": "JanitorAI-personage (directe link of UUID)", |
| 1378 | 1378 | "char_import_4": "Pygmalion.chat-teken (directe link of UUID)", |
| 1379 | 1379 | "char_import_5": "AICharacterCardAICharacterCards.com-teken (directe link of ID)", |
| 1380 | 1380 | "char_import_6": "Directe PNG-link (zie", |
| 1381 | 1381 | "char_import_7": "voor toegestane hosts)", |
| 1382 | 1382 | "char_import_8": "RisuRealm-personage (directe link)", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (link direto ou ID)", |
| 1377 | 1377 | "char_import_3": "Personagem JanitorAI (Link Direto ou UUID)", |
| 1378 | 1378 | "char_import_4": "Caractere Pygmalion.chat (Link Direto ou UUID)", |
| 1379 | 1379 | "char_import_5": "Personagem AICharacterCardAICharacterCards.com (link direto ou ID)", |
| 1380 | 1380 | "char_import_6": "Link PNG direto (consulte", |
| 1381 | 1381 | "char_import_7": "para hosts permitidos)", |
| 1382 | 1382 | "char_import_8": "Personagem RisuRealm (link direto)", |
| @@ -966,7 +966,7 @@ | ||
| 966 | 966 | "char_import_2": "Лорбук с Chub (прямая ссылка или ID)", |
| 967 | 967 | "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)", |
| 968 | 968 | "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)", |
| 969 | 969 | "char_import_5": "Персонаж с AICharacterCardAICharacterCards.com (прямая ссылка или ID)", |
| 970 | 970 | "char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", |
| 971 | 971 | "char_import_7": ")", |
| 972 | 972 | "Grammar String": "Грамматика", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub Lorebook (пряме посилання або ID)", |
| 1377 | 1377 | "char_import_3": "Символ JanitorAI (пряме посилання або UUID)", |
| 1378 | 1378 | "char_import_4": "Символ Pygmalion.chat (пряме посилання або UUID)", |
| 1379 | 1379 | "char_import_5": "Символ AICharacterCardAICharacterCards.com (пряме посилання або ідентифікатор)", |
| 1380 | 1380 | "char_import_6": "Пряме посилання на PNG (див", |
| 1381 | 1381 | "char_import_7": "для дозволених хостів)", |
| 1382 | 1382 | "char_import_8": "Персонаж RisuRealm (пряме посилання)", |
| @@ -1376,7 +1376,7 @@ | ||
| 1376 | 1376 | "char_import_2": "Chub (Nhập URL trực tiếp hoặc ID)", |
| 1377 | 1377 | "char_import_3": "JanitorAI (Nhập URL trực tiếp hoặc UUID)", |
| 1378 | 1378 | "char_import_4": "Pygmalion.chat (Nhập URL trực tiếp hoặc UUID)", |
| 1379 | 1379 | "char_import_5": "AICharacterCardAICharacterCards.com (Nhập URL trực tiếp hoặc ID)", |
| 1380 | 1380 | "char_import_6": "Nhập PNG trực tiếp (tham khảo", |
| 1381 | 1381 | "char_import_7": "đối với các máy chủ được phép)", |
| 1382 | 1382 | "char_import_8": "RisuRealm (URL trực tiếp)", |
| @@ -266,7 +266,7 @@ | ||
| 266 | 266 | "Use system prompt": "使用系统提示词", |
| 267 | 267 | "Merges_all_system_messages_desc_1": "合并所有系统消息,直到第一条具有非系统角色的消息,然后通过", |
| 268 | 268 | "Merges_all_system_messages_desc_2": "字段发送。", |
| 269 | 269 | "Show model thoughtsreasoning": "展示思维链", |
| 270 | 270 | "Display the model's internal thoughts in the response.": "展示模型在回复时的内部思维链。", |
| 271 | 271 | "Assistant Prefill": "AI预填", |
| 272 | 272 | "Expand the editor": "展开编辑器", |
| @@ -1191,9 +1191,9 @@ | ||
| 1191 | 1191 | "welcome_message_part_8": "您可随时通过", |
| 1192 | 1192 | "welcome_message_part_9": "图标来更改此设置。", |
| 1193 | 1193 | "Persona Name:": "用户角色名称:", |
| 1194 | 1194 | "Temporarily disable automatic replies from this character": "暂时禁用此角色的自动回复临时禁言此角色", |
| 1195 | 1195 | "Enable automatic replies from this character": "启用此角色的自动回复解除禁言此角色", |
| 1196 | 1196 | "Trigger a message from this character": "从此角色触发消息强制触发该角色发言", |
| 1197 | 1197 | "Move up": "向上移动", |
| 1198 | 1198 | "Move down": "向下移动", |
| 1199 | 1199 | "View character card": "查看角色卡片", |
| @@ -1829,7 +1829,7 @@ | ||
| 1829 | 1829 | "char_import_2": "Chub 知识书(直链或ID)", |
| 1830 | 1830 | "char_import_3": "JanitorAI 角色(直链或UUID)", |
| 1831 | 1831 | "char_import_4": "Pygmalion.chat 角色(直链或UUID)", |
| 1832 | 1832 | "char_import_5": "AICharacterCardAICharacterCards.com 角色(直链或ID)", |
| 1833 | 1833 | "char_import_6": "被允许的PNG直链(请参阅", |
| 1834 | 1834 | "char_import_7": ")", |
| 1835 | 1835 | "char_import_8": "RisuRealm 角色(直链)", |
| @@ -1838,7 +1838,7 @@ | ||
| 1838 | 1838 | "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装", |
| 1839 | 1839 | "Disclaimer:": "免责声明:", |
| 1840 | 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 | 1841 | "Prompt Itemization": "将提示词分条提示词拆分", |
| 1842 | 1842 | "Show Raw Prompt": "显示原始提示词", |
| 1843 | 1843 | "Copy Prompt": "复制提示词", |
| 1844 | 1844 | "Show Prompt Differences": "显示提示词差异", |
| @@ -2045,8 +2045,8 @@ | ||
| 2045 | 2045 | "Post a GitHub issue": "在 GitHub 发布问题", |
| 2046 | 2046 | "Contact the developers": "联系开发者", |
| 2047 | 2047 | "If you're connected to an API, try asking me something!": "若您已经配置好API,尝试发送些什么吧!", |
| 2048 | 2048 | "Title/Memo": "标题/备忘录(备忘)", |
| 2049 | 2049 | "Strategy": "Strategy触发策略", |
| 2050 | 2050 | "Position": "位置插入位置", |
| 2051 | 2051 | "Trigger %": "触发率 %触发概率%" |
| 2052 | 2052 | } |
| @@ -1381,7 +1381,7 @@ | ||
| 1381 | 1381 | "char_import_2": "Chub Lorebook(直接連結或 ID)", |
| 1382 | 1382 | "char_import_3": "JanitorAI 角色(直接連結或 ID)", |
| 1383 | 1383 | "char_import_4": "Pygmalion.chat 角色(直接連結或 ID)", |
| 1384 | 1384 | "char_import_5": "AICharacterCardAICharacterCards.com 角色(直接連結或 ID)", |
| 1385 | 1385 | "char_import_6": "直接 PNG 連結(請參閱", |
| 1386 | 1386 | "char_import_7": "對於允許的主機)", |
| 1387 | 1387 | "char_import_8": "RisuRealm角色(直接連結)", |
| @@ -2357,7 +2357,7 @@ | ||
| 2357 | 2357 | "Forbid": "禁止", |
| 2358 | 2358 | "Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here.": "僅限 Aphrodite 使用。決定採樣器的順序。偏移總是在 softmax 後應用,因此不包括在此。", |
| 2359 | 2359 | "Aphrodite only. Determines the order of samplers.": "僅限 Aphrodite 使用。決定採樣器的順序。", |
| 2360 | 2360 | "Show model thoughtsreasoning": "顯示模型思維鏈", |
| 2361 | 2361 | "Display the model's internal thoughts in the response.": "在回應中顯示模型的思維鏈(內部思考過程)。", |
| 2362 | 2362 | "Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]": "通用(兼容 OpenAI)[LM Studio, LiteLLM 等]", |
| 2363 | 2363 | "Model ID (optional)": "模型 ID(可選)", |
| @@ -267,6 +267,7 @@ import { initSettingsSearch } from './scripts/setting-search.js'; | ||
| 267 | 267 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 268 | 268 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 269 | 269 | import { getContext } from './scripts/st-context.js'; |
| 270 | +import { initReasoning, PromptReasoning } from './scripts/reasoning.js'; | |
| 270 | 271 | |
| 271 | 272 | // API OBJECT FOR EXTERNAL WIRING |
| 272 | 273 | globalThis.SillyTavern = { |
| @@ -443,6 +444,7 @@ export const event_types = { | ||
| 443 | 444 | MESSAGE_DELETED: 'message_deleted', |
| 444 | 445 | MESSAGE_UPDATED: 'message_updated', |
| 445 | 446 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', |
| 447 | + MORE_MESSAGES_LOADED: 'more_messages_loaded', | |
| 446 | 448 | IMPERSONATE_READY: 'impersonate_ready', |
| 447 | 449 | CHAT_CHANGED: 'chat_id_changed', |
| 448 | 450 | GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS', |
| @@ -723,6 +725,7 @@ async function getSystemMessages() { | ||
| 723 | 725 | is_user: false, |
| 724 | 726 | is_system: true, |
| 725 | 727 | mes: await renderTemplateAsync('assistantNote'), |
| 728 | + uses_system_ui: true, | |
| 726 | 729 | extra: { |
| 727 | 730 | isSmallSys: true, |
| 728 | 731 | }, |
| @@ -980,6 +983,7 @@ async function firstLoadInit() { | ||
| 980 | 983 | initServerHistory(); |
| 981 | 984 | initSettingsSearch(); |
| 982 | 985 | initBulkEdit(); |
| 986 | + initReasoning(); | |
| 983 | 987 | await initScrapers(); |
| 984 | 988 | doDailyExtensionUpdatesCheck(); |
| 985 | 989 | await hideLoader(); |
| @@ -1829,7 +1833,7 @@ export async function replaceCurrentChat() { | ||
| 1829 | 1833 | } |
| 1830 | 1834 | } |
| 1831 | 1835 | |
| 1832 | 1836 | export async function showMoreMessages(messagesToLoad = null) { |
| 1833 | 1837 | const firstDisplayedMesId = $('#chat').children('.mes').first().attr('mesid'); |
| 1834 | 1838 | let messageId = Number(firstDisplayedMesId); |
| 1835 | 1839 | let count = messagesToLoad || power_user.chat_truncation || Number.MAX_SAFE_INTEGER; |
| @@ -1859,6 +1863,8 @@ export function showMoreMessages(messagesToLoad = null) { | ||
| 1859 | 1863 | const newHeight = $('#chat').prop('scrollHeight'); |
| 1860 | 1864 | $('#chat').scrollTop(newHeight - prevHeight); |
| 1861 | 1865 | } |
| 1866 | + | |
| 1867 | + await eventSource.emit(event_types.MORE_MESSAGES_LOADED); | |
| 1862 | 1868 | } |
| 1863 | 1869 | |
| 1864 | 1870 | export async function printMessages() { |
| @@ -2196,6 +2202,7 @@ function getMessageFromTemplate({ | ||
| 2196 | 2202 | isUser, |
| 2197 | 2203 | avatarImg, |
| 2198 | 2204 | bias, |
| 2205 | + reasoning, | |
| 2199 | 2206 | isSystem, |
| 2200 | 2207 | title, |
| 2201 | 2208 | timerValue, |
| @@ -2220,6 +2227,7 @@ function getMessageFromTemplate({ | ||
| 2220 | 2227 | mes.find('.avatar img').attr('src', avatarImg); |
| 2221 | 2228 | mes.find('.ch_name .name_text').text(characterName); |
| 2222 | 2229 | mes.find('.mes_bias').html(bias); |
| 2230 | + mes.find('.mes_reasoning').html(reasoning); | |
| 2223 | 2231 | mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`); |
| 2224 | 2232 | mes.find('.mesIDDisplay').text(`#${mesId}`); |
| 2225 | 2233 | tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`); |
| @@ -2234,10 +2242,16 @@ function getMessageFromTemplate({ | ||
| 2234 | 2242 | return mes; |
| 2235 | 2243 | } |
| 2236 | 2244 | |
| 2245 | +/** | |
| 2246 | + * Re-renders a message block with updated content. | |
| 2247 | + * @param {number} messageId Message ID | |
| 2248 | + * @param {object} message Message object | |
| 2249 | + */ | |
| 2237 | 2250 | export function updateMessageBlock(messageId, message) { |
| 2238 | 2251 | const messageElement = $(`#chat [mesid="${messageId}"]`); |
| 2239 | 2252 | const text = message?.extra?.display_text ?? message.mes; |
| 2240 | 2253 | messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId)); |
| 2254 | + messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, -1)); | |
| 2241 | 2255 | addCopyToCodeBlocks(messageElement); |
| 2242 | 2256 | appendMediaToMessage(message, messageElement); |
| 2243 | 2257 | } |
| @@ -2396,6 +2410,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2396 | 2410 | sanitizerOverrides, |
| 2397 | 2411 | ); |
| 2398 | 2412 | const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1); |
| 2413 | + const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, -1); | |
| 2399 | 2414 | let bookmarkLink = mes?.extra?.bookmark_link ?? ''; |
| 2400 | 2415 | |
| 2401 | 2416 | let params = { |
| @@ -2405,6 +2420,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2405 | 2420 | isUser: mes.is_user, |
| 2406 | 2421 | avatarImg: avatarImg, |
| 2407 | 2422 | bias: bias, |
| 2423 | + reasoning: reasoning, | |
| 2408 | 2424 | isSystem: isSystem, |
| 2409 | 2425 | title: title, |
| 2410 | 2426 | bookmarkLink: bookmarkLink, |
| @@ -2464,6 +2480,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2464 | 2480 | const swipeMessage = chatElement.find(`[mesid="${chat.length - 1}"]`); |
| 2465 | 2481 | swipeMessage.attr('swipeid', params.swipeId); |
| 2466 | 2482 | swipeMessage.find('.mes_text').html(messageText).attr('title', title); |
| 2483 | + swipeMessage.find('.mes_reasoning').html(reasoning); | |
| 2467 | 2484 | swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`); |
| 2468 | 2485 | appendMediaToMessage(mes, swipeMessage); |
| 2469 | 2486 | if (power_user.timestamp_model_icon && params.extra?.api) { |
| @@ -3074,6 +3091,7 @@ class StreamingProcessor { | ||
| 3074 | 3091 | this.messageTextDom = null; |
| 3075 | 3092 | this.messageTimerDom = null; |
| 3076 | 3093 | this.messageTokenCounterDom = null; |
| 3094 | + this.messageReasoningDom = null; | |
| 3077 | 3095 | /** @type {HTMLTextAreaElement} */ |
| 3078 | 3096 | this.sendTextarea = document.querySelector('#send_textarea'); |
| 3079 | 3097 | this.type = type; |
| @@ -3089,6 +3107,7 @@ class StreamingProcessor { | ||
| 3089 | 3107 | /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */ |
| 3090 | 3108 | this.messageLogprobs = []; |
| 3091 | 3109 | this.toolCalls = []; |
| 3110 | + this.reasoning = ''; | |
| 3092 | 3111 | } |
| 3093 | 3112 | |
| 3094 | 3113 | #checkDomElements(messageId) { |
| @@ -3097,6 +3116,7 @@ class StreamingProcessor { | ||
| 3097 | 3116 | this.messageTextDom = this.messageDom?.querySelector('.mes_text'); |
| 3098 | 3117 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); |
| 3099 | 3118 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); |
| 3119 | + this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning'); | |
| 3100 | 3120 | } |
| 3101 | 3121 | } |
| 3102 | 3122 | |
| @@ -3174,18 +3194,27 @@ class StreamingProcessor { | ||
| 3174 | 3194 | this.#checkDomElements(messageId); |
| 3175 | 3195 | this.#updateMessageBlockVisibility(); |
| 3176 | 3196 | 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 | 3197 | chat[messageId]['mes'] = processedText; |
| 3181 | 3198 | chat[messageId]['gen_started'] = this.timeStarted; |
| 3182 | 3199 | chat[messageId]['gen_finished'] = currentTime; |
| 3183 | 3200 | |
| 3184 | 3201 | if (currentTokenCount!chat[messageId]['extra']) { |
| 3185 | 3202 | if (!chat[messageId]['extra']) = {}; |
| 3186 | - chat[messageId]['extra'] = {}; | |
| 3203 | + } | |
| 3204 | + | |
| 3205 | + if (this.reasoning) { | |
| 3206 | + chat[messageId]['extra']['reasoning'] = this.reasoning; | |
| 3207 | + if (this.messageReasoningDom instanceof HTMLElement) { | |
| 3208 | + const formattedReasoning = messageFormatting(this.reasoning, '', false, false, -1); | |
| 3209 | + this.messageReasoningDom.innerHTML = formattedReasoning; | |
| 3187 | 3210 | } |
| 3211 | + } | |
| 3212 | + | |
| 3213 | + // Don't waste time calculating token count for streaming | |
| 3214 | + const tokenCountText = (this.reasoning || '') + processedText; | |
| 3215 | + const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(tokenCountText, 0) : 0; | |
| 3188 | 3216 | |
| 3217 | + if (currentTokenCount) { | |
| 3189 | 3218 | chat[messageId]['extra']['token_count'] = currentTokenCount; |
| 3190 | 3219 | if (this.messageTokenCounterDom instanceof HTMLElement) { |
| 3191 | 3220 | this.messageTokenCounterDom.textContent = `${currentTokenCount}t`; |
| @@ -3207,10 +3236,13 @@ class StreamingProcessor { | ||
| 3207 | 3236 | if (this.messageTextDom instanceof HTMLElement) { |
| 3208 | 3237 | this.messageTextDom.innerHTML = formattedText; |
| 3209 | 3238 | } |
| 3239 | + | |
| 3240 | + const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount); | |
| 3210 | 3241 | if (this.messageTimerDom instanceof HTMLElement) { |
| 3211 | 3242 | this.messageTimerDom.textContent = timePassed.timerValue; |
| 3212 | 3243 | this.messageTimerDom.title = timePassed.timerTitle; |
| 3213 | 3244 | } |
| 3245 | + | |
| 3214 | 3246 | this.setFirstSwipe(messageId); |
| 3215 | 3247 | } |
| 3216 | 3248 | |
| @@ -3317,7 +3349,7 @@ class StreamingProcessor { | ||
| 3317 | 3349 | } |
| 3318 | 3350 | |
| 3319 | 3351 | /** |
| 3320 | 3352 | * @returns {Generator<{ text: string, swipes: string[], logprobs: import('./scripts/logprobs.js').TokenLogprobs, toolCalls: any[], state: any }, void, void>} |
| 3321 | 3353 | */ |
| 3322 | 3354 | *nullStreamingGeneration() { |
| 3323 | 3355 | throw new Error('Generation function for streaming is not hooked up'); |
| @@ -3339,7 +3371,7 @@ class StreamingProcessor { | ||
| 3339 | 3371 | try { |
| 3340 | 3372 | const sw = new Stopwatch(1000 / power_user.streaming_fps); |
| 3341 | 3373 | const timestamps = []; |
| 3342 | 3374 | for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) { |
| 3343 | 3375 | timestamps.push(Date.now()); |
| 3344 | 3376 | if (this.isStopped) { |
| 3345 | 3377 | return; |
| @@ -3351,6 +3383,7 @@ class StreamingProcessor { | ||
| 3351 | 3383 | if (logprobs) { |
| 3352 | 3384 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); |
| 3353 | 3385 | } |
| 3386 | + this.reasoning = state?.reasoning ?? ''; | |
| 3354 | 3387 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3355 | 3388 | await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3356 | 3389 | } |
| @@ -3817,6 +3850,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3817 | 3850 | coreChat.pop(); |
| 3818 | 3851 | } |
| 3819 | 3852 | |
| 3853 | + const reasoning = new PromptReasoning(); | |
| 3854 | + for (let i = coreChat.length - 1; i >= 0; i--) { | |
| 3855 | + if (reasoning.isLimitReached()) { | |
| 3856 | + break; | |
| 3857 | + } | |
| 3858 | + coreChat[i] = { ...coreChat[i], mes: reasoning.addToMessage(coreChat[i].mes, coreChat[i].extra?.reasoning) }; | |
| 3859 | + } | |
| 3860 | + | |
| 3820 | 3861 | coreChat = await Promise.all(coreChat.map(async (chatItem, index) => { |
| 3821 | 3862 | let message = chatItem.mes; |
| 3822 | 3863 | let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT; |
| @@ -4738,6 +4779,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4738 | 4779 | //const getData = await response.json(); |
| 4739 | 4780 | let getMessage = extractMessageFromData(data); |
| 4740 | 4781 | let title = extractTitleFromData(data); |
| 4782 | + let reasoning = extractReasoningFromData(data); | |
| 4741 | 4783 | kobold_horde_model = title; |
| 4742 | 4784 | |
| 4743 | 4785 | const swipes = extractMultiSwipes(data, type); |
| @@ -4764,10 +4806,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4764 | 4806 | else { |
| 4765 | 4807 | // Without streaming we'll be having a full message on continuation. Treat it as a last chunk. |
| 4766 | 4808 | if (originalType !== 'continue') { |
| 4767 | 4809 | ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes, reasoning)); |
| 4768 | 4810 | } |
| 4769 | 4811 | else { |
| 4770 | 4812 | ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes, reasoning)); |
| 4771 | 4813 | } |
| 4772 | 4814 | |
| 4773 | 4815 | // This relies on `saveReply` having been called to add the message to the chat, so it must be last. |
| @@ -5672,6 +5714,26 @@ function extractMessageFromData(data) { | ||
| 5672 | 5714 | } |
| 5673 | 5715 | |
| 5674 | 5716 | /** |
| 5717 | + * Extracts the reasoning from the response data. | |
| 5718 | + * @param {object} data Response data | |
| 5719 | + * @returns {string} Extracted reasoning | |
| 5720 | + */ | |
| 5721 | +function extractReasoningFromData(data) { | |
| 5722 | + if (main_api === 'openai' && oai_settings.show_thoughts) { | |
| 5723 | + switch (oai_settings.chat_completion_source) { | |
| 5724 | + case chat_completion_sources.DEEPSEEK: | |
| 5725 | + return data?.choices?.[0]?.message?.reasoning_content ?? ''; | |
| 5726 | + case chat_completion_sources.OPENROUTER: | |
| 5727 | + return data?.choices?.[0]?.message?.reasoning ?? ''; | |
| 5728 | + case chat_completion_sources.MAKERSUITE: | |
| 5729 | + return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; | |
| 5730 | + } | |
| 5731 | + } | |
| 5732 | + | |
| 5733 | + return ''; | |
| 5734 | +} | |
| 5735 | + | |
| 5736 | +/** | |
| 5675 | 5737 | * Extracts multiswipe swipes from the response data. |
| 5676 | 5738 | * @param {Object} data Response data |
| 5677 | 5739 | * @param {string} type Type of generation |
| @@ -5851,7 +5913,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc | ||
| 5851 | 5913 | return getMessage; |
| 5852 | 5914 | } |
| 5853 | 5915 | |
| 5854 | 5916 | export async function saveReply(type, getMessage, fromStreaming, title, swipes, reasoning) { |
| 5855 | 5917 | if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined || |
| 5856 | 5918 | chat[chat.length - 1]['is_user'])) { |
| 5857 | 5919 | type = 'normal'; |
| @@ -5876,8 +5938,10 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | ||
| 5876 | 5938 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5877 | 5939 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5878 | 5940 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5941 | + chat[chat.length - 1]['extra']['reasoning'] = reasoning; | |
| 5879 | 5942 | if (power_user.message_token_count_enabled) { |
| 5880 | 5943 | chat[chat.lengthconst -tokenCountText 1]['extra'][= (reasoning || 'token_count'] =) await+ getTokenCountAsync(chat[chat.length - 1]['mes'], 0); |
| 5944 | + chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | |
| 5881 | 5945 | } |
| 5882 | 5946 | const chat_id = (chat.length - 1); |
| 5883 | 5947 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); |
| @@ -5896,8 +5960,10 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | ||
| 5896 | 5960 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5897 | 5961 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5898 | 5962 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5963 | + chat[chat.length - 1]['extra']['reasoning'] += reasoning; | |
| 5899 | 5964 | if (power_user.message_token_count_enabled) { |
| 5900 | 5965 | chat[chat.lengthconst -tokenCountText 1]['extra'][= (reasoning || 'token_count'] =) await+ getTokenCountAsync(chat[chat.length - 1]['mes'], 0); |
| 5966 | + chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | |
| 5901 | 5967 | } |
| 5902 | 5968 | const chat_id = (chat.length - 1); |
| 5903 | 5969 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); |
| @@ -5913,8 +5979,10 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | ||
| 5913 | 5979 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5914 | 5980 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5915 | 5981 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 5982 | + chat[chat.length - 1]['extra']['reasoning'] += reasoning; | |
| 5916 | 5983 | if (power_user.message_token_count_enabled) { |
| 5917 | 5984 | chat[chat.lengthconst -tokenCountText 1]['extra'][= (reasoning || 'token_count'] =) await+ getTokenCountAsync(chat[chat.length - 1]['mes'], 0); |
| 5985 | + chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | |
| 5918 | 5986 | } |
| 5919 | 5987 | const chat_id = (chat.length - 1); |
| 5920 | 5988 | await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id); |
| @@ -5930,6 +5998,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | ||
| 5930 | 5998 | chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); |
| 5931 | 5999 | chat[chat.length - 1]['extra']['api'] = getGeneratingApi(); |
| 5932 | 6000 | chat[chat.length - 1]['extra']['model'] = getGeneratingModel(); |
| 6001 | + chat[chat.length - 1]['extra']['reasoning'] = reasoning; | |
| 5933 | 6002 | if (power_user.trim_spaces) { |
| 5934 | 6003 | getMessage = getMessage.trim(); |
| 5935 | 6004 | } |
| @@ -5939,7 +6008,8 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | ||
| 5939 | 6008 | chat[chat.length - 1]['gen_finished'] = generationFinished; |
| 5940 | 6009 | |
| 5941 | 6010 | if (power_user.message_token_count_enabled) { |
| 5942 | 6011 | chat[chat.lengthconst -tokenCountText 1]['extra'][= (reasoning || 'token_count'] =) await+ getTokenCountAsync(chat[chat.length - 1]['mes'], 0); |
| 6012 | + chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0); | |
| 5943 | 6013 | } |
| 5944 | 6014 | |
| 5945 | 6015 | if (selected_group) { |
| @@ -6004,6 +6074,19 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes) | ||
| 6004 | 6074 | return { type, getMessage }; |
| 6005 | 6075 | } |
| 6006 | 6076 | |
| 6077 | +export function syncCurrentSwipeInfoExtras() { | |
| 6078 | + if (!chat.length) { | |
| 6079 | + return; | |
| 6080 | + } | |
| 6081 | + const currentMessage = chat[chat.length - 1]; | |
| 6082 | + if (currentMessage && Array.isArray(currentMessage.swipe_info) && typeof currentMessage.swipe_id === 'number') { | |
| 6083 | + const swipeInfo = currentMessage.swipe_info[currentMessage.swipe_id]; | |
| 6084 | + if (swipeInfo && typeof swipeInfo === 'object') { | |
| 6085 | + swipeInfo.extra = structuredClone(currentMessage.extra); | |
| 6086 | + } | |
| 6087 | + } | |
| 6088 | +} | |
| 6089 | + | |
| 6007 | 6090 | function saveImageToMessage(img, mes) { |
| 6008 | 6091 | if (mes && img.image) { |
| 6009 | 6092 | if (!mes.extra || typeof mes.extra !== 'object') { |
| @@ -7982,9 +8065,23 @@ function updateEditArrowClasses() { | ||
| 7982 | 8065 | } |
| 7983 | 8066 | } |
| 7984 | 8067 | |
| 7985 | -function closeMessageEditor() { | |
| 8068 | +/** | |
| 7986 | - if (this_edit_mes_id) { | |
| 8069 | + * Closes the message editor. | |
| 7987 | - $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click(); | |
| 8070 | + * @param {'message'|'reasoning'|'all'} what What to close. Default is 'all'. | |
| 8071 | + */ | |
| 8072 | +export function closeMessageEditor(what = 'all') { | |
| 8073 | + if (what === 'message' || what === 'all') { | |
| 8074 | + if (this_edit_mes_id) { | |
| 8075 | + $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click(); | |
| 8076 | + } | |
| 8077 | + } | |
| 8078 | + if (what === 'reasoning' || what === 'all') { | |
| 8079 | + document.querySelectorAll('.reasoning_edit_textarea').forEach((el) => { | |
| 8080 | + const cancelButton = el.closest('.mes')?.querySelector('.mes_reasoning_edit_cancel'); | |
| 8081 | + if (cancelButton instanceof HTMLElement) { | |
| 8082 | + cancelButton.click(); | |
| 8083 | + } | |
| 8084 | + }); | |
| 7988 | 8085 | } |
| 7989 | 8086 | } |
| 7990 | 8087 | |
| @@ -8417,6 +8514,9 @@ function swipe_left() { // when we swipe left..but no generation. | ||
| 8417 | 8514 | streamingProcessor.onStopStreaming(); |
| 8418 | 8515 | } |
| 8419 | 8516 | |
| 8517 | + // Make sure ad-hoc changes to extras are saved before swiping away | |
| 8518 | + syncCurrentSwipeInfoExtras(); | |
| 8519 | + | |
| 8420 | 8520 | const swipe_duration = 120; |
| 8421 | 8521 | const swipe_range = '700px'; |
| 8422 | 8522 | chat[chat.length - 1]['swipe_id']--; |
| @@ -8468,7 +8568,8 @@ function swipe_left() { // when we swipe left..but no generation. | ||
| 8468 | 8568 | } |
| 8469 | 8569 | |
| 8470 | 8570 | const swipeMessage = $('#chat').find(`[mesid="${chat.length - 1}"]`); |
| 8471 | 8571 | const tokenCounttokenCountText = await getTokenCountAsync(chat[chat.length - 1]?.mes,extra?.reasoning 0|| '') + chat[chat.length - 1].mes; |
| 8572 | + const tokenCount = await getTokenCountAsync(tokenCountText, 0); | |
| 8472 | 8573 | chat[chat.length - 1]['extra']['token_count'] = tokenCount; |
| 8473 | 8574 | swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`); |
| 8474 | 8575 | } |
| @@ -8551,6 +8652,9 @@ const swipe_right = () => { | ||
| 8551 | 8652 | return unblockGeneration(); |
| 8552 | 8653 | } |
| 8553 | 8654 | |
| 8655 | + // Make sure ad-hoc changes to extras are saved before swiping away | |
| 8656 | + syncCurrentSwipeInfoExtras(); | |
| 8657 | + | |
| 8554 | 8658 | const swipe_duration = 200; |
| 8555 | 8659 | const swipe_range = 700; |
| 8556 | 8660 | //console.log(swipe_range); |
| @@ -8632,6 +8736,7 @@ const swipe_right = () => { | ||
| 8632 | 8736 | // resets the timer |
| 8633 | 8737 | swipeMessage.find('.mes_timer').html(''); |
| 8634 | 8738 | swipeMessage.find('.tokenCounterDisplay').text(''); |
| 8739 | + swipeMessage.find('.mes_reasoning').html(''); | |
| 8635 | 8740 | } else { |
| 8636 | 8741 | //console.log('showing previously generated swipe candidate, or "..."'); |
| 8637 | 8742 | //console.log('onclick right swipe calling addOneMessage'); |
| @@ -8642,7 +8747,8 @@ const swipe_right = () => { | ||
| 8642 | 8747 | chat[chat.length - 1].extra = {}; |
| 8643 | 8748 | } |
| 8644 | 8749 | |
| 8645 | 8750 | const tokenCounttokenCountText = await getTokenCountAsync(chat[chat.length - 1]?.mes,extra?.reasoning 0|| '') + chat[chat.length - 1].mes; |
| 8751 | + const tokenCount = await getTokenCountAsync(tokenCountText, 0); | |
| 8646 | 8752 | chat[chat.length - 1]['extra']['token_count'] = tokenCount; |
| 8647 | 8753 | swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`); |
| 8648 | 8754 | } |
| @@ -9444,7 +9550,8 @@ function addDebugFunctions() { | ||
| 9444 | 9550 | message.extra = {}; |
| 9445 | 9551 | } |
| 9446 | 9552 | |
| 9447 | - message.extra.token_count = await getTokenCountAsync(message.mes, 0); | |
| 9553 | + const tokenCountText = (message?.extra?.reasoning || '') + message.mes; | |
| 9554 | + message.extra.token_count = await getTokenCountAsync(tokenCountText, 0); | |
| 9448 | 9555 | } |
| 9449 | 9556 | |
| 9450 | 9557 | await saveChatConditional(); |
| @@ -11213,14 +11320,15 @@ jQuery(async function () { | ||
| 11213 | 11320 | |
| 11214 | 11321 | $(document).keyup(function (e) { |
| 11215 | 11322 | if (e.key === 'Escape') { |
| 11216 | 11323 | const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0; |
| 11217 | 11324 | if (isEditVisible && power_user.auto_save_msg_edits === false) { |
| 11218 | 11325 | closeMessageEditor('all'); |
| 11219 | 11326 | $('#send_textarea').focus(); |
| 11220 | 11327 | return; |
| 11221 | 11328 | } |
| 11222 | 11329 | if (isEditVisible && power_user.auto_save_msg_edits === true) { |
| 11223 | 11330 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click(); |
| 11331 | + closeMessageEditor('reasoning'); | |
| 11224 | 11332 | $('#send_textarea').focus(); |
| 11225 | 11333 | return; |
| 11226 | 11334 | } |
| @@ -11454,8 +11562,8 @@ jQuery(async function () { | ||
| 11454 | 11562 | $('#avatar-and-name-block').slideToggle(); |
| 11455 | 11563 | }); |
| 11456 | 11564 | |
| 11457 | 11565 | $(document).on('mouseup touchend', '#show_more_messages', async function () => { |
| 11458 | 11566 | await showMoreMessages(); |
| 11459 | 11567 | }); |
| 11460 | 11568 | |
| 11461 | 11569 | $(document).on('click', '.open_characters_library', async function () { |
| @@ -482,10 +482,10 @@ function highlightNewBackground(bg) { | ||
| 482 | 482 | */ |
| 483 | 483 | function setFittingClass(fitting) { |
| 484 | 484 | const backgrounds = $('#bg1, #bg_custom'); |
| 485 | - backgrounds.toggleClass('cover', fitting === 'cover'); | |
| 485 | + for (const option of ['cover', 'contain', 'stretch', 'center']) { | |
| 486 | 486 | backgrounds.toggleClass('contain'option, fittingoption === 'contain'fitting); |
| 487 | - backgrounds.toggleClass('stretch', fitting === 'stretch'); | |
| 487 | + } | |
| 488 | - backgrounds.toggleClass('center', fitting === 'center'); | |
| 488 | + background_settings.fitting = fitting; | |
| 489 | 489 | } |
| 490 | 490 | |
| 491 | 491 | function onBackgroundFilterInput() { |
| @@ -59,6 +59,16 @@ const hash_derivations = { | ||
| 59 | 59 | // Tulu-3-8B |
| 60 | 60 | // Tulu-3-70B |
| 61 | 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' | |
| 62 | 72 | }; |
| 63 | 73 | |
| 64 | 74 | const substr_derivations = { |
| @@ -11,6 +11,7 @@ import { | ||
| 11 | 11 | getCurrentChatId, |
| 12 | 12 | getRequestHeaders, |
| 13 | 13 | hideSwipeButtons, |
| 14 | + name1, | |
| 14 | 15 | name2, |
| 15 | 16 | reloadCurrentChat, |
| 16 | 17 | saveChatDebounced, |
| @@ -21,6 +22,7 @@ import { | ||
| 21 | 22 | chat_metadata, |
| 22 | 23 | neutralCharacterName, |
| 23 | 24 | updateChatMetadata, |
| 25 | + system_message_types, | |
| 24 | 26 | } from '../script.js'; |
| 25 | 27 | import { selected_group } from './group-chats.js'; |
| 26 | 28 | import { power_user } from './power-user.js'; |
| @@ -34,6 +36,7 @@ import { | ||
| 34 | 36 | humanFileSize, |
| 35 | 37 | saveBase64AsFile, |
| 36 | 38 | extractTextFromOffice, |
| 39 | + download, | |
| 37 | 40 | } from './utils.js'; |
| 38 | 41 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; |
| 39 | 42 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| @@ -41,6 +44,7 @@ import { ScraperManager } from './scrapers.js'; | ||
| 41 | 44 | import { DragAndDropHandler } from './dragdrop.js'; |
| 42 | 45 | import { renderTemplateAsync } from './templates.js'; |
| 43 | 46 | import { t } from './i18n.js'; |
| 47 | +import { humanizedDateTime } from './RossAscends-mods.js'; | |
| 44 | 48 | |
| 45 | 49 | /** |
| 46 | 50 | * @typedef {Object} FileAttachment |
| @@ -1437,6 +1441,19 @@ jQuery(function () { | ||
| 1437 | 1441 | await viewMessageFile(messageId); |
| 1438 | 1442 | }); |
| 1439 | 1443 | |
| 1444 | + $(document).on('click', '.assistant_note_export', async function () { | |
| 1445 | + const chatToSave = [ | |
| 1446 | + { | |
| 1447 | + user_name: name1, | |
| 1448 | + character_name: name2, | |
| 1449 | + chat_metadata: chat_metadata, | |
| 1450 | + }, | |
| 1451 | + ...chat.filter(x => x?.extra?.type !== system_message_types.ASSISTANT_NOTE), | |
| 1452 | + ]; | |
| 1453 | + | |
| 1454 | + download(JSON.stringify(chatToSave, null, 4), `Assistant - ${humanizedDateTime()}.json`, 'application/json'); | |
| 1455 | + }); | |
| 1456 | + | |
| 1440 | 1457 | // Do not change. #attachFile is added by extension. |
| 1441 | 1458 | $(document).on('click', '#attachFile', function () { |
| 1442 | 1459 | $('#file_form_input').trigger('click'); |
| @@ -54,6 +54,8 @@ | ||
| 54 | 54 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> |
| 55 | 55 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> |
| 56 | 56 | <option data-type="google" value="gemini-2.0-flash-exp">gemini-2.0-flash-exp</option> |
| 57 | + <option data-type="google" value="gemini-2.0-flash-thinking-exp">gemini-2.0-flash-thinking-exp</option> | |
| 58 | + <option data-type="google" value="gemini-2.0-flash-thinking-exp-01-21">gemini-2.0-flash-thinking-exp-01-21</option> | |
| 57 | 59 | <option data-type="google" value="gemini-2.0-flash-thinking-exp-1219">gemini-2.0-flash-thinking-exp-1219</option> |
| 58 | 60 | <option data-type="google" value="gemini-1.5-flash">gemini-1.5-flash</option> |
| 59 | 61 | <option data-type="google" value="gemini-1.5-flash-latest">gemini-1.5-flash-latest</option> |
| @@ -30,6 +30,7 @@ const CC_COMMANDS = [ | ||
| 30 | 30 | 'api-url', |
| 31 | 31 | 'model', |
| 32 | 32 | 'proxy', |
| 33 | + 'stop-strings', | |
| 33 | 34 | ]; |
| 34 | 35 | |
| 35 | 36 | const TC_COMMANDS = [ |
| @@ -43,6 +44,7 @@ const TC_COMMANDS = [ | ||
| 43 | 44 | 'context', |
| 44 | 45 | 'instruct-state', |
| 45 | 46 | 'tokenizer', |
| 47 | + 'stop-strings', | |
| 46 | 48 | ]; |
| 47 | 49 | |
| 48 | 50 | const FANCY_NAMES = { |
| @@ -57,6 +59,7 @@ const FANCY_NAMES = { | ||
| 57 | 59 | 'instruct': 'Instruct Template', |
| 58 | 60 | 'context': 'Context Template', |
| 59 | 61 | 'tokenizer': 'Tokenizer', |
| 62 | + 'stop-strings': 'Custom Stopping Strings', | |
| 60 | 63 | }; |
| 61 | 64 | |
| 62 | 65 | /** |
| @@ -138,6 +141,7 @@ const profilesProvider = () => [ | ||
| 138 | 141 | * @property {string} [context] Context Template |
| 139 | 142 | * @property {string} [instruct-state] Instruct Mode |
| 140 | 143 | * @property {string} [tokenizer] Tokenizer |
| 144 | + * @property {string} [stop-strings] Custom Stopping Strings | |
| 141 | 145 | * @property {string[]} [exclude] Commands to exclude |
| 142 | 146 | */ |
| 143 | 147 | |
| @@ -30,6 +30,7 @@ import { GoogleTranslateTtsProvider } from './google-translate.js'; | ||
| 30 | 30 | export { talkingAnimation }; |
| 31 | 31 | |
| 32 | 32 | const UPDATE_INTERVAL = 1000; |
| 33 | +const wrapper = new ModuleWorkerWrapper(moduleWorker); | |
| 33 | 34 | |
| 34 | 35 | let voiceMapEntries = []; |
| 35 | 36 | let voiceMap = {}; // {charName:voiceid, charName2:voiceid2} |
| @@ -120,7 +121,7 @@ async function onNarrateOneMessage() { | ||
| 120 | 121 | } |
| 121 | 122 | |
| 122 | 123 | resetTtsPlayback(); |
| 123 | 124 | ttsJobQueue.pushprocessAndQueueTtsMessage(message); |
| 124 | 125 | moduleWorker(); |
| 125 | 126 | } |
| 126 | 127 | |
| @@ -147,7 +148,7 @@ async function onNarrateText(args, text) { | ||
| 147 | 148 | } |
| 148 | 149 | |
| 149 | 150 | resetTtsPlayback(); |
| 150 | 151 | ttsJobQueue.pushprocessAndQueueTtsMessage({ mes: text, name: name }); |
| 151 | 152 | await moduleWorker(); |
| 152 | 153 | |
| 153 | 154 | // Return back to the chat voices |
| @@ -220,6 +221,36 @@ function isTtsProcessing() { | ||
| 220 | 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 | 254 | function debugTtsPlayback() { |
| 224 | 255 | console.log(JSON.stringify( |
| 225 | 256 | { |
| @@ -350,7 +381,7 @@ function onAudioControlClicked() { | ||
| 350 | 381 | talkingAnimation(false); |
| 351 | 382 | } else { |
| 352 | 383 | // Default play behavior if not processing or playing is to play the last message. |
| 353 | 384 | ttsJobQueue.pushprocessAndQueueTtsMessage(context.chat[context.chat.length - 1]); |
| 354 | 385 | } |
| 355 | 386 | updateUiAudioPlayState(); |
| 356 | 387 | } |
| @@ -376,6 +407,7 @@ function completeCurrentAudioJob() { | ||
| 376 | 407 | currentAudioJob = null; |
| 377 | 408 | talkingAnimation(false); //stop lip animation |
| 378 | 409 | // updateUiPlayState(); |
| 410 | + wrapper.update(); | |
| 379 | 411 | } |
| 380 | 412 | |
| 381 | 413 | /** |
| @@ -466,7 +498,7 @@ async function processTtsQueue() { | ||
| 466 | 498 | } |
| 467 | 499 | |
| 468 | 500 | if (extension_settings.tts.skip_tags) { |
| 469 | 501 | text = text.replace(/<.*?>.[\s\S]*?<\/.*?>/g, '').trim(); |
| 470 | 502 | } |
| 471 | 503 | |
| 472 | 504 | if (!extension_settings.tts.pass_asterisks) { |
| @@ -569,6 +601,7 @@ function loadSettings() { | ||
| 569 | 601 | $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only); |
| 570 | 602 | $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); |
| 571 | 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 | 605 | $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); |
| 573 | 606 | $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); |
| 574 | 607 | $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); |
| @@ -638,6 +671,11 @@ function onPeriodicAutoGenerationClick() { | ||
| 638 | 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 | 680 | function onNarrateDialoguesClick() { |
| 643 | 681 | extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked'); |
| @@ -816,7 +854,12 @@ async function onMessageEvent(messageId, lastCharIndex) { | ||
| 816 | 854 | lastChatId = context.chatId; |
| 817 | 855 | |
| 818 | 856 | console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); |
| 819 | - ttsJobQueue.push(message); | |
| 857 | + | |
| 858 | + if (extension_settings.tts.periodic_auto_generation) { | |
| 859 | + ttsJobQueue.push(message); | |
| 860 | + } else { | |
| 861 | + processAndQueueTtsMessage(message); | |
| 862 | + } | |
| 820 | 863 | } |
| 821 | 864 | |
| 822 | 865 | async function onMessageDeleted() { |
| @@ -1156,6 +1199,7 @@ jQuery(async function () { | ||
| 1156 | 1199 | $('#tts_pass_asterisks').on('click', onPassAsterisksClick); |
| 1157 | 1200 | $('#tts_auto_generation').on('click', onAutoGenerationClick); |
| 1158 | 1201 | $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick); |
| 1202 | + $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick); | |
| 1159 | 1203 | $('#tts_narrate_user').on('click', onNarrateUserClick); |
| 1160 | 1204 | |
| 1161 | 1205 | $('#playback_rate').on('input', function () { |
| @@ -1177,7 +1221,6 @@ jQuery(async function () { | ||
| 1177 | 1221 | loadSettings(); // Depends on Extension Controls and loadTtsProvider |
| 1178 | 1222 | loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies |
| 1179 | 1223 | addAudioControl(); // Depends on Extension Controls |
| 1180 | - const wrapper = new ModuleWorkerWrapper(moduleWorker); | |
| 1181 | 1224 | setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things |
| 1182 | 1225 | eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback); |
| 1183 | 1226 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| @@ -30,6 +30,10 @@ | ||
| 30 | 30 | <input type="checkbox" id="tts_periodic_auto_generation"> |
| 31 | 31 | <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small> |
| 32 | 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 | 37 | <label class="checkbox_label" for="tts_narrate_quoted"> |
| 34 | 38 | <input type="checkbox" id="tts_narrate_quoted"> |
| 35 | 39 | <small data-i18n="Only narrate quotes">Only narrate "quotes"</small> |
| @@ -81,6 +81,7 @@ import { t } from './i18n.js'; | ||
| 81 | 81 | |
| 82 | 82 | export { |
| 83 | 83 | selected_group, |
| 84 | + openGroupId, | |
| 84 | 85 | is_group_automode_enabled, |
| 85 | 86 | hideMutedSprites, |
| 86 | 87 | is_group_generating, |
| @@ -1367,6 +1368,15 @@ function getGroupCharacterBlock(character) { | ||
| 1367 | 1368 | template.find('.ch_fav').val(isFav); |
| 1368 | 1369 | template.toggleClass('is_fav', isFav); |
| 1369 | 1370 | |
| 1371 | + const auxFieldName = power_user.aux_field || 'character_version'; | |
| 1372 | + const auxFieldValue = (character.data && character.data[auxFieldName]) || ''; | |
| 1373 | + if (auxFieldValue) { | |
| 1374 | + template.find('.character_version').text(auxFieldValue); | |
| 1375 | + } | |
| 1376 | + else { | |
| 1377 | + template.find('.character_version').hide(); | |
| 1378 | + } | |
| 1379 | + | |
| 1370 | 1380 | let queuePosition = groupChatQueueOrder.get(character.avatar); |
| 1371 | 1381 | if (queuePosition) { |
| 1372 | 1382 | template.find('.queue_position').text(queuePosition); |
| @@ -188,7 +188,7 @@ export async function generateKoboldWithStreaming(generate_data, signal) { | ||
| 188 | 188 | if (data?.token) { |
| 189 | 189 | text += data.token; |
| 190 | 190 | } |
| 191 | 191 | yield { text, swipes: [], toolCalls: [], state: {} }; |
| 192 | 192 | } |
| 193 | 193 | }; |
| 194 | 194 | } |
| @@ -746,7 +746,7 @@ export async function generateNovelWithStreaming(generate_data, signal) { | ||
| 746 | 746 | text += data.token; |
| 747 | 747 | } |
| 748 | 748 | |
| 749 | 749 | yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [], state: {} }; |
| 750 | 750 | } |
| 751 | 751 | }; |
| 752 | 752 | } |
| @@ -1096,8 +1096,8 @@ async function preparePromptsForChatCompletion({ Scenario, charPersonality, name | ||
| 1096 | 1096 | // Unordered prompts without marker |
| 1097 | 1097 | { role: 'system', content: impersonationPrompt, identifier: 'impersonate' }, |
| 1098 | 1098 | { role: 'system', content: quietPrompt, identifier: 'quietPrompt' }, |
| 1099 | - { role: 'system', content: bias, identifier: 'bias' }, | |
| 1100 | 1099 | { role: 'system', content: groupNudge, identifier: 'groupNudge' }, |
| 1100 | + { role: 'assistant', content: bias, identifier: 'bias' }, | |
| 1101 | 1101 | ]; |
| 1102 | 1102 | |
| 1103 | 1103 | // Tavern Extras - Summary |
| @@ -1922,7 +1922,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1922 | 1922 | } |
| 1923 | 1923 | |
| 1924 | 1924 | // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite |
| 1925 | 1925 | 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 | 1926 | await validateReverseProxy(); |
| 1927 | 1927 | generate_data['reverse_proxy'] = oai_settings.reverse_proxy; |
| 1928 | 1928 | generate_data['proxy_password'] = oai_settings.proxy_password; |
| @@ -2030,6 +2030,16 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2030 | 2030 | // https://api-docs.deepseek.com/api/create-chat-completion |
| 2031 | 2031 | if (isDeepSeek) { |
| 2032 | 2032 | generate_data.top_p = generate_data.top_p || Number.EPSILON; |
| 2033 | + | |
| 2034 | + if (generate_data.model.endsWith('-reasoner')) { | |
| 2035 | + delete generate_data.top_p; | |
| 2036 | + delete generate_data.temperature; | |
| 2037 | + delete generate_data.frequency_penalty; | |
| 2038 | + delete generate_data.presence_penalty; | |
| 2039 | + delete generate_data.top_logprobs; | |
| 2040 | + delete generate_data.logprobs; | |
| 2041 | + delete generate_data.logit_bias; | |
| 2042 | + } | |
| 2033 | 2043 | } |
| 2034 | 2044 | |
| 2035 | 2045 | if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano) && oai_settings.seed >= 0) { |
| @@ -2085,6 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2085 | 2095 | let text = ''; |
| 2086 | 2096 | const swipes = []; |
| 2087 | 2097 | const toolCalls = []; |
| 2098 | + const state = { reasoning: '' }; | |
| 2088 | 2099 | while (true) { |
| 2089 | 2100 | const { done, value } = await reader.read(); |
| 2090 | 2101 | if (done) return; |
| @@ -2095,14 +2106,14 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2095 | 2106 | |
| 2096 | 2107 | if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { |
| 2097 | 2108 | const swipeIndex = parsed.choices[0].index - 1; |
| 2098 | 2109 | swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state); |
| 2099 | 2110 | } else { |
| 2100 | 2111 | text += getStreamingReply(parsed, state); |
| 2101 | 2112 | } |
| 2102 | 2113 | |
| 2103 | 2114 | ToolManager.parseToolCalls(toolCalls, parsed); |
| 2104 | 2115 | |
| 2105 | 2116 | yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls, state: state }; |
| 2106 | 2117 | } |
| 2107 | 2118 | }; |
| 2108 | 2119 | } |
| @@ -2129,14 +2140,33 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 2129 | 2140 | } |
| 2130 | 2141 | } |
| 2131 | 2142 | |
| 2132 | -function getStreamingReply(data) { | |
| 2143 | +/** | |
| 2144 | + * Extracts the reply from the response data from a chat completions-like source | |
| 2145 | + * @param {object} data Response data from the chat completions-like source | |
| 2146 | + * @param {object} state Additional state to keep track of | |
| 2147 | + * @returns {string} The reply extracted from the response data | |
| 2148 | + */ | |
| 2149 | +function getStreamingReply(data, state) { | |
| 2133 | 2150 | if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) { |
| 2134 | 2151 | return data?.delta?.text || ''; |
| 2135 | 2152 | } 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') || ''; | |
| 2153 | + if (oai_settings.show_thoughts) { | |
| 2154 | + state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || ''); | |
| 2155 | + } | |
| 2156 | + return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || ''; | |
| 2137 | 2157 | } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) { |
| 2138 | 2158 | return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || ''; |
| 2139 | - } else { | |
| 2159 | + } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { | |
| 2160 | + if (oai_settings.show_thoughts) { | |
| 2161 | + state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); | |
| 2162 | + } | |
| 2163 | + return data.choices?.[0]?.delta?.content || ''; | |
| 2164 | + } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) { | |
| 2165 | + if (oai_settings.show_thoughts) { | |
| 2166 | + state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); | |
| 2167 | + } | |
| 2168 | + return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | |
| 2169 | + } else { | |
| 2140 | 2170 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 2141 | 2171 | } |
| 2142 | 2172 | } |
| @@ -3346,7 +3376,7 @@ async function getStatusOpen() { | ||
| 3346 | 3376 | chat_completion_source: oai_settings.chat_completion_source, |
| 3347 | 3377 | }; |
| 3348 | 3378 | |
| 3349 | 3379 | 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 | 3380 | await validateReverseProxy(); |
| 3351 | 3381 | } |
| 3352 | 3382 | |
| @@ -4204,7 +4234,7 @@ async function onModelChange() { | ||
| 4204 | 4234 | $('#openai_max_context').attr('max', max_32k); |
| 4205 | 4235 | } else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206')) { |
| 4206 | 4236 | $('#openai_max_context').attr('max', max_2mil); |
| 4207 | 4237 | } else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash-exp') || value.includes('gemini-2.0-flash-thinking-exp')) { |
| 4208 | 4238 | $('#openai_max_context').attr('max', max_1mil); |
| 4209 | 4239 | } else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') { |
| 4210 | 4240 | $('#openai_max_context').attr('max', max_32k); |
| @@ -4488,7 +4518,7 @@ async function onModelChange() { | ||
| 4488 | 4518 | if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) { |
| 4489 | 4519 | if (oai_settings.max_context_unlocked) { |
| 4490 | 4520 | $('#openai_max_context').attr('max', unlocked_max); |
| 4491 | 4521 | } else if (oai_settings.deepseek_model ==['deepseek-reasoner', 'deepseek-chat'].includes(oai_settings.deepseek_model)) { |
| 4492 | 4522 | $('#openai_max_context').attr('max', max_64k); |
| 4493 | 4523 | } else if (oai_settings.deepseek_model == 'deepseek-coder') { |
| 4494 | 4524 | $('#openai_max_context').attr('max', max_16k); |
| @@ -4725,7 +4755,7 @@ async function onConnectButtonClick(e) { | ||
| 4725 | 4755 | await writeSecret(SECRET_KEYS.DEEPSEEK, api_key_deepseek); |
| 4726 | 4756 | } |
| 4727 | 4757 | |
| 4728 | 4758 | if (!secret_state[SECRET_KEYS.DEEPSEEK] && !oai_settings.reverse_proxy) { |
| 4729 | 4759 | console.log('No secret key saved for DeepSeek'); |
| 4730 | 4760 | return; |
| 4731 | 4761 | } |
| @@ -4901,6 +4931,8 @@ export function isImageInliningSupported() { | ||
| 4901 | 4931 | const visionSupportedModels = [ |
| 4902 | 4932 | 'gpt-4-vision', |
| 4903 | 4933 | 'gemini-2.0-flash-thinking-exp-1219', |
| 4934 | + 'gemini-2.0-flash-thinking-exp-01-21', | |
| 4935 | + 'gemini-2.0-flash-thinking-exp', | |
| 4904 | 4936 | 'gemini-2.0-flash-exp', |
| 4905 | 4937 | 'gemini-1.5-flash', |
| 4906 | 4938 | 'gemini-1.5-flash-latest', |
| @@ -253,6 +253,14 @@ let power_user = { | ||
| 253 | 253 | content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.', |
| 254 | 254 | }, |
| 255 | 255 | |
| 256 | + reasoning: { | |
| 257 | + add_to_prompts: false, | |
| 258 | + prefix: '<think>\n', | |
| 259 | + suffix: '\n</think>', | |
| 260 | + separator: '\n\n', | |
| 261 | + max_additions: 1, | |
| 262 | + }, | |
| 263 | + | |
| 256 | 264 | personas: {}, |
| 257 | 265 | default_persona: null, |
| 258 | 266 | persona_descriptions: {}, |
| @@ -2534,7 +2542,7 @@ async function loadUntilMesId(mesId) { | ||
| 2534 | 2542 | let target; |
| 2535 | 2543 | |
| 2536 | 2544 | while (getFirstDisplayedMessageId() > mesId && getFirstDisplayedMessageId() !== 0) { |
| 2537 | 2545 | await showMoreMessages(); |
| 2538 | 2546 | await delay(1); |
| 2539 | 2547 | target = $('#chat').find(`.mes[mesid=${mesId}]`); |
| 2540 | 2548 | |
| @@ -4064,4 +4072,45 @@ $(document).ready(() => { | ||
| 4064 | 4072 | ], |
| 4065 | 4073 | helpString: 'activates a movingUI preset by name', |
| 4066 | 4074 | })); |
| 4075 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 4076 | + name: 'stop-strings', | |
| 4077 | + aliases: ['stopping-strings', 'custom-stopping-strings', 'custom-stop-strings'], | |
| 4078 | + helpString: ` | |
| 4079 | + <div> | |
| 4080 | + Sets a list of custom stopping strings. Gets the list if no value is provided. | |
| 4081 | + </div> | |
| 4082 | + <div> | |
| 4083 | + <strong>Examples:</strong> | |
| 4084 | + </div> | |
| 4085 | + <ul> | |
| 4086 | + <li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li> | |
| 4087 | + <li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li> | |
| 4088 | + </ul> | |
| 4089 | + `, | |
| 4090 | + returns: ARGUMENT_TYPE.LIST, | |
| 4091 | + unnamedArgumentList: [ | |
| 4092 | + SlashCommandArgument.fromProps({ | |
| 4093 | + description: 'list of strings', | |
| 4094 | + typeList: [ARGUMENT_TYPE.LIST], | |
| 4095 | + acceptsMultiple: false, | |
| 4096 | + isRequired: false, | |
| 4097 | + }), | |
| 4098 | + ], | |
| 4099 | + callback: (_, value) => { | |
| 4100 | + if (String(value ?? '').trim()) { | |
| 4101 | + const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value); | |
| 4102 | + if (!parsedValue || !Array.isArray(parsedValue)) { | |
| 4103 | + throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.'); | |
| 4104 | + } | |
| 4105 | + parsedValue.forEach((item, index) => { | |
| 4106 | + parsedValue[index] = String(item); | |
| 4107 | + }); | |
| 4108 | + power_user.custom_stopping_strings = JSON.stringify(parsedValue); | |
| 4109 | + $('#custom_stopping_strings').val(power_user.custom_stopping_strings); | |
| 4110 | + saveSettingsDebounced(); | |
| 4111 | + } | |
| 4112 | + | |
| 4113 | + return power_user.custom_stopping_strings; | |
| 4114 | + }, | |
| 4115 | + })); | |
| 4067 | 4116 | }); |
| @@ -0,0 +1,297 @@ | ||
| 1 | +import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; | |
| 2 | +import { t } from './i18n.js'; | |
| 3 | +import { MacrosParser } from './macros.js'; | |
| 4 | +import { Popup } from './popup.js'; | |
| 5 | +import { power_user } from './power-user.js'; | |
| 6 | +import { SlashCommand } from './slash-commands/SlashCommand.js'; | |
| 7 | +import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | |
| 8 | +import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | |
| 9 | +import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | |
| 10 | +import { copyText } from './utils.js'; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * Gets a message from a jQuery element. | |
| 14 | + * @param {Element} element | |
| 15 | + * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}} | |
| 16 | + */ | |
| 17 | +function getMessageFromJquery(element) { | |
| 18 | + const messageBlock = $(element).closest('.mes'); | |
| 19 | + const messageId = Number(messageBlock.attr('mesid')); | |
| 20 | + const message = chat[messageId]; | |
| 21 | + return { messageId: messageId, message, messageBlock }; | |
| 22 | +} | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * Helper class for adding reasoning to messages. | |
| 26 | + * Keeps track of the number of reasoning additions. | |
| 27 | + */ | |
| 28 | +export class PromptReasoning { | |
| 29 | + static REASONING_PLACEHOLDER = '\u200B'; | |
| 30 | + static REASONING_PLACEHOLDER_REGEX = new RegExp(`${PromptReasoning.REASONING_PLACEHOLDER}$`); | |
| 31 | + | |
| 32 | + constructor() { | |
| 33 | + this.counter = 0; | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * Checks if the limit of reasoning additions has been reached. | |
| 38 | + * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise. | |
| 39 | + */ | |
| 40 | + isLimitReached() { | |
| 41 | + if (!power_user.reasoning.add_to_prompts) { | |
| 42 | + return true; | |
| 43 | + } | |
| 44 | + | |
| 45 | + return this.counter >= power_user.reasoning.max_additions; | |
| 46 | + } | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * Add reasoning to a message according to the power user settings. | |
| 50 | + * @param {string} content Message content | |
| 51 | + * @param {string} reasoning Message reasoning | |
| 52 | + * @returns {string} Message content with reasoning | |
| 53 | + */ | |
| 54 | + addToMessage(content, reasoning) { | |
| 55 | + // Disabled or reached limit of additions | |
| 56 | + if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) { | |
| 57 | + return content; | |
| 58 | + } | |
| 59 | + | |
| 60 | + // No reasoning provided or a placeholder | |
| 61 | + if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) { | |
| 62 | + return content; | |
| 63 | + } | |
| 64 | + | |
| 65 | + // Increment the counter | |
| 66 | + this.counter++; | |
| 67 | + | |
| 68 | + // Substitute macros in variable parts | |
| 69 | + const prefix = substituteParams(power_user.reasoning.prefix || ''); | |
| 70 | + const separator = substituteParams(power_user.reasoning.separator || ''); | |
| 71 | + const suffix = substituteParams(power_user.reasoning.suffix || ''); | |
| 72 | + | |
| 73 | + // Combine parts with reasoning and content | |
| 74 | + return `${prefix}${reasoning}${suffix}${separator}${content}`; | |
| 75 | + } | |
| 76 | +} | |
| 77 | + | |
| 78 | +function loadReasoningSettings() { | |
| 79 | + $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts); | |
| 80 | + $('#reasoning_add_to_prompts').on('change', function () { | |
| 81 | + power_user.reasoning.add_to_prompts = !!$(this).prop('checked'); | |
| 82 | + saveSettingsDebounced(); | |
| 83 | + }); | |
| 84 | + | |
| 85 | + $('#reasoning_prefix').val(power_user.reasoning.prefix); | |
| 86 | + $('#reasoning_prefix').on('input', function () { | |
| 87 | + power_user.reasoning.prefix = String($(this).val()); | |
| 88 | + saveSettingsDebounced(); | |
| 89 | + }); | |
| 90 | + | |
| 91 | + $('#reasoning_suffix').val(power_user.reasoning.suffix); | |
| 92 | + $('#reasoning_suffix').on('input', function () { | |
| 93 | + power_user.reasoning.suffix = String($(this).val()); | |
| 94 | + saveSettingsDebounced(); | |
| 95 | + }); | |
| 96 | + | |
| 97 | + $('#reasoning_separator').val(power_user.reasoning.separator); | |
| 98 | + $('#reasoning_separator').on('input', function () { | |
| 99 | + power_user.reasoning.separator = String($(this).val()); | |
| 100 | + saveSettingsDebounced(); | |
| 101 | + }); | |
| 102 | + | |
| 103 | + $('#reasoning_max_additions').val(power_user.reasoning.max_additions); | |
| 104 | + $('#reasoning_max_additions').on('input', function () { | |
| 105 | + power_user.reasoning.max_additions = Number($(this).val()); | |
| 106 | + saveSettingsDebounced(); | |
| 107 | + }); | |
| 108 | +} | |
| 109 | + | |
| 110 | +function registerReasoningSlashCommands() { | |
| 111 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 112 | + name: 'reasoning-get', | |
| 113 | + returns: ARGUMENT_TYPE.STRING, | |
| 114 | + 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.`, | |
| 115 | + unnamedArgumentList: [ | |
| 116 | + SlashCommandArgument.fromProps({ | |
| 117 | + description: 'Message ID. If not provided, the message ID of the last message is used.', | |
| 118 | + typeList: ARGUMENT_TYPE.NUMBER, | |
| 119 | + enumProvider: commonEnumProviders.messages(), | |
| 120 | + }), | |
| 121 | + ], | |
| 122 | + callback: (_args, value) => { | |
| 123 | + const messageId = !isNaN(Number(value)) ? Number(value) : chat.length - 1; | |
| 124 | + const message = chat[messageId]; | |
| 125 | + const reasoning = String(message?.extra?.reasoning ?? ''); | |
| 126 | + return reasoning.replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, ''); | |
| 127 | + }, | |
| 128 | + })); | |
| 129 | + | |
| 130 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 131 | + name: 'reasoning-set', | |
| 132 | + returns: ARGUMENT_TYPE.STRING, | |
| 133 | + helpString: t`Set the reasoning block of a message. Returns the reasoning block content.`, | |
| 134 | + namedArgumentList: [ | |
| 135 | + SlashCommandNamedArgument.fromProps({ | |
| 136 | + name: 'at', | |
| 137 | + description: 'Message ID. If not provided, the message ID of the last message is used.', | |
| 138 | + typeList: ARGUMENT_TYPE.NUMBER, | |
| 139 | + enumProvider: commonEnumProviders.messages(), | |
| 140 | + }), | |
| 141 | + ], | |
| 142 | + unnamedArgumentList: [ | |
| 143 | + SlashCommandArgument.fromProps({ | |
| 144 | + description: 'Reasoning block content.', | |
| 145 | + typeList: ARGUMENT_TYPE.STRING, | |
| 146 | + }), | |
| 147 | + ], | |
| 148 | + callback: async (args, value) => { | |
| 149 | + const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1; | |
| 150 | + const message = chat[messageId]; | |
| 151 | + if (!message?.extra) { | |
| 152 | + return ''; | |
| 153 | + } | |
| 154 | + | |
| 155 | + message.extra.reasoning = String(value ?? ''); | |
| 156 | + await saveChatConditional(); | |
| 157 | + | |
| 158 | + closeMessageEditor('reasoning'); | |
| 159 | + updateMessageBlock(messageId, message); | |
| 160 | + return message.extra.reasoning; | |
| 161 | + }, | |
| 162 | + })); | |
| 163 | +} | |
| 164 | + | |
| 165 | +function registerReasoningMacros() { | |
| 166 | + MacrosParser.registerMacro('reasoningPrefix', () => power_user.reasoning.prefix, t`Reasoning Prefix`); | |
| 167 | + MacrosParser.registerMacro('reasoningSuffix', () => power_user.reasoning.suffix, t`Reasoning Suffix`); | |
| 168 | + MacrosParser.registerMacro('reasoningSeparator', () => power_user.reasoning.separator, t`Reasoning Separator`); | |
| 169 | +} | |
| 170 | + | |
| 171 | +function setReasoningEventHandlers(){ | |
| 172 | + $(document).on('click', '.mes_reasoning_copy', (e) => { | |
| 173 | + e.stopPropagation(); | |
| 174 | + e.preventDefault(); | |
| 175 | + }); | |
| 176 | + | |
| 177 | + $(document).on('click', '.mes_reasoning_edit', function (e) { | |
| 178 | + e.stopPropagation(); | |
| 179 | + e.preventDefault(); | |
| 180 | + const { message, messageBlock } = getMessageFromJquery(this); | |
| 181 | + if (!message?.extra) { | |
| 182 | + return; | |
| 183 | + } | |
| 184 | + | |
| 185 | + const reasoning = String(message?.extra?.reasoning ?? ''); | |
| 186 | + const chatElement = document.getElementById('chat'); | |
| 187 | + const textarea = document.createElement('textarea'); | |
| 188 | + const reasoningBlock = messageBlock.find('.mes_reasoning'); | |
| 189 | + textarea.classList.add('reasoning_edit_textarea'); | |
| 190 | + textarea.value = reasoning.replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, ''); | |
| 191 | + $(textarea).insertBefore(reasoningBlock); | |
| 192 | + | |
| 193 | + if (!CSS.supports('field-sizing', 'content')) { | |
| 194 | + const resetHeight = function () { | |
| 195 | + const scrollTop = chatElement.scrollTop; | |
| 196 | + textarea.style.height = '0px'; | |
| 197 | + textarea.style.height = `${textarea.scrollHeight}px`; | |
| 198 | + chatElement.scrollTop = scrollTop; | |
| 199 | + }; | |
| 200 | + | |
| 201 | + textarea.addEventListener('input', resetHeight); | |
| 202 | + resetHeight(); | |
| 203 | + } | |
| 204 | + | |
| 205 | + textarea.focus(); | |
| 206 | + textarea.setSelectionRange(textarea.value.length, textarea.value.length); | |
| 207 | + | |
| 208 | + const textareaRect = textarea.getBoundingClientRect(); | |
| 209 | + const chatRect = chatElement.getBoundingClientRect(); | |
| 210 | + | |
| 211 | + // Scroll if textarea bottom is below visible area | |
| 212 | + if (textareaRect.bottom > chatRect.bottom) { | |
| 213 | + const scrollOffset = textareaRect.bottom - chatRect.bottom; | |
| 214 | + chatElement.scrollTop += scrollOffset; | |
| 215 | + } | |
| 216 | + }); | |
| 217 | + | |
| 218 | + $(document).on('click', '.mes_reasoning_edit_done', async function (e) { | |
| 219 | + e.stopPropagation(); | |
| 220 | + e.preventDefault(); | |
| 221 | + const { message, messageId, messageBlock } = getMessageFromJquery(this); | |
| 222 | + if (!message?.extra) { | |
| 223 | + return; | |
| 224 | + } | |
| 225 | + | |
| 226 | + const textarea = messageBlock.find('.reasoning_edit_textarea'); | |
| 227 | + const reasoning = String(textarea.val()); | |
| 228 | + message.extra.reasoning = reasoning; | |
| 229 | + await saveChatConditional(); | |
| 230 | + updateMessageBlock(messageId, message); | |
| 231 | + textarea.remove(); | |
| 232 | + }); | |
| 233 | + | |
| 234 | + $(document).on('click', '.mes_reasoning_edit_cancel', function (e) { | |
| 235 | + e.stopPropagation(); | |
| 236 | + e.preventDefault(); | |
| 237 | + | |
| 238 | + const { messageBlock } = getMessageFromJquery(this); | |
| 239 | + const textarea = messageBlock.find('.reasoning_edit_textarea'); | |
| 240 | + textarea.remove(); | |
| 241 | + }); | |
| 242 | + | |
| 243 | + $(document).on('click', '.mes_edit_add_reasoning', async function () { | |
| 244 | + const { message, messageId } = getMessageFromJquery(this); | |
| 245 | + if (!message?.extra) { | |
| 246 | + return; | |
| 247 | + } | |
| 248 | + | |
| 249 | + if (message.extra.reasoning) { | |
| 250 | + toastr.info(t`Reasoning already exists.`, t`Edit Message`); | |
| 251 | + return; | |
| 252 | + } | |
| 253 | + | |
| 254 | + message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER; | |
| 255 | + await saveChatConditional(); | |
| 256 | + closeMessageEditor(); | |
| 257 | + updateMessageBlock(messageId, message); | |
| 258 | + }); | |
| 259 | + | |
| 260 | + $(document).on('click', '.mes_reasoning_delete', async function (e) { | |
| 261 | + e.stopPropagation(); | |
| 262 | + e.preventDefault(); | |
| 263 | + | |
| 264 | + const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`); | |
| 265 | + | |
| 266 | + if (!confirm) { | |
| 267 | + return; | |
| 268 | + } | |
| 269 | + | |
| 270 | + const { message, messageId } = getMessageFromJquery(this); | |
| 271 | + if (!message?.extra) { | |
| 272 | + return; | |
| 273 | + } | |
| 274 | + message.extra.reasoning = ''; | |
| 275 | + await saveChatConditional(); | |
| 276 | + updateMessageBlock(messageId, message); | |
| 277 | + }); | |
| 278 | + | |
| 279 | + $(document).on('pointerup', '.mes_reasoning_copy', async function () { | |
| 280 | + const { message } = getMessageFromJquery(this); | |
| 281 | + const reasoning = String(message?.extra?.reasoning ?? '').replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, ''); | |
| 282 | + | |
| 283 | + if (!reasoning) { | |
| 284 | + return; | |
| 285 | + } | |
| 286 | + | |
| 287 | + await copyText(reasoning); | |
| 288 | + toastr.info(t`Copied!`, '', { timeOut: 2000 }); | |
| 289 | + }); | |
| 290 | +} | |
| 291 | + | |
| 292 | +export function initReasoning() { | |
| 293 | + loadReasoningSettings(); | |
| 294 | + setReasoningEventHandlers(); | |
| 295 | + registerReasoningSlashCommands(); | |
| 296 | + registerReasoningMacros(); | |
| 297 | +} | |
| @@ -42,6 +42,7 @@ import { | ||
| 42 | 42 | showMoreMessages, |
| 43 | 43 | stopGeneration, |
| 44 | 44 | substituteParams, |
| 45 | + syncCurrentSwipeInfoExtras, | |
| 45 | 46 | system_avatar, |
| 46 | 47 | system_message_types, |
| 47 | 48 | this_chid, |
| @@ -1968,8 +1969,8 @@ export function initDefaultSlashCommands() { | ||
| 1968 | 1969 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1969 | 1970 | name: 'chat-render', |
| 1970 | 1971 | helpString: 'Renders a specified number of messages into the chat window. Displays all messages if no argument is provided.', |
| 1971 | 1972 | callback: async (args, number) => { |
| 1972 | 1973 | await showMoreMessages(number && !isNaN(Number(number)) ? Number(number) : Number.MAX_SAFE_INTEGER); |
| 1973 | 1974 | if (isTrueBoolean(String(args?.scroll ?? ''))) { |
| 1974 | 1975 | $('#chat').scrollTop(0); |
| 1975 | 1976 | } |
| @@ -2814,8 +2815,11 @@ async function addSwipeCallback(args, value) { | ||
| 2814 | 2815 | const newSwipeId = lastMessage.swipes.length - 1; |
| 2815 | 2816 | |
| 2816 | 2817 | if (isTrueBoolean(args.switch)) { |
| 2818 | + // Make sure ad-hoc changes to extras are saved before swiping away | |
| 2819 | + syncCurrentSwipeInfoExtras(); | |
| 2817 | 2820 | lastMessage.swipe_id = newSwipeId; |
| 2818 | 2821 | lastMessage.mes = lastMessage.swipes[newSwipeId]; |
| 2822 | + lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {}); | |
| 2819 | 2823 | } |
| 2820 | 2824 | |
| 2821 | 2825 | await saveChatConditional(); |
| @@ -220,6 +220,36 @@ async function* parseStreamData(json) { | ||
| 220 | 220 | } |
| 221 | 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 | 253 | else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { |
| 224 | 254 | for (let j = 0; j < json.choices[0].delta.content.length; j++) { |
| 225 | 255 | const str = json.choices[0].delta.content[j]; |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | import { |
| 2 | 2 | activateSendButtons, |
| 3 | 3 | addOneMessage, |
| 4 | + appendMediaToMessage, | |
| 4 | 5 | callPopup, |
| 5 | 6 | characters, |
| 6 | 7 | chat, |
| @@ -12,6 +13,7 @@ import { | ||
| 12 | 13 | extension_prompts, |
| 13 | 14 | Generate, |
| 14 | 15 | generateQuietPrompt, |
| 16 | + getCharacters, | |
| 15 | 17 | getCurrentChatId, |
| 16 | 18 | getRequestHeaders, |
| 17 | 19 | getThumbnailUrl, |
| @@ -40,6 +42,7 @@ import { | ||
| 40 | 42 | substituteParamsExtended, |
| 41 | 43 | this_chid, |
| 42 | 44 | updateChatMetadata, |
| 45 | + updateMessageBlock, | |
| 43 | 46 | } from '../script.js'; |
| 44 | 47 | import { |
| 45 | 48 | extension_settings, |
| @@ -55,7 +58,7 @@ import { MacrosParser } from './macros.js'; | ||
| 55 | 58 | import { oai_settings } from './openai.js'; |
| 56 | 59 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 57 | 60 | import { power_user, registerDebugFunction } from './power-user.js'; |
| 58 | 61 | import { humanizedDateTime, isMobile, shouldSendOnEnter } from './RossAscends-mods.js'; |
| 59 | 62 | import { ScraperManager } from './scrapers.js'; |
| 60 | 63 | import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js'; |
| 61 | 64 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -65,7 +68,7 @@ import { tag_map, tags } from './tags.js'; | ||
| 65 | 68 | import { textgenerationwebui_settings } from './textgen-settings.js'; |
| 66 | 69 | import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js'; |
| 67 | 70 | import { ToolManager } from './tool-calling.js'; |
| 68 | 71 | import { timestampToMoment, uuidv4 } from './utils.js'; |
| 69 | 72 | |
| 70 | 73 | export function getContext() { |
| 71 | 74 | return { |
| @@ -167,6 +170,11 @@ export function getContext() { | ||
| 167 | 170 | chatCompletionSettings: oai_settings, |
| 168 | 171 | textCompletionSettings: textgenerationwebui_settings, |
| 169 | 172 | powerUserSettings: power_user, |
| 173 | + getCharacters, | |
| 174 | + uuidv4, | |
| 175 | + humanizedDateTime, | |
| 176 | + updateMessageBlock, | |
| 177 | + appendMediaToMessage, | |
| 170 | 178 | }; |
| 171 | 179 | } |
| 172 | 180 | |
| @@ -1,3 +1,9 @@ | ||
| 1 | -<div> | |
| 1 | +<div data-type="assistant_note"> | |
| 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> | |
| 2 | + <div> | |
| 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 | 9 | </div> |
| @@ -7,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> |
| 8 | 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 | 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 | 10 | <li><span data-i18n="char_import_5">AICharacterCardAICharacterCards.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 | 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 | 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 | 13 | </ul> |
| @@ -986,6 +986,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) { | ||
| 986 | 986 | let logprobs = null; |
| 987 | 987 | const swipes = []; |
| 988 | 988 | const toolCalls = []; |
| 989 | + const state = {}; | |
| 989 | 990 | while (true) { |
| 990 | 991 | const { done, value } = await reader.read(); |
| 991 | 992 | if (done) return; |
| @@ -1004,7 +1005,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) { | ||
| 1004 | 1005 | logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities); |
| 1005 | 1006 | } |
| 1006 | 1007 | |
| 1007 | 1008 | yield { text, swipes, logprobs, toolCalls, state }; |
| 1008 | 1009 | } |
| 1009 | 1010 | }; |
| 1010 | 1011 | } |
| @@ -1231,7 +1232,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | ||
| 1231 | 1232 | 'top_p': settings.top_p, |
| 1232 | 1233 | 'typical_p': settings.typical_p, |
| 1233 | 1234 | 'typical': settings.typical_p, |
| 1234 | 1235 | 'sampler_seed': settings.seed >= 0 ? settings.seed : undefined, |
| 1235 | 1236 | 'min_p': settings.min_p, |
| 1236 | 1237 | 'repetition_penalty': settings.rep_pen, |
| 1237 | 1238 | 'frequency_penalty': settings.freq_pen, |
| @@ -1294,7 +1295,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | ||
| 1294 | 1295 | 'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined, |
| 1295 | 1296 | 'speculative_ngram': settings.type === TABBY ? settings.speculative_ngram : undefined, |
| 1296 | 1297 | 'do_sample': settings.type === OOBA ? settings.do_sample : undefined, |
| 1297 | 1298 | 'seed': settings.seed >= 0 ? settings.seed : undefined, |
| 1298 | 1299 | 'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1, |
| 1299 | 1300 | 'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '', |
| 1300 | 1301 | 'grammar_string': settings.grammar_string, |
| @@ -292,36 +292,44 @@ input[type='checkbox']:focus-visible { | ||
| 292 | 292 | filter: grayscale(25%); |
| 293 | 293 | } |
| 294 | 294 | |
| 295 | 295 | .mes_text table {, |
| 296 | +.mes_reasoning table { | |
| 296 | 297 | border-spacing: 0; |
| 297 | 298 | border-collapse: collapse; |
| 298 | 299 | margin-bottom: 10px; |
| 299 | 300 | } |
| 300 | 301 | |
| 301 | 302 | .mes_text td, |
| 302 | 303 | .mes_text th {, |
| 304 | +.mes_reasoning td, | |
| 305 | +.mes_reasoning th { | |
| 303 | 306 | border: 1px solid; |
| 304 | 307 | border-collapse: collapse; |
| 305 | 308 | padding: 0.25em; |
| 306 | 309 | } |
| 307 | 310 | |
| 308 | 311 | .mes_text p {, |
| 312 | +.mes_reasoning p { | |
| 309 | 313 | margin-top: 0; |
| 310 | 314 | margin-bottom: 10px; |
| 311 | 315 | } |
| 312 | 316 | |
| 313 | 317 | .mes_text li tt {, |
| 318 | +.mes_reasoning li tt { | |
| 314 | 319 | display: inline-block; |
| 315 | 320 | } |
| 316 | 321 | |
| 317 | 322 | .mes_text ol, |
| 318 | 323 | .mes_text ul {, |
| 324 | +.mes_reasoning ol, | |
| 325 | +.mes_reasoning ul { | |
| 319 | 326 | margin-top: 5px; |
| 320 | 327 | margin-bottom: 5px; |
| 321 | 328 | } |
| 322 | 329 | |
| 323 | 330 | .mes_text br, |
| 324 | 331 | .mes_bias br {, |
| 332 | +.mes_reasoning br { | |
| 325 | 333 | content: ' '; |
| 326 | 334 | } |
| 327 | 335 | |
| @@ -332,8 +340,62 @@ input[type='checkbox']:focus-visible { | ||
| 332 | 340 | color: var(--SmartThemeQuoteColor); |
| 333 | 341 | } |
| 334 | 342 | |
| 343 | +.mes_reasoning { | |
| 344 | + display: block; | |
| 345 | + border: 1px solid var(--SmartThemeBorderColor); | |
| 346 | + background-color: var(--black30a); | |
| 347 | + border-radius: 5px; | |
| 348 | + padding: 5px; | |
| 349 | + margin: 5px 0; | |
| 350 | + overflow-y: auto; | |
| 351 | +} | |
| 352 | + | |
| 353 | +.mes_reasoning_summary { | |
| 354 | + cursor: pointer; | |
| 355 | + position: relative; | |
| 356 | + margin: 2px; | |
| 357 | +} | |
| 358 | + | |
| 359 | +@supports not selector(:has(*)) { | |
| 360 | + .mes_reasoning_details { | |
| 361 | + display: none !important; | |
| 362 | + } | |
| 363 | +} | |
| 364 | + | |
| 365 | +.mes_bias:empty, | |
| 366 | +.mes_reasoning:empty, | |
| 367 | +.mes_reasoning_details:has(.mes_reasoning:empty), | |
| 368 | +.mes_block:has(.edit_textarea) .mes_reasoning_details, | |
| 369 | +.mes_reasoning_details:not([open]) .mes_reasoning_actions, | |
| 370 | +.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning, | |
| 371 | +.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_done, | |
| 372 | +.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_cancel, | |
| 373 | +.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.mes_reasoning_edit_done, .mes_reasoning_edit_cancel) { | |
| 374 | + display: none; | |
| 375 | +} | |
| 376 | + | |
| 377 | +.mes_reasoning_actions { | |
| 378 | + position: absolute; | |
| 379 | + right: 0; | |
| 380 | + top: 0; | |
| 381 | + | |
| 382 | + display: flex; | |
| 383 | + gap: 4px; | |
| 384 | + flex-wrap: nowrap; | |
| 385 | + justify-content: flex-end; | |
| 386 | + transition: all 200ms; | |
| 387 | + overflow-x: hidden; | |
| 388 | + padding: 1px; | |
| 389 | +} | |
| 390 | + | |
| 391 | +.mes_reasoning_summary>span { | |
| 392 | + margin-left: 0.5em; | |
| 393 | +} | |
| 394 | + | |
| 335 | 395 | .mes_text i, |
| 336 | 396 | .mes_text em {, |
| 397 | +.mes_reasoning i, | |
| 398 | +.mes_reasoning em { | |
| 337 | 399 | color: var(--SmartThemeEmColor); |
| 338 | 400 | } |
| 339 | 401 | |
| @@ -342,20 +404,24 @@ input[type='checkbox']:focus-visible { | ||
| 342 | 404 | color: inherit; |
| 343 | 405 | } |
| 344 | 406 | |
| 345 | 407 | .mes_text u {, |
| 408 | +.mes_reasoning u { | |
| 346 | 409 | color: var(--SmartThemeUnderlineColor); |
| 347 | 410 | } |
| 348 | 411 | |
| 349 | 412 | .mes_text q {, |
| 413 | +.mes_reasoning q { | |
| 350 | 414 | color: var(--SmartThemeQuoteColor); |
| 351 | 415 | } |
| 352 | 416 | |
| 353 | 417 | .mes_text font[color] em, |
| 354 | 418 | .mes_text font[color] i {, |
| 355 | - color: inherit; | |
| 419 | +.mes_text font[color] u, | |
| 356 | -} | |
| 420 | +.mes_text font[color] q, | |
| 357 | - | |
| 421 | +.mes_reasoning font[color] em, | |
| 358 | 422 | .mes_textmes_reasoning font[color] q {i, |
| 423 | +.mes_reasoning font[color] u, | |
| 424 | +.mes_reasoning font[color] q { | |
| 359 | 425 | color: inherit; |
| 360 | 426 | } |
| 361 | 427 | |
| @@ -363,7 +429,8 @@ input[type='checkbox']:focus-visible { | ||
| 363 | 429 | display: block; |
| 364 | 430 | } |
| 365 | 431 | |
| 366 | 432 | .mes_text blockquote {, |
| 433 | +.mes_reasoning blockquote { | |
| 367 | 434 | border-left: 3px solid var(--SmartThemeQuoteColor); |
| 368 | 435 | padding-left: 10px; |
| 369 | 436 | background-color: var(--black30a); |
| @@ -373,18 +440,24 @@ input[type='checkbox']:focus-visible { | ||
| 373 | 440 | .mes_text strong em, |
| 374 | 441 | .mes_text strong, |
| 375 | 442 | .mes_text h2, |
| 376 | 443 | .mes_text h1 {, |
| 444 | +.mes_reasoning strong em, | |
| 445 | +.mes_reasoning strong, | |
| 446 | +.mes_reasoning h2, | |
| 447 | +.mes_reasoning h1 { | |
| 377 | 448 | font-weight: bold; |
| 378 | 449 | } |
| 379 | 450 | |
| 380 | 451 | .mes_text pre code {, |
| 452 | +.mes_reasoning pre code { | |
| 381 | 453 | position: relative; |
| 382 | 454 | display: block; |
| 383 | 455 | overflow-x: auto; |
| 384 | 456 | padding: 1em; |
| 385 | 457 | } |
| 386 | 458 | |
| 387 | 459 | .mes_text img:not(.mes_img) {, |
| 460 | +.mes_reasoning img:not(.mes_img) { | |
| 388 | 461 | max-width: 100%; |
| 389 | 462 | max-height: var(--doc-height); |
| 390 | 463 | } |
| @@ -1027,6 +1100,11 @@ body .panelControlBar { | ||
| 1027 | 1100 | /*only affects bubblechat to make it sit nicely at the bottom*/ |
| 1028 | 1101 | } |
| 1029 | 1102 | |
| 1103 | +.last_mes:has(.mes_text:empty):has(.mes_reasoning_details[open]) .mes_reasoning:not(:empty) { | |
| 1104 | + margin-bottom: 30px; | |
| 1105 | +} | |
| 1106 | + | |
| 1107 | +.last_mes .mes_reasoning, | |
| 1030 | 1108 | .last_mes .mes_text { |
| 1031 | 1109 | padding-right: 30px; |
| 1032 | 1110 | } |
| @@ -1240,14 +1318,18 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter { | ||
| 1240 | 1318 | overflow-y: clip; |
| 1241 | 1319 | } |
| 1242 | 1320 | |
| 1243 | 1321 | .mes_text {, |
| 1322 | +.mes_reasoning { | |
| 1244 | 1323 | font-weight: 500; |
| 1245 | 1324 | line-height: calc(var(--mainFontSize) + .5rem); |
| 1325 | + max-width: 100%; | |
| 1326 | + overflow-wrap: anywhere; | |
| 1327 | +} | |
| 1328 | + | |
| 1329 | +.mes_text { | |
| 1246 | 1330 | padding-left: 0; |
| 1247 | 1331 | padding-top: 5px; |
| 1248 | 1332 | padding-bottom: 5px; |
| 1249 | - max-width: 100%; | |
| 1250 | - overflow-wrap: anywhere; | |
| 1251 | 1333 | } |
| 1252 | 1334 | |
| 1253 | 1335 | br { |
| @@ -2926,7 +3008,7 @@ input[type=search]:focus::-webkit-search-cancel-button { | ||
| 2926 | 3008 | position: relative; |
| 2927 | 3009 | } |
| 2928 | 3010 | |
| 2929 | 3011 | #rm_print_characters_block.character_name_block .ch_name, |
| 2930 | 3012 | .avatar-container .ch_name { |
| 2931 | 3013 | flex: 1 1 auto; |
| 2932 | 3014 | white-space: nowrap; |
| @@ -2936,6 +3018,13 @@ input[type=search]:focus::-webkit-search-cancel-button { | ||
| 2936 | 3018 | display: block; |
| 2937 | 3019 | } |
| 2938 | 3020 | |
| 3021 | +.character_name_block .character_version { | |
| 3022 | + text-overflow: ellipsis; | |
| 3023 | + overflow: hidden; | |
| 3024 | + text-wrap: nowrap; | |
| 3025 | + max-width: 50%; | |
| 3026 | +} | |
| 3027 | + | |
| 2939 | 3028 | #rm_print_characters_block .character_name_block> :last-child { |
| 2940 | 3029 | flex: 0 100000 auto; |
| 2941 | 3030 | /* Force shrinking first */ |
| @@ -4148,10 +4237,12 @@ input[type="range"]::-webkit-slider-thumb { | ||
| 4148 | 4237 | align-items: center; |
| 4149 | 4238 | } |
| 4150 | 4239 | |
| 4240 | +.mes_reasoning_edit_cancel, | |
| 4151 | 4241 | .mes_edit_cancel.menu_button { |
| 4152 | 4242 | background-color: var(--crimson70a); |
| 4153 | 4243 | } |
| 4154 | 4244 | |
| 4245 | +.mes_reasoning_edit_done, | |
| 4155 | 4246 | .mes_edit_done.menu_button { |
| 4156 | 4247 | background-color: var(--okGreen70a); |
| 4157 | 4248 | } |
| @@ -4160,6 +4251,7 @@ input[type="range"]::-webkit-slider-thumb { | ||
| 4160 | 4251 | opacity: 1; |
| 4161 | 4252 | } |
| 4162 | 4253 | |
| 4254 | +.reasoning_edit_textarea, | |
| 4163 | 4255 | .edit_textarea { |
| 4164 | 4256 | padding: 5px; |
| 4165 | 4257 | margin: 0; |
| @@ -5648,6 +5740,7 @@ body:not(.movingUI) .drawer-content.maximized { | ||
| 5648 | 5740 | |
| 5649 | 5741 | .model-card .details-container { |
| 5650 | 5742 | text-align: right; |
| 5743 | + line-height: 0.9; | |
| 5651 | 5744 | } |
| 5652 | 5745 | |
| 5653 | 5746 | .model-card:hover { |
| @@ -5670,7 +5763,7 @@ body:not(.movingUI) .drawer-content.maximized { | ||
| 5670 | 5763 | } |
| 5671 | 5764 | |
| 5672 | 5765 | .model-title { |
| 5673 | - font-size: 13px; | |
| 5766 | + font-size: calc(var(--mainFontSize) * 0.95); | |
| 5674 | 5767 | font-weight: bold; |
| 5675 | 5768 | overflow: hidden; |
| 5676 | 5769 | } |
| @@ -5686,7 +5779,7 @@ body:not(.movingUI) .drawer-content.maximized { | ||
| 5686 | 5779 | .model-class, |
| 5687 | 5780 | .model-context-length, |
| 5688 | 5781 | .model-date-added { |
| 5689 | - font-size: 10px; | |
| 5782 | + font-size: calc(var(--mainFontSize) * 0.75); | |
| 5690 | 5783 | } |
| 5691 | 5784 | |
| 5692 | 5785 | .model-class, |
| @@ -5768,3 +5861,17 @@ body:not(.movingUI) .drawer-content.maximized { | ||
| 5768 | 5861 | .alternate_greetings_list { |
| 5769 | 5862 | overflow-y: scroll; |
| 5770 | 5863 | } |
| 5864 | + | |
| 5865 | +.mes_text div[data-type="assistant_note"]:has(.assistant_note_export) { | |
| 5866 | + display: flex; | |
| 5867 | + flex-direction: row; | |
| 5868 | + flex-wrap: nowrap; | |
| 5869 | + justify-content: space-between; | |
| 5870 | + align-items: center; | |
| 5871 | + gap: 10px; | |
| 5872 | + padding: 0 2px; | |
| 5873 | +} | |
| 5874 | + | |
| 5875 | +.mes_text div[data-type="assistant_note"]:has(.assistant_note_export)>div:not(.assistant_note_export) { | |
| 5876 | + flex: 1; | |
| 5877 | +} | |
| @@ -18,10 +18,9 @@ import { hideBin } from 'yargs/helpers'; | ||
| 18 | 18 | |
| 19 | 19 | // express/server related library imports |
| 20 | 20 | import cors from 'cors'; |
| 21 | 21 | import { doubleCsrfcsrfSync } from 'csrf-csrfsync'; |
| 22 | 22 | import express from 'express'; |
| 23 | 23 | import compression from 'compression'; |
| 24 | -import cookieParser from 'cookie-parser'; | |
| 25 | 24 | import cookieSession from 'cookie-session'; |
| 26 | 25 | import multer from 'multer'; |
| 27 | 26 | import responseTime from 'response-time'; |
| @@ -40,7 +39,6 @@ util.inspect.defaultOptions.depth = 4; | ||
| 40 | 39 | import { loadPlugins } from './src/plugin-loader.js'; |
| 41 | 40 | import { |
| 42 | 41 | initUserStorage, |
| 43 | - getCsrfSecret, | |
| 44 | 42 | getCookieSecret, |
| 45 | 43 | getCookieSessionName, |
| 46 | 44 | getAllEnabledUsers, |
| @@ -67,6 +65,7 @@ import { | ||
| 67 | 65 | forwardFetchResponse, |
| 68 | 66 | removeColorFormatting, |
| 69 | 67 | getSeparator, |
| 68 | + safeReadFileSync, | |
| 70 | 69 | } from './src/util.js'; |
| 71 | 70 | import { UPLOADS_DIRECTORY } from './src/constants.js'; |
| 72 | 71 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; |
| @@ -347,8 +346,8 @@ if (enableCorsProxy) { | ||
| 347 | 346 | } |
| 348 | 347 | |
| 349 | 348 | function getSessionCookieAge() { |
| 350 | 349 | // Defaults to 24 hours in"no secondsexpiration" if not set |
| 351 | 350 | const configValue = getConfigValue('sessionTimeout', 24 * 60 * 60-1); |
| 352 | 351 | |
| 353 | 352 | // Convert to milliseconds |
| 354 | 353 | if (configValue > 0) { |
| @@ -377,27 +376,38 @@ app.use(setUserDataMiddleware); | ||
| 377 | 376 | |
| 378 | 377 | // CSRF Protection // |
| 379 | 378 | if (!disableCsrf) { |
| 380 | 379 | const COOKIES_SECRETcsrfSyncProtection = getCookieSecretcsrfSync();{ |
| 381 | - | |
| 380 | + getTokenFromState: (req) => { | |
| 382 | - const { generateToken, doubleCsrfProtection } = doubleCsrf({ | |
| 381 | + if (!req.session) { | |
| 383 | - getSecret: getCsrfSecret, | |
| 382 | + console.error('(CSRF error) getTokenFromState: Session object not initialized'); | |
| 384 | - cookieName: 'X-CSRF-Token', | |
| 383 | + return; | |
| 385 | - cookieOptions: { | |
| 384 | + } | |
| 386 | - sameSite: 'strict', | |
| 385 | + return req.session.csrfToken; | |
| 387 | - secure: false, | |
| 386 | + }, | |
| 387 | + getTokenFromRequest: (req) => { | |
| 388 | + return req.headers['x-csrf-token']?.toString(); | |
| 389 | + }, | |
| 390 | + storeTokenInState: (req, token) => { | |
| 391 | + if (!req.session) { | |
| 392 | + console.error('(CSRF error) storeTokenInState: Session object not initialized'); | |
| 393 | + return; | |
| 394 | + } | |
| 395 | + req.session.csrfToken = token; | |
| 388 | 396 | }, |
| 389 | 397 | size: 6432, |
| 390 | - getTokenFromRequest: (req) => req.headers['x-csrf-token'], | |
| 391 | 398 | }); |
| 392 | 399 | |
| 393 | 400 | app.get('/csrf-token', (req, res) => { |
| 394 | 401 | res.json({ |
| 395 | 402 | 'token': csrfSyncProtection.generateToken(res, req), |
| 396 | 403 | }); |
| 397 | 404 | }); |
| 398 | 405 | |
| 399 | - app.use(cookieParser(COOKIES_SECRET)); | |
| 406 | + // Customize the error message | |
| 400 | - app.use(doubleCsrfProtection); | |
| 407 | + csrfSyncProtection.invalidCsrfTokenError.message = color.red('Invalid CSRF token. Please refresh the page and try again.'); | |
| 408 | + csrfSyncProtection.invalidCsrfTokenError.stack = undefined; | |
| 409 | + | |
| 410 | + app.use(csrfSyncProtection.csrfSynchronisedProtection); | |
| 401 | 411 | } else { |
| 402 | 412 | console.warn('\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n'); |
| 403 | 413 | app.get('/csrf-token', (req, res) => { |
| @@ -921,6 +931,16 @@ async function verifySecuritySettings() { | ||
| 921 | 931 | } |
| 922 | 932 | } |
| 923 | 933 | |
| 934 | +/** | |
| 935 | + * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered. | |
| 936 | + */ | |
| 937 | +function apply404Middleware() { | |
| 938 | + const notFoundWebpage = safeReadFileSync('./public/error/url-not-found.html') ?? ''; | |
| 939 | + app.use((req, res) => { | |
| 940 | + res.status(404).send(notFoundWebpage); | |
| 941 | + }); | |
| 942 | +} | |
| 943 | + | |
| 924 | 944 | // User storage module needs to be initialized before starting the server |
| 925 | 945 | initUserStorage(dataRoot) |
| 926 | 946 | .then(ensurePublicDirectoriesExist) |
| @@ -928,4 +948,5 @@ initUserStorage(dataRoot) | ||
| 928 | 948 | .then(migrateSystemPrompts) |
| 929 | 949 | .then(verifySecuritySettings) |
| 930 | 950 | .then(preSetupTasks) |
| 951 | + .then(apply404Middleware) | |
| 931 | 952 | .finally(startServer); |
| @@ -9,6 +9,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 9 | 9 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 10 | 10 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; |
| 11 | 11 | import { getImages, tryParse } from '../util.js'; |
| 12 | +import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; | |
| 12 | 13 | |
| 13 | 14 | export const router = express.Router(); |
| 14 | 15 | |
| @@ -17,7 +18,7 @@ router.post('/get', jsonParser, function (request, response) { | ||
| 17 | 18 | response.send(JSON.stringify(images)); |
| 18 | 19 | }); |
| 19 | 20 | |
| 20 | 21 | router.post('/delete', jsonParser, getFileNameValidationFunction('avatar'), function (request, response) { |
| 21 | 22 | if (!request.body) return response.sendStatus(400); |
| 22 | 23 | |
| 23 | 24 | if (request.body.avatar !== sanitize(request.body.avatar)) { |
| @@ -37,6 +37,8 @@ import { | ||
| 37 | 37 | getTiktokenTokenizer, |
| 38 | 38 | sentencepieceTokenizers, |
| 39 | 39 | TEXT_COMPLETION_MODELS, |
| 40 | + webTokenizers, | |
| 41 | + getWebTokenizer, | |
| 40 | 42 | } from '../tokenizers.js'; |
| 41 | 43 | |
| 42 | 44 | const API_OPENAI = 'https://api.openai.com/v1'; |
| @@ -61,6 +63,7 @@ const API_DEEPSEEK = 'https://api.deepseek.com/beta'; | ||
| 61 | 63 | * @returns |
| 62 | 64 | */ |
| 63 | 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 | 67 | switch (type) { |
| 65 | 68 | case 'merge': |
| 66 | 69 | case 'claude': |
| @@ -70,7 +73,9 @@ function postProcessPrompt(messages, type, names) { | ||
| 70 | 73 | case 'strict': |
| 71 | 74 | return mergeMessages(messages, names, true, true); |
| 72 | 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 | 79 | default: |
| 75 | 80 | return messages; |
| 76 | 81 | } |
| @@ -284,6 +289,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 284 | 289 | const model = String(request.body.model); |
| 285 | 290 | const stream = Boolean(request.body.stream); |
| 286 | 291 | const showThoughts = Boolean(request.body.show_thoughts); |
| 292 | + const isThinking = model.includes('thinking'); | |
| 287 | 293 | |
| 288 | 294 | const generationConfig = { |
| 289 | 295 | stopSequences: request.body.stop, |
| @@ -324,6 +330,12 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 324 | 330 | body.systemInstruction = prompt.system_instruction; |
| 325 | 331 | } |
| 326 | 332 | |
| 333 | + if (isThinking && showThoughts) { | |
| 334 | + generationConfig.thinkingConfig = { | |
| 335 | + includeThoughts: true, | |
| 336 | + }; | |
| 337 | + } | |
| 338 | + | |
| 327 | 339 | return body; |
| 328 | 340 | } |
| 329 | 341 | |
| @@ -337,7 +349,6 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 337 | 349 | controller.abort(); |
| 338 | 350 | }); |
| 339 | 351 | |
| 340 | - const isThinking = model.includes('thinking'); | |
| 341 | 352 | const apiVersion = isThinking ? 'v1alpha' : 'v1beta'; |
| 342 | 353 | const responseType = (stream ? 'streamGenerateContent' : 'generateContent'); |
| 343 | 354 | |
| @@ -382,11 +393,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 382 | 393 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 383 | 394 | console.log('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 | 397 | if (!responseText) { |
| 391 | 398 | let message = 'Google AI Studio Candidate text empty'; |
| 392 | 399 | console.log(message, generateResponseJson); |
| @@ -394,7 +401,7 @@ async function sendMakerSuiteRequest(request, response) { | ||
| 394 | 401 | } |
| 395 | 402 | |
| 396 | 403 | // Wrap it back to OAI format |
| 397 | 404 | const reply = { choices: [{ 'message': { 'content': responseText } }], responseContent }; |
| 398 | 405 | return response.send(reply); |
| 399 | 406 | } |
| 400 | 407 | } catch (error) { |
| @@ -636,6 +643,89 @@ async function sendCohereRequest(request, response) { | ||
| 636 | 643 | } |
| 637 | 644 | } |
| 638 | 645 | |
| 646 | +/** | |
| 647 | + * Sends a request to DeepSeek API. | |
| 648 | + * @param {express.Request} request Express request | |
| 649 | + * @param {express.Response} response Express response | |
| 650 | + */ | |
| 651 | +async function sendDeepSeekRequest(request, response) { | |
| 652 | + const apiUrl = new URL(request.body.reverse_proxy || API_DEEPSEEK).toString(); | |
| 653 | + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); | |
| 654 | + | |
| 655 | + if (!apiKey && !request.body.reverse_proxy) { | |
| 656 | + console.log('DeepSeek API key is missing.'); | |
| 657 | + return response.status(400).send({ error: true }); | |
| 658 | + } | |
| 659 | + | |
| 660 | + const controller = new AbortController(); | |
| 661 | + request.socket.removeAllListeners('close'); | |
| 662 | + request.socket.on('close', function () { | |
| 663 | + controller.abort(); | |
| 664 | + }); | |
| 665 | + | |
| 666 | + try { | |
| 667 | + let bodyParams = {}; | |
| 668 | + | |
| 669 | + if (request.body.logprobs > 0) { | |
| 670 | + bodyParams['top_logprobs'] = request.body.logprobs; | |
| 671 | + bodyParams['logprobs'] = true; | |
| 672 | + } | |
| 673 | + | |
| 674 | + const postProcessType = String(request.body.model).endsWith('-reasoner') ? 'deepseek-reasoner' : 'deepseek'; | |
| 675 | + const processedMessages = postProcessPrompt(request.body.messages, postProcessType, getPromptNames(request)); | |
| 676 | + | |
| 677 | + const requestBody = { | |
| 678 | + 'messages': processedMessages, | |
| 679 | + 'model': request.body.model, | |
| 680 | + 'temperature': request.body.temperature, | |
| 681 | + 'max_tokens': request.body.max_tokens, | |
| 682 | + 'stream': request.body.stream, | |
| 683 | + 'presence_penalty': request.body.presence_penalty, | |
| 684 | + 'frequency_penalty': request.body.frequency_penalty, | |
| 685 | + 'top_p': request.body.top_p, | |
| 686 | + 'stop': request.body.stop, | |
| 687 | + 'seed': request.body.seed, | |
| 688 | + ...bodyParams, | |
| 689 | + }; | |
| 690 | + | |
| 691 | + const config = { | |
| 692 | + method: 'POST', | |
| 693 | + headers: { | |
| 694 | + 'Content-Type': 'application/json', | |
| 695 | + 'Authorization': 'Bearer ' + apiKey, | |
| 696 | + }, | |
| 697 | + body: JSON.stringify(requestBody), | |
| 698 | + signal: controller.signal, | |
| 699 | + }; | |
| 700 | + | |
| 701 | + console.log('DeepSeek request:', requestBody); | |
| 702 | + | |
| 703 | + const generateResponse = await fetch(apiUrl + '/chat/completions', config); | |
| 704 | + | |
| 705 | + if (request.body.stream) { | |
| 706 | + forwardFetchResponse(generateResponse, response); | |
| 707 | + } else { | |
| 708 | + if (!generateResponse.ok) { | |
| 709 | + const errorText = await generateResponse.text(); | |
| 710 | + console.log(`DeepSeek API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`); | |
| 711 | + const errorJson = tryParse(errorText) ?? { error: true }; | |
| 712 | + return response.status(500).send(errorJson); | |
| 713 | + } | |
| 714 | + const generateResponseJson = await generateResponse.json(); | |
| 715 | + console.log('DeepSeek response:', generateResponseJson); | |
| 716 | + return response.send(generateResponseJson); | |
| 717 | + } | |
| 718 | + } catch (error) { | |
| 719 | + console.log('Error communicating with DeepSeek API: ', error); | |
| 720 | + if (!response.headersSent) { | |
| 721 | + response.send({ error: true }); | |
| 722 | + } else { | |
| 723 | + response.end(); | |
| 724 | + } | |
| 725 | + } | |
| 726 | +} | |
| 727 | + | |
| 728 | + | |
| 639 | 729 | export const router = express.Router(); |
| 640 | 730 | |
| 641 | 731 | router.post('/status', jsonParser, async function (request, response_getstatus_openai) { |
| @@ -680,8 +770,8 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o | ||
| 680 | 770 | api_key_openai = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); |
| 681 | 771 | headers = {}; |
| 682 | 772 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.DEEPSEEK) { |
| 683 | 773 | api_url = new URL(request.body.reverse_proxy || API_DEEPSEEK.replace('/beta', '')); |
| 684 | 774 | api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK); |
| 685 | 775 | headers = {}; |
| 686 | 776 | } else { |
| 687 | 777 | console.log('This chat completion source is not supported yet.'); |
| @@ -777,6 +867,14 @@ router.post('/bias', jsonParser, async function (request, response) { | ||
| 777 | 867 | return response.send({}); |
| 778 | 868 | } |
| 779 | 869 | encodeFunction = (text) => new Uint32Array(instance.encodeIds(text)); |
| 870 | + } else if (webTokenizers.includes(model)) { | |
| 871 | + const tokenizer = getWebTokenizer(model); | |
| 872 | + const instance = await tokenizer?.get(); | |
| 873 | + if (!instance) { | |
| 874 | + console.warn('Tokenizer not initialized:', model); | |
| 875 | + return response.send({}); | |
| 876 | + } | |
| 877 | + encodeFunction = (text) => new Uint32Array(instance.encode(text)); | |
| 780 | 878 | } else { |
| 781 | 879 | const tokenizer = getTiktokenTokenizer(model); |
| 782 | 880 | encodeFunction = (tokenizer.encode.bind(tokenizer)); |
| @@ -841,6 +939,7 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 841 | 939 | case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response); |
| 842 | 940 | case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response); |
| 843 | 941 | case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response); |
| 942 | + case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response); | |
| 844 | 943 | } |
| 845 | 944 | |
| 846 | 945 | let apiUrl; |
| @@ -899,6 +998,10 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 899 | 998 | bodyParams['route'] = 'fallback'; |
| 900 | 999 | } |
| 901 | 1000 | |
| 1001 | + if (request.body.show_thoughts) { | |
| 1002 | + bodyParams['include_reasoning'] = true; | |
| 1003 | + } | |
| 1004 | + | |
| 902 | 1005 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1); |
| 903 | 1006 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { |
| 904 | 1007 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); |
| @@ -954,18 +1057,6 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 954 | 1057 | apiKey = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY); |
| 955 | 1058 | headers = {}; |
| 956 | 1059 | 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 | 1060 | } else { |
| 970 | 1061 | console.log('This chat completion source is not supported yet.'); |
| 971 | 1062 | return response.status(400).send({ error: true }); |
| @@ -1103,4 +1194,3 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 1103 | 1194 | } |
| 1104 | 1195 | } |
| 1105 | 1196 | }); |
| 1106 | - | |
| @@ -7,6 +7,7 @@ import sanitize from 'sanitize-filename'; | ||
| 7 | 7 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 8 | 8 | import { invalidateThumbnail } from './thumbnails.js'; |
| 9 | 9 | import { getImages } from '../util.js'; |
| 10 | +import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; | |
| 10 | 11 | |
| 11 | 12 | export const router = express.Router(); |
| 12 | 13 | |
| @@ -15,7 +16,7 @@ router.post('/all', jsonParser, function (request, response) { | ||
| 15 | 16 | response.send(JSON.stringify(images)); |
| 16 | 17 | }); |
| 17 | 18 | |
| 18 | 19 | router.post('/delete', jsonParser, getFileNameValidationFunction('bg'), function (request, response) { |
| 19 | 20 | if (!request.body) return response.sendStatus(400); |
| 20 | 21 | |
| 21 | 22 | if (request.body.bg !== sanitize(request.body.bg)) { |
| @@ -14,6 +14,7 @@ import jimp from 'jimp'; | ||
| 14 | 14 | |
| 15 | 15 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; |
| 16 | 16 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 17 | +import { default as validateAvatarUrlMiddleware, getFileNameValidationFunction } from '../middleware/validateFileName.js'; | |
| 17 | 18 | import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js'; |
| 18 | 19 | import { TavernCardValidator } from '../validator/TavernCardValidator.js'; |
| 19 | 20 | import { parse, write } from '../character-card-parser.js'; |
| @@ -73,12 +74,18 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u | ||
| 73 | 74 | * Read the image, resize, and save it as a PNG into the buffer. |
| 74 | 75 | * @returns {Promise<Buffer>} Image buffer |
| 75 | 76 | */ |
| 76 | 77 | async function getInputImage() { |
| 77 | - if (Buffer.isBuffer(inputFile)) { | |
| 78 | + try { | |
| 78 | - return parseImageBuffer(inputFile, crop); | |
| 79 | + if (Buffer.isBuffer(inputFile)) { | |
| 79 | - } | |
| 80 | + return await parseImageBuffer(inputFile, crop); | |
| 81 | + } | |
| 80 | 82 | |
| 81 | 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 | 91 | const inputImage = await getInputImage(); |
| @@ -756,7 +763,7 @@ router.post('/create', urlencodedParser, async function (request, response) { | ||
| 756 | 763 | } |
| 757 | 764 | }); |
| 758 | 765 | |
| 759 | 766 | router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 760 | 767 | if (!request.body.avatar_url || !request.body.new_name) { |
| 761 | 768 | return response.sendStatus(400); |
| 762 | 769 | } |
| @@ -803,7 +810,7 @@ router.post('/rename', jsonParser, async function (request, response) { | ||
| 803 | 810 | } |
| 804 | 811 | }); |
| 805 | 812 | |
| 806 | 813 | router.post('/edit', urlencodedParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 807 | 814 | if (!request.body) { |
| 808 | 815 | console.error('Error: no response body detected'); |
| 809 | 816 | response.status(400).send('Error: no response body detected'); |
| @@ -852,7 +859,7 @@ router.post('/edit', urlencodedParser, async function (request, response) { | ||
| 852 | 859 | * @param {Object} response - The HTTP response object. |
| 853 | 860 | * @returns {void} |
| 854 | 861 | */ |
| 855 | 862 | router.post('/edit-attribute', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 856 | 863 | console.log(request.body); |
| 857 | 864 | if (!request.body) { |
| 858 | 865 | console.error('Error: no response body detected'); |
| @@ -898,7 +905,7 @@ router.post('/edit-attribute', jsonParser, async function (request, response) { | ||
| 898 | 905 | * |
| 899 | 906 | * @returns {void} |
| 900 | 907 | * */ |
| 901 | 908 | router.post('/merge-attributes', jsonParser, getFileNameValidationFunction('avatar'), async function (request, response) { |
| 902 | 909 | try { |
| 903 | 910 | const update = request.body; |
| 904 | 911 | const avatarPath = path.join(request.user.directories.characters, update.avatar); |
| @@ -929,7 +936,7 @@ router.post('/merge-attributes', jsonParser, async function (request, response) | ||
| 929 | 936 | } |
| 930 | 937 | }); |
| 931 | 938 | |
| 932 | 939 | router.post('/delete', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 933 | 940 | if (!request.body || !request.body.avatar_url) { |
| 934 | 941 | return response.sendStatus(400); |
| 935 | 942 | } |
| @@ -992,7 +999,7 @@ router.post('/all', jsonParser, async function (request, response) { | ||
| 992 | 999 | } |
| 993 | 1000 | }); |
| 994 | 1001 | |
| 995 | 1002 | router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 996 | 1003 | try { |
| 997 | 1004 | if (!request.body) return response.sendStatus(400); |
| 998 | 1005 | const item = request.body.avatar_url; |
| @@ -1011,7 +1018,7 @@ router.post('/get', jsonParser, async function (request, response) { | ||
| 1011 | 1018 | } |
| 1012 | 1019 | }); |
| 1013 | 1020 | |
| 1014 | 1021 | router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1015 | 1022 | if (!request.body) return response.sendStatus(400); |
| 1016 | 1023 | |
| 1017 | 1024 | const characterDirectory = (request.body.avatar_url).replace('.png', ''); |
| @@ -1160,7 +1167,7 @@ router.post('/import', urlencodedParser, async function (request, response) { | ||
| 1160 | 1167 | } |
| 1161 | 1168 | }); |
| 1162 | 1169 | |
| 1163 | 1170 | router.post('/duplicate', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1164 | 1171 | try { |
| 1165 | 1172 | if (!request.body.avatar_url) { |
| 1166 | 1173 | console.log('avatar URL not found in request body'); |
| @@ -1207,7 +1214,7 @@ router.post('/duplicate', jsonParser, async function (request, response) { | ||
| 1207 | 1214 | } |
| 1208 | 1215 | }); |
| 1209 | 1216 | |
| 1210 | 1217 | router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1211 | 1218 | try { |
| 1212 | 1219 | if (!request.body.format || !request.body.avatar_url) { |
| 1213 | 1220 | return response.sendStatus(400); |
| @@ -9,6 +9,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 9 | 9 | import _ from 'lodash'; |
| 10 | 10 | |
| 11 | 11 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 12 | +import validateAvatarUrlMiddleware from '../middleware/validateFileName.js'; | |
| 12 | 13 | import { |
| 13 | 14 | getConfigValue, |
| 14 | 15 | humanizedISO8601DateTime, |
| @@ -294,7 +295,7 @@ function importRisuChat(userName, characterName, jsonData) { | ||
| 294 | 295 | |
| 295 | 296 | export const router = express.Router(); |
| 296 | 297 | |
| 297 | 298 | router.post('/save', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 298 | 299 | try { |
| 299 | 300 | const directoryName = String(request.body.avatar_url).replace('.png', ''); |
| 300 | 301 | const chatData = request.body.chat; |
| @@ -310,7 +311,7 @@ router.post('/save', jsonParser, function (request, response) { | ||
| 310 | 311 | } |
| 311 | 312 | }); |
| 312 | 313 | |
| 313 | 314 | router.post('/get', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 314 | 315 | try { |
| 315 | 316 | const dirName = String(request.body.avatar_url).replace('.png', ''); |
| 316 | 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 | 351 | router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 351 | 352 | if (!request.body || !request.body.original_file || !request.body.renamed_file) { |
| 352 | 353 | return response.sendStatus(400); |
| 353 | 354 | } |
| @@ -372,7 +373,7 @@ router.post('/rename', jsonParser, async function (request, response) { | ||
| 372 | 373 | return response.send({ ok: true, sanitizedFileName }); |
| 373 | 374 | }); |
| 374 | 375 | |
| 375 | 376 | router.post('/delete', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 376 | 377 | const dirName = String(request.body.avatar_url).replace('.png', ''); |
| 377 | 378 | const fileName = String(request.body.chatfile); |
| 378 | 379 | const filePath = path.join(request.user.directories.chats, dirName, sanitize(fileName)); |
| @@ -388,7 +389,7 @@ router.post('/delete', jsonParser, function (request, response) { | ||
| 388 | 389 | return response.send('ok'); |
| 389 | 390 | }); |
| 390 | 391 | |
| 391 | 392 | router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 392 | 393 | if (!request.body.file || (!request.body.avatar_url && request.body.is_group === false)) { |
| 393 | 394 | return response.sendStatus(400); |
| 394 | 395 | } |
| @@ -478,7 +479,7 @@ router.post('/group/import', urlencodedParser, function (request, response) { | ||
| 478 | 479 | } |
| 479 | 480 | }); |
| 480 | 481 | |
| 481 | 482 | router.post('/import', urlencodedParser, validateAvatarUrlMiddleware, function (request, response) { |
| 482 | 483 | if (!request.body) return response.sendStatus(400); |
| 483 | 484 | |
| 484 | 485 | const format = request.body.file_type; |
| @@ -626,7 +627,7 @@ router.post('/group/save', jsonParser, (request, response) => { | ||
| 626 | 627 | return response.send({ ok: true }); |
| 627 | 628 | }); |
| 628 | 629 | |
| 629 | 630 | router.post('/search', jsonParser, validateAvatarUrlMiddleware, function (request, response) { |
| 630 | 631 | try { |
| 631 | 632 | const { query, avatar_url, group_id } = request.body; |
| 632 | 633 | let chatFiles = []; |
| @@ -9,9 +9,10 @@ import { SETTINGS_FILE } from '../constants.js'; | ||
| 9 | 9 | import { getConfigValue, generateTimestamp, removeOldBackups } from '../util.js'; |
| 10 | 10 | import { jsonParser } from '../express-common.js'; |
| 11 | 11 | import { getAllUserHandles, getUserDirectories } from '../users.js'; |
| 12 | +import { getFileNameValidationFunction } from '../middleware/validateFileName.js'; | |
| 12 | 13 | |
| 13 | 14 | const ENABLE_EXTENSIONS = !!getConfigValue('enableExtensionsextensions.enabled', true); |
| 14 | 15 | const ENABLE_EXTENSIONS_AUTO_UPDATE = !!getConfigValue('enableExtensionsAutoUpdateextensions.autoUpdate', true); |
| 15 | 16 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); |
| 16 | 17 | |
| 17 | 18 | // 10 minutes |
| @@ -296,7 +297,7 @@ router.post('/get-snapshots', jsonParser, async (request, response) => { | ||
| 296 | 297 | } |
| 297 | 298 | }); |
| 298 | 299 | |
| 299 | 300 | router.post('/load-snapshot', jsonParser, getFileNameValidationFunction('name'), async (request, response) => { |
| 300 | 301 | try { |
| 301 | 302 | const userFilesPattern = getFilePrefix(request.user.profile.handle); |
| 302 | 303 | |
| @@ -330,7 +331,7 @@ router.post('/make-snapshot', jsonParser, async (request, response) => { | ||
| 330 | 331 | } |
| 331 | 332 | }); |
| 332 | 333 | |
| 333 | 334 | router.post('/restore-snapshot', jsonParser, getFileNameValidationFunction('name'), async (request, response) => { |
| 334 | 335 | try { |
| 335 | 336 | const userFilesPattern = getFilePrefix(request.user.profile.handle); |
| 336 | 337 | |
| @@ -238,6 +238,15 @@ export const sentencepieceTokenizers = [ | ||
| 238 | 238 | 'jamba', |
| 239 | 239 | ]; |
| 240 | 240 | |
| 241 | +export const webTokenizers = [ | |
| 242 | + 'claude', | |
| 243 | + 'llama3', | |
| 244 | + 'command-r', | |
| 245 | + 'qwen2', | |
| 246 | + 'nemo', | |
| 247 | + 'deepseek', | |
| 248 | +]; | |
| 249 | + | |
| 241 | 250 | /** |
| 242 | 251 | * Gets the Sentencepiece tokenizer by the model name. |
| 243 | 252 | * @param {string} model Sentencepiece model name |
| @@ -276,6 +285,39 @@ export function getSentencepiceTokenizer(model) { | ||
| 276 | 285 | } |
| 277 | 286 | |
| 278 | 287 | /** |
| 288 | + * Gets the Web tokenizer by the model name. | |
| 289 | + * @param {string} model Web tokenizer model name | |
| 290 | + * @returns {WebTokenizer|null} Web tokenizer | |
| 291 | + */ | |
| 292 | +export function getWebTokenizer(model) { | |
| 293 | + if (model.includes('llama3')) { | |
| 294 | + return llama3_tokenizer; | |
| 295 | + } | |
| 296 | + | |
| 297 | + if (model.includes('claude')) { | |
| 298 | + return claude_tokenizer; | |
| 299 | + } | |
| 300 | + | |
| 301 | + if (model.includes('command-r')) { | |
| 302 | + return commandTokenizer; | |
| 303 | + } | |
| 304 | + | |
| 305 | + if (model.includes('qwen2')) { | |
| 306 | + return qwen2Tokenizer; | |
| 307 | + } | |
| 308 | + | |
| 309 | + if (model.includes('nemo')) { | |
| 310 | + return nemoTokenizer; | |
| 311 | + } | |
| 312 | + | |
| 313 | + if (model.includes('deepseek')) { | |
| 314 | + return deepseekTokenizer; | |
| 315 | + } | |
| 316 | + | |
| 317 | + return null; | |
| 318 | +} | |
| 319 | + | |
| 320 | +/** | |
| 279 | 321 | * Counts the token ids for the given text using the Sentencepiece tokenizer. |
| 280 | 322 | * @param {SentencePieceTokenizer} tokenizer Sentencepiece tokenizer |
| 281 | 323 | * @param {string} text Text to tokenize |
| @@ -23,6 +23,7 @@ router.post('/logout', async (request, response) => { | ||
| 23 | 23 | } |
| 24 | 24 | |
| 25 | 25 | request.session.handle = null; |
| 26 | + request.session.csrfToken = null; | |
| 26 | 27 | request.session = null; |
| 27 | 28 | return response.sendStatus(204); |
| 28 | 29 | } catch (error) { |
| @@ -164,7 +164,7 @@ function getSourceSettings(source, request) { | ||
| 164 | 164 | }; |
| 165 | 165 | case 'transformers': |
| 166 | 166 | return { |
| 167 | 167 | model: getConfigValue('extrasextensions.embeddingModelmodels.embedding', ''), |
| 168 | 168 | }; |
| 169 | 169 | case 'palm': |
| 170 | 170 | return { |
| @@ -5,17 +5,18 @@ | ||
| 5 | 5 | import { Buffer } from 'node:buffer'; |
| 6 | 6 | import storage from 'node-persist'; |
| 7 | 7 | import { getAllUserHandles, toKey, getPasswordHash } from '../users.js'; |
| 8 | 8 | import { getConfig, getConfigValue, safeReadFileSync } from '../util.js'; |
| 9 | 9 | |
| 10 | 10 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false); |
| 11 | 11 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); |
| 12 | 12 | |
| 13 | -const unauthorizedResponse = (res) => { | |
| 14 | - res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"'); | |
| 15 | - return res.status(401).send('Authentication required'); | |
| 16 | -}; | |
| 17 | - | |
| 18 | 13 | const basicAuthMiddleware = async function (request, response, callback) { |
| 14 | + const unauthorizedWebpage = safeReadFileSync('./public/error/unauthorized.html') ?? ''; | |
| 15 | + const unauthorizedResponse = (res) => { | |
| 16 | + res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"'); | |
| 17 | + return res.status(401).send(unauthorizedWebpage); | |
| 18 | + }; | |
| 19 | + | |
| 19 | 20 | const config = getConfig(); |
| 20 | 21 | const authHeader = request.headers.authorization; |
| 21 | 22 | |
| @@ -0,0 +1,34 @@ | ||
| 1 | +import path from 'node:path'; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Gets a middleware function that validates the field in the request body. | |
| 5 | + * @param {string} fieldName Field name | |
| 6 | + * @returns {import('express').RequestHandler} Middleware function | |
| 7 | + */ | |
| 8 | +export function getFileNameValidationFunction(fieldName) { | |
| 9 | + /** | |
| 10 | + * Validates the field in the request body. | |
| 11 | + * @param {import('express').Request} req Request object | |
| 12 | + * @param {import('express').Response} res Response object | |
| 13 | + * @param {import('express').NextFunction} next Next middleware | |
| 14 | + */ | |
| 15 | + return function validateAvatarUrlMiddleware(req, res, next) { | |
| 16 | + if (req.body && fieldName in req.body && typeof req.body[fieldName] === 'string') { | |
| 17 | + const forbiddenRegExp = path.sep === '/' ? /[/\x00]/ : /[/\x00\\]/; | |
| 18 | + if (forbiddenRegExp.test(req.body[fieldName])) { | |
| 19 | + console.error('An error occurred while validating the request body', { | |
| 20 | + handle: req.user.profile.handle, | |
| 21 | + path: req.originalUrl, | |
| 22 | + field: fieldName, | |
| 23 | + value: req.body[fieldName], | |
| 24 | + }); | |
| 25 | + return res.sendStatus(400); | |
| 26 | + } | |
| 27 | + } | |
| 28 | + | |
| 29 | + next(); | |
| 30 | + }; | |
| 31 | +} | |
| 32 | + | |
| 33 | +const avatarUrlValidationFunction = getFileNameValidationFunction('avatar_url'); | |
| 34 | +export default avatarUrlValidationFunction; | |
| @@ -1,10 +1,11 @@ | ||
| 1 | 1 | import path from 'node:path'; |
| 2 | 2 | import fs from 'node:fs'; |
| 3 | 3 | import process from 'node:process'; |
| 4 | +import Handlebars from 'handlebars'; | |
| 4 | 5 | import ipMatching from 'ip-matching'; |
| 5 | 6 | |
| 6 | 7 | import { getIpFromRequest } from '../express-common.js'; |
| 7 | 8 | import { color, getConfigValue, safeReadFileSync } from '../util.js'; |
| 8 | 9 | |
| 9 | 10 | const whitelistPath = path.join(process.cwd(), './whitelist.txt'); |
| 10 | 11 | const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false); |
| @@ -52,12 +53,16 @@ function getForwardedIp(req) { | ||
| 52 | 53 | * @returns {import('express').RequestHandler} The middleware function |
| 53 | 54 | */ |
| 54 | 55 | export default function whitelistMiddleware(whitelistMode, listen) { |
| 56 | + const forbiddenWebpage = Handlebars.compile( | |
| 57 | + safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '', | |
| 58 | + ); | |
| 59 | + | |
| 55 | 60 | return function (req, res, next) { |
| 56 | 61 | const clientIp = getIpFromRequest(req); |
| 57 | 62 | const forwardedIp = getForwardedIp(req); |
| 63 | + const userAgent = req.headers['user-agent']; | |
| 58 | 64 | |
| 59 | 65 | if (listen && !knownIPs.has(clientIp)) { |
| 60 | - const userAgent = req.headers['user-agent']; | |
| 61 | 66 | console.log(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`)); |
| 62 | 67 | knownIPs.add(clientIp); |
| 63 | 68 | |
| @@ -76,9 +81,15 @@ export default function whitelistMiddleware(whitelistMode, listen) { | ||
| 76 | 81 | || forwardedIp && whitelistMode === true && !whitelist.some(x => ipMatching.matches(forwardedIp, ipMatching.getMatch(x))) |
| 77 | 82 | ) { |
| 78 | 83 | // Log the connection attempt with real IP address |
| 79 | - const ipDetails = forwardedIp ? `${clientIp} (forwarded from ${forwardedIp})` : clientIp; | |
| 84 | + const ipDetails = forwardedIp | |
| 80 | - console.log(color.red('Forbidden: Connection attempt from ' + ipDetails + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.\n')); | |
| 85 | + ? `${clientIp} (forwarded from ${forwardedIp})` | |
| 81 | - return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + ipDetails + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.'); | |
| 86 | + : clientIp; | |
| 87 | + console.log( | |
| 88 | + color.red( | |
| 89 | + `Blocked connection from ${clientIp}; User Agent: ${userAgent}\n\tTo allow this connection, add its IP address to the whitelist or disable whitelist mode by editing config.yaml in the root directory of your SillyTavern installation.\n`, | |
| 90 | + ), | |
| 91 | + ); | |
| 92 | + return res.status(403).send(forbiddenWebpage({ ipDetails })); | |
| 82 | 93 | } |
| 83 | 94 | next(); |
| 84 | 95 | }; |
| @@ -360,6 +360,8 @@ export function convertCohereMessages(messages, names) { | ||
| 360 | 360 | */ |
| 361 | 361 | export function convertGooglePrompt(messages, model, useSysPrompt, names) { |
| 362 | 362 | const visionSupportedModels = [ |
| 363 | + 'gemini-2.0-flash-thinking-exp', | |
| 364 | + 'gemini-2.0-flash-thinking-exp-01-21', | |
| 363 | 365 | 'gemini-2.0-flash-thinking-exp-1219', |
| 364 | 366 | 'gemini-2.0-flash-exp', |
| 365 | 367 | 'gemini-1.5-flash', |
| @@ -19,31 +19,31 @@ const tasks = { | ||
| 19 | 19 | 'text-classification': { |
| 20 | 20 | defaultModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx', |
| 21 | 21 | pipeline: null, |
| 22 | 22 | configField: 'extrasextensions.classificationModelmodels.classification', |
| 23 | 23 | quantized: true, |
| 24 | 24 | }, |
| 25 | 25 | 'image-to-text': { |
| 26 | 26 | defaultModel: 'Xenova/vit-gpt2-image-captioning', |
| 27 | 27 | pipeline: null, |
| 28 | 28 | configField: 'extrasextensions.captioningModelmodels.captioning', |
| 29 | 29 | quantized: true, |
| 30 | 30 | }, |
| 31 | 31 | 'feature-extraction': { |
| 32 | 32 | defaultModel: 'Xenova/all-mpnet-base-v2', |
| 33 | 33 | pipeline: null, |
| 34 | 34 | configField: 'extrasextensions.embeddingModelmodels.embedding', |
| 35 | 35 | quantized: true, |
| 36 | 36 | }, |
| 37 | 37 | 'automatic-speech-recognition': { |
| 38 | 38 | defaultModel: 'Xenova/whisper-small', |
| 39 | 39 | pipeline: null, |
| 40 | 40 | configField: 'extrasextensions.speechToTextModelmodels.speechToText', |
| 41 | 41 | quantized: true, |
| 42 | 42 | }, |
| 43 | 43 | 'text-to-speech': { |
| 44 | 44 | defaultModel: 'Xenova/speecht5_tts', |
| 45 | 45 | pipeline: null, |
| 46 | 46 | configField: 'extrasextensions.textToSpeechModelmodels.textToSpeech', |
| 47 | 47 | quantized: false, |
| 48 | 48 | }, |
| 49 | 49 | }; |
| @@ -132,7 +132,7 @@ export async function getPipeline(task, forceModel = '') { | ||
| 132 | 132 | |
| 133 | 133 | const cacheDir = path.join(globalThis.DATA_ROOT, '_cache'); |
| 134 | 134 | const model = forceModel || getModelForTask(task); |
| 135 | 135 | const localOnly = !getConfigValue('extrasextensions.disableAutoDownloadmodels.autoDownload', falsetrue); |
| 136 | 136 | console.log('Initializing transformers.js pipeline for task', task, 'with model', model); |
| 137 | 137 | const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly }); |
| 138 | 138 | tasks[task].pipeline = instance; |
| @@ -458,7 +458,8 @@ export function getPasswordSalt() { | ||
| 458 | 458 | */ |
| 459 | 459 | export function getCookieSessionName() { |
| 460 | 460 | // Get server hostname and hash it to generate a session suffix |
| 461 | - const suffix = crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 8); | |
| 461 | + const hostname = os.hostname() || 'localhost'; | |
| 462 | + const suffix = crypto.createHash('sha256').update(hostname).digest('hex').slice(0, 8); | |
| 462 | 463 | return `session-${suffix}`; |
| 463 | 464 | } |
| 464 | 465 | |
| @@ -871,3 +871,14 @@ export class MemoryLimitedMap { | ||
| 871 | 871 | return this.map[Symbol.iterator](); |
| 872 | 872 | } |
| 873 | 873 | } |
| 874 | + | |
| 875 | +/** | |
| 876 | + * A 'safe' version of `fs.readFileSync()`. Returns the contents of a file if it exists, falling back to a default value if not. | |
| 877 | + * @param {string} filePath Path of the file to be read. | |
| 878 | + * @param {Parameters<typeof fs.readFileSync>[1]} options Options object to pass through to `fs.readFileSync()` (default: `{ encoding: 'utf-8' }`). | |
| 879 | + * @returns The contents at `filePath` if it exists, or `null` if not. | |
| 880 | + */ | |
| 881 | +export function safeReadFileSync(filePath, options = { encoding: 'utf-8' }) { | |
| 882 | + if (fs.existsSync(filePath)) return fs.readFileSync(filePath, options); | |
| 883 | + return null; | |
| 884 | +} | |