Add settings.json-backed KV string storage

Fixes #3461, #3443
This commit is contained in:
Cohee
2025-02-11 20:17:48 +02:00
parent c3dd3e246e
commit d5bdf1cb90
16 changed files with 205 additions and 99 deletions

View File

@ -270,6 +270,7 @@ import { initBulkEdit } from './scripts/bulk-edit.js';
import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
import { getContext } from './scripts/st-context.js'; import { getContext } from './scripts/st-context.js';
import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI } from './scripts/reasoning.js'; import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI } from './scripts/reasoning.js';
import { accountStorage } from './scripts/util/AccountStorage.js';
// API OBJECT FOR EXTERNAL WIRING // API OBJECT FOR EXTERNAL WIRING
globalThis.SillyTavern = { globalThis.SillyTavern = {
@ -419,7 +420,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
const entityId = getCurrentEntityId(); const entityId = getCurrentEntityId();
const warningShownKey = `mediaWarningShown:${entityId}`; const warningShownKey = `mediaWarningShown:${entityId}`;
if (localStorage.getItem(warningShownKey) === null) { if (accountStorage.getItem(warningShownKey) === null) {
const warningToast = toastr.warning( const warningToast = toastr.warning(
t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`,
t`External media has been blocked`, t`External media has been blocked`,
@ -430,7 +431,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
}, },
); );
localStorage.setItem(warningShownKey, 'true'); accountStorage.setItem(warningShownKey, 'true');
} }
} }
}); });
@ -1490,7 +1491,7 @@ export async function printCharacters(fullRefresh = false) {
$('#rm_print_characters_pagination').pagination({ $('#rm_print_characters_pagination').pagination({
dataSource: entities, dataSource: entities,
pageSize: Number(localStorage.getItem(storageKey)) || per_page_default, pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default,
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
pageRange: 1, pageRange: 1,
pageNumber: saveCharactersPage || 1, pageNumber: saveCharactersPage || 1,
@ -1534,7 +1535,7 @@ export async function printCharacters(fullRefresh = false) {
eventSource.emit(event_types.CHARACTER_PAGE_LOADED); eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
}, },
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value); accountStorage.setItem(storageKey, e.target.value);
}, },
afterPaging: function (e) { afterPaging: function (e) {
saveCharactersPage = e; saveCharactersPage = e;
@ -6902,10 +6903,11 @@ export async function getSettings() {
$('#your_name').val(name1); $('#your_name').val(name1);
} }
accountStorage.init(settings?.accountStorage);
await setUserControls(data.enable_accounts); await setUserControls(data.enable_accounts);
// Allow subscribers to mutate settings // Allow subscribers to mutate settings
eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); await eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings);
//Load KoboldAI settings //Load KoboldAI settings
koboldai_setting_names = data.koboldai_setting_names; koboldai_setting_names = data.koboldai_setting_names;
@ -7094,6 +7096,7 @@ export async function saveSettings(loopCounter = 0) {
url: '/api/settings/save', url: '/api/settings/save',
data: JSON.stringify({ data: JSON.stringify({
firstRun: firstRun, firstRun: firstRun,
accountStorage: accountStorage.state,
currentVersion: currentVersion, currentVersion: currentVersion,
username: name1, username: name1,
active_character: active_character, active_character: active_character,
@ -7549,7 +7552,7 @@ export function select_rm_info(type, charId, previousCharId = null) {
} }
try { try {
const perPage = Number(localStorage.getItem('Characters_PerPage')) || per_page_default; const perPage = Number(accountStorage.getItem('Characters_PerPage')) || per_page_default;
const page = Math.floor(charIndex / perPage) + 1; const page = Math.floor(charIndex / perPage) + 1;
const selector = `#rm_print_characters_block [title*="${avatarFileName}"]`; const selector = `#rm_print_characters_block [title*="${avatarFileName}"]`;
$('#rm_print_characters_pagination').pagination('go', page); $('#rm_print_characters_pagination').pagination('go', page);
@ -7581,7 +7584,7 @@ export function select_rm_info(type, charId, previousCharId = null) {
return; return;
} }
const perPage = Number(localStorage.getItem('Characters_PerPage')) || per_page_default; const perPage = Number(accountStorage.getItem('Characters_PerPage')) || per_page_default;
const page = Math.floor(charIndex / perPage) + 1; const page = Math.floor(charIndex / perPage) + 1;
$('#rm_print_characters_pagination').pagination('go', page); $('#rm_print_characters_pagination').pagination('go', page);
const selector = `#rm_print_characters_block [grid="${charId}"]`; const selector = `#rm_print_characters_block [grid="${charId}"]`;
@ -9650,8 +9653,8 @@ function addDebugFunctions() {
}); });
registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => { registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => {
localStorage.setItem('RegenerateWithCtrlEnter', localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true'); accountStorage.setItem('RegenerateWithCtrlEnter', accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true');
toastr.info('Regenerate warning is now ' + (localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled')); toastr.info('Regenerate warning is now ' + (accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled'));
}); });
registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => { registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => {

View File

@ -27,7 +27,6 @@ import {
send_on_enter_options, send_on_enter_options,
} from './power-user.js'; } from './power-user.js';
import { LoadLocal, SaveLocal, LoadLocalBool } from './f-localStorage.js';
import { selected_group, is_group_generating, openGroupById } from './group-chats.js'; import { selected_group, is_group_generating, openGroupById } from './group-chats.js';
import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js'; import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js';
import { import {
@ -41,6 +40,7 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { Popup } from './popup.js'; import { Popup } from './popup.js';
import { accountStorage } from './util/AccountStorage.js';
var RPanelPin = document.getElementById('rm_button_panel_pin'); var RPanelPin = document.getElementById('rm_button_panel_pin');
var LPanelPin = document.getElementById('lm_button_panel_pin'); var LPanelPin = document.getElementById('lm_button_panel_pin');
@ -409,19 +409,19 @@ function RA_autoconnect(PrevApi) {
function OpenNavPanels() { function OpenNavPanels() {
if (!isMobile()) { if (!isMobile()) {
//auto-open R nav if locked and previously open //auto-open R nav if locked and previously open
if (LoadLocalBool('NavLockOn') == true && LoadLocalBool('NavOpened') == true) { if (accountStorage.getItem('NavLockOn') == 'true' && accountStorage.getItem('NavOpened') == 'true') {
//console.log("RA -- clicking right nav to open"); //console.log("RA -- clicking right nav to open");
$('#rightNavDrawerIcon').click(); $('#rightNavDrawerIcon').click();
} }
//auto-open L nav if locked and previously open //auto-open L nav if locked and previously open
if (LoadLocalBool('LNavLockOn') == true && LoadLocalBool('LNavOpened') == true) { if (accountStorage.getItem('LNavLockOn') == 'true' && accountStorage.getItem('LNavOpened') == 'true') {
console.debug('RA -- clicking left nav to open'); console.debug('RA -- clicking left nav to open');
$('#leftNavDrawerIcon').click(); $('#leftNavDrawerIcon').click();
} }
//auto-open WI if locked and previously open //auto-open WI if locked and previously open
if (LoadLocalBool('WINavLockOn') == true && LoadLocalBool('WINavOpened') == true) { if (accountStorage.getItem('WINavLockOn') == 'true' && accountStorage.getItem('WINavOpened') == 'true') {
console.debug('RA -- clicking WI to open'); console.debug('RA -- clicking WI to open');
$('#WIDrawerIcon').click(); $('#WIDrawerIcon').click();
} }
@ -434,7 +434,7 @@ function restoreUserInput() {
return; return;
} }
const userInput = LoadLocal('userInput'); const userInput = localStorage.getItem('userInput');
if (userInput) { if (userInput) {
$('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true })); $('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true }));
} }
@ -442,7 +442,8 @@ function restoreUserInput() {
function saveUserInput() { function saveUserInput() {
const userInput = String($('#send_textarea').val()); const userInput = String($('#send_textarea').val());
SaveLocal('userInput', userInput); localStorage.setItem('userInput', userInput);
console.debug('User Input -- ', userInput);
} }
const saveUserInputDebounced = debounce(saveUserInput); const saveUserInputDebounced = debounce(saveUserInput);
@ -739,7 +740,7 @@ export function initRossMods() {
//toggle pin class when lock toggle clicked //toggle pin class when lock toggle clicked
$(RPanelPin).on('click', function () { $(RPanelPin).on('click', function () {
SaveLocal('NavLockOn', $(RPanelPin).prop('checked')); accountStorage.setItem('NavLockOn', $(RPanelPin).prop('checked'));
if ($(RPanelPin).prop('checked') == true) { if ($(RPanelPin).prop('checked') == true) {
//console.log('adding pin class to right nav'); //console.log('adding pin class to right nav');
$(RightNavPanel).addClass('pinnedOpen'); $(RightNavPanel).addClass('pinnedOpen');
@ -757,7 +758,7 @@ export function initRossMods() {
} }
}); });
$(LPanelPin).on('click', function () { $(LPanelPin).on('click', function () {
SaveLocal('LNavLockOn', $(LPanelPin).prop('checked')); accountStorage.setItem('LNavLockOn', $(LPanelPin).prop('checked'));
if ($(LPanelPin).prop('checked') == true) { if ($(LPanelPin).prop('checked') == true) {
//console.log('adding pin class to Left nav'); //console.log('adding pin class to Left nav');
$(LeftNavPanel).addClass('pinnedOpen'); $(LeftNavPanel).addClass('pinnedOpen');
@ -776,7 +777,7 @@ export function initRossMods() {
}); });
$(WIPanelPin).on('click', function () { $(WIPanelPin).on('click', function () {
SaveLocal('WINavLockOn', $(WIPanelPin).prop('checked')); accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));
if ($(WIPanelPin).prop('checked') == true) { if ($(WIPanelPin).prop('checked') == true) {
console.debug('adding pin class to WI'); console.debug('adding pin class to WI');
$(WorldInfo).addClass('pinnedOpen'); $(WorldInfo).addClass('pinnedOpen');
@ -796,8 +797,8 @@ export function initRossMods() {
}); });
// read the state of right Nav Lock and apply to rightnav classlist // read the state of right Nav Lock and apply to rightnav classlist
$(RPanelPin).prop('checked', LoadLocalBool('NavLockOn')); $(RPanelPin).prop('checked', accountStorage.getItem('NavLockOn') == 'true');
if (LoadLocalBool('NavLockOn') == true) { if (accountStorage.getItem('NavLockOn') == 'true') {
//console.log('setting pin class via local var'); //console.log('setting pin class via local var');
$(RightNavPanel).addClass('pinnedOpen'); $(RightNavPanel).addClass('pinnedOpen');
$(RightNavDrawerIcon).addClass('drawerPinnedOpen'); $(RightNavDrawerIcon).addClass('drawerPinnedOpen');
@ -808,8 +809,8 @@ export function initRossMods() {
$(RightNavDrawerIcon).addClass('drawerPinnedOpen'); $(RightNavDrawerIcon).addClass('drawerPinnedOpen');
} }
// read the state of left Nav Lock and apply to leftnav classlist // read the state of left Nav Lock and apply to leftnav classlist
$(LPanelPin).prop('checked', LoadLocalBool('LNavLockOn')); $(LPanelPin).prop('checked', accountStorage.getItem('LNavLockOn') === 'true');
if (LoadLocalBool('LNavLockOn') == true) { if (accountStorage.getItem('LNavLockOn') == 'true') {
//console.log('setting pin class via local var'); //console.log('setting pin class via local var');
$(LeftNavPanel).addClass('pinnedOpen'); $(LeftNavPanel).addClass('pinnedOpen');
$(LeftNavDrawerIcon).addClass('drawerPinnedOpen'); $(LeftNavDrawerIcon).addClass('drawerPinnedOpen');
@ -821,8 +822,8 @@ export function initRossMods() {
} }
// read the state of left Nav Lock and apply to leftnav classlist // read the state of left Nav Lock and apply to leftnav classlist
$(WIPanelPin).prop('checked', LoadLocalBool('WINavLockOn')); $(WIPanelPin).prop('checked', accountStorage.getItem('WINavLockOn') === 'true');
if (LoadLocalBool('WINavLockOn') == true) { if (accountStorage.getItem('WINavLockOn') == 'true') {
//console.log('setting pin class via local var'); //console.log('setting pin class via local var');
$(WorldInfo).addClass('pinnedOpen'); $(WorldInfo).addClass('pinnedOpen');
$(WIDrawerIcon).addClass('drawerPinnedOpen'); $(WIDrawerIcon).addClass('drawerPinnedOpen');
@ -837,22 +838,22 @@ export function initRossMods() {
//save state of Right nav being open or closed //save state of Right nav being open or closed
$('#rightNavDrawerIcon').on('click', function () { $('#rightNavDrawerIcon').on('click', function () {
if (!$('#rightNavDrawerIcon').hasClass('openIcon')) { if (!$('#rightNavDrawerIcon').hasClass('openIcon')) {
SaveLocal('NavOpened', 'true'); accountStorage.setItem('NavOpened', 'true');
} else { SaveLocal('NavOpened', 'false'); } } else { accountStorage.setItem('NavOpened', 'false'); }
}); });
//save state of Left nav being open or closed //save state of Left nav being open or closed
$('#leftNavDrawerIcon').on('click', function () { $('#leftNavDrawerIcon').on('click', function () {
if (!$('#leftNavDrawerIcon').hasClass('openIcon')) { if (!$('#leftNavDrawerIcon').hasClass('openIcon')) {
SaveLocal('LNavOpened', 'true'); accountStorage.setItem('LNavOpened', 'true');
} else { SaveLocal('LNavOpened', 'false'); } } else { accountStorage.setItem('LNavOpened', 'false'); }
}); });
//save state of Left nav being open or closed //save state of Left nav being open or closed
$('#WorldInfo').on('click', function () { $('#WorldInfo').on('click', function () {
if (!$('#WorldInfo').hasClass('openIcon')) { if (!$('#WorldInfo').hasClass('openIcon')) {
SaveLocal('WINavOpened', 'true'); accountStorage.setItem('WINavOpened', 'true');
} else { SaveLocal('WINavOpened', 'false'); } } else { accountStorage.setItem('WINavOpened', 'false'); }
}); });
var chatbarInFocus = false; var chatbarInFocus = false;
@ -868,8 +869,8 @@ export function initRossMods() {
OpenNavPanels(); OpenNavPanels();
}, 300); }, 300);
$(SelectedCharacterTab).click(function () { SaveLocal('SelectedNavTab', 'rm_button_selected_ch'); }); $(SelectedCharacterTab).click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); });
$('#rm_button_characters').click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); }); $('#rm_button_characters').click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_characters'); });
// when a char is selected from the list, save them as the auto-load character for next page load // when a char is selected from the list, save them as the auto-load character for next page load
@ -1077,7 +1078,7 @@ export function initRossMods() {
} }
else if (is_send_press == false) { else if (is_send_press == false) {
const skipConfirmKey = 'RegenerateWithCtrlEnter'; const skipConfirmKey = 'RegenerateWithCtrlEnter';
const skipConfirm = LoadLocalBool(skipConfirmKey); const skipConfirm = accountStorage.getItem(skipConfirmKey) === 'true';
function doRegenerate() { function doRegenerate() {
console.debug('Regenerating with Ctrl+Enter'); console.debug('Regenerating with Ctrl+Enter');
$('#option_regenerate').trigger('click'); $('#option_regenerate').trigger('click');
@ -1097,7 +1098,7 @@ export function initRossMods() {
return; return;
} }
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter); accountStorage.setItem(skipConfirmKey, String(regenerateWithCtrlEnter));
doRegenerate(); doRegenerate();
} }
return; return;

View File

@ -45,6 +45,7 @@ import { DragAndDropHandler } from './dragdrop.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { humanizedDateTime } from './RossAscends-mods.js'; import { humanizedDateTime } from './RossAscends-mods.js';
import { accountStorage } from './util/AccountStorage.js';
/** /**
* @typedef {Object} FileAttachment * @typedef {Object} FileAttachment
@ -1078,8 +1079,8 @@ async function openAttachmentManager() {
renderAttachments(); renderAttachments();
}); });
let sortField = localStorage.getItem('DataBank_sortField') || 'created'; let sortField = accountStorage.getItem('DataBank_sortField') || 'created';
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc'; let sortOrder = accountStorage.getItem('DataBank_sortOrder') || 'desc';
let filterString = ''; let filterString = '';
const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {})); const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {}));
@ -1095,8 +1096,8 @@ async function openAttachmentManager() {
sortField = this.selectedOptions[0].dataset.sortField; sortField = this.selectedOptions[0].dataset.sortField;
sortOrder = this.selectedOptions[0].dataset.sortOrder; sortOrder = this.selectedOptions[0].dataset.sortOrder;
localStorage.setItem('DataBank_sortField', sortField); accountStorage.setItem('DataBank_sortField', sortField);
localStorage.setItem('DataBank_sortOrder', sortOrder); accountStorage.setItem('DataBank_sortOrder', sortOrder);
renderAttachments(); renderAttachments();
}); });
function handleBulkAction(action) { function handleBulkAction(action) {

View File

@ -9,6 +9,7 @@ import { getContext } from './st-context.js';
import { isAdmin } from './user.js'; import { isAdmin } from './user.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { accountStorage } from './util/AccountStorage.js';
export { export {
getContext, getContext,
@ -714,7 +715,7 @@ async function showExtensionsDetails() {
htmlExternal.append(htmlLoading); htmlExternal.append(htmlLoading);
const sortOrderKey = 'extensions_sortByName'; const sortOrderKey = 'extensions_sortByName';
const sortByName = localStorage.getItem(sortOrderKey) === 'true'; const sortByName = accountStorage.getItem(sortOrderKey) === 'true';
const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder; const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder;
const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData); const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData);
@ -745,7 +746,7 @@ async function showExtensionsDetails() {
text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`, text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`,
action: async () => { action: async () => {
abortController.abort(); abortController.abort();
localStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true'); accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
await showExtensionsDetails(); await showExtensionsDetails();
}, },
}; };
@ -1153,11 +1154,11 @@ async function checkForExtensionUpdates(force) {
const currentDate = new Date().toDateString(); const currentDate = new Date().toDateString();
// Don't nag more than once a day // Don't nag more than once a day
if (localStorage.getItem(STORAGE_NAG_KEY) === currentDate) { if (accountStorage.getItem(STORAGE_NAG_KEY) === currentDate) {
return; return;
} }
localStorage.setItem(STORAGE_NAG_KEY, currentDate); accountStorage.setItem(STORAGE_NAG_KEY, currentDate);
} }
const isCurrentUserAdmin = isAdmin(); const isCurrentUserAdmin = isAdmin();

View File

@ -8,6 +8,7 @@ import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js';
import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
import { executeSlashCommands } from '../../slash-commands.js'; import { executeSlashCommands } from '../../slash-commands.js';
import { accountStorage } from '../../util/AccountStorage.js';
import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js';
export { MODULE_NAME }; export { MODULE_NAME };
@ -432,14 +433,14 @@ jQuery(async () => {
connectButton.on('click', async function () { connectButton.on('click', async function () {
const url = DOMPurify.sanitize(String(assetsJsonUrl.val())); const url = DOMPurify.sanitize(String(assetsJsonUrl.val()));
const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`;
const skipConfirm = localStorage.getItem(rememberKey) === 'true'; const skipConfirm = accountStorage.getItem(rememberKey) === 'true';
const confirmation = skipConfirm || await Popup.show.confirm('Loading Asset List', `<span>Are you sure you want to connect to the following url?</span><var>${url}</var>`, { const confirmation = skipConfirm || await Popup.show.confirm('Loading Asset List', `<span>Are you sure you want to connect to the following url?</span><var>${url}</var>`, {
customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }],
onClose: popup => { onClose: popup => {
if (popup.result) { if (popup.result) {
const rememberValue = popup.inputResults.get('assets-remember'); const rememberValue = popup.inputResults.get('assets-remember');
localStorage.setItem(rememberKey, String(rememberValue)); accountStorage.setItem(rememberKey, String(rememberValue));
} }
}, },
}); });

View File

@ -10,6 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js'; import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js';
import { t } from '../../i18n.js'; import { t } from '../../i18n.js';
import { accountStorage } from '../../util/AccountStorage.js';
/** /**
* @typedef {object} RegexScript * @typedef {object} RegexScript
@ -440,8 +441,8 @@ async function checkEmbeddedRegexScripts() {
if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) { if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) {
const checkKey = `AlertRegex_${characters[chid].avatar}`; const checkKey = `AlertRegex_${characters[chid].avatar}`;
if (!localStorage.getItem(checkKey)) { if (!accountStorage.getItem(checkKey)) {
localStorage.setItem(checkKey, 'true'); accountStorage.setItem(checkKey, 'true');
const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {});
const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' }); const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' });

View File

@ -1,27 +0,0 @@
////////////////// LOCAL STORAGE HANDLING /////////////////////
export function SaveLocal(target, val) {
localStorage.setItem(target, val);
console.debug('SaveLocal -- ' + target + ' : ' + val);
}
export function LoadLocal(target) {
console.debug('LoadLocal -- ' + target);
return localStorage.getItem(target);
}
export function LoadLocalBool(target) {
let result = localStorage.getItem(target) === 'true';
return result;
}
export function CheckLocal() {
console.log('----------local storage---------');
var i;
for (i = 0; i < localStorage.length; i++) {
console.log(localStorage.key(i) + ' : ' + localStorage.getItem(localStorage.key(i)));
}
console.log('------------------------------');
}
export function ClearLocal() { localStorage.clear(); console.log('Removed All Local Storage'); }
/////////////////////////////////////////////////////////////////////////

View File

@ -78,6 +78,7 @@ import { FILTER_TYPES, FilterHelper } from './filters.js';
import { isExternalMediaAllowed } from './chats.js'; import { isExternalMediaAllowed } from './chats.js';
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { accountStorage } from './util/AccountStorage.js';
export { export {
selected_group, selected_group,
@ -1309,10 +1310,10 @@ function printGroupCandidates() {
formatNavigator: PAGINATION_TEMPLATE, formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true, showNavigator: true,
showSizeChanger: true, showSizeChanger: true,
pageSize: Number(localStorage.getItem(storageKey)) || 5, pageSize: Number(accountStorage.getItem(storageKey)) || 5,
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value); accountStorage.setItem(storageKey, e.target.value);
}, },
callback: function (data) { callback: function (data) {
$('#rm_group_add_members').empty(); $('#rm_group_add_members').empty();
@ -1336,10 +1337,10 @@ function printGroupMembers() {
formatNavigator: PAGINATION_TEMPLATE, formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true, showNavigator: true,
showSizeChanger: true, showSizeChanger: true,
pageSize: Number(localStorage.getItem(storageKey)) || 5, pageSize: Number(accountStorage.getItem(storageKey)) || 5,
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value); accountStorage.setItem(storageKey, e.target.value);
}, },
callback: function (data) { callback: function (data) {
$('.rm_group_members').empty(); $('.rm_group_members').empty();

View File

@ -73,6 +73,7 @@ import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js
import { Popup, POPUP_RESULT } from './popup.js'; import { Popup, POPUP_RESULT } from './popup.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { ToolManager } from './tool-calling.js'; import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js';
export { export {
openai_messages_count, openai_messages_count,
@ -414,7 +415,7 @@ async function validateReverseProxy() {
throw err; throw err;
} }
const rememberKey = `Proxy_SkipConfirm_${getStringHash(oai_settings.reverse_proxy)}`; const rememberKey = `Proxy_SkipConfirm_${getStringHash(oai_settings.reverse_proxy)}`;
const skipConfirm = localStorage.getItem(rememberKey) === 'true'; const skipConfirm = accountStorage.getItem(rememberKey) === 'true';
const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) })); const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) }));
@ -425,7 +426,7 @@ async function validateReverseProxy() {
throw new Error('Proxy connection denied.'); throw new Error('Proxy connection denied.');
} }
localStorage.setItem(rememberKey, String(true)); accountStorage.setItem(rememberKey, String(true));
} }
/** /**

View File

@ -25,6 +25,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { openWorldInfoEditor, world_names } from './world-info.js'; import { openWorldInfoEditor, world_names } from './world-info.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { accountStorage } from './util/AccountStorage.js';
let savePersonasPage = 0; let savePersonasPage = 0;
const GRID_STORAGE_KEY = 'Personas_GridView'; const GRID_STORAGE_KEY = 'Personas_GridView';
@ -34,7 +35,7 @@ export let user_avatar = '';
export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick)); export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick));
function switchPersonaGridView() { function switchPersonaGridView() {
const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true'; const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true';
$('#user_avatar_block').toggleClass('gridView', state); $('#user_avatar_block').toggleClass('gridView', state);
} }
@ -182,7 +183,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
const storageKey = 'Personas_PerPage'; const storageKey = 'Personas_PerPage';
const listId = '#user_avatar_block'; const listId = '#user_avatar_block';
const perPage = Number(localStorage.getItem(storageKey)) || 5; const perPage = Number(accountStorage.getItem(storageKey)) || 5;
$('#persona_pagination_container').pagination({ $('#persona_pagination_container').pagination({
dataSource: entities, dataSource: entities,
@ -205,7 +206,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
highlightSelectedAvatar(); highlightSelectedAvatar();
}, },
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value); accountStorage.setItem(storageKey, e.target.value);
}, },
afterPaging: function (e) { afterPaging: function (e) {
savePersonasPage = e; savePersonasPage = e;
@ -1132,8 +1133,8 @@ export function initPersonas() {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#persona_grid_toggle').on('click', () => { $('#persona_grid_toggle').on('click', () => {
const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true'; const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true';
localStorage.setItem(GRID_STORAGE_KEY, String(!state)); accountStorage.setItem(GRID_STORAGE_KEY, String(!state));
switchPersonaGridView(); switchPersonaGridView();
}); });

View File

@ -54,6 +54,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { POPUP_TYPE, callGenericPopup } from './popup.js'; import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js'; import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js'; import { fuzzySearchCategories } from './filters.js';
import { accountStorage } from './util/AccountStorage.js';
export { export {
loadPowerUserSettings, loadPowerUserSettings,
@ -2019,7 +2020,7 @@ export function renderStoryString(params) {
*/ */
function validateStoryString(storyString, params) { function validateStoryString(storyString, params) {
/** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */
const cache = JSON.parse(localStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; const cache = JSON.parse(accountStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} };
const hash = getStringHash(storyString); const hash = getStringHash(storyString);
@ -2056,7 +2057,7 @@ function validateStoryString(storyString, params) {
toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation');
} }
localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); accountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache));
} }

View File

@ -75,6 +75,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
import { accountStorage } from './util/AccountStorage.js';
export { export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
}; };
@ -3606,9 +3607,9 @@ export async function sendMessageAs(args, text) {
if (!name) { if (!name) {
const namelessWarningKey = 'sendAsNamelessWarningShown'; const namelessWarningKey = 'sendAsNamelessWarningShown';
if (localStorage.getItem(namelessWarningKey) !== 'true') { if (accountStorage.getItem(namelessWarningKey) !== 'true') {
toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
localStorage.setItem(namelessWarningKey, 'true'); accountStorage.setItem(namelessWarningKey, 'true');
} }
name = name2; name = name2;
} }

View File

@ -68,12 +68,14 @@ import { tag_map, tags } from './tags.js';
import { textgenerationwebui_settings } from './textgen-settings.js'; import { textgenerationwebui_settings } from './textgen-settings.js';
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js'; import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
import { ToolManager } from './tool-calling.js'; import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js';
import { timestampToMoment, uuidv4 } from './utils.js'; import { timestampToMoment, uuidv4 } from './utils.js';
import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js'; import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js'; import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
export function getContext() { export function getContext() {
return { return {
accountStorage,
chat, chat,
characters, characters,
groups, groups,

View File

@ -6,6 +6,7 @@ import { tokenizers } from './tokenizers.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { POPUP_TYPE, callGenericPopup } from './popup.js'; import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { accountStorage } from './util/AccountStorage.js';
let mancerModels = []; let mancerModels = [];
let togetherModels = []; let togetherModels = [];
@ -336,7 +337,7 @@ export async function loadFeatherlessModels(data) {
populateClassSelection(data); populateClassSelection(data);
// Retrieve the stored number of items per page or default to 10 // Retrieve the stored number of items per page or default to 10
const perPage = Number(localStorage.getItem(storageKey)) || 10; const perPage = Number(accountStorage.getItem(storageKey)) || 10;
// Initialize pagination // Initialize pagination
applyFiltersAndSort(); applyFiltersAndSort();
@ -412,7 +413,7 @@ export async function loadFeatherlessModels(data) {
}, },
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
const newPerPage = e.target.value; const newPerPage = e.target.value;
localStorage.setItem('Models_PerPage', newPerPage); accountStorage.setItem(storageKey, newPerPage);
setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number
}, },
}); });
@ -513,7 +514,7 @@ export async function loadFeatherlessModels(data) {
const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model); const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model);
featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1; featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1;
setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); setupPagination(filteredModels, Number(accountStorage.getItem(storageKey)) || perPage, featherlessCurrentPage);
} }
// Required to keep the /model command function // Required to keep the /model command function

View File

@ -0,0 +1,116 @@
import { saveSettingsDebounced } from '../../script.js';
const MIGRATED_MARKER = '__migrated';
const MIGRATABLE_KEYS = [
/^AlertRegex_/,
/^AlertWI_/,
/^Assets_SkipConfirm_/,
/^Characters_PerPage$/,
/^DataBank_sortField$/,
/^DataBank_sortOrder$/,
/^extension_update_nag$/,
/^extensions_sortByName$/,
/^FeatherlessModels_PerPage$/,
/^GroupMembers_PerPage$/,
/^GroupCandidates_PerPage$/,
/^LNavLockOn$/,
/^LNavOpened$/,
/^mediaWarningShown:/,
/^NavLockOn$/,
/^NavOpened$/,
/^Personas_PerPage$/,
/^Personas_GridView$/,
/^Proxy_SkipConfirm_/,
/^RegenerateWithCtrlEnter$/,
/^SelectedNavTab$/,
/^sendAsNamelessWarningShown$/,
/^StoryStringValidationCache$/,
/^WINavOpened$/,
/^WI_PerPage$/,
/^world_info_sort_order$/,
];
/**
* Provides access to account storage of arbitrary key-value pairs.
*/
class AccountStorage {
constructor() {
this.state = {};
this.ready = false;
}
#migrateLocalStorage() {
for (let i = 0; i < globalThis.localStorage.length; i++) {
const key = globalThis.localStorage.key(i);
const value = globalThis.localStorage.getItem(key);
if (MIGRATABLE_KEYS.some(k => k.test(key))) {
this.state[key] = value;
globalThis.localStorage.removeItem(key);
}
}
}
/**
* Initialize the account storage.
* @param {Object} state Initial state
*/
init(state) {
if (state && typeof state === 'object') {
this.state = Object.assign(this.state, state);
}
if (!Object.hasOwn(this.state, MIGRATED_MARKER)) {
this.#migrateLocalStorage();
this.state[MIGRATED_MARKER] = 1;
saveSettingsDebounced();
}
this.ready = true;
}
/**
* Get the value of a key in account storage.
* @param {string} key Key to get
* @returns {string|null} Value of the key
*/
getItem(key) {
if (!this.ready) {
console.warn(`AccountStorage not ready (trying to read from ${key})`);
}
return Object.hasOwn(this.state, key) ? String(this.state[key]) : null;
}
/**
* Set a key in account storage.
* @param {string} key Key to set
* @param {string} value Value to set
*/
setItem(key, value) {
if (!this.ready) {
console.warn(`AccountStorage not ready (trying to write to ${key})`);
}
this.state[key] = String(value);
saveSettingsDebounced();
}
/**
* Remove a key from account storage.
* @param {string} key Key to remove
*/
removeItem(key) {
if (!this.ready) {
console.warn(`AccountStorage not ready (trying to remove ${key})`);
}
delete this.state[key];
saveSettingsDebounced();
}
}
/**
* Account storage instance.
*/
export const accountStorage = new AccountStorage();

View File

@ -21,6 +21,7 @@ import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
import { StructuredCloneMap } from './util/StructuredCloneMap.js'; import { StructuredCloneMap } from './util/StructuredCloneMap.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { accountStorage } from './util/AccountStorage.js';
export const world_info_insertion_strategy = { export const world_info_insertion_strategy = {
evenly: 0, evenly: 0,
@ -872,7 +873,7 @@ export function setWorldInfoSettings(settings, data) {
$('#world_editor_select').append(`<option value='${i}'>${item}</option>`); $('#world_editor_select').append(`<option value='${i}'>${item}</option>`);
}); });
$('#world_info_sort_order').val(localStorage.getItem(SORT_ORDER_KEY) || '0'); $('#world_info_sort_order').val(accountStorage.getItem(SORT_ORDER_KEY) || '0');
$('#world_info').trigger('change'); $('#world_info').trigger('change');
$('#world_editor_select').trigger('change'); $('#world_editor_select').trigger('change');
@ -1947,13 +1948,13 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
if (typeof navigation === 'number' && Number(navigation) >= 0) { if (typeof navigation === 'number' && Number(navigation) >= 0) {
const data = getDataArray(); const data = getDataArray();
const uidIndex = data.findIndex(x => x.uid === navigation); const uidIndex = data.findIndex(x => x.uid === navigation);
const perPage = Number(localStorage.getItem(storageKey)) || perPageDefault; const perPage = Number(accountStorage.getItem(storageKey)) || perPageDefault;
startPage = Math.floor(uidIndex / perPage) + 1; startPage = Math.floor(uidIndex / perPage) + 1;
} }
$('#world_info_pagination').pagination({ $('#world_info_pagination').pagination({
dataSource: getDataArray, dataSource: getDataArray,
pageSize: Number(localStorage.getItem(storageKey)) || perPageDefault, pageSize: Number(accountStorage.getItem(storageKey)) || perPageDefault,
sizeChangerOptions: [10, 25, 50, 100, 500, 1000], sizeChangerOptions: [10, 25, 50, 100, 500, 1000],
showSizeChanger: true, showSizeChanger: true,
pageRange: 1, pageRange: 1,
@ -1983,7 +1984,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
worldEntriesList.append(blocks); worldEntriesList.append(blocks);
}, },
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value); accountStorage.setItem(storageKey, e.target.value);
}, },
afterPaging: function () { afterPaging: function () {
$('#world_popup_entries_list textarea[name="comment"]').each(function () { $('#world_popup_entries_list textarea[name="comment"]').each(function () {
@ -2188,7 +2189,7 @@ function verifyWorldInfoSearchSortRule() {
// If search got cleared, we make sure to hide the option and go back to the one before // If search got cleared, we make sure to hide the option and go back to the one before
if (!searchTerm && !isHidden) { if (!searchTerm && !isHidden) {
searchOption.attr('hidden', ''); searchOption.attr('hidden', '');
selector.val(localStorage.getItem(SORT_ORDER_KEY) || '0'); selector.val(accountStorage.getItem(SORT_ORDER_KEY) || '0');
} }
} }
@ -4753,8 +4754,8 @@ export function checkEmbeddedWorld(chid) {
// Only show the alert once per character // Only show the alert once per character
const checkKey = `AlertWI_${characters[chid].avatar}`; const checkKey = `AlertWI_${characters[chid].avatar}`;
const worldName = characters[chid]?.data?.extensions?.world; const worldName = characters[chid]?.data?.extensions?.world;
if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { if (!accountStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
localStorage.setItem(checkKey, 'true'); accountStorage.setItem(checkKey, 'true');
if (power_user.world_import_dialog) { if (power_user.world_import_dialog) {
const html = `<h3>This character has an embedded World/Lorebook.</h3> const html = `<h3>This character has an embedded World/Lorebook.</h3>
@ -5198,7 +5199,7 @@ jQuery(() => {
$('#world_info_sort_order').on('change', function () { $('#world_info_sort_order').on('change', function () {
const value = String($(this).find(':selected').val()); const value = String($(this).find(':selected').val());
// Save sort order, but do not save search sorting, as this is a temporary sorting option // Save sort order, but do not save search sorting, as this is a temporary sorting option
if (value !== 'search') localStorage.setItem(SORT_ORDER_KEY, value); if (value !== 'search') accountStorage.setItem(SORT_ORDER_KEY, value);
updateEditor(navigation_option.none); updateEditor(navigation_option.none);
}); });