Split user directories from public, part 1
This commit is contained in:
parent
b3b7017bf2
commit
cd5aec7368
|
@ -45,3 +45,4 @@ access.log
|
||||||
/cache/
|
/cache/
|
||||||
public/css/user.css
|
public/css/user.css
|
||||||
/plugins/
|
/plugins/
|
||||||
|
/data
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# -- NETWORK CONFIGURATION --
|
# -- NETWORK CONFIGURATION --
|
||||||
|
# Root directory for user data storage
|
||||||
|
dataRoot: ./data
|
||||||
# Listen for incoming connections
|
# Listen for incoming connections
|
||||||
listen: false
|
listen: false
|
||||||
# Server port
|
# Server port
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
"filename": "settings.json",
|
||||||
|
"type": "settings"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "themes/Dark Lite.json",
|
"filename": "themes/Dark Lite.json",
|
||||||
"type": "theme"
|
"type": "theme"
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { UserDirectoryList, User } from "./src/users";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
export interface Request {
|
||||||
|
user: {
|
||||||
|
profile: User;
|
||||||
|
directories: UserDirectoryList;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -106,7 +106,6 @@ function addMissingConfigValues() {
|
||||||
*/
|
*/
|
||||||
function createDefaultFiles() {
|
function createDefaultFiles() {
|
||||||
const files = {
|
const files = {
|
||||||
settings: './public/settings.json',
|
|
||||||
config: './config.yaml',
|
config: './config.yaml',
|
||||||
user: './public/css/user.css',
|
user: './public/css/user.css',
|
||||||
};
|
};
|
||||||
|
@ -167,29 +166,6 @@ function copyWasmFiles() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the custom background into settings.json.
|
|
||||||
*/
|
|
||||||
function migrateBackground() {
|
|
||||||
if (!fs.existsSync('./public/css/bg_load.css')) return;
|
|
||||||
|
|
||||||
const bgCSS = fs.readFileSync('./public/css/bg_load.css', 'utf-8');
|
|
||||||
const bgMatch = /url\('([^']*)'\)/.exec(bgCSS);
|
|
||||||
if (!bgMatch) return;
|
|
||||||
const bgFilename = bgMatch[1].replace('../backgrounds/', '');
|
|
||||||
|
|
||||||
const settings = fs.readFileSync('./public/settings.json', 'utf-8');
|
|
||||||
const settingsJSON = JSON.parse(settings);
|
|
||||||
if (Object.hasOwn(settingsJSON, 'background')) {
|
|
||||||
console.log(color.yellow('Both bg_load.css and the "background" setting exist. Please delete bg_load.css manually.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsJSON.background = { name: bgFilename, url: `url('backgrounds/${bgFilename}')` };
|
|
||||||
fs.writeFileSync('./public/settings.json', JSON.stringify(settingsJSON, null, 4));
|
|
||||||
fs.rmSync('./public/css/bg_load.css');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 0. Convert config.conf to config.yaml
|
// 0. Convert config.conf to config.yaml
|
||||||
convertConfig();
|
convertConfig();
|
||||||
|
@ -199,8 +175,6 @@ try {
|
||||||
copyWasmFiles();
|
copyWasmFiles();
|
||||||
// 3. Add missing config values
|
// 3. Add missing config values
|
||||||
addMissingConfigValues();
|
addMissingConfigValues();
|
||||||
// 4. Migrate bg_load.css to settings.json
|
|
||||||
migrateBackground();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
41
server.js
41
server.js
|
@ -33,6 +33,7 @@ util.inspect.defaultOptions.maxStringLength = null;
|
||||||
util.inspect.defaultOptions.depth = 4;
|
util.inspect.defaultOptions.depth = 4;
|
||||||
|
|
||||||
// local library imports
|
// local library imports
|
||||||
|
const { initUserStorage, userDataMiddleware, getUserDirectories, getAllUserHandles } = require('./src/users');
|
||||||
const basicAuthMiddleware = require('./src/middleware/basicAuth');
|
const basicAuthMiddleware = require('./src/middleware/basicAuth');
|
||||||
const whitelistMiddleware = require('./src/middleware/whitelist');
|
const whitelistMiddleware = require('./src/middleware/whitelist');
|
||||||
const contentManager = require('./src/endpoints/content-manager');
|
const contentManager = require('./src/endpoints/content-manager');
|
||||||
|
@ -112,7 +113,7 @@ const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
|
||||||
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
|
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
|
||||||
const basicAuthMode = getConfigValue('basicAuthMode', false);
|
const basicAuthMode = getConfigValue('basicAuthMode', false);
|
||||||
|
|
||||||
const { DIRECTORIES, UPLOADS_PATH } = require('./src/constants');
|
const { UPLOADS_PATH, PUBLIC_DIRECTORIES } = require('./src/constants');
|
||||||
|
|
||||||
// CORS Settings //
|
// CORS Settings //
|
||||||
const CORS = cors({
|
const CORS = cors({
|
||||||
|
@ -211,29 +212,8 @@ if (enableCorsProxy) {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(express.static(process.cwd() + '/public', {}));
|
app.use(express.static(process.cwd() + '/public', {}));
|
||||||
|
app.use(userDataMiddleware(app));
|
||||||
|
|
||||||
app.use('/backgrounds', (req, res) => {
|
|
||||||
const filePath = decodeURIComponent(path.join(process.cwd(), DIRECTORIES.backgrounds, req.url.replace(/%20/g, ' ')));
|
|
||||||
fs.readFile(filePath, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
res.status(404).send('File not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//res.contentType('image/jpeg');
|
|
||||||
res.send(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use('/characters', (req, res) => {
|
|
||||||
const filePath = decodeURIComponent(path.join(process.cwd(), DIRECTORIES.characters, req.url.replace(/%20/g, ' ')));
|
|
||||||
fs.readFile(filePath, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
res.status(404).send('File not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.send(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar'));
|
app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar'));
|
||||||
app.get('/', function (request, response) {
|
app.get('/', function (request, response) {
|
||||||
response.sendFile(process.cwd() + '/public/index.html');
|
response.sendFile(process.cwd() + '/public/index.html');
|
||||||
|
@ -487,6 +467,7 @@ const setupTasks = async function () {
|
||||||
|
|
||||||
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
|
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
|
||||||
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
|
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
|
||||||
|
await initUserStorage();
|
||||||
await settingsEndpoint.init();
|
await settingsEndpoint.init();
|
||||||
ensurePublicDirectoriesExist();
|
ensurePublicDirectoriesExist();
|
||||||
contentManager.checkForNewContent();
|
contentManager.checkForNewContent();
|
||||||
|
@ -579,10 +560,20 @@ if (cliArguments.ssl) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensurePublicDirectoriesExist() {
|
async function ensurePublicDirectoriesExist() {
|
||||||
for (const dir of Object.values(DIRECTORIES)) {
|
for (const dir of Object.values(PUBLIC_DIRECTORIES)) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userHandles = await getAllUserHandles();
|
||||||
|
for (const handle of userHandles) {
|
||||||
|
const userDirectories = getUserDirectories(handle);
|
||||||
|
for (const dir of Object.values(userDirectories)) {
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,34 +1,51 @@
|
||||||
const DIRECTORIES = {
|
const PUBLIC_DIRECTORIES = {
|
||||||
worlds: 'public/worlds/',
|
|
||||||
user: 'public/user',
|
|
||||||
avatars: 'public/User Avatars',
|
|
||||||
images: 'public/img/',
|
images: 'public/img/',
|
||||||
userImages: 'public/user/images/',
|
|
||||||
groups: 'public/groups/',
|
|
||||||
groupChats: 'public/group chats',
|
|
||||||
chats: 'public/chats/',
|
|
||||||
characters: 'public/characters/',
|
|
||||||
backgrounds: 'public/backgrounds',
|
|
||||||
novelAI_Settings: 'public/NovelAI Settings',
|
|
||||||
koboldAI_Settings: 'public/KoboldAI Settings',
|
|
||||||
openAI_Settings: 'public/OpenAI Settings',
|
|
||||||
textGen_Settings: 'public/TextGen Settings',
|
|
||||||
thumbnails: 'thumbnails/',
|
|
||||||
thumbnailsBg: 'thumbnails/bg/',
|
|
||||||
thumbnailsAvatar: 'thumbnails/avatar/',
|
|
||||||
themes: 'public/themes',
|
|
||||||
movingUI: 'public/movingUI',
|
|
||||||
extensions: 'public/scripts/extensions',
|
|
||||||
instruct: 'public/instruct',
|
|
||||||
context: 'public/context',
|
|
||||||
backups: 'backups/',
|
backups: 'backups/',
|
||||||
quickreplies: 'public/QuickReplies',
|
|
||||||
assets: 'public/assets',
|
|
||||||
comfyWorkflows: 'public/user/workflows',
|
|
||||||
files: 'public/user/files',
|
|
||||||
sounds: 'public/sounds',
|
sounds: 'public/sounds',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('./users').UserDirectoryList}
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
const USER_DIRECTORY_TEMPLATE = Object.freeze({
|
||||||
|
root: '',
|
||||||
|
thumbnails: 'thumbnails',
|
||||||
|
thumbnailsBg: 'thumbnails/bg',
|
||||||
|
thumbnailsAvatar: 'thumbnails/avatar',
|
||||||
|
worlds: 'worlds',
|
||||||
|
user: 'user',
|
||||||
|
avatars: 'User Avatars',
|
||||||
|
userImages: 'user/images',
|
||||||
|
groups: 'groups',
|
||||||
|
groupChats: 'group chats',
|
||||||
|
chats: 'chats',
|
||||||
|
characters: 'characters',
|
||||||
|
backgrounds: 'backgrounds',
|
||||||
|
novelAI_Settings: 'NovelAI Settings',
|
||||||
|
koboldAI_Settings: 'KoboldAI Settings',
|
||||||
|
openAI_Settings: 'OpenAI Settings',
|
||||||
|
textGen_Settings: 'TextGen Settings',
|
||||||
|
themes: 'themes',
|
||||||
|
movingUI: 'movingUI',
|
||||||
|
extensions: 'scripts/extensions',
|
||||||
|
instruct: 'instruct',
|
||||||
|
context: 'context',
|
||||||
|
quickreplies: 'QuickReplies',
|
||||||
|
assets: 'assets',
|
||||||
|
comfyWorkflows: 'user/workflows',
|
||||||
|
files: 'user/files',
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_USER = Object.freeze({
|
||||||
|
uuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
handle: 'user0',
|
||||||
|
name: 'User',
|
||||||
|
created: 0,
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
|
||||||
const UNSAFE_EXTENSIONS = [
|
const UNSAFE_EXTENSIONS = [
|
||||||
'.php',
|
'.php',
|
||||||
'.exe',
|
'.exe',
|
||||||
|
@ -270,7 +287,9 @@ const OPENROUTER_KEYS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
DIRECTORIES,
|
DEFAULT_USER,
|
||||||
|
PUBLIC_DIRECTORIES,
|
||||||
|
USER_DIRECTORY_TEMPLATE,
|
||||||
UNSAFE_EXTENSIONS,
|
UNSAFE_EXTENSIONS,
|
||||||
UPLOADS_PATH,
|
UPLOADS_PATH,
|
||||||
GEMINI_SAFETY,
|
GEMINI_SAFETY,
|
||||||
|
|
|
@ -6,17 +6,16 @@ const sanitize = require('sanitize-filename');
|
||||||
const { getConfigValue } = require('../util');
|
const { getConfigValue } = require('../util');
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
const contentDirectory = path.join(process.cwd(), 'default/content');
|
const contentDirectory = path.join(process.cwd(), 'default/content');
|
||||||
const contentLogPath = path.join(contentDirectory, 'content.log');
|
|
||||||
const contentIndexPath = path.join(contentDirectory, 'index.json');
|
const contentIndexPath = path.join(contentDirectory, 'index.json');
|
||||||
const { DIRECTORIES } = require('../constants');
|
const { getAllUserHandles, getUserDirectories } = require('../users');
|
||||||
const presetFolders = [DIRECTORIES.koboldAI_Settings, DIRECTORIES.openAI_Settings, DIRECTORIES.novelAI_Settings, DIRECTORIES.textGen_Settings];
|
|
||||||
const characterCardParser = require('../character-card-parser.js');
|
const characterCardParser = require('../character-card-parser.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the default presets from the content directory.
|
* Gets the default presets from the content directory.
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
* @returns {object[]} Array of default presets
|
* @returns {object[]} Array of default presets
|
||||||
*/
|
*/
|
||||||
function getDefaultPresets() {
|
function getDefaultPresets(directories) {
|
||||||
try {
|
try {
|
||||||
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
||||||
const contentIndex = JSON.parse(contentIndexText);
|
const contentIndex = JSON.parse(contentIndexText);
|
||||||
|
@ -26,7 +25,7 @@ function getDefaultPresets() {
|
||||||
for (const contentItem of contentIndex) {
|
for (const contentItem of contentIndex) {
|
||||||
if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context') {
|
if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context') {
|
||||||
contentItem.name = path.parse(contentItem.filename).name;
|
contentItem.name = path.parse(contentItem.filename).name;
|
||||||
contentItem.folder = getTargetByType(contentItem.type);
|
contentItem.folder = getTargetByType(contentItem.type, directories);
|
||||||
presets.push(contentItem);
|
presets.push(contentItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,40 +58,25 @@ function getDefaultPresetFile(filename) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function migratePresets() {
|
async function checkForNewContent() {
|
||||||
for (const presetFolder of presetFolders) {
|
|
||||||
const presetPath = path.join(process.cwd(), presetFolder);
|
|
||||||
const presetFiles = fs.readdirSync(presetPath);
|
|
||||||
|
|
||||||
for (const presetFile of presetFiles) {
|
|
||||||
const presetFilePath = path.join(presetPath, presetFile);
|
|
||||||
const newFileName = presetFile.replace('.settings', '.json');
|
|
||||||
const newFilePath = path.join(presetPath, newFileName);
|
|
||||||
const backupFileName = presetFolder.replace('/', '_') + '_' + presetFile;
|
|
||||||
const backupFilePath = path.join(DIRECTORIES.backups, backupFileName);
|
|
||||||
|
|
||||||
if (presetFilePath.endsWith('.settings')) {
|
|
||||||
if (!fs.existsSync(newFilePath)) {
|
|
||||||
fs.cpSync(presetFilePath, backupFilePath);
|
|
||||||
fs.cpSync(presetFilePath, newFilePath);
|
|
||||||
console.log(`Migrated ${presetFilePath} to ${newFilePath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForNewContent() {
|
|
||||||
try {
|
try {
|
||||||
migratePresets();
|
|
||||||
|
|
||||||
if (getConfigValue('skipContentCheck', false)) {
|
if (getConfigValue('skipContentCheck', false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentLog = getContentLog();
|
|
||||||
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
||||||
const contentIndex = JSON.parse(contentIndexText);
|
const contentIndex = JSON.parse(contentIndexText);
|
||||||
|
const userHandles = await getAllUserHandles();
|
||||||
|
|
||||||
|
for (const userHandle of userHandles) {
|
||||||
|
const directories = getUserDirectories(userHandle);
|
||||||
|
|
||||||
|
if (!fs.existsSync(directories.root)) {
|
||||||
|
fs.mkdirSync(directories.root, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentLogPath = path.join(directories.root, 'content.log');
|
||||||
|
const contentLog = getContentLog(contentLogPath);
|
||||||
|
|
||||||
for (const contentItem of contentIndex) {
|
for (const contentItem of contentIndex) {
|
||||||
// If the content item is already in the log, skip it
|
// If the content item is already in the log, skip it
|
||||||
|
@ -108,7 +92,7 @@ function checkForNewContent() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentTarget = getTargetByType(contentItem.type);
|
const contentTarget = getTargetByType(contentItem.type, directories);
|
||||||
|
|
||||||
if (!contentTarget) {
|
if (!contentTarget) {
|
||||||
console.log(`Content file ${contentItem.filename} has unknown type ${contentItem.type}`);
|
console.log(`Content file ${contentItem.filename} has unknown type ${contentItem.type}`);
|
||||||
|
@ -128,51 +112,63 @@ function checkForNewContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(contentLogPath, contentLog.join('\n'));
|
fs.writeFileSync(contentLogPath, contentLog.join('\n'));
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Content check failed', err);
|
console.log('Content check failed', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTargetByType(type) {
|
/**
|
||||||
|
* Gets the target directory for the specified asset type.
|
||||||
|
* @param {string} type Asset type
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
|
* @returns {string | null} Target directory
|
||||||
|
*/
|
||||||
|
function getTargetByType(type, directories) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case 'settings':
|
||||||
|
return directories.root;
|
||||||
case 'character':
|
case 'character':
|
||||||
return DIRECTORIES.characters;
|
return directories.characters;
|
||||||
case 'sprites':
|
case 'sprites':
|
||||||
return DIRECTORIES.characters;
|
return directories.characters;
|
||||||
case 'background':
|
case 'background':
|
||||||
return DIRECTORIES.backgrounds;
|
return directories.backgrounds;
|
||||||
case 'world':
|
case 'world':
|
||||||
return DIRECTORIES.worlds;
|
return directories.worlds;
|
||||||
case 'sound':
|
|
||||||
return DIRECTORIES.sounds;
|
|
||||||
case 'avatar':
|
case 'avatar':
|
||||||
return DIRECTORIES.avatars;
|
return directories.avatars;
|
||||||
case 'theme':
|
case 'theme':
|
||||||
return DIRECTORIES.themes;
|
return directories.themes;
|
||||||
case 'workflow':
|
case 'workflow':
|
||||||
return DIRECTORIES.comfyWorkflows;
|
return directories.comfyWorkflows;
|
||||||
case 'kobold_preset':
|
case 'kobold_preset':
|
||||||
return DIRECTORIES.koboldAI_Settings;
|
return directories.koboldAI_Settings;
|
||||||
case 'openai_preset':
|
case 'openai_preset':
|
||||||
return DIRECTORIES.openAI_Settings;
|
return directories.openAI_Settings;
|
||||||
case 'novel_preset':
|
case 'novel_preset':
|
||||||
return DIRECTORIES.novelAI_Settings;
|
return directories.novelAI_Settings;
|
||||||
case 'textgen_preset':
|
case 'textgen_preset':
|
||||||
return DIRECTORIES.textGen_Settings;
|
return directories.textGen_Settings;
|
||||||
case 'instruct':
|
case 'instruct':
|
||||||
return DIRECTORIES.instruct;
|
return directories.instruct;
|
||||||
case 'context':
|
case 'context':
|
||||||
return DIRECTORIES.context;
|
return directories.context;
|
||||||
case 'moving_ui':
|
case 'moving_ui':
|
||||||
return DIRECTORIES.movingUI;
|
return directories.movingUI;
|
||||||
case 'quick_replies':
|
case 'quick_replies':
|
||||||
return DIRECTORIES.quickreplies;
|
return directories.quickreplies;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContentLog() {
|
/**
|
||||||
|
* Gets the content log from the content log file.
|
||||||
|
* @param {string} contentLogPath Path to the content log file
|
||||||
|
* @returns {string[]} Array of content log lines
|
||||||
|
*/
|
||||||
|
function getContentLog(contentLogPath) {
|
||||||
if (!fs.existsSync(contentLogPath)) {
|
if (!fs.existsSync(contentLogPath)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,30 +3,30 @@ const path = require('path');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
const { DIRECTORIES } = require('../constants');
|
|
||||||
const { getDefaultPresetFile, getDefaultPresets } = require('./content-manager');
|
const { getDefaultPresetFile, getDefaultPresets } = require('./content-manager');
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the folder and extension for the preset settings based on the API source ID.
|
* Gets the folder and extension for the preset settings based on the API source ID.
|
||||||
* @param {string} apiId API source ID
|
* @param {string} apiId API source ID
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
* @returns {object} Object containing the folder and extension for the preset settings
|
* @returns {object} Object containing the folder and extension for the preset settings
|
||||||
*/
|
*/
|
||||||
function getPresetSettingsByAPI(apiId) {
|
function getPresetSettingsByAPI(apiId, directories) {
|
||||||
switch (apiId) {
|
switch (apiId) {
|
||||||
case 'kobold':
|
case 'kobold':
|
||||||
case 'koboldhorde':
|
case 'koboldhorde':
|
||||||
return { folder: DIRECTORIES.koboldAI_Settings, extension: '.json' };
|
return { folder: directories.koboldAI_Settings, extension: '.json' };
|
||||||
case 'novel':
|
case 'novel':
|
||||||
return { folder: DIRECTORIES.novelAI_Settings, extension: '.json' };
|
return { folder: directories.novelAI_Settings, extension: '.json' };
|
||||||
case 'textgenerationwebui':
|
case 'textgenerationwebui':
|
||||||
return { folder: DIRECTORIES.textGen_Settings, extension: '.json' };
|
return { folder: directories.textGen_Settings, extension: '.json' };
|
||||||
case 'openai':
|
case 'openai':
|
||||||
return { folder: DIRECTORIES.openAI_Settings, extension: '.json' };
|
return { folder: directories.openAI_Settings, extension: '.json' };
|
||||||
case 'instruct':
|
case 'instruct':
|
||||||
return { folder: DIRECTORIES.instruct, extension: '.json' };
|
return { folder: directories.instruct, extension: '.json' };
|
||||||
case 'context':
|
case 'context':
|
||||||
return { folder: DIRECTORIES.context, extension: '.json' };
|
return { folder: directories.context, extension: '.json' };
|
||||||
default:
|
default:
|
||||||
return { folder: null, extension: null };
|
return { folder: null, extension: null };
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ router.post('/save', jsonParser, function (request, response) {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
const settings = getPresetSettingsByAPI(request.body.apiId, request.user.directories);
|
||||||
const filename = name + settings.extension;
|
const filename = name + settings.extension;
|
||||||
|
|
||||||
if (!settings.folder) {
|
if (!settings.folder) {
|
||||||
|
@ -58,7 +58,7 @@ router.post('/delete', jsonParser, function (request, response) {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
const settings = getPresetSettingsByAPI(request.body.apiId, request.user.directories);
|
||||||
const filename = name + settings.extension;
|
const filename = name + settings.extension;
|
||||||
|
|
||||||
if (!settings.folder) {
|
if (!settings.folder) {
|
||||||
|
@ -77,9 +77,9 @@ router.post('/delete', jsonParser, function (request, response) {
|
||||||
|
|
||||||
router.post('/restore', jsonParser, function (request, response) {
|
router.post('/restore', jsonParser, function (request, response) {
|
||||||
try {
|
try {
|
||||||
const settings = getPresetSettingsByAPI(request.body.apiId);
|
const settings = getPresetSettingsByAPI(request.body.apiId, request.user.directories);
|
||||||
const name = sanitize(request.body.name);
|
const name = sanitize(request.body.name);
|
||||||
const defaultPresets = getDefaultPresets();
|
const defaultPresets = getDefaultPresets(request.user.directories);
|
||||||
|
|
||||||
const defaultPreset = defaultPresets.find(p => p.name === name && p.folder === settings.folder);
|
const defaultPreset = defaultPresets.find(p => p.name === name && p.folder === settings.folder);
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ router.post('/save-openai', jsonParser, function (request, response) {
|
||||||
if (!name) return response.sendStatus(400);
|
if (!name) return response.sendStatus(400);
|
||||||
|
|
||||||
const filename = `${name}.json`;
|
const filename = `${name}.json`;
|
||||||
const fullpath = path.join(DIRECTORIES.openAI_Settings, filename);
|
const fullpath = path.join(request.user.directories.openAI_Settings, filename);
|
||||||
writeFileAtomicSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');
|
writeFileAtomicSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');
|
||||||
return response.send({ name });
|
return response.send({ name });
|
||||||
});
|
});
|
||||||
|
@ -116,7 +116,7 @@ router.post('/delete-openai', jsonParser, function (request, response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = request.body.name;
|
const name = request.body.name;
|
||||||
const pathToFile = path.join(DIRECTORIES.openAI_Settings, `${name}.json`);
|
const pathToFile = path.join(request.user.directories.openAI_Settings, `${name}.json`);
|
||||||
|
|
||||||
if (fs.existsSync(pathToFile)) {
|
if (fs.existsSync(pathToFile)) {
|
||||||
fs.rmSync(pathToFile);
|
fs.rmSync(pathToFile);
|
||||||
|
|
|
@ -109,64 +109,6 @@ function readSecretState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates secrets from settings.json to secrets.json
|
|
||||||
* @param {string} settingsFile Path to settings.json
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function migrateSecrets(settingsFile) {
|
|
||||||
const palmKey = readSecret('api_key_palm');
|
|
||||||
if (palmKey) {
|
|
||||||
console.log('Migrating Palm key...');
|
|
||||||
writeSecret(SECRET_KEYS.MAKERSUITE, palmKey);
|
|
||||||
deleteSecret('api_key_palm');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(settingsFile)) {
|
|
||||||
console.log('Settings file does not exist');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let modified = false;
|
|
||||||
const fileContents = fs.readFileSync(settingsFile, 'utf8');
|
|
||||||
const settings = JSON.parse(fileContents);
|
|
||||||
const oaiKey = settings?.api_key_openai;
|
|
||||||
const hordeKey = settings?.horde_settings?.api_key;
|
|
||||||
const novelKey = settings?.api_key_novel;
|
|
||||||
|
|
||||||
if (typeof oaiKey === 'string') {
|
|
||||||
console.log('Migrating OpenAI key...');
|
|
||||||
writeSecret(SECRET_KEYS.OPENAI, oaiKey);
|
|
||||||
delete settings.api_key_openai;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof hordeKey === 'string') {
|
|
||||||
console.log('Migrating Horde key...');
|
|
||||||
writeSecret(SECRET_KEYS.HORDE, hordeKey);
|
|
||||||
delete settings.horde_settings.api_key;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof novelKey === 'string') {
|
|
||||||
console.log('Migrating Novel key...');
|
|
||||||
writeSecret(SECRET_KEYS.NOVEL, novelKey);
|
|
||||||
delete settings.api_key_novel;
|
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modified) {
|
|
||||||
console.log('Writing updated settings.json...');
|
|
||||||
const settingsContent = JSON.stringify(settings, null, 4);
|
|
||||||
writeFileAtomicSync(settingsFile, settingsContent, 'utf-8');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Could not migrate secrets file. Proceed with caution.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads all secrets from the secrets file
|
* Reads all secrets from the secrets file
|
||||||
* @returns {Record<string, string> | undefined} Secrets
|
* @returns {Record<string, string> | undefined} Secrets
|
||||||
|
@ -251,7 +193,6 @@ module.exports = {
|
||||||
writeSecret,
|
writeSecret,
|
||||||
readSecret,
|
readSecret,
|
||||||
readSecretState,
|
readSecretState,
|
||||||
migrateSecrets,
|
|
||||||
getAllSecrets,
|
getAllSecrets,
|
||||||
SECRET_KEYS,
|
SECRET_KEYS,
|
||||||
router,
|
router,
|
||||||
|
|
|
@ -2,13 +2,13 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
const { DIRECTORIES } = require('../constants');
|
const { PUBLIC_DIRECTORIES } = require('../constants');
|
||||||
const { getConfigValue, generateTimestamp, removeOldBackups } = require('../util');
|
const { getConfigValue, generateTimestamp, removeOldBackups } = require('../util');
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
const { migrateSecrets } = require('./secrets');
|
const { getAllUserHandles, getUserDirectories } = require('../users');
|
||||||
|
|
||||||
|
const SETTINGS_FILE = 'settings.json';
|
||||||
const enableExtensions = getConfigValue('enableExtensions', true);
|
const enableExtensions = getConfigValue('enableExtensions', true);
|
||||||
const SETTINGS_FILE = './public/settings.json';
|
|
||||||
|
|
||||||
function readAndParseFromDirectory(directoryPath, fileExtension = '.json') {
|
function readAndParseFromDirectory(directoryPath, fileExtension = '.json') {
|
||||||
const files = fs
|
const files = fs
|
||||||
|
@ -61,16 +61,22 @@ function readPresetsFromDirectory(directoryPath, options = {}) {
|
||||||
return { fileContents, fileNames };
|
return { fileContents, fileNames };
|
||||||
}
|
}
|
||||||
|
|
||||||
function backupSettings() {
|
async function backupSettings() {
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(DIRECTORIES.backups)) {
|
if (!fs.existsSync(PUBLIC_DIRECTORIES.backups)) {
|
||||||
fs.mkdirSync(DIRECTORIES.backups);
|
fs.mkdirSync(PUBLIC_DIRECTORIES.backups);
|
||||||
}
|
}
|
||||||
|
|
||||||
const backupFile = path.join(DIRECTORIES.backups, `settings_${generateTimestamp()}.json`);
|
const userHandles = await getAllUserHandles();
|
||||||
fs.copyFileSync(SETTINGS_FILE, backupFile);
|
|
||||||
|
|
||||||
removeOldBackups('settings_');
|
for (const handle of userHandles) {
|
||||||
|
const userDirectories = getUserDirectories(handle);
|
||||||
|
const backupFile = path.join(PUBLIC_DIRECTORIES.backups, `settings_${handle}_${generateTimestamp()}.json`);
|
||||||
|
const sourceFile = path.join(userDirectories.root, SETTINGS_FILE);
|
||||||
|
fs.copyFileSync(sourceFile, backupFile);
|
||||||
|
|
||||||
|
removeOldBackups(`settings_${handle}`);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Could not backup settings file', err);
|
console.log('Could not backup settings file', err);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +86,8 @@ const router = express.Router();
|
||||||
|
|
||||||
router.post('/save', jsonParser, function (request, response) {
|
router.post('/save', jsonParser, function (request, response) {
|
||||||
try {
|
try {
|
||||||
writeFileAtomicSync('public/settings.json', JSON.stringify(request.body, null, 4), 'utf8');
|
const pathToSettings = path.join(request.user.directories.root, SETTINGS_FILE);
|
||||||
|
writeFileAtomicSync(pathToSettings, JSON.stringify(request.body, null, 4), 'utf8');
|
||||||
response.send({ result: 'ok' });
|
response.send({ result: 'ok' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
@ -92,48 +99,49 @@ router.post('/save', jsonParser, function (request, response) {
|
||||||
router.post('/get', jsonParser, (request, response) => {
|
router.post('/get', jsonParser, (request, response) => {
|
||||||
let settings;
|
let settings;
|
||||||
try {
|
try {
|
||||||
settings = fs.readFileSync('public/settings.json', 'utf8');
|
const pathToSettings = path.join(request.user.directories.root, SETTINGS_FILE);
|
||||||
|
settings = fs.readFileSync(pathToSettings, 'utf8');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return response.sendStatus(500);
|
return response.sendStatus(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NovelAI Settings
|
// NovelAI Settings
|
||||||
const { fileContents: novelai_settings, fileNames: novelai_setting_names }
|
const { fileContents: novelai_settings, fileNames: novelai_setting_names }
|
||||||
= readPresetsFromDirectory(DIRECTORIES.novelAI_Settings, {
|
= readPresetsFromDirectory(request.user.directories.novelAI_Settings, {
|
||||||
sortFunction: sortByName(DIRECTORIES.novelAI_Settings),
|
sortFunction: sortByName(request.user.directories.novelAI_Settings),
|
||||||
removeFileExtension: true,
|
removeFileExtension: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// OpenAI Settings
|
// OpenAI Settings
|
||||||
const { fileContents: openai_settings, fileNames: openai_setting_names }
|
const { fileContents: openai_settings, fileNames: openai_setting_names }
|
||||||
= readPresetsFromDirectory(DIRECTORIES.openAI_Settings, {
|
= readPresetsFromDirectory(request.user.directories.openAI_Settings, {
|
||||||
sortFunction: sortByName(DIRECTORIES.openAI_Settings), removeFileExtension: true,
|
sortFunction: sortByName(request.user.directories.openAI_Settings), removeFileExtension: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TextGenerationWebUI Settings
|
// TextGenerationWebUI Settings
|
||||||
const { fileContents: textgenerationwebui_presets, fileNames: textgenerationwebui_preset_names }
|
const { fileContents: textgenerationwebui_presets, fileNames: textgenerationwebui_preset_names }
|
||||||
= readPresetsFromDirectory(DIRECTORIES.textGen_Settings, {
|
= readPresetsFromDirectory(request.user.directories.textGen_Settings, {
|
||||||
sortFunction: sortByName(DIRECTORIES.textGen_Settings), removeFileExtension: true,
|
sortFunction: sortByName(request.user.directories.textGen_Settings), removeFileExtension: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
//Kobold
|
//Kobold
|
||||||
const { fileContents: koboldai_settings, fileNames: koboldai_setting_names }
|
const { fileContents: koboldai_settings, fileNames: koboldai_setting_names }
|
||||||
= readPresetsFromDirectory(DIRECTORIES.koboldAI_Settings, {
|
= readPresetsFromDirectory(request.user.directories.koboldAI_Settings, {
|
||||||
sortFunction: sortByName(DIRECTORIES.koboldAI_Settings), removeFileExtension: true,
|
sortFunction: sortByName(request.user.directories.koboldAI_Settings), removeFileExtension: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const worldFiles = fs
|
const worldFiles = fs
|
||||||
.readdirSync(DIRECTORIES.worlds)
|
.readdirSync(request.user.directories.worlds)
|
||||||
.filter(file => path.extname(file).toLowerCase() === '.json')
|
.filter(file => path.extname(file).toLowerCase() === '.json')
|
||||||
.sort((a, b) => a.localeCompare(b));
|
.sort((a, b) => a.localeCompare(b));
|
||||||
const world_names = worldFiles.map(item => path.parse(item).name);
|
const world_names = worldFiles.map(item => path.parse(item).name);
|
||||||
|
|
||||||
const themes = readAndParseFromDirectory(DIRECTORIES.themes);
|
const themes = readAndParseFromDirectory(request.user.directories.themes);
|
||||||
const movingUIPresets = readAndParseFromDirectory(DIRECTORIES.movingUI);
|
const movingUIPresets = readAndParseFromDirectory(request.user.directories.movingUI);
|
||||||
const quickReplyPresets = readAndParseFromDirectory(DIRECTORIES.quickreplies);
|
const quickReplyPresets = readAndParseFromDirectory(request.user.directories.quickreplies);
|
||||||
|
|
||||||
const instruct = readAndParseFromDirectory(DIRECTORIES.instruct);
|
const instruct = readAndParseFromDirectory(request.user.directories.instruct);
|
||||||
const context = readAndParseFromDirectory(DIRECTORIES.context);
|
const context = readAndParseFromDirectory(request.user.directories.context);
|
||||||
|
|
||||||
response.send({
|
response.send({
|
||||||
settings,
|
settings,
|
||||||
|
@ -155,10 +163,11 @@ router.post('/get', jsonParser, (request, response) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sync for now, but should probably be migrated to async file APIs
|
/**
|
||||||
|
* Initializes the settings endpoint
|
||||||
|
*/
|
||||||
async function init() {
|
async function init() {
|
||||||
backupSettings();
|
await backupSettings();
|
||||||
migrateSecrets(SETTINGS_FILE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { router, init };
|
module.exports = { router, init };
|
||||||
|
|
|
@ -4,7 +4,6 @@ const fs = require('fs');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
const { DIRECTORIES } = require('../constants');
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ router.post('/save', jsonParser, (request, response) => {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = path.join(DIRECTORIES.themes, sanitize(request.body.name) + '.json');
|
const filename = path.join(request.user.directories.themes, sanitize(request.body.name) + '.json');
|
||||||
writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
|
writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
|
||||||
|
|
||||||
return response.sendStatus(200);
|
return response.sendStatus(200);
|
||||||
|
@ -25,7 +24,7 @@ router.post('/delete', jsonParser, function (request, response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filename = path.join(DIRECTORIES.themes, sanitize(request.body.name) + '.json');
|
const filename = path.join(request.user.directories.themes, sanitize(request.body.name) + '.json');
|
||||||
if (!fs.existsSync(filename)) {
|
if (!fs.existsSync(filename)) {
|
||||||
console.error('Theme file not found:', filename);
|
console.error('Theme file not found:', filename);
|
||||||
return response.sendStatus(404);
|
return response.sendStatus(404);
|
||||||
|
|
|
@ -4,24 +4,25 @@ const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const jimp = require('jimp');
|
const jimp = require('jimp');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
const { DIRECTORIES } = require('../constants');
|
const { getAllUserHandles, getUserDirectories } = require('../users');
|
||||||
const { getConfigValue } = require('../util');
|
const { getConfigValue } = require('../util');
|
||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a path to thumbnail folder based on the type.
|
* Gets a path to thumbnail folder based on the type.
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
* @param {'bg' | 'avatar'} type Thumbnail type
|
* @param {'bg' | 'avatar'} type Thumbnail type
|
||||||
* @returns {string} Path to the thumbnails folder
|
* @returns {string} Path to the thumbnails folder
|
||||||
*/
|
*/
|
||||||
function getThumbnailFolder(type) {
|
function getThumbnailFolder(directories, type) {
|
||||||
let thumbnailFolder;
|
let thumbnailFolder;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'bg':
|
case 'bg':
|
||||||
thumbnailFolder = DIRECTORIES.thumbnailsBg;
|
thumbnailFolder = directories.thumbnailsBg;
|
||||||
break;
|
break;
|
||||||
case 'avatar':
|
case 'avatar':
|
||||||
thumbnailFolder = DIRECTORIES.thumbnailsAvatar;
|
thumbnailFolder = directories.thumbnailsAvatar;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,18 +31,19 @@ function getThumbnailFolder(type) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a path to the original images folder based on the type.
|
* Gets a path to the original images folder based on the type.
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
* @param {'bg' | 'avatar'} type Thumbnail type
|
* @param {'bg' | 'avatar'} type Thumbnail type
|
||||||
* @returns {string} Path to the original images folder
|
* @returns {string} Path to the original images folder
|
||||||
*/
|
*/
|
||||||
function getOriginalFolder(type) {
|
function getOriginalFolder(directories, type) {
|
||||||
let originalFolder;
|
let originalFolder;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'bg':
|
case 'bg':
|
||||||
originalFolder = DIRECTORIES.backgrounds;
|
originalFolder = directories.backgrounds;
|
||||||
break;
|
break;
|
||||||
case 'avatar':
|
case 'avatar':
|
||||||
originalFolder = DIRECTORIES.characters;
|
originalFolder = directories.characters;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +52,12 @@ function getOriginalFolder(type) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the generated thumbnail from the disk.
|
* Removes the generated thumbnail from the disk.
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
* @param {'bg' | 'avatar'} type Type of the thumbnail
|
* @param {'bg' | 'avatar'} type Type of the thumbnail
|
||||||
* @param {string} file Name of the file
|
* @param {string} file Name of the file
|
||||||
*/
|
*/
|
||||||
function invalidateThumbnail(type, file) {
|
function invalidateThumbnail(directories, type, file) {
|
||||||
const folder = getThumbnailFolder(type);
|
const folder = getThumbnailFolder(directories, type);
|
||||||
if (folder === undefined) throw new Error('Invalid thumbnail type');
|
if (folder === undefined) throw new Error('Invalid thumbnail type');
|
||||||
|
|
||||||
const pathToThumbnail = path.join(folder, file);
|
const pathToThumbnail = path.join(folder, file);
|
||||||
|
@ -66,13 +69,14 @@ function invalidateThumbnail(type, file) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a thumbnail for the given file.
|
* Generates a thumbnail for the given file.
|
||||||
|
* @param {import('../users').UserDirectoryList} directories User directories
|
||||||
* @param {'bg' | 'avatar'} type Type of the thumbnail
|
* @param {'bg' | 'avatar'} type Type of the thumbnail
|
||||||
* @param {string} file Name of the file
|
* @param {string} file Name of the file
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function generateThumbnail(type, file) {
|
async function generateThumbnail(directories, type, file) {
|
||||||
let thumbnailFolder = getThumbnailFolder(type);
|
let thumbnailFolder = getThumbnailFolder(directories, type);
|
||||||
let originalFolder = getOriginalFolder(type);
|
let originalFolder = getOriginalFolder(directories, type);
|
||||||
if (thumbnailFolder === undefined || originalFolder === undefined) throw new Error('Invalid thumbnail type');
|
if (thumbnailFolder === undefined || originalFolder === undefined) throw new Error('Invalid thumbnail type');
|
||||||
|
|
||||||
const pathToCachedFile = path.join(thumbnailFolder, file);
|
const pathToCachedFile = path.join(thumbnailFolder, file);
|
||||||
|
@ -133,7 +137,10 @@ async function generateThumbnail(type, file) {
|
||||||
* @returns {Promise<void>} Promise that resolves when the cache is validated
|
* @returns {Promise<void>} Promise that resolves when the cache is validated
|
||||||
*/
|
*/
|
||||||
async function ensureThumbnailCache() {
|
async function ensureThumbnailCache() {
|
||||||
const cacheFiles = fs.readdirSync(DIRECTORIES.thumbnailsBg);
|
const userHandles = await getAllUserHandles();
|
||||||
|
for (const handle of userHandles) {
|
||||||
|
const directories = getUserDirectories(handle);
|
||||||
|
const cacheFiles = fs.readdirSync(directories.thumbnailsBg);
|
||||||
|
|
||||||
// files exist, all ok
|
// files exist, all ok
|
||||||
if (cacheFiles.length) {
|
if (cacheFiles.length) {
|
||||||
|
@ -142,16 +149,17 @@ async function ensureThumbnailCache() {
|
||||||
|
|
||||||
console.log('Generating thumbnails cache. Please wait...');
|
console.log('Generating thumbnails cache. Please wait...');
|
||||||
|
|
||||||
const bgFiles = fs.readdirSync(DIRECTORIES.backgrounds);
|
const bgFiles = fs.readdirSync(directories.backgrounds);
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
|
|
||||||
for (const file of bgFiles) {
|
for (const file of bgFiles) {
|
||||||
tasks.push(generateThumbnail('bg', file));
|
tasks.push(generateThumbnail(directories, 'bg', file));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
console.log(`Done! Generated: ${bgFiles.length} preview images`);
|
console.log(`Done! Generated: ${bgFiles.length} preview images`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -176,13 +184,13 @@ router.get('/', jsonParser, async function (request, response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getConfigValue('disableThumbnails', false) == true) {
|
if (getConfigValue('disableThumbnails', false) == true) {
|
||||||
let folder = getOriginalFolder(type);
|
let folder = getOriginalFolder(request.user.directories, type);
|
||||||
if (folder === undefined) return response.sendStatus(400);
|
if (folder === undefined) return response.sendStatus(400);
|
||||||
const pathToOriginalFile = path.join(folder, file);
|
const pathToOriginalFile = path.join(folder, file);
|
||||||
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
|
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathToCachedFile = await generateThumbnail(type, file);
|
const pathToCachedFile = await generateThumbnail(request.user.directories, type, file);
|
||||||
|
|
||||||
if (!pathToCachedFile) {
|
if (!pathToCachedFile) {
|
||||||
return response.sendStatus(404);
|
return response.sendStatus(404);
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER } = require('./constants');
|
||||||
|
const { getConfigValue } = require('./util');
|
||||||
|
|
||||||
|
const DATA_ROOT = getConfigValue('dataRoot', './data');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} User
|
||||||
|
* @property {string} uuid - The user's id
|
||||||
|
* @property {string} handle - The user's short handle. Used for directories and other references
|
||||||
|
* @property {string} name - The user's name. Displayed in the UI
|
||||||
|
* @property {number} created - The timestamp when the user was created
|
||||||
|
* @property {string} password - SHA256 hash of the user's password
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} UserDirectoryList
|
||||||
|
* @property {string} root - The root directory for the user
|
||||||
|
* @property {string} thumbnails - The directory where the thumbnails are stored
|
||||||
|
* @property {string} thumbnailsBg - The directory where the background thumbnails are stored
|
||||||
|
* @property {string} thumbnailsAvatar - The directory where the avatar thumbnails are stored
|
||||||
|
* @property {string} worlds - The directory where the WI are stored
|
||||||
|
* @property {string} user - The directory where the user's public data is stored
|
||||||
|
* @property {string} avatars - The directory where the avatars are stored
|
||||||
|
* @property {string} userImages - The directory where the images are stored
|
||||||
|
* @property {string} groups - The directory where the groups are stored
|
||||||
|
* @property {string} groupChats - The directory where the group chats are stored
|
||||||
|
* @property {string} chats - The directory where the chats are stored
|
||||||
|
* @property {string} characters - The directory where the characters are stored
|
||||||
|
* @property {string} backgrounds - The directory where the backgrounds are stored
|
||||||
|
* @property {string} novelAI_Settings - The directory where the NovelAI settings are stored
|
||||||
|
* @property {string} koboldAI_Settings - The directory where the KoboldAI settings are stored
|
||||||
|
* @property {string} openAI_Settings - The directory where the OpenAI settings are stored
|
||||||
|
* @property {string} textGen_Settings - The directory where the TextGen settings are stored
|
||||||
|
* @property {string} themes - The directory where the themes are stored
|
||||||
|
* @property {string} movingUI - The directory where the moving UI data is stored
|
||||||
|
* @property {string} extensions - The directory where the extensions are stored
|
||||||
|
* @property {string} instruct - The directory where the instruct templates is stored
|
||||||
|
* @property {string} context - The directory where the context templates is stored
|
||||||
|
* @property {string} quickreplies - The directory where the quick replies are stored
|
||||||
|
* @property {string} assets - The directory where the assets are stored
|
||||||
|
* @property {string} comfyWorkflows - The directory where the ComfyUI workflows are stored
|
||||||
|
* @property {string} files - The directory where the uploaded files are stored
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the user storage. Currently a no-op.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function initUserStorage() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a user for the current request. Hard coded to return the default user.
|
||||||
|
* @param {import('express').Request} _req - The request object. Currently unused.
|
||||||
|
* @returns {Promise<string>} - The user's handle
|
||||||
|
*/
|
||||||
|
async function getCurrentUserHandle(_req) {
|
||||||
|
return DEFAULT_USER.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of all user handles. Currently hard coded to return the default user's handle.
|
||||||
|
* @returns {Promise<string[]>} - The list of user handles
|
||||||
|
*/
|
||||||
|
async function getAllUserHandles() {
|
||||||
|
return [DEFAULT_USER.handle];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the directories listing for the provided user.
|
||||||
|
* @param {import('express').Request} req - The request object
|
||||||
|
* @returns {Promise<UserDirectoryList>} - The user's directories like {worlds: 'data/user0/worlds/', ...
|
||||||
|
*/
|
||||||
|
async function getCurrentUserDirectories(req) {
|
||||||
|
const handle = await getCurrentUserHandle(req);
|
||||||
|
return getUserDirectories(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the directories listing for the provided user.
|
||||||
|
* @param {string} handle User handle
|
||||||
|
* @returns {UserDirectoryList} User directories
|
||||||
|
*/
|
||||||
|
function getUserDirectories(handle) {
|
||||||
|
const directories = structuredClone(USER_DIRECTORY_TEMPLATE);
|
||||||
|
for (const key in directories) {
|
||||||
|
directories[key] = path.join(DATA_ROOT, handle, USER_DIRECTORY_TEMPLATE[key]);
|
||||||
|
}
|
||||||
|
return directories;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to add user data to the request object.
|
||||||
|
* @param {import('express').Application} app - The express app
|
||||||
|
* @returns {import('express').RequestHandler}
|
||||||
|
*/
|
||||||
|
function userDataMiddleware(app) {
|
||||||
|
app.use('/backgrounds/:path', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(process.cwd(), req.user.directories.backgrounds, decodeURIComponent(req.params.path));
|
||||||
|
const data = await fsPromises.readFile(filePath);
|
||||||
|
return res.send(data);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use('/characters/:path', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(process.cwd(), req.user.directories.characters, decodeURIComponent(req.params.path));
|
||||||
|
const data = await fsPromises.readFile(filePath);
|
||||||
|
return res.send(data);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use('/User Avatars/:path', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(process.cwd(), req.user.directories.avatars, decodeURIComponent(req.params.path));
|
||||||
|
const data = await fsPromises.readFile(filePath);
|
||||||
|
return res.send(data);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to add user data to the request object.
|
||||||
|
* @param {import('express').Request} req Request object
|
||||||
|
* @param {import('express').Response} res Response object
|
||||||
|
* @param {import('express').NextFunction} next Next function
|
||||||
|
*/
|
||||||
|
return async (req, res, next) => {
|
||||||
|
const directories = await getCurrentUserDirectories(req);
|
||||||
|
req.user.profile = DEFAULT_USER;
|
||||||
|
req.user.directories = directories;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
initUserStorage,
|
||||||
|
getCurrentUserDirectories,
|
||||||
|
getCurrentUserHandle,
|
||||||
|
getAllUserHandles,
|
||||||
|
getUserDirectories,
|
||||||
|
userDataMiddleware,
|
||||||
|
};
|
|
@ -8,7 +8,7 @@ const yaml = require('yaml');
|
||||||
const { default: simpleGit } = require('simple-git');
|
const { default: simpleGit } = require('simple-git');
|
||||||
const { Readable } = require('stream');
|
const { Readable } = require('stream');
|
||||||
|
|
||||||
const { DIRECTORIES } = require('./constants');
|
const { PUBLIC_DIRECTORIES } = require('./constants');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the config object from the config.yaml file.
|
* Returns the config object from the config.yaml file.
|
||||||
|
@ -355,9 +355,9 @@ function generateTimestamp() {
|
||||||
function removeOldBackups(prefix) {
|
function removeOldBackups(prefix) {
|
||||||
const MAX_BACKUPS = 25;
|
const MAX_BACKUPS = 25;
|
||||||
|
|
||||||
let files = fs.readdirSync(DIRECTORIES.backups).filter(f => f.startsWith(prefix));
|
let files = fs.readdirSync(PUBLIC_DIRECTORIES.backups).filter(f => f.startsWith(prefix));
|
||||||
if (files.length > MAX_BACKUPS) {
|
if (files.length > MAX_BACKUPS) {
|
||||||
files = files.map(f => path.join(DIRECTORIES.backups, f));
|
files = files.map(f => path.join(PUBLIC_DIRECTORIES.backups, f));
|
||||||
files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
||||||
|
|
||||||
fs.rmSync(files[0]);
|
fs.rmSync(files[0]);
|
||||||
|
|
Loading…
Reference in New Issue