diff --git a/public/script.js b/public/script.js
index 53cd0a5e1..48e623736 100644
--- a/public/script.js
+++ b/public/script.js
@@ -270,6 +270,7 @@ import { initBulkEdit } from './scripts/bulk-edit.js';
import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
import { getContext } from './scripts/st-context.js';
import { extractReasoningFromData, initReasoning, PromptReasoning, removeReasoningFromString, updateReasoningTimeUI } from './scripts/reasoning.js';
+import { accountStorage } from './scripts/util/AccountStorage.js';
// API OBJECT FOR EXTERNAL WIRING
globalThis.SillyTavern = {
@@ -419,7 +420,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
const entityId = getCurrentEntityId();
const warningShownKey = `mediaWarningShown:${entityId}`;
- if (localStorage.getItem(warningShownKey) === null) {
+ if (accountStorage.getItem(warningShownKey) === null) {
const warningToast = toastr.warning(
t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`,
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({
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],
pageRange: 1,
pageNumber: saveCharactersPage || 1,
@@ -1534,7 +1535,7 @@ export async function printCharacters(fullRefresh = false) {
eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
},
afterSizeSelectorChange: function (e) {
- localStorage.setItem(storageKey, e.target.value);
+ accountStorage.setItem(storageKey, e.target.value);
},
afterPaging: function (e) {
saveCharactersPage = e;
@@ -6903,10 +6904,11 @@ export async function getSettings() {
$('#your_name').val(name1);
}
+ accountStorage.init(settings?.accountStorage);
await setUserControls(data.enable_accounts);
// 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
koboldai_setting_names = data.koboldai_setting_names;
@@ -7074,7 +7076,8 @@ function selectKoboldGuiPreset() {
export async function saveSettings(loopCounter = 0) {
if (!settingsReady) {
- console.warn('Settings not ready, aborting save');
+ console.warn('Settings not ready, scheduling another save');
+ saveSettingsDebounced();
return;
}
@@ -7095,6 +7098,7 @@ export async function saveSettings(loopCounter = 0) {
url: '/api/settings/save',
data: JSON.stringify({
firstRun: firstRun,
+ accountStorage: accountStorage.getState(),
currentVersion: currentVersion,
username: name1,
active_character: active_character,
@@ -7550,7 +7554,7 @@ export function select_rm_info(type, charId, previousCharId = null) {
}
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 selector = `#rm_print_characters_block [title*="${avatarFileName}"]`;
$('#rm_print_characters_pagination').pagination('go', page);
@@ -7582,7 +7586,7 @@ export function select_rm_info(type, charId, previousCharId = null) {
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;
$('#rm_print_characters_pagination').pagination('go', page);
const selector = `#rm_print_characters_block [grid="${charId}"]`;
@@ -9449,6 +9453,9 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
continue;
}
+ accountStorage.removeItem(`AlertWI_${character.avatar}`);
+ accountStorage.removeItem(`AlertRegex_${character.avatar}`);
+ accountStorage.removeItem(`mediaWarningShown:${character.avatar}`);
delete tag_map[character.avatar];
select_rm_info('char_delete', character.name);
@@ -9651,8 +9658,8 @@ function addDebugFunctions() {
});
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');
- toastr.info('Regenerate warning is now ' + (localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled'));
+ accountStorage.setItem('RegenerateWithCtrlEnter', accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true');
+ 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 () => {
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index f76b4e95f..236955596 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -27,7 +27,6 @@ import {
send_on_enter_options,
} from './power-user.js';
-import { LoadLocal, SaveLocal, LoadLocalBool } from './f-localStorage.js';
import { selected_group, is_group_generating, openGroupById } from './group-chats.js';
import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js';
import {
@@ -41,6 +40,8 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex
import { debounce_timeout } from './constants.js';
import { Popup } from './popup.js';
+import { accountStorage } from './util/AccountStorage.js';
+import { getCurrentUserHandle } from './user.js';
var RPanelPin = document.getElementById('rm_button_panel_pin');
var LPanelPin = document.getElementById('lm_button_panel_pin');
@@ -409,32 +410,34 @@ function RA_autoconnect(PrevApi) {
function OpenNavPanels() {
if (!isMobile()) {
//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");
$('#rightNavDrawerIcon').click();
}
//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');
$('#leftNavDrawerIcon').click();
}
//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');
$('#WIDrawerIcon').click();
}
}
}
+const getUserInputKey = () => getCurrentUserHandle() + '_userInput';
+
function restoreUserInput() {
if (!power_user.restore_user_input) {
console.debug('restoreUserInput disabled');
return;
}
- const userInput = LoadLocal('userInput');
+ const userInput = localStorage.getItem(getUserInputKey());
if (userInput) {
$('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true }));
}
@@ -442,7 +445,8 @@ function restoreUserInput() {
function saveUserInput() {
const userInput = String($('#send_textarea').val());
- SaveLocal('userInput', userInput);
+ localStorage.setItem(getUserInputKey(), userInput);
+ console.debug('User Input -- ', userInput);
}
const saveUserInputDebounced = debounce(saveUserInput);
@@ -739,7 +743,7 @@ export function initRossMods() {
//toggle pin class when lock toggle clicked
$(RPanelPin).on('click', function () {
- SaveLocal('NavLockOn', $(RPanelPin).prop('checked'));
+ accountStorage.setItem('NavLockOn', $(RPanelPin).prop('checked'));
if ($(RPanelPin).prop('checked') == true) {
//console.log('adding pin class to right nav');
$(RightNavPanel).addClass('pinnedOpen');
@@ -757,7 +761,7 @@ export function initRossMods() {
}
});
$(LPanelPin).on('click', function () {
- SaveLocal('LNavLockOn', $(LPanelPin).prop('checked'));
+ accountStorage.setItem('LNavLockOn', $(LPanelPin).prop('checked'));
if ($(LPanelPin).prop('checked') == true) {
//console.log('adding pin class to Left nav');
$(LeftNavPanel).addClass('pinnedOpen');
@@ -776,7 +780,7 @@ export function initRossMods() {
});
$(WIPanelPin).on('click', function () {
- SaveLocal('WINavLockOn', $(WIPanelPin).prop('checked'));
+ accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));
if ($(WIPanelPin).prop('checked') == true) {
console.debug('adding pin class to WI');
$(WorldInfo).addClass('pinnedOpen');
@@ -796,8 +800,8 @@ export function initRossMods() {
});
// read the state of right Nav Lock and apply to rightnav classlist
- $(RPanelPin).prop('checked', LoadLocalBool('NavLockOn'));
- if (LoadLocalBool('NavLockOn') == true) {
+ $(RPanelPin).prop('checked', accountStorage.getItem('NavLockOn') == 'true');
+ if (accountStorage.getItem('NavLockOn') == 'true') {
//console.log('setting pin class via local var');
$(RightNavPanel).addClass('pinnedOpen');
$(RightNavDrawerIcon).addClass('drawerPinnedOpen');
@@ -808,8 +812,8 @@ export function initRossMods() {
$(RightNavDrawerIcon).addClass('drawerPinnedOpen');
}
// read the state of left Nav Lock and apply to leftnav classlist
- $(LPanelPin).prop('checked', LoadLocalBool('LNavLockOn'));
- if (LoadLocalBool('LNavLockOn') == true) {
+ $(LPanelPin).prop('checked', accountStorage.getItem('LNavLockOn') === 'true');
+ if (accountStorage.getItem('LNavLockOn') == 'true') {
//console.log('setting pin class via local var');
$(LeftNavPanel).addClass('pinnedOpen');
$(LeftNavDrawerIcon).addClass('drawerPinnedOpen');
@@ -821,8 +825,8 @@ export function initRossMods() {
}
// read the state of left Nav Lock and apply to leftnav classlist
- $(WIPanelPin).prop('checked', LoadLocalBool('WINavLockOn'));
- if (LoadLocalBool('WINavLockOn') == true) {
+ $(WIPanelPin).prop('checked', accountStorage.getItem('WINavLockOn') === 'true');
+ if (accountStorage.getItem('WINavLockOn') == 'true') {
//console.log('setting pin class via local var');
$(WorldInfo).addClass('pinnedOpen');
$(WIDrawerIcon).addClass('drawerPinnedOpen');
@@ -837,22 +841,22 @@ export function initRossMods() {
//save state of Right nav being open or closed
$('#rightNavDrawerIcon').on('click', function () {
if (!$('#rightNavDrawerIcon').hasClass('openIcon')) {
- SaveLocal('NavOpened', 'true');
- } else { SaveLocal('NavOpened', 'false'); }
+ accountStorage.setItem('NavOpened', 'true');
+ } else { accountStorage.setItem('NavOpened', 'false'); }
});
//save state of Left nav being open or closed
$('#leftNavDrawerIcon').on('click', function () {
if (!$('#leftNavDrawerIcon').hasClass('openIcon')) {
- SaveLocal('LNavOpened', 'true');
- } else { SaveLocal('LNavOpened', 'false'); }
+ accountStorage.setItem('LNavOpened', 'true');
+ } else { accountStorage.setItem('LNavOpened', 'false'); }
});
//save state of Left nav being open or closed
$('#WorldInfo').on('click', function () {
if (!$('#WorldInfo').hasClass('openIcon')) {
- SaveLocal('WINavOpened', 'true');
- } else { SaveLocal('WINavOpened', 'false'); }
+ accountStorage.setItem('WINavOpened', 'true');
+ } else { accountStorage.setItem('WINavOpened', 'false'); }
});
var chatbarInFocus = false;
@@ -868,8 +872,8 @@ export function initRossMods() {
OpenNavPanels();
}, 300);
- $(SelectedCharacterTab).click(function () { SaveLocal('SelectedNavTab', 'rm_button_selected_ch'); });
- $('#rm_button_characters').click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); });
+ $(SelectedCharacterTab).click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); });
+ $('#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
@@ -1077,7 +1081,7 @@ export function initRossMods() {
}
else if (is_send_press == false) {
const skipConfirmKey = 'RegenerateWithCtrlEnter';
- const skipConfirm = LoadLocalBool(skipConfirmKey);
+ const skipConfirm = accountStorage.getItem(skipConfirmKey) === 'true';
function doRegenerate() {
console.debug('Regenerating with Ctrl+Enter');
$('#option_regenerate').trigger('click');
@@ -1097,7 +1101,7 @@ export function initRossMods() {
return;
}
- SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
+ accountStorage.setItem(skipConfirmKey, String(regenerateWithCtrlEnter));
doRegenerate();
}
return;
diff --git a/public/scripts/chats.js b/public/scripts/chats.js
index 32c8e93e5..042030f2c 100644
--- a/public/scripts/chats.js
+++ b/public/scripts/chats.js
@@ -45,6 +45,7 @@ import { DragAndDropHandler } from './dragdrop.js';
import { renderTemplateAsync } from './templates.js';
import { t } from './i18n.js';
import { humanizedDateTime } from './RossAscends-mods.js';
+import { accountStorage } from './util/AccountStorage.js';
/**
* @typedef {Object} FileAttachment
@@ -1078,8 +1079,8 @@ async function openAttachmentManager() {
renderAttachments();
});
- let sortField = localStorage.getItem('DataBank_sortField') || 'created';
- let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
+ let sortField = accountStorage.getItem('DataBank_sortField') || 'created';
+ let sortOrder = accountStorage.getItem('DataBank_sortOrder') || 'desc';
let filterString = '';
const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {}));
@@ -1095,8 +1096,8 @@ async function openAttachmentManager() {
sortField = this.selectedOptions[0].dataset.sortField;
sortOrder = this.selectedOptions[0].dataset.sortOrder;
- localStorage.setItem('DataBank_sortField', sortField);
- localStorage.setItem('DataBank_sortOrder', sortOrder);
+ accountStorage.setItem('DataBank_sortField', sortField);
+ accountStorage.setItem('DataBank_sortOrder', sortOrder);
renderAttachments();
});
function handleBulkAction(action) {
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index c0ba05067..83f8dfe48 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -9,6 +9,7 @@ import { getContext } from './st-context.js';
import { isAdmin } from './user.js';
import { t } from './i18n.js';
import { debounce_timeout } from './constants.js';
+import { accountStorage } from './util/AccountStorage.js';
export {
getContext,
@@ -714,7 +715,7 @@ async function showExtensionsDetails() {
htmlExternal.append(htmlLoading);
const sortOrderKey = 'extensions_sortByName';
- const sortByName = localStorage.getItem(sortOrderKey) === 'true';
+ const sortByName = accountStorage.getItem(sortOrderKey) === 'true';
const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder;
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`,
action: async () => {
abortController.abort();
- localStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
+ accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true');
await showExtensionsDetails();
},
};
@@ -1153,11 +1154,11 @@ async function checkForExtensionUpdates(force) {
const currentDate = new Date().toDateString();
// Don't nag more than once a day
- if (localStorage.getItem(STORAGE_NAG_KEY) === currentDate) {
+ if (accountStorage.getItem(STORAGE_NAG_KEY) === currentDate) {
return;
}
- localStorage.setItem(STORAGE_NAG_KEY, currentDate);
+ accountStorage.setItem(STORAGE_NAG_KEY, currentDate);
}
const isCurrentUserAdmin = isAdmin();
diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js
index d491e8aaf..a9c48f318 100644
--- a/public/scripts/extensions/assets/index.js
+++ b/public/scripts/extensions/assets/index.js
@@ -8,6 +8,7 @@ import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js';
import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
import { executeSlashCommands } from '../../slash-commands.js';
+import { accountStorage } from '../../util/AccountStorage.js';
import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js';
export { MODULE_NAME };
@@ -432,14 +433,14 @@ jQuery(async () => {
connectButton.on('click', async function () {
const url = DOMPurify.sanitize(String(assetsJsonUrl.val()));
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', `Are you sure you want to connect to the following url?${url}`, {
customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }],
onClose: popup => {
if (popup.result) {
const rememberValue = popup.inputResults.get('assets-remember');
- localStorage.setItem(rememberKey, String(rememberValue));
+ accountStorage.setItem(rememberKey, String(rememberValue));
}
},
});
diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js
index ff7d5121f..d1e0ac012 100644
--- a/public/scripts/extensions/quick-reply/src/QuickReply.js
+++ b/public/scripts/extensions/quick-reply/src/QuickReply.js
@@ -10,6 +10,7 @@ import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecut
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js';
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
+import { accountStorage } from '../../../util/AccountStorage.js';
import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js';
import { log, quickReplyApi, warn } from '../index.js';
import { QuickReplyContextLink } from './QuickReplyContextLink.js';
@@ -544,9 +545,9 @@ export class QuickReply {
this.editorSyntax = messageSyntaxInner;
/**@type {HTMLInputElement}*/
const wrap = dom.querySelector('#qr--modal-wrap');
- wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false');
+ wrap.checked = JSON.parse(accountStorage.getItem('qr--wrap') ?? 'false');
wrap.addEventListener('click', () => {
- localStorage.setItem('qr--wrap', JSON.stringify(wrap.checked));
+ accountStorage.setItem('qr--wrap', JSON.stringify(wrap.checked));
updateWrap();
});
const updateWrap = () => {
@@ -594,27 +595,27 @@ export class QuickReply {
};
/**@type {HTMLInputElement}*/
const tabSize = dom.querySelector('#qr--modal-tabSize');
- tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4');
+ tabSize.value = JSON.parse(accountStorage.getItem('qr--tabSize') ?? '4');
const updateTabSize = () => {
message.style.tabSize = tabSize.value;
messageSyntaxInner.style.tabSize = tabSize.value;
updateScrollDebounced();
};
tabSize.addEventListener('change', () => {
- localStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value)));
+ accountStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value)));
updateTabSize();
});
/**@type {HTMLInputElement}*/
const executeShortcut = dom.querySelector('#qr--modal-executeShortcut');
- executeShortcut.checked = JSON.parse(localStorage.getItem('qr--executeShortcut') ?? 'true');
+ executeShortcut.checked = JSON.parse(accountStorage.getItem('qr--executeShortcut') ?? 'true');
executeShortcut.addEventListener('click', () => {
- localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked));
+ accountStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked));
});
/**@type {HTMLInputElement}*/
const syntax = dom.querySelector('#qr--modal-syntax');
- syntax.checked = JSON.parse(localStorage.getItem('qr--syntax') ?? 'true');
+ syntax.checked = JSON.parse(accountStorage.getItem('qr--syntax') ?? 'true');
syntax.addEventListener('click', () => {
- localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
+ accountStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
updateSyntaxEnabled();
});
if (navigator.keyboard) {
diff --git a/public/scripts/extensions/regex/index.js b/public/scripts/extensions/regex/index.js
index 8ac6ae6d6..5166befea 100644
--- a/public/scripts/extensions/regex/index.js
+++ b/public/scripts/extensions/regex/index.js
@@ -10,6 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js';
import { t } from '../../i18n.js';
+import { accountStorage } from '../../util/AccountStorage.js';
/**
* @typedef {object} RegexScript
@@ -440,8 +441,8 @@ async function checkEmbeddedRegexScripts() {
if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) {
const checkKey = `AlertRegex_${characters[chid].avatar}`;
- if (!localStorage.getItem(checkKey)) {
- localStorage.setItem(checkKey, 'true');
+ if (!accountStorage.getItem(checkKey)) {
+ accountStorage.setItem(checkKey, 'true');
const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {});
const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' });
diff --git a/public/scripts/f-localStorage.js b/public/scripts/f-localStorage.js
deleted file mode 100644
index adcb3af4e..000000000
--- a/public/scripts/f-localStorage.js
+++ /dev/null
@@ -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'); }
-
-/////////////////////////////////////////////////////////////////////////
diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js
index 5bf675fa2..54f19e90e 100644
--- a/public/scripts/group-chats.js
+++ b/public/scripts/group-chats.js
@@ -78,6 +78,7 @@ import { FILTER_TYPES, FilterHelper } from './filters.js';
import { isExternalMediaAllowed } from './chats.js';
import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { t } from './i18n.js';
+import { accountStorage } from './util/AccountStorage.js';
export {
selected_group,
@@ -1309,10 +1310,10 @@ function printGroupCandidates() {
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
showSizeChanger: true,
- pageSize: Number(localStorage.getItem(storageKey)) || 5,
+ pageSize: Number(accountStorage.getItem(storageKey)) || 5,
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
afterSizeSelectorChange: function (e) {
- localStorage.setItem(storageKey, e.target.value);
+ accountStorage.setItem(storageKey, e.target.value);
},
callback: function (data) {
$('#rm_group_add_members').empty();
@@ -1336,10 +1337,10 @@ function printGroupMembers() {
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
showSizeChanger: true,
- pageSize: Number(localStorage.getItem(storageKey)) || 5,
+ pageSize: Number(accountStorage.getItem(storageKey)) || 5,
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
afterSizeSelectorChange: function (e) {
- localStorage.setItem(storageKey, e.target.value);
+ accountStorage.setItem(storageKey, e.target.value);
},
callback: function (data) {
$('.rm_group_members').empty();
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 3eb068f16..8248dff25 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -73,6 +73,7 @@ import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js
import { Popup, POPUP_RESULT } from './popup.js';
import { t } from './i18n.js';
import { ToolManager } from './tool-calling.js';
+import { accountStorage } from './util/AccountStorage.js';
export {
openai_messages_count,
@@ -414,7 +415,7 @@ async function validateReverseProxy() {
throw err;
}
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) }));
@@ -425,7 +426,7 @@ async function validateReverseProxy() {
throw new Error('Proxy connection denied.');
}
- localStorage.setItem(rememberKey, String(true));
+ accountStorage.setItem(rememberKey, String(true));
}
/**
diff --git a/public/scripts/personas.js b/public/scripts/personas.js
index 69558fb7b..b90ebd7b5 100644
--- a/public/scripts/personas.js
+++ b/public/scripts/personas.js
@@ -25,6 +25,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { t } from './i18n.js';
import { openWorldInfoEditor, world_names } from './world-info.js';
import { renderTemplateAsync } from './templates.js';
+import { accountStorage } from './util/AccountStorage.js';
let savePersonasPage = 0;
const GRID_STORAGE_KEY = 'Personas_GridView';
@@ -34,7 +35,7 @@ export let user_avatar = '';
export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick));
function switchPersonaGridView() {
- const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true';
+ const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true';
$('#user_avatar_block').toggleClass('gridView', state);
}
@@ -182,7 +183,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
const storageKey = 'Personas_PerPage';
const listId = '#user_avatar_block';
- const perPage = Number(localStorage.getItem(storageKey)) || 5;
+ const perPage = Number(accountStorage.getItem(storageKey)) || 5;
$('#persona_pagination_container').pagination({
dataSource: entities,
@@ -205,7 +206,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
highlightSelectedAvatar();
},
afterSizeSelectorChange: function (e) {
- localStorage.setItem(storageKey, e.target.value);
+ accountStorage.setItem(storageKey, e.target.value);
},
afterPaging: function (e) {
savePersonasPage = e;
@@ -1132,8 +1133,8 @@ export function initPersonas() {
saveSettingsDebounced();
});
$('#persona_grid_toggle').on('click', () => {
- const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true';
- localStorage.setItem(GRID_STORAGE_KEY, String(!state));
+ const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true';
+ accountStorage.setItem(GRID_STORAGE_KEY, String(!state));
switchPersonaGridView();
});
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 66b298954..8e71ea76a 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -54,6 +54,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
+import { accountStorage } from './util/AccountStorage.js';
export {
loadPowerUserSettings,
@@ -2019,7 +2020,7 @@ export function renderStoryString(params) {
*/
function validateStoryString(storyString, params) {
/** @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);
@@ -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');
}
- localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache));
+ accountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache));
}
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index f22a5f2b0..42eb20f4a 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -75,6 +75,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
+import { accountStorage } from './util/AccountStorage.js';
export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
};
@@ -3606,9 +3607,9 @@ export async function sendMessageAs(args, text) {
if (!name) {
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 });
- localStorage.setItem(namelessWarningKey, 'true');
+ accountStorage.setItem(namelessWarningKey, 'true');
}
name = name2;
}
diff --git a/public/scripts/st-context.js b/public/scripts/st-context.js
index 3fb99bd61..a9a604032 100644
--- a/public/scripts/st-context.js
+++ b/public/scripts/st-context.js
@@ -68,12 +68,14 @@ import { tag_map, tags } from './tags.js';
import { textgenerationwebui_settings } from './textgen-settings.js';
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
import { ToolManager } from './tool-calling.js';
+import { accountStorage } from './util/AccountStorage.js';
import { timestampToMoment, uuidv4 } from './utils.js';
import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
export function getContext() {
return {
+ accountStorage,
chat,
characters,
groups,
diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js
index 9523cfba5..962df1a5d 100644
--- a/public/scripts/textgen-models.js
+++ b/public/scripts/textgen-models.js
@@ -6,6 +6,7 @@ import { tokenizers } from './tokenizers.js';
import { renderTemplateAsync } from './templates.js';
import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { t } from './i18n.js';
+import { accountStorage } from './util/AccountStorage.js';
let mancerModels = [];
let togetherModels = [];
@@ -336,7 +337,7 @@ export async function loadFeatherlessModels(data) {
populateClassSelection(data);
// 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
applyFiltersAndSort();
@@ -412,7 +413,7 @@ export async function loadFeatherlessModels(data) {
},
afterSizeSelectorChange: function (e) {
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
},
});
@@ -513,7 +514,7 @@ export async function loadFeatherlessModels(data) {
const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model);
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
diff --git a/public/scripts/user.js b/public/scripts/user.js
index 5db961019..4aa61967b 100644
--- a/public/scripts/user.js
+++ b/public/scripts/user.js
@@ -43,6 +43,14 @@ export function isAdmin() {
return Boolean(currentUser.admin);
}
+/**
+ * Gets the handle string of the current user.
+ * @returns {string} User handle
+ */
+export function getCurrentUserHandle() {
+ return currentUser?.handle || 'default-user';
+}
+
/**
* Get the current user.
* @returns {Promise}
diff --git a/public/scripts/util/AccountStorage.js b/public/scripts/util/AccountStorage.js
new file mode 100644
index 000000000..01d07c0aa
--- /dev/null
+++ b/public/scripts/util/AccountStorage.js
@@ -0,0 +1,137 @@
+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_/,
+ /^qr--executeShortcut$/,
+ /^qr--syntax$/,
+ /^qr--tabSize$/,
+ /^qr--wrap$/,
+ /^RegenerateWithCtrlEnter$/,
+ /^SelectedNavTab$/,
+ /^sendAsNamelessWarningShown$/,
+ /^StoryStringValidationCache$/,
+ /^WINavOpened$/,
+ /^WI_PerPage$/,
+ /^world_info_sort_order$/,
+];
+
+/**
+ * Provides access to account storage of arbitrary key-value pairs.
+ */
+class AccountStorage {
+ /**
+ * @type {Record} Storage state
+ */
+ #state = {};
+
+ /**
+ * @type {boolean} If the storage was initialized
+ */
+ #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})`);
+ }
+
+ if (!Object.hasOwn(this.#state, key)) {
+ return;
+ }
+
+ delete this.#state[key];
+ saveSettingsDebounced();
+ }
+
+ /**
+ * Gets a snapshot of the storage state.
+ * @returns {Record} A deep clone of the storage state
+ */
+ getState() {
+ return structuredClone(this.#state);
+ }
+}
+
+/**
+ * Account storage instance.
+ */
+export const accountStorage = new AccountStorage();
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index 2dfb4c869..57866be99 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -21,6 +21,7 @@ import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
import { StructuredCloneMap } from './util/StructuredCloneMap.js';
import { renderTemplateAsync } from './templates.js';
import { t } from './i18n.js';
+import { accountStorage } from './util/AccountStorage.js';
export const world_info_insertion_strategy = {
evenly: 0,
@@ -872,7 +873,7 @@ export function setWorldInfoSettings(settings, data) {
$('#world_editor_select').append(``);
});
- $('#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_editor_select').trigger('change');
@@ -1947,13 +1948,13 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
if (typeof navigation === 'number' && Number(navigation) >= 0) {
const data = getDataArray();
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;
}
$('#world_info_pagination').pagination({
dataSource: getDataArray,
- pageSize: Number(localStorage.getItem(storageKey)) || perPageDefault,
+ pageSize: Number(accountStorage.getItem(storageKey)) || perPageDefault,
sizeChangerOptions: [10, 25, 50, 100, 500, 1000],
showSizeChanger: true,
pageRange: 1,
@@ -1983,7 +1984,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
worldEntriesList.append(blocks);
},
afterSizeSelectorChange: function (e) {
- localStorage.setItem(storageKey, e.target.value);
+ accountStorage.setItem(storageKey, e.target.value);
},
afterPaging: 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 (!searchTerm && !isHidden) {
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
const checkKey = `AlertWI_${characters[chid].avatar}`;
const worldName = characters[chid]?.data?.extensions?.world;
- if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
- localStorage.setItem(checkKey, 'true');
+ if (!accountStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
+ accountStorage.setItem(checkKey, 'true');
if (power_user.world_import_dialog) {
const html = `
This character has an embedded World/Lorebook.
@@ -5198,7 +5199,7 @@ jQuery(() => {
$('#world_info_sort_order').on('change', function () {
const value = String($(this).find(':selected').val());
// 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);
});