Merge branch 'staging' into feat/xtc
| @@ -9,6 +9,11 @@ declare global { | |||
| 9 | }; | 9 | }; |
| 10 | } | 10 | } |
| 11 | } | 11 | } |
| 12 | |||
| 13 | /** | ||
| 14 | * The root directory for user data. | ||
| 15 | */ | ||
| 16 | var DATA_ROOT: string; | ||
| 12 | } | 17 | } |
| 13 | 18 | ||
| 14 | declare module 'express-session' { | 19 | declare module 'express-session' { |
| @@ -27,6 +27,7 @@ | |||
| 27 | "google-translate-api-browser": "^3.0.1", | 27 | "google-translate-api-browser": "^3.0.1", |
| 28 | "he": "^1.2.0", | 28 | "he": "^1.2.0", |
| 29 | "helmet": "^7.1.0", | 29 | "helmet": "^7.1.0", |
| 30 | "iconv-lite": "^0.6.3", | ||
| 30 | "ip-matching": "^2.1.2", | 31 | "ip-matching": "^2.1.2", |
| 31 | "ipaddr.js": "^2.0.1", | 32 | "ipaddr.js": "^2.0.1", |
| 32 | "jimp": "^0.22.10", | 33 | "jimp": "^0.22.10", |
| @@ -42,7 +43,7 @@ | |||
| 42 | "rate-limiter-flexible": "^5.0.0", | 43 | "rate-limiter-flexible": "^5.0.0", |
| 43 | "response-time": "^2.3.2", | 44 | "response-time": "^2.3.2", |
| 44 | "sanitize-filename": "^1.6.3", | 45 | "sanitize-filename": "^1.6.3", |
| 45 | "sillytavern-transformers": "^2.14.6", | 46 | "sillytavern-transformers": "2.14.6", |
| 46 | "simple-git": "^3.19.1", | 47 | "simple-git": "^3.19.1", |
| 47 | "tiktoken": "^1.0.15", | 48 | "tiktoken": "^1.0.15", |
| 48 | "vectra": "^0.2.2", | 49 | "vectra": "^0.2.2", |
| @@ -1492,6 +1493,18 @@ | |||
| 1492 | "node": ">= 0.8" | 1493 | "node": ">= 0.8" |
| 1493 | } | 1494 | } |
| 1494 | }, | 1495 | }, |
| 1496 | "node_modules/body-parser/node_modules/iconv-lite": { | ||
| 1497 | "version": "0.4.24", | ||
| 1498 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", | ||
| 1499 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", | ||
| 1500 | "license": "MIT", | ||
| 1501 | "dependencies": { | ||
| 1502 | "safer-buffer": ">= 2.1.2 < 3" | ||
| 1503 | }, | ||
| 1504 | "engines": { | ||
| 1505 | "node": ">=0.10.0" | ||
| 1506 | } | ||
| 1507 | }, | ||
| 1495 | "node_modules/boolbase": { | 1508 | "node_modules/boolbase": { |
| 1496 | "version": "1.0.0", | 1509 | "version": "1.0.0", |
| 1497 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", | 1510 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", |
| @@ -3282,12 +3295,12 @@ | |||
| 3282 | } | 3295 | } |
| 3283 | }, | 3296 | }, |
| 3284 | "node_modules/iconv-lite": { | 3297 | "node_modules/iconv-lite": { |
| 3285 | "version": "0.4.24", | 3298 | "version": "0.6.3", |
| 3286 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", | 3299 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", |
| 3287 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", | 3300 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", |
| 3288 | "license": "MIT", | 3301 | "license": "MIT", |
| 3289 | "dependencies": { | 3302 | "dependencies": { |
| 3290 | "safer-buffer": ">= 2.1.2 < 3" | 3303 | "safer-buffer": ">= 2.1.2 < 3.0.0" |
| 3291 | }, | 3304 | }, |
| 3292 | "engines": { | 3305 | "engines": { |
| 3293 | "node": ">=0.10.0" | 3306 | "node": ">=0.10.0" |
| @@ -4618,6 +4631,18 @@ | |||
| 4618 | "node": ">= 0.8" | 4631 | "node": ">= 0.8" |
| 4619 | } | 4632 | } |
| 4620 | }, | 4633 | }, |
| 4634 | "node_modules/raw-body/node_modules/iconv-lite": { | ||
| 4635 | "version": "0.4.24", | ||
| 4636 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", | ||
| 4637 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", | ||
| 4638 | "license": "MIT", | ||
| 4639 | "dependencies": { | ||
| 4640 | "safer-buffer": ">= 2.1.2 < 3" | ||
| 4641 | }, | ||
| 4642 | "engines": { | ||
| 4643 | "node": ">=0.10.0" | ||
| 4644 | } | ||
| 4645 | }, | ||
| 4621 | "node_modules/readable-stream": { | 4646 | "node_modules/readable-stream": { |
| 4622 | "version": "2.3.8", | 4647 | "version": "2.3.8", |
| 4623 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", | 4648 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", |
| @@ -17,6 +17,7 @@ | |||
| 17 | "google-translate-api-browser": "^3.0.1", | 17 | "google-translate-api-browser": "^3.0.1", |
| 18 | "he": "^1.2.0", | 18 | "he": "^1.2.0", |
| 19 | "helmet": "^7.1.0", | 19 | "helmet": "^7.1.0", |
| 20 | "iconv-lite": "^0.6.3", | ||
| 20 | "ip-matching": "^2.1.2", | 21 | "ip-matching": "^2.1.2", |
| 21 | "ipaddr.js": "^2.0.1", | 22 | "ipaddr.js": "^2.0.1", |
| 22 | "jimp": "^0.22.10", | 23 | "jimp": "^0.22.10", |
| @@ -32,7 +33,7 @@ | |||
| 32 | "rate-limiter-flexible": "^5.0.0", | 33 | "rate-limiter-flexible": "^5.0.0", |
| 33 | "response-time": "^2.3.2", | 34 | "response-time": "^2.3.2", |
| 34 | "sanitize-filename": "^1.6.3", | 35 | "sanitize-filename": "^1.6.3", |
| 35 | "sillytavern-transformers": "^2.14.6", | 36 | "sillytavern-transformers": "2.14.6", |
| 36 | "simple-git": "^3.19.1", | 37 | "simple-git": "^3.19.1", |
| 37 | "tiktoken": "^1.0.15", | 38 | "tiktoken": "^1.0.15", |
| 38 | "vectra": "^0.2.2", | 39 | "vectra": "^0.2.2", |
| @@ -99,6 +99,6 @@ | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | #bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button { | 101 | #bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button { |
| 102 | width: 100px; | 102 | width: unset; |
| 103 | padding: 0.25em; | 103 | padding: 0.25em; |
| 104 | } | 104 | } |
| @@ -4121,6 +4121,10 @@ | |||
| 4121 | <input id="world_import_dialog" type="checkbox" /> | 4121 | <input id="world_import_dialog" type="checkbox" /> |
| 4122 | <small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small> | 4122 | <small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small> |
| 4123 | </label> | 4123 | </label> |
| 4124 | <label data-newbie-hidden class="checkbox_label" for="enable_auto_select_input" title="Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields." data-i18n="[title]Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields."> | ||
| 4125 | <input id="enable_auto_select_input" type="checkbox" /> | ||
| 4126 | <small data-i18n="Auto-select Input Text">Auto-select Input Text</small> | ||
| 4127 | </label> | ||
| 4124 | <label class="checkbox_label" for="restore_user_input" title="Restore unsaved user input on page refresh." data-i18n="[title]Restore unsaved user input on page refresh"> | 4128 | <label class="checkbox_label" for="restore_user_input" title="Restore unsaved user input on page refresh." data-i18n="[title]Restore unsaved user input on page refresh"> |
| 4125 | <input id="restore_user_input" type="checkbox" /> | 4129 | <input id="restore_user_input" type="checkbox" /> |
| 4126 | <small data-i18n="Restore User Input">Restore User Input</small> | 4130 | <small data-i18n="Restore User Input">Restore User Input</small> |
| @@ -5008,7 +5012,7 @@ | |||
| 5008 | <div class="popup-crop-wrap"> | 5012 | <div class="popup-crop-wrap"> |
| 5009 | <img class="popup-crop-image" src=""> | 5013 | <img class="popup-crop-image" src=""> |
| 5010 | </div> | 5014 | </div> |
| 5011 | <textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea> | 5015 | <textarea class="popup-input text_pole result-control auto-select" rows="1" data-result="1" data-result-event="submit"></textarea> |
| 5012 | <div class="popup-inputs"></div> | 5016 | <div class="popup-inputs"></div> |
| 5013 | <div class="popup-controls"> | 5017 | <div class="popup-controls"> |
| 5014 | <div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div> | 5018 | <div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div> |
| @@ -5426,9 +5426,11 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc | |||
| 5426 | getMessage = fixMarkdown(getMessage, false); | 5426 | getMessage = fixMarkdown(getMessage, false); |
| 5427 | } | 5427 | } |
| 5428 | 5428 | ||
| 5429 | const nameToTrim2 = isImpersonate ? name1 : name2; | 5429 | const nameToTrim2 = isImpersonate |
| 5430 | ? (!power_user.allow_name1_display ? name1 : '') | ||
| 5431 | : (!power_user.allow_name2_display ? name2 : ''); | ||
| 5430 | 5432 | ||
| 5431 | if (getMessage.startsWith(nameToTrim2 + ':')) { | 5433 | if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) { |
| 5432 | getMessage = getMessage.replace(nameToTrim2 + ':', ''); | 5434 | getMessage = getMessage.replace(nameToTrim2 + ':', ''); |
| 5433 | getMessage = getMessage.trimStart(); | 5435 | getMessage = getMessage.trimStart(); |
| 5434 | } | 5436 | } |
| @@ -8390,6 +8392,9 @@ const CONNECT_API_MAP = { | |||
| 8390 | }, | 8392 | }, |
| 8391 | }; | 8393 | }; |
| 8392 | 8394 | ||
| 8395 | // Collect all unique API names in an array | ||
| 8396 | export const UNIQUE_APIS = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))]; | ||
| 8397 | |||
| 8393 | // Fill connections map from textgen_types and chat_completion_sources | 8398 | // Fill connections map from textgen_types and chat_completion_sources |
| 8394 | for (const textGenType of Object.values(textgen_types)) { | 8399 | for (const textGenType of Object.values(textgen_types)) { |
| 8395 | if (CONNECT_API_MAP[textGenType]) continue; | 8400 | if (CONNECT_API_MAP[textGenType]) continue; |
| @@ -8964,9 +8969,6 @@ jQuery(async function () { | |||
| 8964 | return ''; | 8969 | return ''; |
| 8965 | } | 8970 | } |
| 8966 | 8971 | ||
| 8967 | // Collect all unique API names in an array | ||
| 8968 | const uniqueAPIs = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))]; | ||
| 8969 | |||
| 8970 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 8972 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 8971 | name: 'dupe', | 8973 | name: 'dupe', |
| 8972 | callback: duplicateCharacter, | 8974 | callback: duplicateCharacter, |
| @@ -8975,13 +8977,13 @@ jQuery(async function () { | |||
| 8975 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 8977 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 8976 | name: 'api', | 8978 | name: 'api', |
| 8977 | callback: connectAPISlash, | 8979 | callback: connectAPISlash, |
| 8980 | returns: 'the current API', | ||
| 8978 | unnamedArgumentList: [ | 8981 | unnamedArgumentList: [ |
| 8979 | SlashCommandArgument.fromProps({ | 8982 | SlashCommandArgument.fromProps({ |
| 8980 | description: 'API to connect to', | 8983 | description: 'API to connect to', |
| 8981 | typeList: [ARGUMENT_TYPE.STRING], | 8984 | typeList: [ARGUMENT_TYPE.STRING], |
| 8982 | isRequired: false, | ||
| 8983 | enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => | 8985 | enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => |
| 8984 | new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)), | 8986 | new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)), |
| 8985 | selected[0].toUpperCase() ?? enumIcons.default)), | 8987 | selected[0].toUpperCase() ?? enumIcons.default)), |
| 8986 | }), | 8988 | }), |
| 8987 | ], | 8989 | ], |
| @@ -10655,6 +10657,15 @@ jQuery(async function () { | |||
| 10655 | $(document).on('click', '.open_alternate_greetings', openAlternateGreetings); | 10657 | $(document).on('click', '.open_alternate_greetings', openAlternateGreetings); |
| 10656 | /* $('#set_character_world').on('click', openCharacterWorldPopup); */ | 10658 | /* $('#set_character_world').on('click', openCharacterWorldPopup); */ |
| 10657 | 10659 | ||
| 10660 | $(document).on('focus', 'input.auto-select, textarea.auto-select', function () { | ||
| 10661 | if (!power_user.enable_auto_select_input) return; | ||
| 10662 | const control = $(this)[0]; | ||
| 10663 | if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) { | ||
| 10664 | control.select(); | ||
| 10665 | console.debug('Auto-selecting content of input control', control); | ||
| 10666 | } | ||
| 10667 | }); | ||
| 10668 | |||
| 10658 | $(document).keyup(function (e) { | 10669 | $(document).keyup(function (e) { |
| 10659 | if (e.key === 'Escape') { | 10670 | if (e.key === 'Escape') { |
| 10660 | const isEditVisible = $('#curEditTextarea').is(':visible'); | 10671 | const isEditVisible = $('#curEditTextarea').is(':visible'); |
| @@ -10738,7 +10749,7 @@ jQuery(async function () { | |||
| 10738 | } | 10749 | } |
| 10739 | } break; | 10750 | } break; |
| 10740 | case 'import_tags': { | 10751 | case 'import_tags': { |
| 10741 | await importTags(characters[this_chid], { forceShow: true }); | 10752 | await importTags(characters[this_chid], { importSetting: tag_import_setting.ASK }); |
| 10742 | } break; | 10753 | } break; |
| 10743 | /*case 'delete_button': | 10754 | /*case 'delete_button': |
| 10744 | popup_type = "del_ch"; | 10755 | popup_type = "del_ch"; |
| @@ -18,7 +18,7 @@ import { | |||
| 18 | import { favsToHotswap } from './RossAscends-mods.js'; | 18 | import { favsToHotswap } from './RossAscends-mods.js'; |
| 19 | import { hideLoader, showLoader } from './loader.js'; | 19 | import { hideLoader, showLoader } from './loader.js'; |
| 20 | import { convertCharacterToPersona } from './personas.js'; | 20 | import { convertCharacterToPersona } from './personas.js'; |
| 21 | import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap } from './tags.js'; | 21 | import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js'; |
| 22 | 22 | ||
| 23 | /** | 23 | /** |
| 24 | * Static object representing the actions of the | 24 | * Static object representing the actions of the |
| @@ -197,10 +197,10 @@ class BulkTagPopupHandler { | |||
| 197 | #getHtml = () => { | 197 | #getHtml = () => { |
| 198 | const characterData = JSON.stringify({ characterIds: this.characterIds }); | 198 | const characterData = JSON.stringify({ characterIds: this.characterIds }); |
| 199 | return `<div id="bulk_tag_shadow_popup"> | 199 | return `<div id="bulk_tag_shadow_popup"> |
| 200 | <div id="bulk_tag_popup"> | 200 | <div id="bulk_tag_popup" class="wider_dialogue_popup"> |
| 201 | <div id="bulk_tag_popup_holder"> | 201 | <div id="bulk_tag_popup_holder"> |
| 202 | <h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3> | 202 | <h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3> |
| 203 | <small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters.</small> | 203 | <small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters. Import all or existing tags for all selected characters.</small> |
| 204 | <div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div> | 204 | <div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div> |
| 205 | <br> | 205 | <br> |
| 206 | <div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'> | 206 | <div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'> |
| @@ -219,6 +219,12 @@ class BulkTagPopupHandler { | |||
| 219 | <i class="fa-solid fa-trash-can margin-right-10px"></i> | 219 | <i class="fa-solid fa-trash-can margin-right-10px"></i> |
| 220 | Mutual | 220 | Mutual |
| 221 | </div> | 221 | </div> |
| 222 | <div id="bulk_tag_popup_import_all_tags" class="menu_button" title="Import all tags from selected characters" data-i18n="[title]Import all tags from selected characters"> | ||
| 223 | Import All | ||
| 224 | </div> | ||
| 225 | <div id="bulk_tag_popup_import_existing_tags" class="menu_button" title="Import existing tags from selected characters" data-i18n="[title]Import existing tags from selected characters"> | ||
| 226 | Import Existing | ||
| 227 | </div> | ||
| 222 | <div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div> | 228 | <div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div> |
| 223 | </div> | 229 | </div> |
| 224 | </div> | 230 | </div> |
| @@ -254,6 +260,30 @@ class BulkTagPopupHandler { | |||
| 254 | document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this)); | 260 | document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this)); |
| 255 | document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this)); | 261 | document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this)); |
| 256 | document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this)); | 262 | document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this)); |
| 263 | document.querySelector('#bulk_tag_popup_import_all_tags').addEventListener('click', this.importAllTags.bind(this)); | ||
| 264 | document.querySelector('#bulk_tag_popup_import_existing_tags').addEventListener('click', this.importExistingTags.bind(this)); | ||
| 265 | } | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Import existing tags for all selected characters | ||
| 269 | */ | ||
| 270 | async importExistingTags() { | ||
| 271 | for (const characterId of this.characterIds) { | ||
| 272 | await importTags(characters[characterId], { importSetting: tag_import_setting.ONLY_EXISTING }); | ||
| 273 | } | ||
| 274 | |||
| 275 | $('#bulkTagList').empty(); | ||
| 276 | } | ||
| 277 | |||
| 278 | /** | ||
| 279 | * Import all tags for all selected characters | ||
| 280 | */ | ||
| 281 | async importAllTags() { | ||
| 282 | for (const characterId of this.characterIds) { | ||
| 283 | await importTags(characters[characterId], { importSetting: tag_import_setting.ALL }); | ||
| 284 | } | ||
| 285 | |||
| 286 | $('#bulkTagList').empty(); | ||
| 257 | } | 287 | } |
| 258 | 288 | ||
| 259 | /** | 289 | /** |
| @@ -954,6 +954,11 @@ export function initRossMods() { | |||
| 954 | * @param {KeyboardEvent} event | 954 | * @param {KeyboardEvent} event |
| 955 | */ | 955 | */ |
| 956 | async function processHotkeys(event) { | 956 | async function processHotkeys(event) { |
| 957 | // Default hotkeys and shortcuts shouldn't work if any popup is currently open | ||
| 958 | if (Popup.util.isPopupOpen()) { | ||
| 959 | return; | ||
| 960 | } | ||
| 961 | |||
| 957 | //Enter to send when send_textarea in focus | 962 | //Enter to send when send_textarea in focus |
| 958 | if (document.activeElement == hotkeyTargets['send_textarea']) { | 963 | if (document.activeElement == hotkeyTargets['send_textarea']) { |
| 959 | const sendOnEnter = shouldSendOnEnter(); | 964 | const sendOnEnter = shouldSendOnEnter(); |
| @@ -1107,10 +1112,6 @@ export function initRossMods() { | |||
| 1107 | } | 1112 | } |
| 1108 | 1113 | ||
| 1109 | if (event.key == 'Escape') { //closes various panels | 1114 | if (event.key == 'Escape') { //closes various panels |
| 1110 | // Do not close panels if we are currently inside a popup | ||
| 1111 | if (Popup.util.isPopupOpen()) | ||
| 1112 | return; | ||
| 1113 | |||
| 1114 | //dont override Escape hotkey functions from script.js | 1115 | //dont override Escape hotkey functions from script.js |
| 1115 | //"close edit box" and "cancel stream generation". | 1116 | //"close edit box" and "cancel stream generation". |
| 1116 | if ($('#curEditTextarea').is(':visible') || $('#mes_stop').is(':visible')) { | 1117 | if ($('#curEditTextarea').is(':visible') || $('#mes_stop').is(':visible')) { |
| @@ -38,6 +38,7 @@ | |||
| 38 | <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option> | 38 | <option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option> |
| 39 | <option data-type="openai" value="gpt-4o">gpt-4o</option> | 39 | <option data-type="openai" value="gpt-4o">gpt-4o</option> |
| 40 | <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option> | 40 | <option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option> |
| 41 | <option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option> | ||
| 41 | <option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option> | 42 | <option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option> |
| 42 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> | 43 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> |
| 43 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> | 44 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> |
| @@ -401,7 +401,7 @@ async function processFiles(chat) { | |||
| 401 | const dataBankCollectionIds = await ingestDataBankAttachments(); | 401 | const dataBankCollectionIds = await ingestDataBankAttachments(); |
| 402 | 402 | ||
| 403 | if (dataBankCollectionIds.length) { | 403 | if (dataBankCollectionIds.length) { |
| 404 | const queryText = await getQueryText(chat); | 404 | const queryText = await getQueryText(chat, 'file'); |
| 405 | await injectDataBankChunks(queryText, dataBankCollectionIds); | 405 | await injectDataBankChunks(queryText, dataBankCollectionIds); |
| 406 | } | 406 | } |
| 407 | 407 | ||
| @@ -435,7 +435,7 @@ async function processFiles(chat) { | |||
| 435 | await vectorizeFile(fileText, fileName, collectionId, settings.chunk_size, settings.overlap_percent); | 435 | await vectorizeFile(fileText, fileName, collectionId, settings.chunk_size, settings.overlap_percent); |
| 436 | } | 436 | } |
| 437 | 437 | ||
| 438 | const queryText = await getQueryText(chat); | 438 | const queryText = await getQueryText(chat, 'file'); |
| 439 | const fileChunks = await retrieveFileChunks(queryText, collectionId); | 439 | const fileChunks = await retrieveFileChunks(queryText, collectionId); |
| 440 | 440 | ||
| 441 | message.mes = `${fileChunks}\n\n${message.mes}`; | 441 | message.mes = `${fileChunks}\n\n${message.mes}`; |
| @@ -596,7 +596,7 @@ async function rearrangeChat(chat) { | |||
| 596 | return; | 596 | return; |
| 597 | } | 597 | } |
| 598 | 598 | ||
| 599 | const queryText = await getQueryText(chat); | 599 | const queryText = await getQueryText(chat, 'chat'); |
| 600 | 600 | ||
| 601 | if (queryText.length === 0) { | 601 | if (queryText.length === 0) { |
| 602 | console.debug('Vectors: No text to query'); | 602 | console.debug('Vectors: No text to query'); |
| @@ -683,15 +683,16 @@ const onChatEvent = debounce(async () => await moduleWorker.update(), debounce_t | |||
| 683 | /** | 683 | /** |
| 684 | * Gets the text to query from the chat | 684 | * Gets the text to query from the chat |
| 685 | * @param {object[]} chat Chat messages | 685 | * @param {object[]} chat Chat messages |
| 686 | * @param {'file'|'chat'|'world-info'} initiator Initiator of the query | ||
| 686 | * @returns {Promise<string>} Text to query | 687 | * @returns {Promise<string>} Text to query |
| 687 | */ | 688 | */ |
| 688 | async function getQueryText(chat) { | 689 | async function getQueryText(chat, initiator) { |
| 689 | let queryText = ''; | 690 | let queryText = ''; |
| 690 | let i = 0; | 691 | let i = 0; |
| 691 | 692 | ||
| 692 | let hashedMessages = chat.map(x => ({ text: String(substituteParams(x.mes)) })); | 693 | let hashedMessages = chat.map(x => ({ text: String(substituteParams(x.mes)) })); |
| 693 | 694 | ||
| 694 | if (settings.summarize && settings.summarize_sent) { | 695 | if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) { |
| 695 | hashedMessages = await summarize(hashedMessages, settings.summary_source); | 696 | hashedMessages = await summarize(hashedMessages, settings.summary_source); |
| 696 | } | 697 | } |
| 697 | 698 | ||
| @@ -1279,7 +1280,7 @@ async function activateWorldInfo(chat) { | |||
| 1279 | } | 1280 | } |
| 1280 | 1281 | ||
| 1281 | // Perform a multi-query | 1282 | // Perform a multi-query |
| 1282 | const queryText = await getQueryText(chat); | 1283 | const queryText = await getQueryText(chat, 'world-info'); |
| 1283 | 1284 | ||
| 1284 | if (queryText.length === 0) { | 1285 | if (queryText.length === 0) { |
| 1285 | console.debug('Vectors: No text to query for WI'); | 1286 | console.debug('Vectors: No text to query for WI'); |
| @@ -4763,12 +4763,13 @@ export function isImageInliningSupported() { | |||
| 4763 | 'gpt-4-turbo', | 4763 | 'gpt-4-turbo', |
| 4764 | 'gpt-4o', | 4764 | 'gpt-4o', |
| 4765 | 'gpt-4o-mini', | 4765 | 'gpt-4o-mini', |
| 4766 | 'chatgpt-4o-latest', | ||
| 4766 | 'yi-vision', | 4767 | 'yi-vision', |
| 4767 | ]; | 4768 | ]; |
| 4768 | 4769 | ||
| 4769 | switch (oai_settings.chat_completion_source) { | 4770 | switch (oai_settings.chat_completion_source) { |
| 4770 | case chat_completion_sources.OPENAI: | 4771 | case chat_completion_sources.OPENAI: |
| 4771 | return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('chatgpt-4o-latest') && !oai_settings.openai_model.includes('gpt-4-turbo-preview')); | 4772 | return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview')); |
| 4772 | case chat_completion_sources.MAKERSUITE: | 4773 | case chat_completion_sources.MAKERSUITE: |
| 4773 | return visionSupportedModels.some(model => oai_settings.google_model.includes(model)); | 4774 | return visionSupportedModels.some(model => oai_settings.google_model.includes(model)); |
| 4774 | case chat_completion_sources.CLAUDE: | 4775 | case chat_completion_sources.CLAUDE: |
| @@ -596,7 +596,7 @@ export class Popup { | |||
| 596 | 596 | ||
| 597 | /** @returns {boolean} Checks if any modal popup dialog is open */ | 597 | /** @returns {boolean} Checks if any modal popup dialog is open */ |
| 598 | isPopupOpen() { | 598 | isPopupOpen() { |
| 599 | return Popup.util.popups.length > 0; | 599 | return Popup.util.popups.filter(x => x.dlg.hasAttribute('open')).length > 0; |
| 600 | }, | 600 | }, |
| 601 | 601 | ||
| 602 | /** | 602 | /** |
| @@ -202,6 +202,7 @@ let power_user = { | |||
| 202 | trim_spaces: true, | 202 | trim_spaces: true, |
| 203 | relaxed_api_urls: false, | 203 | relaxed_api_urls: false, |
| 204 | world_import_dialog: true, | 204 | world_import_dialog: true, |
| 205 | enable_auto_select_input: false, | ||
| 205 | tag_import_setting: tag_import_setting.ASK, | 206 | tag_import_setting: tag_import_setting.ASK, |
| 206 | disable_group_trimming: false, | 207 | disable_group_trimming: false, |
| 207 | single_line: false, | 208 | single_line: false, |
| @@ -1611,6 +1612,7 @@ async function loadPowerUserSettings(settings, data) { | |||
| 1611 | $('#single_line').prop('checked', power_user.single_line); | 1612 | $('#single_line').prop('checked', power_user.single_line); |
| 1612 | $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); | 1613 | $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); |
| 1613 | $('#world_import_dialog').prop('checked', power_user.world_import_dialog); | 1614 | $('#world_import_dialog').prop('checked', power_user.world_import_dialog); |
| 1615 | $('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input); | ||
| 1614 | $('#trim_spaces').prop('checked', power_user.trim_spaces); | 1616 | $('#trim_spaces').prop('checked', power_user.trim_spaces); |
| 1615 | $('#continue_on_send').prop('checked', power_user.continue_on_send); | 1617 | $('#continue_on_send').prop('checked', power_user.continue_on_send); |
| 1616 | $('#quick_continue').prop('checked', power_user.quick_continue); | 1618 | $('#quick_continue').prop('checked', power_user.quick_continue); |
| @@ -3788,6 +3790,12 @@ $(document).ready(() => { | |||
| 3788 | saveSettingsDebounced(); | 3790 | saveSettingsDebounced(); |
| 3789 | }); | 3791 | }); |
| 3790 | 3792 | ||
| 3793 | $('#enable_auto_select_input').on('input', function () { | ||
| 3794 | const value = !!$(this).prop('checked'); | ||
| 3795 | power_user.enable_auto_select_input = value; | ||
| 3796 | saveSettingsDebounced(); | ||
| 3797 | }); | ||
| 3798 | |||
| 3791 | $('#spoiler_free_mode').on('input', function () { | 3799 | $('#spoiler_free_mode').on('input', function () { |
| 3792 | power_user.spoiler_free_mode = !!$(this).prop('checked'); | 3800 | power_user.spoiler_free_mode = !!$(this).prop('checked'); |
| 3793 | switchSpoilerMode(); | 3801 | switchSpoilerMode(); |
| @@ -7,9 +7,19 @@ export const markdownUnderscoreExt = () => { | |||
| 7 | } | 7 | } |
| 8 | 8 | ||
| 9 | return [{ | 9 | return [{ |
| 10 | type: 'lang', | 10 | type: 'output', |
| 11 | regex: new RegExp('\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'g'), | 11 | regex: new RegExp('(<code>[\\s\\S]*?<\\/code>)|(?<!\\S)_(?!_)([^_\\n]+?)(?<!_)_(?!\\w)', 'g'), |
| 12 | replace: '<em>$1</em>', | 12 | replace: function(match, codeContent, italicContent) { |
| 13 | if (codeContent) { | ||
| 14 | // If it's inside <code> tags, return unchanged | ||
| 15 | return match; | ||
| 16 | } else if (italicContent) { | ||
| 17 | // If it's an italic group, apply the replacement | ||
| 18 | return '<em>' + italicContent + '</em>'; | ||
| 19 | } | ||
| 20 | // If none of the conditions are met, return the original match | ||
| 21 | return match; | ||
| 22 | }, | ||
| 13 | }]; | 23 | }]; |
| 14 | } catch (e) { | 24 | } catch (e) { |
| 15 | console.error('Error in Showdown-underscore extension:', e); | 25 | console.error('Error in Showdown-underscore extension:', e); |
| @@ -1,7 +1,9 @@ | |||
| 1 | import { | 1 | import { |
| 2 | Generate, | 2 | Generate, |
| 3 | UNIQUE_APIS, | ||
| 3 | activateSendButtons, | 4 | activateSendButtons, |
| 4 | addOneMessage, | 5 | addOneMessage, |
| 6 | api_server, | ||
| 5 | callPopup, | 7 | callPopup, |
| 6 | characters, | 8 | characters, |
| 7 | chat, | 9 | chat, |
| @@ -49,8 +51,8 @@ import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSel | |||
| 49 | import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js'; | 51 | import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js'; |
| 50 | import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js'; | 52 | import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js'; |
| 51 | import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; | 53 | import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; |
| 52 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | 54 | import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 53 | import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js'; | 55 | import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js'; |
| 54 | import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; | 56 | import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; |
| 55 | import { registerVariableCommands, resolveVariable } from './variables.js'; | 57 | import { registerVariableCommands, resolveVariable } from './variables.js'; |
| 56 | import { background_settings } from './backgrounds.js'; | 58 | import { background_settings } from './backgrounds.js'; |
| @@ -1496,7 +1498,8 @@ export function initDefaultSlashCommands() { | |||
| 1496 | ], | 1498 | ], |
| 1497 | helpString: 'Sets the specified prompt manager entry/entries on or off.', | 1499 | helpString: 'Sets the specified prompt manager entry/entries on or off.', |
| 1498 | })); | 1500 | })); |
| 1499 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pick-icon', | 1501 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1502 | name: 'pick-icon', | ||
| 1500 | callback: async () => ((await showFontAwesomePicker()) ?? false).toString(), | 1503 | callback: async () => ((await showFontAwesomePicker()) ?? false).toString(), |
| 1501 | returns: 'The chosen icon name or false if cancelled.', | 1504 | returns: 'The chosen icon name or false if cancelled.', |
| 1502 | helpString: ` | 1505 | helpString: ` |
| @@ -1511,6 +1514,72 @@ export function initDefaultSlashCommands() { | |||
| 1511 | </div> | 1514 | </div> |
| 1512 | `, | 1515 | `, |
| 1513 | })); | 1516 | })); |
| 1517 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1518 | name: 'api-url', | ||
| 1519 | callback: setApiUrlCallback, | ||
| 1520 | returns: 'the current API url', | ||
| 1521 | aliases: ['server'], | ||
| 1522 | namedArgumentList: [ | ||
| 1523 | SlashCommandNamedArgument.fromProps({ | ||
| 1524 | name: 'api', | ||
| 1525 | description: 'API to set/get the URL for - if not provided, current API is used', | ||
| 1526 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1527 | enumList: [ | ||
| 1528 | new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'), | ||
| 1529 | new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'), | ||
| 1530 | ...Object.values(textgen_types).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')), | ||
| 1531 | ], | ||
| 1532 | }), | ||
| 1533 | SlashCommandNamedArgument.fromProps({ | ||
| 1534 | name: 'connect', | ||
| 1535 | description: 'Whether to auto-connect to the API after setting the URL', | ||
| 1536 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 1537 | defaultValue: 'true', | ||
| 1538 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 1539 | }), | ||
| 1540 | ], | ||
| 1541 | unnamedArgumentList: [ | ||
| 1542 | SlashCommandArgument.fromProps({ | ||
| 1543 | description: 'API url to connect to', | ||
| 1544 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1545 | }), | ||
| 1546 | ], | ||
| 1547 | helpString: ` | ||
| 1548 | <div> | ||
| 1549 | Set the API url / server url for the currently selected API, including the port. If no argument is provided, it will return the current API url. | ||
| 1550 | </div> | ||
| 1551 | <div> | ||
| 1552 | If a manual API is provided to <b>set</b> the URL, make sure to set <code>connect=false</code>, as auto-connect only works for the currently selected API, | ||
| 1553 | or consider switching to it with <code>/api</code> first. | ||
| 1554 | </div> | ||
| 1555 | <div> | ||
| 1556 | This slash command works for most of the Text Completion sources, KoboldAI Classic, and also Custom OpenAI compatible for the Chat Completion sources. If unsure which APIs are supported, | ||
| 1557 | check the auto-completion of the optional <code>api</code> argument of this command. | ||
| 1558 | </div> | ||
| 1559 | `, | ||
| 1560 | })); | ||
| 1561 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1562 | name: 'tokenizer', | ||
| 1563 | callback: selectTokenizerCallback, | ||
| 1564 | returns: 'current tokenizer', | ||
| 1565 | unnamedArgumentList: [ | ||
| 1566 | SlashCommandArgument.fromProps({ | ||
| 1567 | description: 'tokenizer name', | ||
| 1568 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1569 | enumList: getAvailableTokenizers().map(tokenizer => | ||
| 1570 | new SlashCommandEnumValue(tokenizer.tokenizerKey, tokenizer.tokenizerName, enumTypes.enum, enumIcons.default)), | ||
| 1571 | }), | ||
| 1572 | ], | ||
| 1573 | helpString: ` | ||
| 1574 | <div> | ||
| 1575 | Selects tokenizer by name. Gets the current tokenizer if no name is provided. | ||
| 1576 | </div> | ||
| 1577 | <div> | ||
| 1578 | <strong>Available tokenizers:</strong> | ||
| 1579 | <pre><code>${getAvailableTokenizers().map(t => t.tokenizerKey).join(', ')}</code></pre> | ||
| 1580 | </div> | ||
| 1581 | `, | ||
| 1582 | })); | ||
| 1514 | 1583 | ||
| 1515 | registerVariableCommands(); | 1584 | registerVariableCommands(); |
| 1516 | } | 1585 | } |
| @@ -1788,7 +1857,7 @@ async function popupCallback(args, value) { | |||
| 1788 | return String(value); | 1857 | return String(value); |
| 1789 | } | 1858 | } |
| 1790 | 1859 | ||
| 1791 | function getMessagesCallback(args, value) { | 1860 | async function getMessagesCallback(args, value) { |
| 1792 | const includeNames = !isFalseBoolean(args?.names); | 1861 | const includeNames = !isFalseBoolean(args?.names); |
| 1793 | const includeHidden = isTrueBoolean(args?.hidden); | 1862 | const includeHidden = isTrueBoolean(args?.hidden); |
| 1794 | const role = args?.role; | 1863 | const role = args?.role; |
| @@ -1821,33 +1890,34 @@ function getMessagesCallback(args, value) { | |||
| 1821 | throw new Error(`Invalid role provided. Expected one of: system, assistant, user. Got: ${role}`); | 1890 | throw new Error(`Invalid role provided. Expected one of: system, assistant, user. Got: ${role}`); |
| 1822 | }; | 1891 | }; |
| 1823 | 1892 | ||
| 1824 | const messages = []; | 1893 | const processMessage = async (mesId) => { |
| 1825 | 1894 | const msg = chat[mesId]; | |
| 1826 | for (let messageId = range.start; messageId <= range.end; messageId++) { | 1895 | if (!msg) { |
| 1827 | const message = chat[messageId]; | 1896 | console.warn(`WARN: No message found with ID ${mesId}`); |
| 1828 | if (!message) { | 1897 | return null; |
| 1829 | console.warn(`WARN: No message found with ID ${messageId}`); | ||
| 1830 | continue; | ||
| 1831 | } | 1898 | } |
| 1832 | 1899 | ||
| 1833 | if (role && !filterByRole(message)) { | 1900 | if (role && !filterByRole(msg)) { |
| 1834 | console.debug(`/messages: Skipping message with ID ${messageId} due to role filter`); | 1901 | console.debug(`/messages: Skipping message with ID ${mesId} due to role filter`); |
| 1835 | continue; | 1902 | return null; |
| 1836 | } | 1903 | } |
| 1837 | 1904 | ||
| 1838 | if (!includeHidden && message.is_system) { | 1905 | if (!includeHidden && msg.is_system) { |
| 1839 | console.debug(`/messages: Skipping hidden message with ID ${messageId}`); | 1906 | console.debug(`/messages: Skipping hidden message with ID ${mesId}`); |
| 1840 | continue; | 1907 | return null; |
| 1841 | } | 1908 | } |
| 1842 | 1909 | ||
| 1843 | if (includeNames) { | 1910 | return includeNames ? `${msg.name}: ${msg.mes}` : msg.mes; |
| 1844 | messages.push(`${message.name}: ${message.mes}`); | 1911 | }; |
| 1845 | } else { | 1912 | |
| 1846 | messages.push(message.mes); | 1913 | const messagePromises = []; |
| 1847 | } | ||
| 1848 | } | ||
| 1849 | 1914 | ||
| 1850 | return messages.join('\n\n'); | 1915 | for (let rInd = range.start; rInd <= range.end; ++rInd) |
| 1916 | messagePromises.push(processMessage(rInd)); | ||
| 1917 | |||
| 1918 | const messages = await Promise.all(messagePromises); | ||
| 1919 | |||
| 1920 | return messages.filter(m => m !== null).join('\n\n'); | ||
| 1851 | } | 1921 | } |
| 1852 | 1922 | ||
| 1853 | async function runCallback(args, name) { | 1923 | async function runCallback(args, name) { |
| @@ -3418,6 +3488,123 @@ function setPromptEntryCallback(args, targetState) { | |||
| 3418 | return ''; | 3488 | return ''; |
| 3419 | } | 3489 | } |
| 3420 | 3490 | ||
| 3491 | /** | ||
| 3492 | * Sets the API URL and triggers the text generation web UI button click. | ||
| 3493 | * | ||
| 3494 | * @param {object} args - named args | ||
| 3495 | * @param {string?} [args.api=null] - the API name to set/get the URL for | ||
| 3496 | * @param {string?} [args.connect=true] - whether to connect to the API after setting | ||
| 3497 | * @param {string} url - the API URL to set | ||
| 3498 | * @returns {Promise<string>} | ||
| 3499 | */ | ||
| 3500 | async function setApiUrlCallback({ api = null, connect = 'true' }, url) { | ||
| 3501 | const autoConnect = isTrueBoolean(connect); | ||
| 3502 | |||
| 3503 | // Special handling for Chat Completion Custom OpenAI compatible, that one can also support API url handling | ||
| 3504 | const isCurrentlyCustomOpenai = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.CUSTOM; | ||
| 3505 | if (api === chat_completion_sources.CUSTOM || (!api && isCurrentlyCustomOpenai)) { | ||
| 3506 | if (!url) { | ||
| 3507 | return oai_settings.custom_url ?? ''; | ||
| 3508 | } | ||
| 3509 | |||
| 3510 | if (!isCurrentlyCustomOpenai && autoConnect) { | ||
| 3511 | toastr.warning('Custom OpenAI API is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.'); | ||
| 3512 | return ''; | ||
| 3513 | } | ||
| 3514 | |||
| 3515 | $('#custom_api_url_text').val(url).trigger('input'); | ||
| 3516 | |||
| 3517 | if (autoConnect) { | ||
| 3518 | $('#api_button_openai').trigger('click'); | ||
| 3519 | } | ||
| 3520 | |||
| 3521 | return url; | ||
| 3522 | } | ||
| 3523 | |||
| 3524 | // Special handling for Kobold Classic API | ||
| 3525 | const isCurrentlyKoboldClassic = main_api === 'kobold'; | ||
| 3526 | if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) { | ||
| 3527 | if (!url) { | ||
| 3528 | return api_server ?? ''; | ||
| 3529 | } | ||
| 3530 | |||
| 3531 | if (!isCurrentlyKoboldClassic && autoConnect) { | ||
| 3532 | toastr.warning('Kobold Classic API is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.'); | ||
| 3533 | return ''; | ||
| 3534 | } | ||
| 3535 | |||
| 3536 | $('#api_url_text').val(url).trigger('input'); | ||
| 3537 | // trigger blur debounced, so we hide the autocomplete menu | ||
| 3538 | setTimeout(() => $('#api_url_text').trigger('blur'), 1); | ||
| 3539 | |||
| 3540 | if (autoConnect) { | ||
| 3541 | $('#api_button').trigger('click'); | ||
| 3542 | } | ||
| 3543 | |||
| 3544 | return api_server ?? ''; | ||
| 3545 | } | ||
| 3546 | |||
| 3547 | // Do some checks and get the api type we are targeting with this command | ||
| 3548 | if (api && !Object.values(textgen_types).includes(api)) { | ||
| 3549 | toastr.warning(`API '${api}' is not a valid text_gen API.`); | ||
| 3550 | return ''; | ||
| 3551 | } | ||
| 3552 | if (!api && !Object.values(textgen_types).includes(textgenerationwebui_settings.type)) { | ||
| 3553 | toastr.warning(`API '${textgenerationwebui_settings.type}' is not a valid text_gen API.`); | ||
| 3554 | return ''; | ||
| 3555 | } | ||
| 3556 | if (api && url && autoConnect && api !== textgenerationwebui_settings.type) { | ||
| 3557 | toastr.warning(`API '${api}' is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.`); | ||
| 3558 | return ''; | ||
| 3559 | } | ||
| 3560 | const type = api || textgenerationwebui_settings.type; | ||
| 3561 | |||
| 3562 | const inputSelector = SERVER_INPUTS[type]; | ||
| 3563 | if (!inputSelector) { | ||
| 3564 | toastr.warning(`API '${type}' does not have a server url input.`); | ||
| 3565 | return ''; | ||
| 3566 | } | ||
| 3567 | |||
| 3568 | // If no url was provided, return the current one | ||
| 3569 | if (!url) { | ||
| 3570 | return textgenerationwebui_settings.server_urls[type] ?? ''; | ||
| 3571 | } | ||
| 3572 | |||
| 3573 | // else, we want to actually set the url | ||
| 3574 | $(inputSelector).val(url).trigger('input'); | ||
| 3575 | // trigger blur debounced, so we hide the autocomplete menu | ||
| 3576 | setTimeout(() => $(inputSelector).trigger('blur'), 1); | ||
| 3577 | |||
| 3578 | // Trigger the auto connect via connect button, if requested | ||
| 3579 | if (autoConnect) { | ||
| 3580 | $('#api_button_textgenerationwebui').trigger('click'); | ||
| 3581 | } | ||
| 3582 | |||
| 3583 | // We still re-acquire the value, as it might have been modified by the validation on connect | ||
| 3584 | return textgenerationwebui_settings.server_urls[type] ?? ''; | ||
| 3585 | } | ||
| 3586 | |||
| 3587 | async function selectTokenizerCallback(_, name) { | ||
| 3588 | if (!name) { | ||
| 3589 | return getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === power_user.tokenizer)?.tokenizerKey ?? ''; | ||
| 3590 | } | ||
| 3591 | |||
| 3592 | const tokenizers = getAvailableTokenizers(); | ||
| 3593 | const fuse = new Fuse(tokenizers, { keys: ['tokenizerKey', 'tokenizerName'] }); | ||
| 3594 | const result = fuse.search(name); | ||
| 3595 | |||
| 3596 | if (result.length === 0) { | ||
| 3597 | toastr.warning(`Tokenizer "${name}" not found`); | ||
| 3598 | return ''; | ||
| 3599 | } | ||
| 3600 | |||
| 3601 | /** @type {import('./tokenizers.js').Tokenizer} */ | ||
| 3602 | const foundTokenizer = result[0].item; | ||
| 3603 | selectTokenizer(foundTokenizer.tokenizerId); | ||
| 3604 | |||
| 3605 | return foundTokenizer.tokenizerKey; | ||
| 3606 | } | ||
| 3607 | |||
| 3421 | export let isExecutingCommandsFromChatInput = false; | 3608 | export let isExecutingCommandsFromChatInput = false; |
| 3422 | export let commandsFromChatInputAbortController; | 3609 | export let commandsFromChatInputAbortController; |
| 3423 | 3610 | ||
| @@ -708,12 +708,12 @@ const ANTI_TROLL_MAX_TAGS = 15; | |||
| 708 | * | 708 | * |
| 709 | * @param {Character} character - The character | 709 | * @param {Character} character - The character |
| 710 | * @param {object} [options] - Options | 710 | * @param {object} [options] - Options |
| 711 | * @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog | 711 | * @param {tag_import_setting} [options.importSetting=null] - Force a tag import setting |
| 712 | * @returns {Promise<boolean>} Boolean indicating whether any tag was imported | 712 | * @returns {Promise<boolean>} Boolean indicating whether any tag was imported |
| 713 | */ | 713 | */ |
| 714 | async function importTags(character, { forceShow = false } = {}) { | 714 | async function importTags(character, { importSetting = null } = {}) { |
| 715 | // Gather the tags to import based on the selected setting | 715 | // Gather the tags to import based on the selected setting |
| 716 | const tagNamesToImport = await handleTagImport(character, { forceShow }); | 716 | const tagNamesToImport = await handleTagImport(character, { importSetting }); |
| 717 | if (!tagNamesToImport?.length) { | 717 | if (!tagNamesToImport?.length) { |
| 718 | console.debug('No tags to import'); | 718 | console.debug('No tags to import'); |
| 719 | return; | 719 | return; |
| @@ -732,10 +732,10 @@ async function importTags(character, { forceShow = false } = {}) { | |||
| 732 | * | 732 | * |
| 733 | * @param {Character} character - The character | 733 | * @param {Character} character - The character |
| 734 | * @param {object} [options] - Options | 734 | * @param {object} [options] - Options |
| 735 | * @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog | 735 | * @param {tag_import_setting} [options.importSetting=null] - Force a tag import setting |
| 736 | * @returns {Promise<string[]>} Array of strings representing the tags to import | 736 | * @returns {Promise<string[]>} Array of strings representing the tags to import |
| 737 | */ | 737 | */ |
| 738 | async function handleTagImport(character, { forceShow = false } = {}) { | 738 | async function handleTagImport(character, { importSetting = null } = {}) { |
| 739 | /** @type {string[]} */ | 739 | /** @type {string[]} */ |
| 740 | const importTags = character.tags.map(t => t.trim()).filter(t => t) | 740 | const importTags = character.tags.map(t => t.trim()).filter(t => t) |
| 741 | .filter(t => !IMPORT_EXLCUDED_TAGS.includes(t)) | 741 | .filter(t => !IMPORT_EXLCUDED_TAGS.includes(t)) |
| @@ -745,9 +745,9 @@ async function handleTagImport(character, { forceShow = false } = {}) { | |||
| 745 | .map(newTag); | 745 | .map(newTag); |
| 746 | const folderTags = getOpenBogusFolders(); | 746 | const folderTags = getOpenBogusFolders(); |
| 747 | 747 | ||
| 748 | // Choose the setting for this dialog. If from settings, verify the setting really exists, otherwise take "ASK". | 748 | // Choose the setting for this dialog. First check override, then saved setting or finally use "ASK". |
| 749 | const setting = forceShow ? tag_import_setting.ASK | 749 | const setting = importSetting ? importSetting : |
| 750 | : Object.values(tag_import_setting).find(setting => setting === power_user.tag_import_setting) ?? tag_import_setting.ASK; | 750 | Object.values(tag_import_setting).find(setting => setting === power_user.tag_import_setting) ?? tag_import_setting.ASK; |
| 751 | 751 | ||
| 752 | switch (setting) { | 752 | switch (setting) { |
| 753 | case tag_import_setting.ALL: | 753 | case tag_import_setting.ALL: |
| @@ -95,7 +95,7 @@ let DREAMGEN_SERVER = 'https://dreamgen.com'; | |||
| 95 | let OPENROUTER_SERVER = 'https://openrouter.ai/api'; | 95 | let OPENROUTER_SERVER = 'https://openrouter.ai/api'; |
| 96 | let FEATHERLESS_SERVER = 'https://api.featherless.ai/v1'; | 96 | let FEATHERLESS_SERVER = 'https://api.featherless.ai/v1'; |
| 97 | 97 | ||
| 98 | const SERVER_INPUTS = { | 98 | export const SERVER_INPUTS = { |
| 99 | [textgen_types.OOBA]: '#textgenerationwebui_api_url_text', | 99 | [textgen_types.OOBA]: '#textgenerationwebui_api_url_text', |
| 100 | [textgen_types.VLLM]: '#vllm_api_url_text', | 100 | [textgen_types.VLLM]: '#vllm_api_url_text', |
| 101 | [textgen_types.APHRODITE]: '#aphrodite_api_url_text', | 101 | [textgen_types.APHRODITE]: '#aphrodite_api_url_text', |
| @@ -1071,6 +1071,34 @@ function getLogprobsNumber() { | |||
| 1071 | return 10; | 1071 | return 10; |
| 1072 | } | 1072 | } |
| 1073 | 1073 | ||
| 1074 | /** | ||
| 1075 | * Replaces {{macro}} in a comma-separated or serialized JSON array string. | ||
| 1076 | * @param {string} str Input string | ||
| 1077 | * @returns {string} Output string | ||
| 1078 | */ | ||
| 1079 | function replaceMacrosInList(str) { | ||
| 1080 | if (!str || typeof str !== 'string') { | ||
| 1081 | return str; | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | try { | ||
| 1085 | const array = JSON.parse(str); | ||
| 1086 | if (!Array.isArray(array)) { | ||
| 1087 | throw new Error('Not an array'); | ||
| 1088 | } | ||
| 1089 | for (let i = 0; i < array.length; i++) { | ||
| 1090 | array[i] = substituteParams(array[i]); | ||
| 1091 | } | ||
| 1092 | return JSON.stringify(array); | ||
| 1093 | } catch { | ||
| 1094 | const array = str.split(','); | ||
| 1095 | for (let i = 0; i < array.length; i++) { | ||
| 1096 | array[i] = substituteParams(array[i]); | ||
| 1097 | } | ||
| 1098 | return array.join(','); | ||
| 1099 | } | ||
| 1100 | } | ||
| 1101 | |||
| 1074 | export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) { | 1102 | export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) { |
| 1075 | const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet'; | 1103 | const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet'; |
| 1076 | const dynatemp = isDynamicTemperatureSupported(); | 1104 | const dynatemp = isDynamicTemperatureSupported(); |
| @@ -1110,7 +1138,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, | |||
| 1110 | 'dry_allowed_length': settings.dry_allowed_length, | 1138 | 'dry_allowed_length': settings.dry_allowed_length, |
| 1111 | 'dry_multiplier': settings.dry_multiplier, | 1139 | 'dry_multiplier': settings.dry_multiplier, |
| 1112 | 'dry_base': settings.dry_base, | 1140 | 'dry_base': settings.dry_base, |
| 1113 | 'dry_sequence_breakers': settings.dry_sequence_breakers, | 1141 | 'dry_sequence_breakers': replaceMacrosInList(settings.dry_sequence_breakers), |
| 1114 | 'dry_penalty_last_n': settings.dry_penalty_last_n, | 1142 | 'dry_penalty_last_n': settings.dry_penalty_last_n, |
| 1115 | 'max_tokens_second': settings.max_tokens_second, | 1143 | 'max_tokens_second': settings.max_tokens_second, |
| 1116 | 'sampler_priority': settings.type === OOBA ? settings.sampler_priority : undefined, | 1144 | 'sampler_priority': settings.type === OOBA ? settings.sampler_priority : undefined, |
| @@ -148,9 +148,45 @@ async function resetTokenCache() { | |||
| 148 | } | 148 | } |
| 149 | 149 | ||
| 150 | /** | 150 | /** |
| 151 | * @typedef {object} Tokenizer | ||
| 152 | * @property {number} tokenizerId - The id of the tokenizer option | ||
| 153 | * @property {string} tokenizerKey - Internal name/key of the tokenizer | ||
| 154 | * @property {string} tokenizerName - Human-readable detailed name of the tokenizer (as displayed in the UI) | ||
| 155 | */ | ||
| 156 | |||
| 157 | /** | ||
| 158 | * Gets all tokenizers available to the user. | ||
| 159 | * @returns {Tokenizer[]} Tokenizer info. | ||
| 160 | */ | ||
| 161 | export function getAvailableTokenizers() { | ||
| 162 | const tokenizerOptions = $('#tokenizer').find('option').toArray(); | ||
| 163 | return tokenizerOptions.map(tokenizerOption => ({ | ||
| 164 | tokenizerId: Number(tokenizerOption.value), | ||
| 165 | tokenizerKey: Object.entries(tokenizers).find(([_, value]) => value === Number(tokenizerOption.value))[0].toLocaleLowerCase(), | ||
| 166 | tokenizerName: tokenizerOption.text, | ||
| 167 | })) | ||
| 168 | } | ||
| 169 | |||
| 170 | /** | ||
| 171 | * Selects tokenizer if not already selected. | ||
| 172 | * @param {number} tokenizerId Tokenizer ID. | ||
| 173 | */ | ||
| 174 | export function selectTokenizer(tokenizerId) { | ||
| 175 | if (tokenizerId !== power_user.tokenizer) { | ||
| 176 | const tokenizer = getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === tokenizerId); | ||
| 177 | if (!tokenizer) { | ||
| 178 | console.warn('Failed to find tokenizer with id', tokenizerId); | ||
| 179 | return; | ||
| 180 | } | ||
| 181 | $('#tokenizer').val(tokenizer.tokenizerId).trigger('change'); | ||
| 182 | toastr.info(`Tokenizer: "${tokenizer.tokenizerName}" selected`); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | /** | ||
| 151 | * Gets the friendly name of the current tokenizer. | 187 | * Gets the friendly name of the current tokenizer. |
| 152 | * @param {string} forApi API to get the tokenizer for. Defaults to the main API. | 188 | * @param {string} forApi API to get the tokenizer for. Defaults to the main API. |
| 153 | * @returns { { tokenizerName: string, tokenizerId: number } } Tokenizer info | 189 | * @returns {Tokenizer} Tokenizer info |
| 154 | */ | 190 | */ |
| 155 | export function getFriendlyTokenizerName(forApi) { | 191 | export function getFriendlyTokenizerName(forApi) { |
| 156 | if (!forApi) { | 192 | if (!forApi) { |
| @@ -185,7 +221,9 @@ export function getFriendlyTokenizerName(forApi) { | |||
| 185 | ? tokenizers.OPENAI | 221 | ? tokenizers.OPENAI |
| 186 | : tokenizerId; | 222 | : tokenizerId; |
| 187 | 223 | ||
| 188 | return { tokenizerName, tokenizerId }; | 224 | const tokenizerKey = Object.entries(tokenizers).find(([_, value]) => value === tokenizerId)[0].toLocaleLowerCase(); |
| 225 | |||
| 226 | return { tokenizerName, tokenizerKey, tokenizerId }; | ||
| 189 | } | 227 | } |
| 190 | 228 | ||
| 191 | /** | 229 | /** |
| @@ -787,17 +787,17 @@ function logSecurityAlert(message) { | |||
| 787 | */ | 787 | */ |
| 788 | function handleServerListenFail(v6Failed, v4Failed) { | 788 | function handleServerListenFail(v6Failed, v4Failed) { |
| 789 | if (v6Failed && !enableIPv4) { | 789 | if (v6Failed && !enableIPv4) { |
| 790 | console.error('fatal error: Failed to start server on IPv6 and IPv4 disabled'); | 790 | console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled')); |
| 791 | process.exit(1); | 791 | process.exit(1); |
| 792 | } | 792 | } |
| 793 | 793 | ||
| 794 | if (v4Failed && !enableIPv6) { | 794 | if (v4Failed && !enableIPv6) { |
| 795 | console.error('fatal error: Failed to start server on IPv4 and IPv6 disabled'); | 795 | console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled')); |
| 796 | process.exit(1); | 796 | process.exit(1); |
| 797 | } | 797 | } |
| 798 | 798 | ||
| 799 | if (v6Failed && v4Failed) { | 799 | if (v6Failed && v4Failed) { |
| 800 | console.error('fatal error: Failed to start server on both IPv6 and IPv4'); | 800 | console.error(color.red('fatal error: Failed to start server on both IPv6 and IPv4')); |
| 801 | process.exit(1); | 801 | process.exit(1); |
| 802 | } | 802 | } |
| 803 | } | 803 | } |
| @@ -846,9 +846,8 @@ async function startHTTPorHTTPS() { | |||
| 846 | try { | 846 | try { |
| 847 | await createFunc(tavernUrlV6); | 847 | await createFunc(tavernUrlV6); |
| 848 | } catch (error) { | 848 | } catch (error) { |
| 849 | if (enableIPv4) { | 849 | console.error('non-fatal error: failed to start server on IPv6'); |
| 850 | console.error('non-fatal error: failed to start server on IPv6', error); | 850 | console.error(error); |
| 851 | } | ||
| 852 | 851 | ||
| 853 | v6Failed = true; | 852 | v6Failed = true; |
| 854 | } | 853 | } |
| @@ -858,9 +857,8 @@ async function startHTTPorHTTPS() { | |||
| 858 | try { | 857 | try { |
| 859 | await createFunc(tavernUrl); | 858 | await createFunc(tavernUrl); |
| 860 | } catch (error) { | 859 | } catch (error) { |
| 861 | if (enableIPv6) { | 860 | console.error('non-fatal error: failed to start server on IPv4'); |
| 862 | console.error('non-fatal error: failed to start server on IPv4', error); | 861 | console.error(error); |
| 863 | } | ||
| 864 | 862 | ||
| 865 | v4Failed = true; | 863 | v4Failed = true; |
| 866 | } | 864 | } |
| @@ -1,6 +1,7 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | const fetch = require('node-fetch').default; |
| 2 | const https = require('https'); | 2 | const https = require('https'); |
| 3 | const express = require('express'); | 3 | const express = require('express'); |
| 4 | const iconv = require('iconv-lite'); | ||
| 4 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 5 | const { readSecret, SECRET_KEYS } = require('./secrets'); |
| 5 | const { getConfigValue, uuidv4 } = require('../util'); | 6 | const { getConfigValue, uuidv4 } = require('../util'); |
| 6 | const { jsonParser } = require('../express-common'); | 7 | const { jsonParser } = require('../express-common'); |
| @@ -80,16 +81,18 @@ router.post('/google', jsonParser, async (request, response) => { | |||
| 80 | const url = generateRequestUrl(text, { to: lang }); | 81 | const url = generateRequestUrl(text, { to: lang }); |
| 81 | 82 | ||
| 82 | https.get(url, (resp) => { | 83 | https.get(url, (resp) => { |
| 83 | let data = ''; | 84 | const data = []; |
| 84 | 85 | ||
| 85 | resp.on('data', (chunk) => { | 86 | resp.on('data', (chunk) => { |
| 86 | data += chunk; | 87 | data.push(chunk); |
| 87 | }); | 88 | }); |
| 88 | 89 | ||
| 89 | resp.on('end', () => { | 90 | resp.on('end', () => { |
| 90 | try { | 91 | try { |
| 91 | const result = normaliseResponse(JSON.parse(data)); | 92 | const decodedData = iconv.decode(Buffer.concat(data), 'utf-8'); |
| 93 | const result = normaliseResponse(JSON.parse(decodedData)); | ||
| 92 | console.log('Translated text: ' + result.text); | 94 | console.log('Translated text: ' + result.text); |
| 95 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); | ||
| 93 | return response.send(result.text); | 96 | return response.send(result.text); |
| 94 | } catch (error) { | 97 | } catch (error) { |
| 95 | console.log('Translation error', error); | 98 | console.log('Translation error', error); |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { pipeline, env, RawImage, Pipeline } from 'sillytavern-transformers'; | 1 | import { pipeline, env, RawImage, Pipeline } from 'sillytavern-transformers'; |
| 2 | import { getConfigValue } from './util.js'; | 2 | import { getConfigValue } from './util.js'; |
| 3 | import path from 'path'; | 3 | import path from 'path'; |
| 4 | import fs from 'fs'; | ||
| 4 | 5 | ||
| 5 | configureTransformers(); | 6 | configureTransformers(); |
| 6 | 7 | ||
| @@ -48,7 +49,7 @@ const tasks = { | |||
| 48 | configField: 'extras.textToSpeechModel', | 49 | configField: 'extras.textToSpeechModel', |
| 49 | quantized: false, | 50 | quantized: false, |
| 50 | }, | 51 | }, |
| 51 | } | 52 | }; |
| 52 | 53 | ||
| 53 | /** | 54 | /** |
| 54 | * Gets a RawImage object from a base64-encoded image. | 55 | * Gets a RawImage object from a base64-encoded image. |
| @@ -85,6 +86,36 @@ function getModelForTask(task) { | |||
| 85 | } | 86 | } |
| 86 | } | 87 | } |
| 87 | 88 | ||
| 89 | async function migrateCacheToDataDir() { | ||
| 90 | const oldCacheDir = path.join(process.cwd(), 'cache'); | ||
| 91 | const newCacheDir = path.join(global.DATA_ROOT, '_cache'); | ||
| 92 | |||
| 93 | if (!fs.existsSync(newCacheDir)) { | ||
| 94 | fs.mkdirSync(newCacheDir, { recursive: true }); | ||
| 95 | } | ||
| 96 | |||
| 97 | if (fs.existsSync(oldCacheDir) && fs.statSync(oldCacheDir).isDirectory()) { | ||
| 98 | const files = fs.readdirSync(oldCacheDir); | ||
| 99 | |||
| 100 | if (files.length === 0) { | ||
| 101 | return; | ||
| 102 | } | ||
| 103 | |||
| 104 | console.log('Migrating model cache files to data directory. Please wait...'); | ||
| 105 | |||
| 106 | for (const file of files) { | ||
| 107 | try { | ||
| 108 | const oldPath = path.join(oldCacheDir, file); | ||
| 109 | const newPath = path.join(newCacheDir, file); | ||
| 110 | fs.cpSync(oldPath, newPath, { recursive: true, force: true }); | ||
| 111 | fs.rmSync(oldPath, { recursive: true, force: true }); | ||
| 112 | } catch (error) { | ||
| 113 | console.warn('Failed to migrate cache file. The model will be re-downloaded.', error); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 88 | /** | 119 | /** |
| 89 | * Gets the transformers.js pipeline for a given task. | 120 | * Gets the transformers.js pipeline for a given task. |
| 90 | * @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for | 121 | * @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for |
| @@ -92,6 +123,8 @@ function getModelForTask(task) { | |||
| 92 | * @returns {Promise<Pipeline>} Pipeline for the task | 123 | * @returns {Promise<Pipeline>} Pipeline for the task |
| 93 | */ | 124 | */ |
| 94 | async function getPipeline(task, forceModel = '') { | 125 | async function getPipeline(task, forceModel = '') { |
| 126 | await migrateCacheToDataDir(); | ||
| 127 | |||
| 95 | if (tasks[task].pipeline) { | 128 | if (tasks[task].pipeline) { |
| 96 | if (forceModel === '' || tasks[task].currentModel === forceModel) { | 129 | if (forceModel === '' || tasks[task].currentModel === forceModel) { |
| 97 | return tasks[task].pipeline; | 130 | return tasks[task].pipeline; |
| @@ -100,11 +133,11 @@ async function getPipeline(task, forceModel = '') { | |||
| 100 | await tasks[task].pipeline.dispose(); | 133 | await tasks[task].pipeline.dispose(); |
| 101 | } | 134 | } |
| 102 | 135 | ||
| 103 | const cache_dir = path.join(process.cwd(), 'cache'); | 136 | const cacheDir = path.join(global.DATA_ROOT, '_cache'); |
| 104 | const model = forceModel || getModelForTask(task); | 137 | const model = forceModel || getModelForTask(task); |
| 105 | const localOnly = getConfigValue('extras.disableAutoDownload', false); | 138 | const localOnly = getConfigValue('extras.disableAutoDownload', false); |
| 106 | console.log('Initializing transformers.js pipeline for task', task, 'with model', model); | 139 | console.log('Initializing transformers.js pipeline for task', task, 'with model', model); |
| 107 | const instance = await pipeline(task, model, { cache_dir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly }); | 140 | const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly }); |
| 108 | tasks[task].pipeline = instance; | 141 | tasks[task].pipeline = instance; |
| 109 | tasks[task].currentModel = model; | 142 | tasks[task].currentModel = model; |
| 110 | return instance; | 143 | return instance; |
| @@ -20,12 +20,6 @@ const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); | |||
| 20 | const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64'); | 20 | const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64'); |
| 21 | 21 | ||
| 22 | /** | 22 | /** |
| 23 | * The root directory for user data. | ||
| 24 | * @type {string} | ||
| 25 | */ | ||
| 26 | let DATA_ROOT = './data'; | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Cache for user directories. | 23 | * Cache for user directories. |
| 30 | * @type {Map<string, UserDirectoryList>} | 24 | * @type {Map<string, UserDirectoryList>} |
| 31 | */ | 25 | */ |
| @@ -138,7 +132,7 @@ async function migrateUserData() { | |||
| 138 | 132 | ||
| 139 | console.log(); | 133 | console.log(); |
| 140 | console.log(color.magenta('Preparing to migrate user data...')); | 134 | console.log(color.magenta('Preparing to migrate user data...')); |
| 141 | console.log(`All public data will be moved to the ${DATA_ROOT} directory.`); | 135 | console.log(`All public data will be moved to the ${global.DATA_ROOT} directory.`); |
| 142 | console.log('This process may take a while depending on the amount of data to move.'); | 136 | console.log('This process may take a while depending on the amount of data to move.'); |
| 143 | console.log(`Backups will be placed in the ${PUBLIC_DIRECTORIES.backups} directory.`); | 137 | console.log(`Backups will be placed in the ${PUBLIC_DIRECTORIES.backups} directory.`); |
| 144 | console.log(`The process will start in ${TIMEOUT} seconds. Press Ctrl+C to cancel.`); | 138 | console.log(`The process will start in ${TIMEOUT} seconds. Press Ctrl+C to cancel.`); |
| @@ -352,11 +346,11 @@ function toAvatarKey(handle) { | |||
| 352 | * @returns {Promise<void>} | 346 | * @returns {Promise<void>} |
| 353 | */ | 347 | */ |
| 354 | async function initUserStorage(dataRoot) { | 348 | async function initUserStorage(dataRoot) { |
| 355 | DATA_ROOT = dataRoot; | 349 | global.DATA_ROOT = dataRoot; |
| 356 | console.log('Using data root:', color.green(DATA_ROOT)); | 350 | console.log('Using data root:', color.green(global.DATA_ROOT)); |
| 357 | console.log(); | 351 | console.log(); |
| 358 | await storage.init({ | 352 | await storage.init({ |
| 359 | dir: path.join(DATA_ROOT, '_storage'), | 353 | dir: path.join(global.DATA_ROOT, '_storage'), |
| 360 | ttl: false, // Never expire | 354 | ttl: false, // Never expire |
| 361 | }); | 355 | }); |
| 362 | 356 | ||
| @@ -457,7 +451,7 @@ function getUserDirectories(handle) { | |||
| 457 | 451 | ||
| 458 | const directories = structuredClone(USER_DIRECTORY_TEMPLATE); | 452 | const directories = structuredClone(USER_DIRECTORY_TEMPLATE); |
| 459 | for (const key in directories) { | 453 | for (const key in directories) { |
| 460 | directories[key] = path.join(DATA_ROOT, handle, USER_DIRECTORY_TEMPLATE[key]); | 454 | directories[key] = path.join(global.DATA_ROOT, handle, USER_DIRECTORY_TEMPLATE[key]); |
| 461 | } | 455 | } |
| 462 | DIRECTORIES_CACHE.set(handle, directories); | 456 | DIRECTORIES_CACHE.set(handle, directories); |
| 463 | return directories; | 457 | return directories; |