mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into feat/xtc
This commit is contained in:
5
index.d.ts
vendored
5
index.d.ts
vendored
@ -9,6 +9,11 @@ declare global {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root directory for user data.
|
||||||
|
*/
|
||||||
|
var DATA_ROOT: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'express-session' {
|
declare module 'express-session' {
|
||||||
|
35
package-lock.json
generated
35
package-lock.json
generated
@ -27,6 +27,7 @@
|
|||||||
"google-translate-api-browser": "^3.0.1",
|
"google-translate-api-browser": "^3.0.1",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"ip-matching": "^2.1.2",
|
"ip-matching": "^2.1.2",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"jimp": "^0.22.10",
|
"jimp": "^0.22.10",
|
||||||
@ -42,7 +43,7 @@
|
|||||||
"rate-limiter-flexible": "^5.0.0",
|
"rate-limiter-flexible": "^5.0.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
@ -1492,6 +1493,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/body-parser/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
@ -3282,12 +3295,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -4618,6 +4631,18 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"google-translate-api-browser": "^3.0.1",
|
"google-translate-api-browser": "^3.0.1",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
"ip-matching": "^2.1.2",
|
"ip-matching": "^2.1.2",
|
||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"jimp": "^0.22.10",
|
"jimp": "^0.22.10",
|
||||||
@ -32,7 +33,7 @@
|
|||||||
"rate-limiter-flexible": "^5.0.0",
|
"rate-limiter-flexible": "^5.0.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sillytavern-transformers": "^2.14.6",
|
"sillytavern-transformers": "2.14.6",
|
||||||
"simple-git": "^3.19.1",
|
"simple-git": "^3.19.1",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"vectra": "^0.2.2",
|
"vectra": "^0.2.2",
|
||||||
|
@ -99,6 +99,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button {
|
#bulk_tag_shadow_popup #bulk_tag_popup #dialogue_popup_controls .menu_button {
|
||||||
width: 100px;
|
width: unset;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
}
|
}
|
||||||
|
@ -4121,6 +4121,10 @@
|
|||||||
<input id="world_import_dialog" type="checkbox" />
|
<input id="world_import_dialog" type="checkbox" />
|
||||||
<small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small>
|
<small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small>
|
||||||
</label>
|
</label>
|
||||||
|
<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.">
|
||||||
|
<input id="enable_auto_select_input" type="checkbox" />
|
||||||
|
<small data-i18n="Auto-select Input Text">Auto-select Input Text</small>
|
||||||
|
</label>
|
||||||
<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">
|
<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">
|
||||||
<input id="restore_user_input" type="checkbox" />
|
<input id="restore_user_input" type="checkbox" />
|
||||||
<small data-i18n="Restore User Input">Restore User Input</small>
|
<small data-i18n="Restore User Input">Restore User Input</small>
|
||||||
@ -5008,7 +5012,7 @@
|
|||||||
<div class="popup-crop-wrap">
|
<div class="popup-crop-wrap">
|
||||||
<img class="popup-crop-image" src="">
|
<img class="popup-crop-image" src="">
|
||||||
</div>
|
</div>
|
||||||
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
|
<textarea class="popup-input text_pole result-control auto-select" rows="1" data-result="1" data-result-event="submit"></textarea>
|
||||||
<div class="popup-inputs"></div>
|
<div class="popup-inputs"></div>
|
||||||
<div class="popup-controls">
|
<div class="popup-controls">
|
||||||
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
|
<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
|
|||||||
getMessage = fixMarkdown(getMessage, false);
|
getMessage = fixMarkdown(getMessage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameToTrim2 = isImpersonate ? name1 : name2;
|
const nameToTrim2 = isImpersonate
|
||||||
|
? (!power_user.allow_name1_display ? name1 : '')
|
||||||
|
: (!power_user.allow_name2_display ? name2 : '');
|
||||||
|
|
||||||
if (getMessage.startsWith(nameToTrim2 + ':')) {
|
if (nameToTrim2 && getMessage.startsWith(nameToTrim2 + ':')) {
|
||||||
getMessage = getMessage.replace(nameToTrim2 + ':', '');
|
getMessage = getMessage.replace(nameToTrim2 + ':', '');
|
||||||
getMessage = getMessage.trimStart();
|
getMessage = getMessage.trimStart();
|
||||||
}
|
}
|
||||||
@ -8390,6 +8392,9 @@ const CONNECT_API_MAP = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Collect all unique API names in an array
|
||||||
|
export const UNIQUE_APIS = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))];
|
||||||
|
|
||||||
// Fill connections map from textgen_types and chat_completion_sources
|
// Fill connections map from textgen_types and chat_completion_sources
|
||||||
for (const textGenType of Object.values(textgen_types)) {
|
for (const textGenType of Object.values(textgen_types)) {
|
||||||
if (CONNECT_API_MAP[textGenType]) continue;
|
if (CONNECT_API_MAP[textGenType]) continue;
|
||||||
@ -8964,9 +8969,6 @@ jQuery(async function () {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all unique API names in an array
|
|
||||||
const uniqueAPIs = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))];
|
|
||||||
|
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'dupe',
|
name: 'dupe',
|
||||||
callback: duplicateCharacter,
|
callback: duplicateCharacter,
|
||||||
@ -8975,13 +8977,13 @@ jQuery(async function () {
|
|||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
name: 'api',
|
name: 'api',
|
||||||
callback: connectAPISlash,
|
callback: connectAPISlash,
|
||||||
|
returns: 'the current API',
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'API to connect to',
|
description: 'API to connect to',
|
||||||
typeList: [ARGUMENT_TYPE.STRING],
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
isRequired: false,
|
|
||||||
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
||||||
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)),
|
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)),
|
||||||
selected[0].toUpperCase() ?? enumIcons.default)),
|
selected[0].toUpperCase() ?? enumIcons.default)),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -10655,6 +10657,15 @@ jQuery(async function () {
|
|||||||
$(document).on('click', '.open_alternate_greetings', openAlternateGreetings);
|
$(document).on('click', '.open_alternate_greetings', openAlternateGreetings);
|
||||||
/* $('#set_character_world').on('click', openCharacterWorldPopup); */
|
/* $('#set_character_world').on('click', openCharacterWorldPopup); */
|
||||||
|
|
||||||
|
$(document).on('focus', 'input.auto-select, textarea.auto-select', function () {
|
||||||
|
if (!power_user.enable_auto_select_input) return;
|
||||||
|
const control = $(this)[0];
|
||||||
|
if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) {
|
||||||
|
control.select();
|
||||||
|
console.debug('Auto-selecting content of input control', control);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).keyup(function (e) {
|
$(document).keyup(function (e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const isEditVisible = $('#curEditTextarea').is(':visible');
|
const isEditVisible = $('#curEditTextarea').is(':visible');
|
||||||
@ -10738,7 +10749,7 @@ jQuery(async function () {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 'import_tags': {
|
case 'import_tags': {
|
||||||
await importTags(characters[this_chid], { forceShow: true });
|
await importTags(characters[this_chid], { importSetting: tag_import_setting.ASK });
|
||||||
} break;
|
} break;
|
||||||
/*case 'delete_button':
|
/*case 'delete_button':
|
||||||
popup_type = "del_ch";
|
popup_type = "del_ch";
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { favsToHotswap } from './RossAscends-mods.js';
|
import { favsToHotswap } from './RossAscends-mods.js';
|
||||||
import { hideLoader, showLoader } from './loader.js';
|
import { hideLoader, showLoader } from './loader.js';
|
||||||
import { convertCharacterToPersona } from './personas.js';
|
import { convertCharacterToPersona } from './personas.js';
|
||||||
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap } from './tags.js';
|
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap, importTags, tag_import_setting } from './tags.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static object representing the actions of the
|
* Static object representing the actions of the
|
||||||
@ -197,10 +197,10 @@ class BulkTagPopupHandler {
|
|||||||
#getHtml = () => {
|
#getHtml = () => {
|
||||||
const characterData = JSON.stringify({ characterIds: this.characterIds });
|
const characterData = JSON.stringify({ characterIds: this.characterIds });
|
||||||
return `<div id="bulk_tag_shadow_popup">
|
return `<div id="bulk_tag_shadow_popup">
|
||||||
<div id="bulk_tag_popup">
|
<div id="bulk_tag_popup" class="wider_dialogue_popup">
|
||||||
<div id="bulk_tag_popup_holder">
|
<div id="bulk_tag_popup_holder">
|
||||||
<h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3>
|
<h3 class="marginBot5">Modify tags of ${this.characterIds.length} characters</h3>
|
||||||
<small class="bulk_tags_desc m-b-1">Add or remove the mutual tags of all selected characters.</small>
|
<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>
|
||||||
<div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div>
|
<div id="bulk_tags_avatars_block" class="avatars_inline avatars_inline_small tags tags_inline"></div>
|
||||||
<br>
|
<br>
|
||||||
<div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'>
|
<div id="bulk_tags_div" class="marginBot5" data-characters='${characterData}'>
|
||||||
@ -219,6 +219,12 @@ class BulkTagPopupHandler {
|
|||||||
<i class="fa-solid fa-trash-can margin-right-10px"></i>
|
<i class="fa-solid fa-trash-can margin-right-10px"></i>
|
||||||
Mutual
|
Mutual
|
||||||
</div>
|
</div>
|
||||||
|
<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">
|
||||||
|
Import All
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
Import Existing
|
||||||
|
</div>
|
||||||
<div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div>
|
<div id="bulk_tag_popup_cancel" class="menu_button" data-i18n="Cancel">Close</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -254,6 +260,30 @@ class BulkTagPopupHandler {
|
|||||||
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this));
|
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this));
|
||||||
document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this));
|
document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this));
|
||||||
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
|
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
|
||||||
|
document.querySelector('#bulk_tag_popup_import_all_tags').addEventListener('click', this.importAllTags.bind(this));
|
||||||
|
document.querySelector('#bulk_tag_popup_import_existing_tags').addEventListener('click', this.importExistingTags.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import existing tags for all selected characters
|
||||||
|
*/
|
||||||
|
async importExistingTags() {
|
||||||
|
for (const characterId of this.characterIds) {
|
||||||
|
await importTags(characters[characterId], { importSetting: tag_import_setting.ONLY_EXISTING });
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#bulkTagList').empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import all tags for all selected characters
|
||||||
|
*/
|
||||||
|
async importAllTags() {
|
||||||
|
for (const characterId of this.characterIds) {
|
||||||
|
await importTags(characters[characterId], { importSetting: tag_import_setting.ALL });
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#bulkTagList').empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -570,7 +600,7 @@ class BulkEditOverlay {
|
|||||||
this.container.removeEventListener('mouseup', cancelHold);
|
this.container.removeEventListener('mouseup', cancelHold);
|
||||||
this.container.removeEventListener('touchend', cancelHold);
|
this.container.removeEventListener('touchend', cancelHold);
|
||||||
},
|
},
|
||||||
BulkEditOverlay.longPressDelay);
|
BulkEditOverlay.longPressDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleLongPressEnd = (event) => {
|
handleLongPressEnd = (event) => {
|
||||||
|
@ -954,6 +954,11 @@ export function initRossMods() {
|
|||||||
* @param {KeyboardEvent} event
|
* @param {KeyboardEvent} event
|
||||||
*/
|
*/
|
||||||
async function processHotkeys(event) {
|
async function processHotkeys(event) {
|
||||||
|
// Default hotkeys and shortcuts shouldn't work if any popup is currently open
|
||||||
|
if (Popup.util.isPopupOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Enter to send when send_textarea in focus
|
//Enter to send when send_textarea in focus
|
||||||
if (document.activeElement == hotkeyTargets['send_textarea']) {
|
if (document.activeElement == hotkeyTargets['send_textarea']) {
|
||||||
const sendOnEnter = shouldSendOnEnter();
|
const sendOnEnter = shouldSendOnEnter();
|
||||||
@ -1107,10 +1112,6 @@ export function initRossMods() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.key == 'Escape') { //closes various panels
|
if (event.key == 'Escape') { //closes various panels
|
||||||
// Do not close panels if we are currently inside a popup
|
|
||||||
if (Popup.util.isPopupOpen())
|
|
||||||
return;
|
|
||||||
|
|
||||||
//dont override Escape hotkey functions from script.js
|
//dont override Escape hotkey functions from script.js
|
||||||
//"close edit box" and "cancel stream generation".
|
//"close edit box" and "cancel stream generation".
|
||||||
if ($('#curEditTextarea').is(':visible') || $('#mes_stop').is(':visible')) {
|
if ($('#curEditTextarea').is(':visible') || $('#mes_stop').is(':visible')) {
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||||
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
<option data-type="openai" value="gpt-4o">gpt-4o</option>
|
||||||
<option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
|
<option data-type="openai" value="gpt-4o-mini">gpt-4o-mini</option>
|
||||||
|
<option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
|
||||||
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
|
<option data-type="anthropic" value="claude-3-5-sonnet-20240620">claude-3-5-sonnet-20240620</option>
|
||||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
||||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||||
|
@ -401,7 +401,7 @@ async function processFiles(chat) {
|
|||||||
const dataBankCollectionIds = await ingestDataBankAttachments();
|
const dataBankCollectionIds = await ingestDataBankAttachments();
|
||||||
|
|
||||||
if (dataBankCollectionIds.length) {
|
if (dataBankCollectionIds.length) {
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'file');
|
||||||
await injectDataBankChunks(queryText, dataBankCollectionIds);
|
await injectDataBankChunks(queryText, dataBankCollectionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,7 +435,7 @@ async function processFiles(chat) {
|
|||||||
await vectorizeFile(fileText, fileName, collectionId, settings.chunk_size, settings.overlap_percent);
|
await vectorizeFile(fileText, fileName, collectionId, settings.chunk_size, settings.overlap_percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'file');
|
||||||
const fileChunks = await retrieveFileChunks(queryText, collectionId);
|
const fileChunks = await retrieveFileChunks(queryText, collectionId);
|
||||||
|
|
||||||
message.mes = `${fileChunks}\n\n${message.mes}`;
|
message.mes = `${fileChunks}\n\n${message.mes}`;
|
||||||
@ -596,7 +596,7 @@ async function rearrangeChat(chat) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'chat');
|
||||||
|
|
||||||
if (queryText.length === 0) {
|
if (queryText.length === 0) {
|
||||||
console.debug('Vectors: No text to query');
|
console.debug('Vectors: No text to query');
|
||||||
@ -683,15 +683,16 @@ const onChatEvent = debounce(async () => await moduleWorker.update(), debounce_t
|
|||||||
/**
|
/**
|
||||||
* Gets the text to query from the chat
|
* Gets the text to query from the chat
|
||||||
* @param {object[]} chat Chat messages
|
* @param {object[]} chat Chat messages
|
||||||
|
* @param {'file'|'chat'|'world-info'} initiator Initiator of the query
|
||||||
* @returns {Promise<string>} Text to query
|
* @returns {Promise<string>} Text to query
|
||||||
*/
|
*/
|
||||||
async function getQueryText(chat) {
|
async function getQueryText(chat, initiator) {
|
||||||
let queryText = '';
|
let queryText = '';
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
let hashedMessages = chat.map(x => ({ text: String(substituteParams(x.mes)) }));
|
let hashedMessages = chat.map(x => ({ text: String(substituteParams(x.mes)) }));
|
||||||
|
|
||||||
if (settings.summarize && settings.summarize_sent) {
|
if (initiator === 'chat' && settings.enabled_chats && settings.summarize && settings.summarize_sent) {
|
||||||
hashedMessages = await summarize(hashedMessages, settings.summary_source);
|
hashedMessages = await summarize(hashedMessages, settings.summary_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1279,7 +1280,7 @@ async function activateWorldInfo(chat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform a multi-query
|
// Perform a multi-query
|
||||||
const queryText = await getQueryText(chat);
|
const queryText = await getQueryText(chat, 'world-info');
|
||||||
|
|
||||||
if (queryText.length === 0) {
|
if (queryText.length === 0) {
|
||||||
console.debug('Vectors: No text to query for WI');
|
console.debug('Vectors: No text to query for WI');
|
||||||
|
@ -4763,12 +4763,13 @@ export function isImageInliningSupported() {
|
|||||||
'gpt-4-turbo',
|
'gpt-4-turbo',
|
||||||
'gpt-4o',
|
'gpt-4o',
|
||||||
'gpt-4o-mini',
|
'gpt-4o-mini',
|
||||||
|
'chatgpt-4o-latest',
|
||||||
'yi-vision',
|
'yi-vision',
|
||||||
];
|
];
|
||||||
|
|
||||||
switch (oai_settings.chat_completion_source) {
|
switch (oai_settings.chat_completion_source) {
|
||||||
case chat_completion_sources.OPENAI:
|
case chat_completion_sources.OPENAI:
|
||||||
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'));
|
return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));
|
||||||
case chat_completion_sources.MAKERSUITE:
|
case chat_completion_sources.MAKERSUITE:
|
||||||
return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
|
return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
|
||||||
case chat_completion_sources.CLAUDE:
|
case chat_completion_sources.CLAUDE:
|
||||||
|
@ -596,7 +596,7 @@ export class Popup {
|
|||||||
|
|
||||||
/** @returns {boolean} Checks if any modal popup dialog is open */
|
/** @returns {boolean} Checks if any modal popup dialog is open */
|
||||||
isPopupOpen() {
|
isPopupOpen() {
|
||||||
return Popup.util.popups.length > 0;
|
return Popup.util.popups.filter(x => x.dlg.hasAttribute('open')).length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,6 +202,7 @@ let power_user = {
|
|||||||
trim_spaces: true,
|
trim_spaces: true,
|
||||||
relaxed_api_urls: false,
|
relaxed_api_urls: false,
|
||||||
world_import_dialog: true,
|
world_import_dialog: true,
|
||||||
|
enable_auto_select_input: false,
|
||||||
tag_import_setting: tag_import_setting.ASK,
|
tag_import_setting: tag_import_setting.ASK,
|
||||||
disable_group_trimming: false,
|
disable_group_trimming: false,
|
||||||
single_line: false,
|
single_line: false,
|
||||||
@ -1611,6 +1612,7 @@ async function loadPowerUserSettings(settings, data) {
|
|||||||
$('#single_line').prop('checked', power_user.single_line);
|
$('#single_line').prop('checked', power_user.single_line);
|
||||||
$('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
|
$('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
|
||||||
$('#world_import_dialog').prop('checked', power_user.world_import_dialog);
|
$('#world_import_dialog').prop('checked', power_user.world_import_dialog);
|
||||||
|
$('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input);
|
||||||
$('#trim_spaces').prop('checked', power_user.trim_spaces);
|
$('#trim_spaces').prop('checked', power_user.trim_spaces);
|
||||||
$('#continue_on_send').prop('checked', power_user.continue_on_send);
|
$('#continue_on_send').prop('checked', power_user.continue_on_send);
|
||||||
$('#quick_continue').prop('checked', power_user.quick_continue);
|
$('#quick_continue').prop('checked', power_user.quick_continue);
|
||||||
@ -3788,6 +3790,12 @@ $(document).ready(() => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#enable_auto_select_input').on('input', function () {
|
||||||
|
const value = !!$(this).prop('checked');
|
||||||
|
power_user.enable_auto_select_input = value;
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
$('#spoiler_free_mode').on('input', function () {
|
$('#spoiler_free_mode').on('input', function () {
|
||||||
power_user.spoiler_free_mode = !!$(this).prop('checked');
|
power_user.spoiler_free_mode = !!$(this).prop('checked');
|
||||||
switchSpoilerMode();
|
switchSpoilerMode();
|
||||||
|
@ -7,9 +7,19 @@ export const markdownUnderscoreExt = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
type: 'lang',
|
type: 'output',
|
||||||
regex: new RegExp('\\b(?<!_)_(?!_)(.*?)(?<!_)_(?!_)\\b', 'g'),
|
regex: new RegExp('(<code>[\\s\\S]*?<\\/code>)|(?<!\\S)_(?!_)([^_\\n]+?)(?<!_)_(?!\\w)', 'g'),
|
||||||
replace: '<em>$1</em>',
|
replace: function(match, codeContent, italicContent) {
|
||||||
|
if (codeContent) {
|
||||||
|
// If it's inside <code> tags, return unchanged
|
||||||
|
return match;
|
||||||
|
} else if (italicContent) {
|
||||||
|
// If it's an italic group, apply the replacement
|
||||||
|
return '<em>' + italicContent + '</em>';
|
||||||
|
}
|
||||||
|
// If none of the conditions are met, return the original match
|
||||||
|
return match;
|
||||||
|
},
|
||||||
}];
|
}];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error in Showdown-underscore extension:', e);
|
console.error('Error in Showdown-underscore extension:', e);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Generate,
|
Generate,
|
||||||
|
UNIQUE_APIS,
|
||||||
activateSendButtons,
|
activateSendButtons,
|
||||||
addOneMessage,
|
addOneMessage,
|
||||||
|
api_server,
|
||||||
callPopup,
|
callPopup,
|
||||||
characters,
|
characters,
|
||||||
chat,
|
chat,
|
||||||
@ -49,8 +51,8 @@ import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSel
|
|||||||
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
|
import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
|
||||||
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
|
import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
|
||||||
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||||
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
||||||
import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync } from './tokenizers.js';
|
import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
|
||||||
import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
||||||
import { registerVariableCommands, resolveVariable } from './variables.js';
|
import { registerVariableCommands, resolveVariable } from './variables.js';
|
||||||
import { background_settings } from './backgrounds.js';
|
import { background_settings } from './backgrounds.js';
|
||||||
@ -1496,8 +1498,9 @@ export function initDefaultSlashCommands() {
|
|||||||
],
|
],
|
||||||
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
helpString: 'Sets the specified prompt manager entry/entries on or off.',
|
||||||
}));
|
}));
|
||||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pick-icon',
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
callback: async()=>((await showFontAwesomePicker()) ?? false).toString(),
|
name: 'pick-icon',
|
||||||
|
callback: async () => ((await showFontAwesomePicker()) ?? false).toString(),
|
||||||
returns: 'The chosen icon name or false if cancelled.',
|
returns: 'The chosen icon name or false if cancelled.',
|
||||||
helpString: `
|
helpString: `
|
||||||
<div>Opens a popup with all the available Font Awesome icons and returns the selected icon's name.</div>
|
<div>Opens a popup with all the available Font Awesome icons and returns the selected icon's name.</div>
|
||||||
@ -1511,6 +1514,72 @@ export function initDefaultSlashCommands() {
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'api-url',
|
||||||
|
callback: setApiUrlCallback,
|
||||||
|
returns: 'the current API url',
|
||||||
|
aliases: ['server'],
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'api',
|
||||||
|
description: 'API to set/get the URL for - if not provided, current API is used',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
enumList: [
|
||||||
|
new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'),
|
||||||
|
new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'),
|
||||||
|
...Object.values(textgen_types).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'connect',
|
||||||
|
description: 'Whether to auto-connect to the API after setting the URL',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'true',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'API url to connect to',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
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.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
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,
|
||||||
|
or consider switching to it with <code>/api</code> first.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
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,
|
||||||
|
check the auto-completion of the optional <code>api</code> argument of this command.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'tokenizer',
|
||||||
|
callback: selectTokenizerCallback,
|
||||||
|
returns: 'current tokenizer',
|
||||||
|
unnamedArgumentList: [
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'tokenizer name',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
enumList: getAvailableTokenizers().map(tokenizer =>
|
||||||
|
new SlashCommandEnumValue(tokenizer.tokenizerKey, tokenizer.tokenizerName, enumTypes.enum, enumIcons.default)),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Selects tokenizer by name. Gets the current tokenizer if no name is provided.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Available tokenizers:</strong>
|
||||||
|
<pre><code>${getAvailableTokenizers().map(t => t.tokenizerKey).join(', ')}</code></pre>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
registerVariableCommands();
|
registerVariableCommands();
|
||||||
}
|
}
|
||||||
@ -1788,7 +1857,7 @@ async function popupCallback(args, value) {
|
|||||||
return String(value);
|
return String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessagesCallback(args, value) {
|
async function getMessagesCallback(args, value) {
|
||||||
const includeNames = !isFalseBoolean(args?.names);
|
const includeNames = !isFalseBoolean(args?.names);
|
||||||
const includeHidden = isTrueBoolean(args?.hidden);
|
const includeHidden = isTrueBoolean(args?.hidden);
|
||||||
const role = args?.role;
|
const role = args?.role;
|
||||||
@ -1821,33 +1890,34 @@ function getMessagesCallback(args, value) {
|
|||||||
throw new Error(`Invalid role provided. Expected one of: system, assistant, user. Got: ${role}`);
|
throw new Error(`Invalid role provided. Expected one of: system, assistant, user. Got: ${role}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages = [];
|
const processMessage = async (mesId) => {
|
||||||
|
const msg = chat[mesId];
|
||||||
for (let messageId = range.start; messageId <= range.end; messageId++) {
|
if (!msg) {
|
||||||
const message = chat[messageId];
|
console.warn(`WARN: No message found with ID ${mesId}`);
|
||||||
if (!message) {
|
return null;
|
||||||
console.warn(`WARN: No message found with ID ${messageId}`);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role && !filterByRole(message)) {
|
if (role && !filterByRole(msg)) {
|
||||||
console.debug(`/messages: Skipping message with ID ${messageId} due to role filter`);
|
console.debug(`/messages: Skipping message with ID ${mesId} due to role filter`);
|
||||||
continue;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!includeHidden && message.is_system) {
|
if (!includeHidden && msg.is_system) {
|
||||||
console.debug(`/messages: Skipping hidden message with ID ${messageId}`);
|
console.debug(`/messages: Skipping hidden message with ID ${mesId}`);
|
||||||
continue;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeNames) {
|
return includeNames ? `${msg.name}: ${msg.mes}` : msg.mes;
|
||||||
messages.push(`${message.name}: ${message.mes}`);
|
};
|
||||||
} else {
|
|
||||||
messages.push(message.mes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages.join('\n\n');
|
const messagePromises = [];
|
||||||
|
|
||||||
|
for (let rInd = range.start; rInd <= range.end; ++rInd)
|
||||||
|
messagePromises.push(processMessage(rInd));
|
||||||
|
|
||||||
|
const messages = await Promise.all(messagePromises);
|
||||||
|
|
||||||
|
return messages.filter(m => m !== null).join('\n\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runCallback(args, name) {
|
async function runCallback(args, name) {
|
||||||
@ -3418,6 +3488,123 @@ function setPromptEntryCallback(args, targetState) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the API URL and triggers the text generation web UI button click.
|
||||||
|
*
|
||||||
|
* @param {object} args - named args
|
||||||
|
* @param {string?} [args.api=null] - the API name to set/get the URL for
|
||||||
|
* @param {string?} [args.connect=true] - whether to connect to the API after setting
|
||||||
|
* @param {string} url - the API URL to set
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async function setApiUrlCallback({ api = null, connect = 'true' }, url) {
|
||||||
|
const autoConnect = isTrueBoolean(connect);
|
||||||
|
|
||||||
|
// Special handling for Chat Completion Custom OpenAI compatible, that one can also support API url handling
|
||||||
|
const isCurrentlyCustomOpenai = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
|
||||||
|
if (api === chat_completion_sources.CUSTOM || (!api && isCurrentlyCustomOpenai)) {
|
||||||
|
if (!url) {
|
||||||
|
return oai_settings.custom_url ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCurrentlyCustomOpenai && autoConnect) {
|
||||||
|
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.');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#custom_api_url_text').val(url).trigger('input');
|
||||||
|
|
||||||
|
if (autoConnect) {
|
||||||
|
$('#api_button_openai').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for Kobold Classic API
|
||||||
|
const isCurrentlyKoboldClassic = main_api === 'kobold';
|
||||||
|
if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) {
|
||||||
|
if (!url) {
|
||||||
|
return api_server ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCurrentlyKoboldClassic && autoConnect) {
|
||||||
|
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.');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#api_url_text').val(url).trigger('input');
|
||||||
|
// trigger blur debounced, so we hide the autocomplete menu
|
||||||
|
setTimeout(() => $('#api_url_text').trigger('blur'), 1);
|
||||||
|
|
||||||
|
if (autoConnect) {
|
||||||
|
$('#api_button').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
return api_server ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do some checks and get the api type we are targeting with this command
|
||||||
|
if (api && !Object.values(textgen_types).includes(api)) {
|
||||||
|
toastr.warning(`API '${api}' is not a valid text_gen API.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!api && !Object.values(textgen_types).includes(textgenerationwebui_settings.type)) {
|
||||||
|
toastr.warning(`API '${textgenerationwebui_settings.type}' is not a valid text_gen API.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (api && url && autoConnect && api !== textgenerationwebui_settings.type) {
|
||||||
|
toastr.warning(`API '${api}' is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const type = api || textgenerationwebui_settings.type;
|
||||||
|
|
||||||
|
const inputSelector = SERVER_INPUTS[type];
|
||||||
|
if (!inputSelector) {
|
||||||
|
toastr.warning(`API '${type}' does not have a server url input.`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no url was provided, return the current one
|
||||||
|
if (!url) {
|
||||||
|
return textgenerationwebui_settings.server_urls[type] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// else, we want to actually set the url
|
||||||
|
$(inputSelector).val(url).trigger('input');
|
||||||
|
// trigger blur debounced, so we hide the autocomplete menu
|
||||||
|
setTimeout(() => $(inputSelector).trigger('blur'), 1);
|
||||||
|
|
||||||
|
// Trigger the auto connect via connect button, if requested
|
||||||
|
if (autoConnect) {
|
||||||
|
$('#api_button_textgenerationwebui').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still re-acquire the value, as it might have been modified by the validation on connect
|
||||||
|
return textgenerationwebui_settings.server_urls[type] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectTokenizerCallback(_, name) {
|
||||||
|
if (!name) {
|
||||||
|
return getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === power_user.tokenizer)?.tokenizerKey ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenizers = getAvailableTokenizers();
|
||||||
|
const fuse = new Fuse(tokenizers, { keys: ['tokenizerKey', 'tokenizerName'] });
|
||||||
|
const result = fuse.search(name);
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
toastr.warning(`Tokenizer "${name}" not found`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('./tokenizers.js').Tokenizer} */
|
||||||
|
const foundTokenizer = result[0].item;
|
||||||
|
selectTokenizer(foundTokenizer.tokenizerId);
|
||||||
|
|
||||||
|
return foundTokenizer.tokenizerKey;
|
||||||
|
}
|
||||||
|
|
||||||
export let isExecutingCommandsFromChatInput = false;
|
export let isExecutingCommandsFromChatInput = false;
|
||||||
export let commandsFromChatInputAbortController;
|
export let commandsFromChatInputAbortController;
|
||||||
|
|
||||||
|
@ -708,12 +708,12 @@ const ANTI_TROLL_MAX_TAGS = 15;
|
|||||||
*
|
*
|
||||||
* @param {Character} character - The character
|
* @param {Character} character - The character
|
||||||
* @param {object} [options] - Options
|
* @param {object} [options] - Options
|
||||||
* @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog
|
* @param {tag_import_setting} [options.importSetting=null] - Force a tag import setting
|
||||||
* @returns {Promise<boolean>} Boolean indicating whether any tag was imported
|
* @returns {Promise<boolean>} Boolean indicating whether any tag was imported
|
||||||
*/
|
*/
|
||||||
async function importTags(character, { forceShow = false } = {}) {
|
async function importTags(character, { importSetting = null } = {}) {
|
||||||
// Gather the tags to import based on the selected setting
|
// Gather the tags to import based on the selected setting
|
||||||
const tagNamesToImport = await handleTagImport(character, { forceShow });
|
const tagNamesToImport = await handleTagImport(character, { importSetting });
|
||||||
if (!tagNamesToImport?.length) {
|
if (!tagNamesToImport?.length) {
|
||||||
console.debug('No tags to import');
|
console.debug('No tags to import');
|
||||||
return;
|
return;
|
||||||
@ -732,10 +732,10 @@ async function importTags(character, { forceShow = false } = {}) {
|
|||||||
*
|
*
|
||||||
* @param {Character} character - The character
|
* @param {Character} character - The character
|
||||||
* @param {object} [options] - Options
|
* @param {object} [options] - Options
|
||||||
* @param {boolean} [options.forceShow=false] - Whether to force showing the import dialog
|
* @param {tag_import_setting} [options.importSetting=null] - Force a tag import setting
|
||||||
* @returns {Promise<string[]>} Array of strings representing the tags to import
|
* @returns {Promise<string[]>} Array of strings representing the tags to import
|
||||||
*/
|
*/
|
||||||
async function handleTagImport(character, { forceShow = false } = {}) {
|
async function handleTagImport(character, { importSetting = null } = {}) {
|
||||||
/** @type {string[]} */
|
/** @type {string[]} */
|
||||||
const importTags = character.tags.map(t => t.trim()).filter(t => t)
|
const importTags = character.tags.map(t => t.trim()).filter(t => t)
|
||||||
.filter(t => !IMPORT_EXLCUDED_TAGS.includes(t))
|
.filter(t => !IMPORT_EXLCUDED_TAGS.includes(t))
|
||||||
@ -745,9 +745,9 @@ async function handleTagImport(character, { forceShow = false } = {}) {
|
|||||||
.map(newTag);
|
.map(newTag);
|
||||||
const folderTags = getOpenBogusFolders();
|
const folderTags = getOpenBogusFolders();
|
||||||
|
|
||||||
// Choose the setting for this dialog. If from settings, verify the setting really exists, otherwise take "ASK".
|
// Choose the setting for this dialog. First check override, then saved setting or finally use "ASK".
|
||||||
const setting = forceShow ? tag_import_setting.ASK
|
const setting = importSetting ? importSetting :
|
||||||
: Object.values(tag_import_setting).find(setting => setting === power_user.tag_import_setting) ?? tag_import_setting.ASK;
|
Object.values(tag_import_setting).find(setting => setting === power_user.tag_import_setting) ?? tag_import_setting.ASK;
|
||||||
|
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
case tag_import_setting.ALL:
|
case tag_import_setting.ALL:
|
||||||
|
@ -95,7 +95,7 @@ let DREAMGEN_SERVER = 'https://dreamgen.com';
|
|||||||
let OPENROUTER_SERVER = 'https://openrouter.ai/api';
|
let OPENROUTER_SERVER = 'https://openrouter.ai/api';
|
||||||
let FEATHERLESS_SERVER = 'https://api.featherless.ai/v1';
|
let FEATHERLESS_SERVER = 'https://api.featherless.ai/v1';
|
||||||
|
|
||||||
const SERVER_INPUTS = {
|
export const SERVER_INPUTS = {
|
||||||
[textgen_types.OOBA]: '#textgenerationwebui_api_url_text',
|
[textgen_types.OOBA]: '#textgenerationwebui_api_url_text',
|
||||||
[textgen_types.VLLM]: '#vllm_api_url_text',
|
[textgen_types.VLLM]: '#vllm_api_url_text',
|
||||||
[textgen_types.APHRODITE]: '#aphrodite_api_url_text',
|
[textgen_types.APHRODITE]: '#aphrodite_api_url_text',
|
||||||
@ -1071,6 +1071,34 @@ function getLogprobsNumber() {
|
|||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces {{macro}} in a comma-separated or serialized JSON array string.
|
||||||
|
* @param {string} str Input string
|
||||||
|
* @returns {string} Output string
|
||||||
|
*/
|
||||||
|
function replaceMacrosInList(str) {
|
||||||
|
if (!str || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const array = JSON.parse(str);
|
||||||
|
if (!Array.isArray(array)) {
|
||||||
|
throw new Error('Not an array');
|
||||||
|
}
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
array[i] = substituteParams(array[i]);
|
||||||
|
}
|
||||||
|
return JSON.stringify(array);
|
||||||
|
} catch {
|
||||||
|
const array = str.split(',');
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
array[i] = substituteParams(array[i]);
|
||||||
|
}
|
||||||
|
return array.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
|
export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
|
||||||
const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
|
const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
|
||||||
const dynatemp = isDynamicTemperatureSupported();
|
const dynatemp = isDynamicTemperatureSupported();
|
||||||
@ -1110,7 +1138,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
|||||||
'dry_allowed_length': settings.dry_allowed_length,
|
'dry_allowed_length': settings.dry_allowed_length,
|
||||||
'dry_multiplier': settings.dry_multiplier,
|
'dry_multiplier': settings.dry_multiplier,
|
||||||
'dry_base': settings.dry_base,
|
'dry_base': settings.dry_base,
|
||||||
'dry_sequence_breakers': settings.dry_sequence_breakers,
|
'dry_sequence_breakers': replaceMacrosInList(settings.dry_sequence_breakers),
|
||||||
'dry_penalty_last_n': settings.dry_penalty_last_n,
|
'dry_penalty_last_n': settings.dry_penalty_last_n,
|
||||||
'max_tokens_second': settings.max_tokens_second,
|
'max_tokens_second': settings.max_tokens_second,
|
||||||
'sampler_priority': settings.type === OOBA ? settings.sampler_priority : undefined,
|
'sampler_priority': settings.type === OOBA ? settings.sampler_priority : undefined,
|
||||||
|
@ -147,10 +147,46 @@ async function resetTokenCache() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} Tokenizer
|
||||||
|
* @property {number} tokenizerId - The id of the tokenizer option
|
||||||
|
* @property {string} tokenizerKey - Internal name/key of the tokenizer
|
||||||
|
* @property {string} tokenizerName - Human-readable detailed name of the tokenizer (as displayed in the UI)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all tokenizers available to the user.
|
||||||
|
* @returns {Tokenizer[]} Tokenizer info.
|
||||||
|
*/
|
||||||
|
export function getAvailableTokenizers() {
|
||||||
|
const tokenizerOptions = $('#tokenizer').find('option').toArray();
|
||||||
|
return tokenizerOptions.map(tokenizerOption => ({
|
||||||
|
tokenizerId: Number(tokenizerOption.value),
|
||||||
|
tokenizerKey: Object.entries(tokenizers).find(([_, value]) => value === Number(tokenizerOption.value))[0].toLocaleLowerCase(),
|
||||||
|
tokenizerName: tokenizerOption.text,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects tokenizer if not already selected.
|
||||||
|
* @param {number} tokenizerId Tokenizer ID.
|
||||||
|
*/
|
||||||
|
export function selectTokenizer(tokenizerId) {
|
||||||
|
if (tokenizerId !== power_user.tokenizer) {
|
||||||
|
const tokenizer = getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === tokenizerId);
|
||||||
|
if (!tokenizer) {
|
||||||
|
console.warn('Failed to find tokenizer with id', tokenizerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#tokenizer').val(tokenizer.tokenizerId).trigger('change');
|
||||||
|
toastr.info(`Tokenizer: "${tokenizer.tokenizerName}" selected`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the friendly name of the current tokenizer.
|
* Gets the friendly name of the current tokenizer.
|
||||||
* @param {string} forApi API to get the tokenizer for. Defaults to the main API.
|
* @param {string} forApi API to get the tokenizer for. Defaults to the main API.
|
||||||
* @returns { { tokenizerName: string, tokenizerId: number } } Tokenizer info
|
* @returns {Tokenizer} Tokenizer info
|
||||||
*/
|
*/
|
||||||
export function getFriendlyTokenizerName(forApi) {
|
export function getFriendlyTokenizerName(forApi) {
|
||||||
if (!forApi) {
|
if (!forApi) {
|
||||||
@ -185,7 +221,9 @@ export function getFriendlyTokenizerName(forApi) {
|
|||||||
? tokenizers.OPENAI
|
? tokenizers.OPENAI
|
||||||
: tokenizerId;
|
: tokenizerId;
|
||||||
|
|
||||||
return { tokenizerName, tokenizerId };
|
const tokenizerKey = Object.entries(tokenizers).find(([_, value]) => value === tokenizerId)[0].toLocaleLowerCase();
|
||||||
|
|
||||||
|
return { tokenizerName, tokenizerKey, tokenizerId };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
16
server.js
16
server.js
@ -787,17 +787,17 @@ function logSecurityAlert(message) {
|
|||||||
*/
|
*/
|
||||||
function handleServerListenFail(v6Failed, v4Failed) {
|
function handleServerListenFail(v6Failed, v4Failed) {
|
||||||
if (v6Failed && !enableIPv4) {
|
if (v6Failed && !enableIPv4) {
|
||||||
console.error('fatal error: Failed to start server on IPv6 and IPv4 disabled');
|
console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v4Failed && !enableIPv6) {
|
if (v4Failed && !enableIPv6) {
|
||||||
console.error('fatal error: Failed to start server on IPv4 and IPv6 disabled');
|
console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v6Failed && v4Failed) {
|
if (v6Failed && v4Failed) {
|
||||||
console.error('fatal error: Failed to start server on both IPv6 and IPv4');
|
console.error(color.red('fatal error: Failed to start server on both IPv6 and IPv4'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -846,9 +846,8 @@ async function startHTTPorHTTPS() {
|
|||||||
try {
|
try {
|
||||||
await createFunc(tavernUrlV6);
|
await createFunc(tavernUrlV6);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (enableIPv4) {
|
console.error('non-fatal error: failed to start server on IPv6');
|
||||||
console.error('non-fatal error: failed to start server on IPv6', error);
|
console.error(error);
|
||||||
}
|
|
||||||
|
|
||||||
v6Failed = true;
|
v6Failed = true;
|
||||||
}
|
}
|
||||||
@ -858,9 +857,8 @@ async function startHTTPorHTTPS() {
|
|||||||
try {
|
try {
|
||||||
await createFunc(tavernUrl);
|
await createFunc(tavernUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (enableIPv6) {
|
console.error('non-fatal error: failed to start server on IPv4');
|
||||||
console.error('non-fatal error: failed to start server on IPv4', error);
|
console.error(error);
|
||||||
}
|
|
||||||
|
|
||||||
v4Failed = true;
|
v4Failed = true;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const fetch = require('node-fetch').default;
|
const fetch = require('node-fetch').default;
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const iconv = require('iconv-lite');
|
||||||
const { readSecret, SECRET_KEYS } = require('./secrets');
|
const { readSecret, SECRET_KEYS } = require('./secrets');
|
||||||
const { getConfigValue, uuidv4 } = require('../util');
|
const { getConfigValue, uuidv4 } = require('../util');
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
@ -80,16 +81,18 @@ router.post('/google', jsonParser, async (request, response) => {
|
|||||||
const url = generateRequestUrl(text, { to: lang });
|
const url = generateRequestUrl(text, { to: lang });
|
||||||
|
|
||||||
https.get(url, (resp) => {
|
https.get(url, (resp) => {
|
||||||
let data = '';
|
const data = [];
|
||||||
|
|
||||||
resp.on('data', (chunk) => {
|
resp.on('data', (chunk) => {
|
||||||
data += chunk;
|
data.push(chunk);
|
||||||
});
|
});
|
||||||
|
|
||||||
resp.on('end', () => {
|
resp.on('end', () => {
|
||||||
try {
|
try {
|
||||||
const result = normaliseResponse(JSON.parse(data));
|
const decodedData = iconv.decode(Buffer.concat(data), 'utf-8');
|
||||||
|
const result = normaliseResponse(JSON.parse(decodedData));
|
||||||
console.log('Translated text: ' + result.text);
|
console.log('Translated text: ' + result.text);
|
||||||
|
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
return response.send(result.text);
|
return response.send(result.text);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Translation error', error);
|
console.log('Translation error', error);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { pipeline, env, RawImage, Pipeline } from 'sillytavern-transformers';
|
import { pipeline, env, RawImage, Pipeline } from 'sillytavern-transformers';
|
||||||
import { getConfigValue } from './util.js';
|
import { getConfigValue } from './util.js';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
configureTransformers();
|
configureTransformers();
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ const tasks = {
|
|||||||
configField: 'extras.textToSpeechModel',
|
configField: 'extras.textToSpeechModel',
|
||||||
quantized: false,
|
quantized: false,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a RawImage object from a base64-encoded image.
|
* Gets a RawImage object from a base64-encoded image.
|
||||||
@ -85,6 +86,36 @@ function getModelForTask(task) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function migrateCacheToDataDir() {
|
||||||
|
const oldCacheDir = path.join(process.cwd(), 'cache');
|
||||||
|
const newCacheDir = path.join(global.DATA_ROOT, '_cache');
|
||||||
|
|
||||||
|
if (!fs.existsSync(newCacheDir)) {
|
||||||
|
fs.mkdirSync(newCacheDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(oldCacheDir) && fs.statSync(oldCacheDir).isDirectory()) {
|
||||||
|
const files = fs.readdirSync(oldCacheDir);
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Migrating model cache files to data directory. Please wait...');
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const oldPath = path.join(oldCacheDir, file);
|
||||||
|
const newPath = path.join(newCacheDir, file);
|
||||||
|
fs.cpSync(oldPath, newPath, { recursive: true, force: true });
|
||||||
|
fs.rmSync(oldPath, { recursive: true, force: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to migrate cache file. The model will be re-downloaded.', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the transformers.js pipeline for a given task.
|
* Gets the transformers.js pipeline for a given task.
|
||||||
* @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for
|
* @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for
|
||||||
@ -92,6 +123,8 @@ function getModelForTask(task) {
|
|||||||
* @returns {Promise<Pipeline>} Pipeline for the task
|
* @returns {Promise<Pipeline>} Pipeline for the task
|
||||||
*/
|
*/
|
||||||
async function getPipeline(task, forceModel = '') {
|
async function getPipeline(task, forceModel = '') {
|
||||||
|
await migrateCacheToDataDir();
|
||||||
|
|
||||||
if (tasks[task].pipeline) {
|
if (tasks[task].pipeline) {
|
||||||
if (forceModel === '' || tasks[task].currentModel === forceModel) {
|
if (forceModel === '' || tasks[task].currentModel === forceModel) {
|
||||||
return tasks[task].pipeline;
|
return tasks[task].pipeline;
|
||||||
@ -100,11 +133,11 @@ async function getPipeline(task, forceModel = '') {
|
|||||||
await tasks[task].pipeline.dispose();
|
await tasks[task].pipeline.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache_dir = path.join(process.cwd(), 'cache');
|
const cacheDir = path.join(global.DATA_ROOT, '_cache');
|
||||||
const model = forceModel || getModelForTask(task);
|
const model = forceModel || getModelForTask(task);
|
||||||
const localOnly = getConfigValue('extras.disableAutoDownload', false);
|
const localOnly = getConfigValue('extras.disableAutoDownload', false);
|
||||||
console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
|
console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
|
||||||
const instance = await pipeline(task, model, { cache_dir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
|
const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
|
||||||
tasks[task].pipeline = instance;
|
tasks[task].pipeline = instance;
|
||||||
tasks[task].currentModel = model;
|
tasks[task].currentModel = model;
|
||||||
return instance;
|
return instance;
|
||||||
|
16
src/users.js
16
src/users.js
@ -19,12 +19,6 @@ const AVATAR_PREFIX = 'avatar:';
|
|||||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||||
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
||||||
|
|
||||||
/**
|
|
||||||
* The root directory for user data.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
let DATA_ROOT = './data';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for user directories.
|
* Cache for user directories.
|
||||||
* @type {Map<string, UserDirectoryList>}
|
* @type {Map<string, UserDirectoryList>}
|
||||||
@ -138,7 +132,7 @@ async function migrateUserData() {
|
|||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
console.log(color.magenta('Preparing to migrate user data...'));
|
console.log(color.magenta('Preparing to migrate user data...'));
|
||||||
console.log(`All public data will be moved to the ${DATA_ROOT} directory.`);
|
console.log(`All public data will be moved to the ${global.DATA_ROOT} directory.`);
|
||||||
console.log('This process may take a while depending on the amount of data to move.');
|
console.log('This process may take a while depending on the amount of data to move.');
|
||||||
console.log(`Backups will be placed in the ${PUBLIC_DIRECTORIES.backups} directory.`);
|
console.log(`Backups will be placed in the ${PUBLIC_DIRECTORIES.backups} directory.`);
|
||||||
console.log(`The process will start in ${TIMEOUT} seconds. Press Ctrl+C to cancel.`);
|
console.log(`The process will start in ${TIMEOUT} seconds. Press Ctrl+C to cancel.`);
|
||||||
@ -352,11 +346,11 @@ function toAvatarKey(handle) {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function initUserStorage(dataRoot) {
|
async function initUserStorage(dataRoot) {
|
||||||
DATA_ROOT = dataRoot;
|
global.DATA_ROOT = dataRoot;
|
||||||
console.log('Using data root:', color.green(DATA_ROOT));
|
console.log('Using data root:', color.green(global.DATA_ROOT));
|
||||||
console.log();
|
console.log();
|
||||||
await storage.init({
|
await storage.init({
|
||||||
dir: path.join(DATA_ROOT, '_storage'),
|
dir: path.join(global.DATA_ROOT, '_storage'),
|
||||||
ttl: false, // Never expire
|
ttl: false, // Never expire
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -457,7 +451,7 @@ function getUserDirectories(handle) {
|
|||||||
|
|
||||||
const directories = structuredClone(USER_DIRECTORY_TEMPLATE);
|
const directories = structuredClone(USER_DIRECTORY_TEMPLATE);
|
||||||
for (const key in directories) {
|
for (const key in directories) {
|
||||||
directories[key] = path.join(DATA_ROOT, handle, USER_DIRECTORY_TEMPLATE[key]);
|
directories[key] = path.join(global.DATA_ROOT, handle, USER_DIRECTORY_TEMPLATE[key]);
|
||||||
}
|
}
|
||||||
DIRECTORIES_CACHE.set(handle, directories);
|
DIRECTORIES_CACHE.set(handle, directories);
|
||||||
return directories;
|
return directories;
|
||||||
|
Reference in New Issue
Block a user