diff --git a/default/config.yaml b/default/config.yaml
index 3e914009a..8b70f73be 100644
--- a/default/config.yaml
+++ b/default/config.yaml
@@ -21,7 +21,7 @@ basicAuthUser:
# Enables CORS proxy middleware
enableCorsProxy: false
# Enable multi-user mode
-enableUserAccounts: true
+enableUserAccounts: false
# Used to sign session cookies. Will be auto-generated if not set
cookieSecret: ''
# Disable security checks - NOT RECOMMENDED
diff --git a/public/index.html b/public/index.html
index 352885649..944235149 100644
--- a/public/index.html
+++ b/public/index.html
@@ -88,6 +88,7 @@
+
SillyTavern
@@ -3452,7 +3453,21 @@
-
+
+
+
+ Account
+
+
+
+ Admin Panel
+
+
+
+ Logout
+
+
+
@@ -5277,17 +5292,16 @@
Enable simple UI mode
+
+ Your Persona
+
-
- Before you get started, you must select a user name.
+
+ Before you get started, you must select a persona name.
This can be changed at any time via the icon.
-
UI Language:
-
-
User Name:
+
Persona Name:
diff --git a/public/script.js b/public/script.js
index e81adefef..dd697b331 100644
--- a/public/script.js
+++ b/public/script.js
@@ -211,6 +211,7 @@ import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermati
import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js';
import { initPresetManager } from './scripts/preset-manager.js';
import { evaluateMacros } from './scripts/macros.js';
+import { currentUser, setUserControls } from './scripts/user.js';
//exporting functions and vars for mods
export {
@@ -6097,7 +6098,7 @@ async function doOnboarding(avatarId) {
template.find('input[name="enable_simple_mode"]').on('input', function () {
simpleUiMode = $(this).is(':checked');
});
- var userName = await callPopup(template, 'input', name1);
+ let userName = await callPopup(template, 'input', currentUser?.name || name1);
if (userName) {
userName = userName.replace('\n', ' ');
@@ -6151,6 +6152,8 @@ async function getSettings() {
$('#your_name').val(name1);
}
+ await setUserControls(data.enable_accounts);
+
// Allow subscribers to mutate settings
eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings);
diff --git a/public/scripts/templates/admin.html b/public/scripts/templates/admin.html
new file mode 100644
index 000000000..4db762b76
--- /dev/null
+++ b/public/scripts/templates/admin.html
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @userhandle
+
+
+
+ Role:
+
+
+
+ Status:
+ Status
+
+
+ Created:
+ Date
+
+
+
+
+
Edit
+
Disable
+
Enable
+
+
+
+
+
+
+
+
+
+
diff --git a/public/scripts/user.js b/public/scripts/user.js
new file mode 100644
index 000000000..b51a463c3
--- /dev/null
+++ b/public/scripts/user.js
@@ -0,0 +1,236 @@
+import { callPopup, getRequestHeaders, renderTemplate } from '../script.js';
+
+/**
+ * @type {import('../../src/users.js').User} Logged in user
+ */
+export let currentUser = null;
+
+/**
+ * Enable or disable user account controls in the UI.
+ * @param {boolean} isEnabled User account controls enabled
+ * @returns {Promise}
+ */
+export async function setUserControls(isEnabled) {
+ if (!isEnabled) {
+ $('#account_controls').hide();
+ return;
+ }
+
+ $('#account_controls').show();
+ await getCurrentUser();
+}
+
+/**
+ * Check if the current user is an admin.
+ * @returns {boolean} True if the current user is an admin
+ */
+function isAdmin() {
+ if (!currentUser) {
+ return false;
+ }
+
+ return Boolean(currentUser.admin);
+}
+
+/**
+ * Get the current user.
+ * @returns {Promise}
+ */
+async function getCurrentUser() {
+ try {
+ const response = await fetch('/api/users/me', {
+ headers: getRequestHeaders(),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to get current user');
+ }
+
+ currentUser = await response.json();
+ $('#admin_button').toggle(isAdmin());
+ } catch (error) {
+ console.error('Error getting current user:', error);
+ }
+}
+
+async function getUsers() {
+ try {
+ const response = await fetch('/api/users/get', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to get users');
+ }
+
+ return response.json();
+ } catch (error) {
+ console.error('Error getting users:', error);
+ }
+}
+
+/**
+ * Enable a user account.
+ * @param {string} handle User handle
+ * @param {function} callback Success callback
+ * @returns {Promise}
+ */
+async function enableUser(handle, callback) {
+ try {
+ const response = await fetch('/api/users/enable', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ handle }),
+ });
+
+ if (!response.ok) {
+ const data = await response.json();
+ toastr.error(data.error || 'Unknown error', 'Failed to enable user');
+ throw new Error('Failed to enable user');
+ }
+
+ callback();
+ } catch (error) {
+ console.error('Error enabling user:', error);
+ }
+}
+
+async function disableUser(handle, callback) {
+ try {
+ const response = await fetch('/api/users/disable', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({ handle }),
+ });
+
+ if (!response.ok) {
+ const data = await response.json();
+ toastr.error(data?.error || 'Unknown error', 'Failed to disable user');
+ throw new Error('Failed to disable user');
+ }
+
+ callback();
+ } catch (error) {
+ console.error('Error disabling user:', error);
+ }
+}
+
+/**
+ * Create a new user.
+ * @param {HTMLFormElement} form Form element
+ */
+async function createUser(form, callback) {
+ const errors = [];
+ const formData = new FormData(form);
+
+ if (!formData.get('handle')) {
+ errors.push('Handle is required');
+ }
+
+ if (formData.get('password') !== formData.get('confirm')) {
+ errors.push('Passwords do not match');
+ }
+
+ if (errors.length) {
+ toastr.error(errors.join(', '), 'Failed to create user');
+ return;
+ }
+
+ const body = {};
+ formData.forEach(function (value, key) {
+ if (key === 'confirm') {
+ return;
+ }
+ if (key.startsWith('_')) {
+ key = key.substring(1);
+ }
+ body[key] = value;
+ });
+
+ try {
+ const response = await fetch('/api/users/create', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ const data = await response.json();
+ toastr.error(data.error || 'Unknown error', 'Failed to create user');
+ throw new Error('Failed to create user');
+ }
+
+ form.reset();
+ callback();
+ } catch (error) {
+ console.error('Error creating user:', error);
+ }
+}
+
+async function openAdminPanel() {
+ async function renderUsers() {
+ const users = await getUsers();
+ template.find('.usersList').empty();
+ for (const user of users) {
+ const userBlock = template.find('.userAccountTemplate .userAccount').clone();
+ userBlock.find('.userName').text(user.name);
+ userBlock.find('.userHandle').text(user.handle);
+ userBlock.find('.userStatus').text(user.enabled ? 'Enabled' : 'Disabled');
+ userBlock.find('.userRole').text(user.admin ? 'Admin' : 'User');
+ userBlock.find('.avatar img').attr('src', user.avatar);
+ userBlock.find('.hasPassword').toggle(user.password);
+ userBlock.find('.noPassword').toggle(!user.password);
+ userBlock.find('.userCreated').text(new Date(user.created).toLocaleString());
+ userBlock.find('.userEnableButton').toggle(!user.enabled).on('click', () => enableUser(user.handle, renderUsers));
+ userBlock.find('.userDisableButton').toggle(user.enabled).on('click', () => disableUser(user.handle, renderUsers));
+ template.find('.usersList').append(userBlock);
+ }
+ }
+
+ const template = $(renderTemplate('admin'));
+
+ template.find('.adminNav > button').on('click', function () {
+ const target = String($(this).data('target-tab'));
+ template.find('.navTab').each(function () {
+ $(this).toggle(this.classList.contains(target));
+ });
+ });
+
+ template.find('.userCreateForm').on('submit', function (event) {
+ if (!(event.target instanceof HTMLFormElement)) {
+ return;
+ }
+
+ event.preventDefault();
+ createUser(event.target, () => {
+ template.find('.manageUsersButton').trigger('click');
+ renderUsers();
+ });
+ });
+
+ callPopup(template, 'text', '', { okButton: 'Close', wide: true, large: true, allowVerticalScrolling: true, allowHorizontalScrolling: false });
+ renderUsers();
+}
+
+/**
+ * Log out the current user.
+ * @returns {Promise}
+ */
+async function logout() {
+ await fetch('/api/users/logout', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ });
+
+ window.location.reload();
+}
+
+jQuery(() => {
+ $('#logout_button').on('click', () => {
+ logout();
+ });
+ $('#admin_button').on('click', () => {
+ openAdminPanel();
+ });
+});
diff --git a/public/scripts/userManagement.js b/public/scripts/userManagement.js
deleted file mode 100644
index e3cd9d34f..000000000
--- a/public/scripts/userManagement.js
+++ /dev/null
@@ -1,161 +0,0 @@
-async function registerNewUser() {
- let handle = String($("#newUserHandle").val());
- let name = String($("#newUserName").val());
- let password = String($("#newUserPassword").val());
- let passwordConfirm = String($("#newUserPasswordConfirm").val());
-
- if (handle.length < 4) {
- alert('Username must be at least 4 characters long');
- return;
- }
-
- if (password.length < 8) {
- alert('Password must be at least 8 characters long');
- return;
- }
-
- if (password !== passwordConfirm) {
- alert("Passwords don't match!")
- return
- }
-
- const newUser = {
- handle: handle,
- name: name || 'Anonymous',
- password: password,
- enabled: true
- };
-
- try {
- const response = await $.ajax({
- url: '/api/users/create',
- type: 'POST',
- contentType: 'application/json',
- data: JSON.stringify(newUser),
- });
-
- console.log(response);
- if (response.handle) {
- console.log('saw user created successfully')
- alert('New user created!')
- $("#userSelectBlock").empty()
- populateUserList()
- $("#userListBlock").show()
- $("#registerNewUserBlock").hide()
- $("#registerNewUserBlock input").val('')
-
- }
- } catch (error) {
- console.error('Error creating new user:', error);
- alert(error.responseText)
- }
-}
-
-async function loginUser() {
- const password = $("#userPassword").val();
- const handle = $('.userSelect.selected').data('foruser');
- const userInfo = {
- handle: handle,
- password: password,
- };
-
- try {
- const response = await $.ajax({
- url: '/api/users/login',
- type: 'POST',
- contentType: 'application/json',
- data: JSON.stringify(userInfo),
- });
-
- console.log(response);
- if (response.handle) {
- console.log('successfully logged in');
- alert(`logged in as ${handle}!`);
- $('#loader').animate({ opacity: 0 }, 300, function () {
- // Insert user handle/password verification code here
- // .finally:
- $('#loader').remove();
- });
- }
- } catch (error) {
- console.error('Error logging in:', error);
- alert(error.responseText);
- }
-}
-
-export async function populateUserList() {
- const userList = await getUserList();
-
- const registerNewUserButtonHTML = `
New User
`
-
- const newUserRegisterationHTML = `
-
- Register New SillyTavern User
-
Username:
-
Display Name:
-
Password:
-
Password confirm:
- This will create a new subfolder in the /data/ directory.
-