mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-02 12:26:59 +01:00
Reset settings option
This commit is contained in:
parent
14d7665072
commit
2b29e14e9f
@ -13,6 +13,7 @@
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="img/apple-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="img/apple-icon-144x144.png" />
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/st-tailwind.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/login.css">
|
||||
@ -21,6 +22,7 @@
|
||||
<!-- fontawesome webfonts-->
|
||||
<link href="css/fontawesome.css" rel="stylesheet">
|
||||
<link href="css/solid.css" rel="stylesheet">
|
||||
<link href="css/user.css" rel="stylesheet">
|
||||
<script src="lib/jquery-3.5.1.min.js"></script>
|
||||
<script src="scripts/login.js"></script>
|
||||
<title>SillyTavern</title>
|
||||
|
@ -80,7 +80,7 @@
|
||||
<div class="flex-container flexNoGap">
|
||||
<span data-i18n="Display Name:">Display Name:</span>
|
||||
<span class="warning">*</span>
|
||||
<input name="_name" class="text_pole" type="text" placeholder="Anonymous" autocomplete="username">
|
||||
<input name="_name" class="text_pole" type="text" placeholder="e.g. John" autocomplete="username">
|
||||
</div>
|
||||
<div class="flex-container flexNoGap">
|
||||
<span data-i18n="Password:">Password:</span>
|
||||
|
8
public/scripts/templates/resetSettings.html
Normal file
8
public/scripts/templates/resetSettings.html
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="flex-container flexFlowColumn marginBot10">
|
||||
<h3 data-i18n="Are you sure you want to reset your settings to factory defaults?">
|
||||
Are you sure you want to reset your settings to factory defaults?
|
||||
</h3>
|
||||
<div data-i18n="Don't forget to save a snapshot of your settings before proceeding.">
|
||||
Don't forget to save a snapshot of your settings before proceeding.
|
||||
</div>
|
||||
</div>
|
@ -1,7 +1,10 @@
|
||||
<div class="flex-container flexFlowColumn justifyLeft flexGap10">
|
||||
<div>
|
||||
<h2 class="marginBot10">
|
||||
<span>Hi,</span> <span class="userName margin0"></span>
|
||||
<h2 class="marginBot10 flex-container">
|
||||
<span>Hi,</span><span class="userName margin0"></span>
|
||||
<div class="userChangeNameButton right_menu_button" title="Change display name.">
|
||||
<i class="fa-fw fa-solid fa-pencil fa-xs"></i>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
@ -10,7 +13,7 @@
|
||||
</h3>
|
||||
<div class="flex-container flexGap10">
|
||||
<div>
|
||||
<div class="avatar" title="To change your user avatar, select a default persona in Persona Management menu.">
|
||||
<div class="avatar" title="To change your user avatar, select a default persona in the Persona Management menu.">
|
||||
<img src="img/ai4.png" alt="avatar">
|
||||
</div>
|
||||
</div>
|
||||
@ -55,7 +58,7 @@
|
||||
<i class="fa-fw fa-solid fa-camera"></i>
|
||||
<span data-i18n="Settings Snapshots">Settings Snapshots</span>
|
||||
</div>
|
||||
<div class="userBackupButton menu_button menu_button_icon" title="Download a complete backup of user data.">
|
||||
<div class="userBackupButton menu_button menu_button_icon" title="Download a complete backup of your user data.">
|
||||
<i class="fa-fw fa-solid fa-download"></i>
|
||||
<span data-i18n="Download Backup">Download Backup</span>
|
||||
</div>
|
||||
@ -67,11 +70,11 @@
|
||||
Danger Zone
|
||||
</h3>
|
||||
<div class="flex-container">
|
||||
<div class="userResetSettings menu_button menu_button_icon" title="Reset your settings to factory defaults.">
|
||||
<div class="userResetSettingsButton menu_button menu_button_icon" title="Reset your settings to factory defaults.">
|
||||
<i class="fa-fw fa-solid fa-cog warning"></i>
|
||||
<span data-i18n="Reset Settings">Reset Settings</span>
|
||||
</div>
|
||||
<div class="userResetAll menu_button menu_button_icon" title="Wipe all user data and reset your account to factory settings.">
|
||||
<div class="userResetAllButton menu_button menu_button_icon" title="Wipe all user data and reset your account to factory settings.">
|
||||
<i class="fa-fw fa-solid fa-skull warning"></i>
|
||||
<span data-i18n="Reset Everything">Reset Everything</span>
|
||||
</div>
|
||||
|
@ -357,6 +357,39 @@ async function deleteUser(handle, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a user's settings.
|
||||
* @param {string} handle User handle
|
||||
* @param {function} callback Success callback
|
||||
*/
|
||||
async function resetSettings(handle, callback) {
|
||||
try {
|
||||
const template = $(renderTemplate('resetSettings'));
|
||||
const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Reset', cancelButton: 'Cancel', wide: false, large: false });
|
||||
|
||||
if (result !== POPUP_RESULT.AFFIRMATIVE) {
|
||||
throw new Error('Reset settings cancelled');
|
||||
}
|
||||
|
||||
const response = await fetch('/api/users/reset-settings', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ handle }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
toastr.error(data.error || 'Unknown error', 'Failed to reset settings');
|
||||
throw new Error('Failed to reset settings');
|
||||
}
|
||||
|
||||
toastr.success('Settings reset successfully', 'Settings Reset');
|
||||
callback();
|
||||
} catch (error) {
|
||||
console.error('Error resetting settings:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function openUserProfile() {
|
||||
await getCurrentUser();
|
||||
const template = $(renderTemplate('userProfile'));
|
||||
@ -367,13 +400,18 @@ async function openUserProfile() {
|
||||
template.find('.userCreated').text(new Date(currentUser.created).toLocaleString());
|
||||
template.find('.hasPassword').toggle(currentUser.password);
|
||||
template.find('.noPassword').toggle(!currentUser.password);
|
||||
template.find('.userChangePasswordButton').on('click', () => changePassword(currentUser.handle, () => { }));
|
||||
template.find('.userChangePasswordButton').on('click', () => changePassword(currentUser.handle, async () => {
|
||||
await getCurrentUser();
|
||||
template.find('.hasPassword').toggle(currentUser.password);
|
||||
template.find('.noPassword').toggle(!currentUser.password);
|
||||
}));
|
||||
template.find('.userBackupButton').on('click', function () {
|
||||
$(this).addClass('disabled');
|
||||
backupUserData(currentUser.handle, () => {
|
||||
$(this).removeClass('disabled');
|
||||
});
|
||||
});
|
||||
template.find('.userResetSettingsButton').on('click', () => resetSettings(currentUser.handle, () => location.reload()));
|
||||
|
||||
callGenericPopup(template, POPUP_TYPE.TEXT, '', { okButton: 'Close', wide: false, large: false, allowVerticalScrolling: true, allowHorizontalScrolling: false });
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ const PUBLIC_DIRECTORIES = {
|
||||
};
|
||||
|
||||
const DEFAULT_AVATAR = '/img/ai4.png';
|
||||
const SETTINGS_FILE = 'settings.json';
|
||||
|
||||
/**
|
||||
* @type {import('./users').UserDirectoryList}
|
||||
@ -300,6 +301,7 @@ const OPENROUTER_KEYS = [
|
||||
module.exports = {
|
||||
DEFAULT_USER,
|
||||
DEFAULT_AVATAR,
|
||||
SETTINGS_FILE,
|
||||
PUBLIC_DIRECTORIES,
|
||||
USER_DIRECTORY_TEMPLATE,
|
||||
UNSAFE_EXTENSIONS,
|
||||
|
@ -15,6 +15,29 @@ const characterCardParser = require('../character-card-parser.js');
|
||||
* @property {string} type
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string} ContentType
|
||||
* @enum {string}
|
||||
*/
|
||||
const CONTENT_TYPES = {
|
||||
SETTINGS: 'settings',
|
||||
CHARACTER: 'character',
|
||||
SPRITES: 'sprites',
|
||||
BACKGROUND: 'background',
|
||||
WORLD: 'world',
|
||||
AVATAR: 'avatar',
|
||||
THEME: 'theme',
|
||||
WORKFLOW: 'workflow',
|
||||
KOBOLD_PRESET: 'kobold_preset',
|
||||
OPENAI_PRESET: 'openai_preset',
|
||||
NOVEL_PRESET: 'novel_preset',
|
||||
TEXTGEN_PRESET: 'textgen_preset',
|
||||
INSTRUCT: 'instruct',
|
||||
CONTEXT: 'context',
|
||||
MOVING_UI: 'moving_ui',
|
||||
QUICK_REPLIES: 'quick_replies',
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the default presets from the content directory.
|
||||
* @param {import('../users').UserDirectoryList} directories User directories
|
||||
@ -67,8 +90,9 @@ function getDefaultPresetFile(filename) {
|
||||
* Seeds content for a user.
|
||||
* @param {ContentItem[]} contentIndex Content index
|
||||
* @param {import('../users').UserDirectoryList} directories User directories
|
||||
* @param {string[]} forceCategories List of categories to force check (even if content check is skipped)
|
||||
*/
|
||||
async function seedContentForUser(contentIndex, directories) {
|
||||
async function seedContentForUser(contentIndex, directories, forceCategories) {
|
||||
if (!fs.existsSync(directories.root)) {
|
||||
fs.mkdirSync(directories.root, { recursive: true });
|
||||
}
|
||||
@ -78,7 +102,7 @@ async function seedContentForUser(contentIndex, directories) {
|
||||
|
||||
for (const contentItem of contentIndex) {
|
||||
// If the content item is already in the log, skip it
|
||||
if (contentLog.includes(contentItem.filename)) {
|
||||
if (contentLog.includes(contentItem.filename) && !forceCategories?.includes(contentItem.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -115,11 +139,12 @@ async function seedContentForUser(contentIndex, directories) {
|
||||
/**
|
||||
* Checks for new content and seeds it for all users.
|
||||
* @param {import('../users').UserDirectoryList[]} directoriesList List of user directories
|
||||
* @param {string[]} forceCategories List of categories to force check (even if content check is skipped)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function checkForNewContent(directoriesList) {
|
||||
async function checkForNewContent(directoriesList, forceCategories = []) {
|
||||
try {
|
||||
if (getConfigValue('skipContentCheck', false)) {
|
||||
if (getConfigValue('skipContentCheck', false) && forceCategories?.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -127,7 +152,7 @@ async function checkForNewContent(directoriesList) {
|
||||
const contentIndex = JSON.parse(contentIndexText);
|
||||
|
||||
for (const directories of directoriesList) {
|
||||
await seedContentForUser(contentIndex, directories);
|
||||
await seedContentForUser(contentIndex, directories, forceCategories);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Content check failed', err);
|
||||
@ -136,43 +161,43 @@ async function checkForNewContent(directoriesList) {
|
||||
|
||||
/**
|
||||
* Gets the target directory for the specified asset type.
|
||||
* @param {string} type Asset type
|
||||
* @param {ContentType} type Asset type
|
||||
* @param {import('../users').UserDirectoryList} directories User directories
|
||||
* @returns {string | null} Target directory
|
||||
*/
|
||||
function getTargetByType(type, directories) {
|
||||
switch (type) {
|
||||
case 'settings':
|
||||
case CONTENT_TYPES.SETTINGS:
|
||||
return directories.root;
|
||||
case 'character':
|
||||
case CONTENT_TYPES.CHARACTER:
|
||||
return directories.characters;
|
||||
case 'sprites':
|
||||
case CONTENT_TYPES.SPRITES:
|
||||
return directories.characters;
|
||||
case 'background':
|
||||
case CONTENT_TYPES.BACKGROUND:
|
||||
return directories.backgrounds;
|
||||
case 'world':
|
||||
case CONTENT_TYPES.WORLD:
|
||||
return directories.worlds;
|
||||
case 'avatar':
|
||||
case CONTENT_TYPES.AVATAR:
|
||||
return directories.avatars;
|
||||
case 'theme':
|
||||
case CONTENT_TYPES.THEME:
|
||||
return directories.themes;
|
||||
case 'workflow':
|
||||
case CONTENT_TYPES.WORKFLOW:
|
||||
return directories.comfyWorkflows;
|
||||
case 'kobold_preset':
|
||||
case CONTENT_TYPES.KOBOLD_PRESET:
|
||||
return directories.koboldAI_Settings;
|
||||
case 'openai_preset':
|
||||
case CONTENT_TYPES.OPENAI_PRESET:
|
||||
return directories.openAI_Settings;
|
||||
case 'novel_preset':
|
||||
case CONTENT_TYPES.NOVEL_PRESET:
|
||||
return directories.novelAI_Settings;
|
||||
case 'textgen_preset':
|
||||
case CONTENT_TYPES.TEXTGEN_PRESET:
|
||||
return directories.textGen_Settings;
|
||||
case 'instruct':
|
||||
case CONTENT_TYPES.INSTRUCT:
|
||||
return directories.instruct;
|
||||
case 'context':
|
||||
case CONTENT_TYPES.CONTEXT:
|
||||
return directories.context;
|
||||
case 'moving_ui':
|
||||
case CONTENT_TYPES.MOVING_UI:
|
||||
return directories.movingUI;
|
||||
case 'quick_replies':
|
||||
case CONTENT_TYPES.QUICK_REPLIES:
|
||||
return directories.quickreplies;
|
||||
default:
|
||||
return null;
|
||||
@ -477,6 +502,7 @@ router.post('/importUUID', jsonParser, async (request, response) => {
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
CONTENT_TYPES,
|
||||
checkForNewContent,
|
||||
getDefaultPresets,
|
||||
getDefaultPresetFile,
|
||||
|
@ -2,12 +2,11 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||
const { PUBLIC_DIRECTORIES } = require('../constants');
|
||||
const { PUBLIC_DIRECTORIES, SETTINGS_FILE } = require('../constants');
|
||||
const { getConfigValue, generateTimestamp, removeOldBackups } = require('../util');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { getAllUserHandles, getUserDirectories } = require('../users');
|
||||
|
||||
const SETTINGS_FILE = 'settings.json';
|
||||
const ENABLE_EXTENSIONS = getConfigValue('enableExtensions', true);
|
||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
const path = require('path');
|
||||
const fsPromises = require('fs').promises;
|
||||
const storage = require('node-persist');
|
||||
const express = require('express');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { getUserAvatar, toKey, getPasswordHash, getPasswordSalt, createBackupArchive } = require('../users');
|
||||
const { SETTINGS_FILE } = require('../constants');
|
||||
const contentManager = require('./content-manager');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -111,6 +115,19 @@ router.post('/backup', jsonParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/reset-settings', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const pathToFile = path.join(request.user.directories.root, SETTINGS_FILE);
|
||||
await fsPromises.rm(pathToFile, { force: true });
|
||||
await contentManager.checkForNewContent([request.user.directories], [contentManager.CONTENT_TYPES.SETTINGS]);
|
||||
|
||||
return response.sendStatus(204);
|
||||
} catch (error) {
|
||||
console.error('Reset settings failed', error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ const express = require('express');
|
||||
const mime = require('mime-types');
|
||||
const archiver = require('archiver');
|
||||
|
||||
const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, DEFAULT_AVATAR } = require('./constants');
|
||||
const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, DEFAULT_AVATAR, SETTINGS_FILE } = require('./constants');
|
||||
const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = require('./util');
|
||||
const { readSecret, writeSecret } = require('./endpoints/secrets');
|
||||
|
||||
@ -431,7 +431,7 @@ function getUserDirectories(handle) {
|
||||
function getUserAvatar(handle) {
|
||||
try {
|
||||
const directory = getUserDirectories(handle);
|
||||
const pathToSettings = path.join(directory.root, 'settings.json');
|
||||
const pathToSettings = path.join(directory.root, SETTINGS_FILE);
|
||||
const settings = fs.existsSync(pathToSettings) ? JSON.parse(fs.readFileSync(pathToSettings, 'utf8')) : {};
|
||||
const avatarFile = settings?.power_user?.default_persona || settings?.user_avatar;
|
||||
if (!avatarFile) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user