mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into persona-improvements
This commit is contained in:
@@ -77,8 +77,6 @@ perUserBasicAuth: false
|
|||||||
## Set to 0 to expire session when the browser is closed
|
## Set to 0 to expire session when the browser is closed
|
||||||
## Set to a negative number to disable session expiration
|
## Set to a negative number to disable session expiration
|
||||||
sessionTimeout: -1
|
sessionTimeout: -1
|
||||||
# Used to sign session cookies. Will be auto-generated if not set
|
|
||||||
cookieSecret: ''
|
|
||||||
# Disable CSRF protection - NOT RECOMMENDED
|
# Disable CSRF protection - NOT RECOMMENDED
|
||||||
disableCsrfProtection: false
|
disableCsrfProtection: false
|
||||||
# Disable startup security checks - NOT RECOMMENDED
|
# Disable startup security checks - NOT RECOMMENDED
|
||||||
|
@@ -109,6 +109,15 @@ const keyMigrationMap = [
|
|||||||
newKey: 'logging.minLogLevel',
|
newKey: 'logging.minLogLevel',
|
||||||
migrate: (value) => value,
|
migrate: (value) => value,
|
||||||
},
|
},
|
||||||
|
// uncomment one release after 1.12.13
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
oldKey: 'cookieSecret',
|
||||||
|
newKey: 'cookieSecret',
|
||||||
|
migrate: () => void 0,
|
||||||
|
remove: true,
|
||||||
|
},
|
||||||
|
*/
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,8 +177,17 @@ function addMissingConfigValues() {
|
|||||||
|
|
||||||
// Migrate old keys to new keys
|
// Migrate old keys to new keys
|
||||||
const migratedKeys = [];
|
const migratedKeys = [];
|
||||||
for (const { oldKey, newKey, migrate } of keyMigrationMap) {
|
for (const { oldKey, newKey, migrate, remove } of keyMigrationMap) {
|
||||||
if (_.has(config, oldKey)) {
|
if (_.has(config, oldKey)) {
|
||||||
|
if (remove) {
|
||||||
|
_.unset(config, oldKey);
|
||||||
|
migratedKeys.push({
|
||||||
|
oldKey,
|
||||||
|
newValue: void 0,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const oldValue = _.get(config, oldKey);
|
const oldValue = _.get(config, oldKey);
|
||||||
const newValue = migrate(oldValue);
|
const newValue = migrate(oldValue);
|
||||||
_.set(config, newKey, newValue);
|
_.set(config, newKey, newValue);
|
||||||
|
51
server.js
51
server.js
@@ -261,47 +261,47 @@ app.use(responseTime());
|
|||||||
|
|
||||||
|
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);
|
const server_port = cliArguments.port ?? getConfigValue('port', DEFAULT_PORT, 'number');
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
|
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN, 'boolean')) && !cliArguments.ssl;
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
|
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN, 'boolean');
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
const listenAddressIPv6 = cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', DEFAULT_LISTEN_ADDRESS_IPV6);
|
const listenAddressIPv6 = cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', DEFAULT_LISTEN_ADDRESS_IPV6);
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', DEFAULT_LISTEN_ADDRESS_IPV4);
|
const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', DEFAULT_LISTEN_ADDRESS_IPV4);
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
|
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY, 'boolean');
|
||||||
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
|
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST, 'boolean');
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
|
globalThis.DATA_ROOT = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
|
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED, 'boolean');
|
||||||
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
|
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH, 'boolean');
|
||||||
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
|
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH, 'boolean');
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
|
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS, 'boolean');
|
||||||
|
|
||||||
const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY);
|
const uploadsPath = path.join(globalThis.DATA_ROOT, UPLOADS_DIRECTORY);
|
||||||
|
|
||||||
|
|
||||||
/** @type {boolean | "auto"} */
|
/** @type {boolean | string} */
|
||||||
let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6);
|
let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? stringToBool(getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6)) ?? DEFAULT_ENABLE_IPV6;
|
||||||
/** @type {boolean | "auto"} */
|
/** @type {boolean | string} */
|
||||||
let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4);
|
let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? stringToBool(getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4)) ?? DEFAULT_ENABLE_IPV4;
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
|
const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);
|
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT, 'number');
|
||||||
|
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);
|
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6, 'boolean');
|
||||||
|
|
||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);
|
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST, 'boolean');
|
||||||
|
|
||||||
const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED);
|
const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED, 'boolean');
|
||||||
const proxyUrl = cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', DEFAULT_PROXY_URL);
|
const proxyUrl = cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', DEFAULT_PROXY_URL);
|
||||||
const proxyBypass = cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', DEFAULT_PROXY_BYPASS);
|
const proxyBypass = cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', DEFAULT_PROXY_BYPASS);
|
||||||
|
|
||||||
@@ -403,7 +403,7 @@ if (enableCorsProxy) {
|
|||||||
|
|
||||||
function getSessionCookieAge() {
|
function getSessionCookieAge() {
|
||||||
// Defaults to "no expiration" if not set
|
// Defaults to "no expiration" if not set
|
||||||
const configValue = getConfigValue('sessionTimeout', -1);
|
const configValue = getConfigValue('sessionTimeout', -1, 'number');
|
||||||
|
|
||||||
// Convert to milliseconds
|
// Convert to milliseconds
|
||||||
if (configValue > 0) {
|
if (configValue > 0) {
|
||||||
@@ -474,7 +474,7 @@ app.use(cookieSession({
|
|||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
maxAge: getSessionCookieAge(),
|
maxAge: getSessionCookieAge(),
|
||||||
secret: getCookieSecret(),
|
secret: getCookieSecret(globalThis.DATA_ROOT),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.use(setUserDataMiddleware);
|
app.use(setUserDataMiddleware);
|
||||||
@@ -884,8 +884,9 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) {
|
|||||||
'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.',
|
'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.',
|
||||||
));
|
));
|
||||||
} else if (!perUserBasicAuth) {
|
} else if (!perUserBasicAuth) {
|
||||||
const basicAuthUser = getConfigValue('basicAuthUser', {});
|
const basicAuthUserName = getConfigValue('basicAuthUser.username', '');
|
||||||
if (!basicAuthUser?.username || !basicAuthUser?.password) {
|
const basicAuthUserPassword = getConfigValue('basicAuthUser.password', '');
|
||||||
|
if (!basicAuthUserName || !basicAuthUserPassword) {
|
||||||
console.warn(color.yellow(
|
console.warn(color.yellow(
|
||||||
'Basic Authentication is enabled, but username or password is not set or empty!',
|
'Basic Authentication is enabled, but username or password is not set or empty!',
|
||||||
));
|
));
|
||||||
@@ -932,7 +933,7 @@ function setWindowTitle(title) {
|
|||||||
function logSecurityAlert(message) {
|
function logSecurityAlert(message) {
|
||||||
if (basicAuthMode || enableWhitelist) return; // safe!
|
if (basicAuthMode || enableWhitelist) return; // safe!
|
||||||
console.error(color.red(message));
|
console.error(color.red(message));
|
||||||
if (getConfigValue('securityOverride', false)) {
|
if (getConfigValue('securityOverride', false, 'boolean')) {
|
||||||
console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
|
console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1145,7 +1146,7 @@ function apply404Middleware() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User storage module needs to be initialized before starting the server
|
// User storage module needs to be initialized before starting the server
|
||||||
initUserStorage(dataRoot)
|
initUserStorage(globalThis.DATA_ROOT)
|
||||||
.then(ensurePublicDirectoriesExist)
|
.then(ensurePublicDirectoriesExist)
|
||||||
.then(migrateUserData)
|
.then(migrateUserData)
|
||||||
.then(migrateSystemPrompts)
|
.then(migrateSystemPrompts)
|
||||||
|
@@ -107,8 +107,8 @@ async function sendClaudeRequest(request, response) {
|
|||||||
const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString();
|
const apiUrl = new URL(request.body.reverse_proxy || API_CLAUDE).toString();
|
||||||
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE);
|
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE);
|
||||||
const divider = '-'.repeat(process.stdout.columns);
|
const divider = '-'.repeat(process.stdout.columns);
|
||||||
const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false) && request.body.model.startsWith('claude-3');
|
const enableSystemPromptCache = getConfigValue('claude.enableSystemPromptCache', false, 'boolean') && request.body.model.startsWith('claude-3');
|
||||||
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1);
|
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
|
||||||
// Disabled if not an integer or negative, or if the model doesn't support it
|
// Disabled if not an integer or negative, or if the model doesn't support it
|
||||||
if (!Number.isInteger(cachingAtDepth) || cachingAtDepth < 0 || !request.body.model.startsWith('claude-3')) {
|
if (!Number.isInteger(cachingAtDepth) || cachingAtDepth < 0 || !request.body.model.startsWith('claude-3')) {
|
||||||
cachingAtDepth = -1;
|
cachingAtDepth = -1;
|
||||||
@@ -1004,7 +1004,7 @@ router.post('/generate', jsonParser, function (request, response) {
|
|||||||
bodyParams.logprobs = true;
|
bodyParams.logprobs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getConfigValue('openai.randomizeUserId', false)) {
|
if (getConfigValue('openai.randomizeUserId', false, 'boolean')) {
|
||||||
bodyParams['user'] = uuidv4();
|
bodyParams['user'] = uuidv4();
|
||||||
}
|
}
|
||||||
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
|
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
|
||||||
@@ -1040,7 +1040,7 @@ router.post('/generate', jsonParser, function (request, response) {
|
|||||||
bodyParams['route'] = 'fallback';
|
bodyParams['route'] = 'fallback';
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1);
|
let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number');
|
||||||
if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) {
|
if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) {
|
||||||
cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);
|
cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);
|
||||||
}
|
}
|
||||||
|
@@ -372,8 +372,8 @@ router.post('/generate', jsonParser, async function (request, response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.body.api_type === TEXTGEN_TYPES.OLLAMA) {
|
if (request.body.api_type === TEXTGEN_TYPES.OLLAMA) {
|
||||||
const keepAlive = getConfigValue('ollama.keepAlive', -1);
|
const keepAlive = Number(getConfigValue('ollama.keepAlive', -1, 'number'));
|
||||||
const numBatch = getConfigValue('ollama.batchSize', -1);
|
const numBatch = Number(getConfigValue('ollama.batchSize', -1, 'number'));
|
||||||
if (numBatch > 0) {
|
if (numBatch > 0) {
|
||||||
request.body['num_batch'] = numBatch;
|
request.body['num_batch'] = numBatch;
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ import { importRisuSprites } from './sprites.js';
|
|||||||
const defaultAvatarPath = './public/img/ai4.png';
|
const defaultAvatarPath = './public/img/ai4.png';
|
||||||
|
|
||||||
// KV-store for parsed character data
|
// KV-store for parsed character data
|
||||||
const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100)); // MB
|
const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100, 'number')); // MB
|
||||||
// With 100 MB limit it would take roughly 3000 characters to reach this limit
|
// With 100 MB limit it would take roughly 3000 characters to reach this limit
|
||||||
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
|
const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity);
|
||||||
// Some Android devices require tighter memory management
|
// Some Android devices require tighter memory management
|
||||||
|
@@ -19,9 +19,9 @@ import {
|
|||||||
formatBytes,
|
formatBytes,
|
||||||
} from '../util.js';
|
} from '../util.js';
|
||||||
|
|
||||||
const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true);
|
const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true, 'boolean');
|
||||||
const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1));
|
const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1, 'number'));
|
||||||
const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000));
|
const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000, 'number'));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a chat to the backups directory.
|
* Saves a chat to the backups directory.
|
||||||
|
@@ -165,7 +165,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
|
|||||||
*/
|
*/
|
||||||
export async function checkForNewContent(directoriesList, forceCategories = []) {
|
export async function checkForNewContent(directoriesList, forceCategories = []) {
|
||||||
try {
|
try {
|
||||||
const contentCheckSkip = getConfigValue('skipContentCheck', false);
|
const contentCheckSkip = getConfigValue('skipContentCheck', false, 'boolean');
|
||||||
if (contentCheckSkip && forceCategories?.length === 0) {
|
if (contentCheckSkip && forceCategories?.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -183,7 +183,7 @@ router.post('/read', jsonParser, (request, response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/view', jsonParser, async (request, response) => {
|
router.post('/view', jsonParser, async (request, response) => {
|
||||||
const allowKeysExposure = getConfigValue('allowKeysExposure', false);
|
const allowKeysExposure = getConfigValue('allowKeysExposure', false, 'boolean');
|
||||||
|
|
||||||
if (!allowKeysExposure) {
|
if (!allowKeysExposure) {
|
||||||
console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.yaml is set to true');
|
console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.yaml is set to true');
|
||||||
@@ -205,7 +205,7 @@ router.post('/view', jsonParser, async (request, response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/find', jsonParser, (request, response) => {
|
router.post('/find', jsonParser, (request, response) => {
|
||||||
const allowKeysExposure = getConfigValue('allowKeysExposure', false);
|
const allowKeysExposure = getConfigValue('allowKeysExposure', false, 'boolean');
|
||||||
const key = request.body.key;
|
const key = request.body.key;
|
||||||
|
|
||||||
if (!allowKeysExposure && !EXPORTABLE_KEYS.includes(key)) {
|
if (!allowKeysExposure && !EXPORTABLE_KEYS.includes(key)) {
|
||||||
|
@@ -11,9 +11,9 @@ import { jsonParser } from '../express-common.js';
|
|||||||
import { getAllUserHandles, getUserDirectories } from '../users.js';
|
import { getAllUserHandles, getUserDirectories } from '../users.js';
|
||||||
import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
|
import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
|
||||||
|
|
||||||
const ENABLE_EXTENSIONS = !!getConfigValue('extensions.enabled', true);
|
const ENABLE_EXTENSIONS = !!getConfigValue('extensions.enabled', true, 'boolean');
|
||||||
const ENABLE_EXTENSIONS_AUTO_UPDATE = !!getConfigValue('extensions.autoUpdate', true);
|
const ENABLE_EXTENSIONS_AUTO_UPDATE = !!getConfigValue('extensions.autoUpdate', true, 'boolean');
|
||||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
const ENABLE_ACCOUNTS = !!getConfigValue('enableUserAccounts', false, 'boolean');
|
||||||
|
|
||||||
// 10 minutes
|
// 10 minutes
|
||||||
const AUTOSAVE_INTERVAL = 10 * 60 * 1000;
|
const AUTOSAVE_INTERVAL = 10 * 60 * 1000;
|
||||||
|
@@ -12,12 +12,15 @@ import { getAllUserHandles, getUserDirectories } from '../users.js';
|
|||||||
import { getConfigValue } from '../util.js';
|
import { getConfigValue } from '../util.js';
|
||||||
import { jsonParser } from '../express-common.js';
|
import { jsonParser } from '../express-common.js';
|
||||||
|
|
||||||
const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true);
|
const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true, 'boolean');
|
||||||
const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95))));
|
const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95, 'number'))));
|
||||||
const pngFormat = String(getConfigValue('thumbnails.format', 'jpg')).toLowerCase().trim() === 'png';
|
const pngFormat = String(getConfigValue('thumbnails.format', 'jpg')).toLowerCase().trim() === 'png';
|
||||||
|
|
||||||
/** @type {Record<string, number[]>} */
|
/** @type {Record<string, number[]>} */
|
||||||
const dimensions = getConfigValue('thumbnails.dimensions', { 'bg': [160, 90], 'avatar': [96, 144] });
|
const dimensions = {
|
||||||
|
'bg': getConfigValue('thumbnails.dimensions.bg', [160, 90]),
|
||||||
|
'avatar': getConfigValue('thumbnails.dimensions.avatar', [96, 144]),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a path to thumbnail folder based on the type.
|
* Gets a path to thumbnail folder based on the type.
|
||||||
|
@@ -56,7 +56,7 @@ export const TEXT_COMPLETION_MODELS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const CHARS_PER_TOKEN = 3.35;
|
const CHARS_PER_TOKEN = 3.35;
|
||||||
const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true);
|
const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true, 'boolean');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a path to the tokenizer model. Downloads the model if it's a URL.
|
* Gets a path to the tokenizer model. Downloads the model if it's a URL.
|
||||||
|
@@ -7,8 +7,8 @@ import { jsonParser, getIpFromRequest, getRealIpFromHeader } from '../express-co
|
|||||||
import { color, Cache, getConfigValue } from '../util.js';
|
import { color, Cache, getConfigValue } from '../util.js';
|
||||||
import { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } from '../users.js';
|
import { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } from '../users.js';
|
||||||
|
|
||||||
const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false);
|
const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false, 'boolean');
|
||||||
const PREFER_REAL_IP_HEADER = getConfigValue('rateLimiting.preferRealIpHeader', false);
|
const PREFER_REAL_IP_HEADER = getConfigValue('rateLimiting.preferRealIpHeader', false, 'boolean');
|
||||||
const MFA_CACHE = new Cache(5 * 60 * 1000);
|
const MFA_CACHE = new Cache(5 * 60 * 1000);
|
||||||
|
|
||||||
const getIpAddress = (request) => PREFER_REAL_IP_HEADER ? getRealIpFromHeader(request) : getIpFromRequest(request);
|
const getIpAddress = (request) => PREFER_REAL_IP_HEADER ? getRealIpFromHeader(request) : getIpFromRequest(request);
|
||||||
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|||||||
import { getRealIpFromHeader } from '../express-common.js';
|
import { getRealIpFromHeader } from '../express-common.js';
|
||||||
import { color, getConfigValue } from '../util.js';
|
import { color, getConfigValue } from '../util.js';
|
||||||
|
|
||||||
const enableAccessLog = getConfigValue('logging.enableAccessLog', true);
|
const enableAccessLog = getConfigValue('logging.enableAccessLog', true, 'boolean');
|
||||||
|
|
||||||
const knownIPs = new Set();
|
const knownIPs = new Set();
|
||||||
|
|
||||||
|
@@ -5,10 +5,10 @@
|
|||||||
import { Buffer } from 'node:buffer';
|
import { Buffer } from 'node:buffer';
|
||||||
import storage from 'node-persist';
|
import storage from 'node-persist';
|
||||||
import { getAllUserHandles, toKey, getPasswordHash } from '../users.js';
|
import { getAllUserHandles, toKey, getPasswordHash } from '../users.js';
|
||||||
import { getConfig, getConfigValue, safeReadFileSync } from '../util.js';
|
import { getConfigValue, safeReadFileSync } from '../util.js';
|
||||||
|
|
||||||
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
|
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean');
|
||||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');
|
||||||
|
|
||||||
const basicAuthMiddleware = async function (request, response, callback) {
|
const basicAuthMiddleware = async function (request, response, callback) {
|
||||||
const unauthorizedWebpage = safeReadFileSync('./public/error/unauthorized.html') ?? '';
|
const unauthorizedWebpage = safeReadFileSync('./public/error/unauthorized.html') ?? '';
|
||||||
@@ -17,7 +17,8 @@ const basicAuthMiddleware = async function (request, response, callback) {
|
|||||||
return res.status(401).send(unauthorizedWebpage);
|
return res.status(401).send(unauthorizedWebpage);
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = getConfig();
|
const basicAuthUserName = getConfigValue('basicAuthUser.username');
|
||||||
|
const basicAuthUserPassword = getConfigValue('basicAuthUser.password');
|
||||||
const authHeader = request.headers.authorization;
|
const authHeader = request.headers.authorization;
|
||||||
|
|
||||||
if (!authHeader) {
|
if (!authHeader) {
|
||||||
@@ -35,7 +36,7 @@ const basicAuthMiddleware = async function (request, response, callback) {
|
|||||||
.toString('utf8')
|
.toString('utf8')
|
||||||
.split(':');
|
.split(':');
|
||||||
|
|
||||||
if (!usePerUserAuth && username === config.basicAuthUser.username && password === config.basicAuthUser.password) {
|
if (!usePerUserAuth && username === basicAuthUserName && password === basicAuthUserPassword) {
|
||||||
return callback();
|
return callback();
|
||||||
} else if (usePerUserAuth) {
|
} else if (usePerUserAuth) {
|
||||||
const userHandles = await getAllUserHandles();
|
const userHandles = await getAllUserHandles();
|
||||||
|
@@ -8,7 +8,7 @@ import { getIpFromRequest } from '../express-common.js';
|
|||||||
import { color, getConfigValue, safeReadFileSync } from '../util.js';
|
import { color, getConfigValue, safeReadFileSync } from '../util.js';
|
||||||
|
|
||||||
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
|
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
|
||||||
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);
|
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false, 'boolean');
|
||||||
let whitelist = getConfigValue('whitelist', []);
|
let whitelist = getConfigValue('whitelist', []);
|
||||||
|
|
||||||
if (fs.existsSync(whitelistPath)) {
|
if (fs.existsSync(whitelistPath)) {
|
||||||
|
@@ -7,8 +7,8 @@ import { default as git, CheckRepoActions } from 'simple-git';
|
|||||||
import { sync as commandExistsSync } from 'command-exists';
|
import { sync as commandExistsSync } from 'command-exists';
|
||||||
import { getConfigValue, color } from './util.js';
|
import { getConfigValue, color } from './util.js';
|
||||||
|
|
||||||
const enableServerPlugins = !!getConfigValue('enableServerPlugins', false);
|
const enableServerPlugins = !!getConfigValue('enableServerPlugins', false, 'boolean');
|
||||||
const enableServerPluginsAutoUpdate = !!getConfigValue('enableServerPluginsAutoUpdate', true);
|
const enableServerPluginsAutoUpdate = !!getConfigValue('enableServerPluginsAutoUpdate', true, 'boolean');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of loaded plugins.
|
* Map of loaded plugins.
|
||||||
|
@@ -572,7 +572,7 @@ export function convertMistralMessages(messages, names) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make the last assistant message a prefill
|
// Make the last assistant message a prefill
|
||||||
const prefixEnabled = getConfigValue('mistral.enablePrefix', false);
|
const prefixEnabled = getConfigValue('mistral.enablePrefix', false, 'boolean');
|
||||||
const lastMsg = messages[messages.length - 1];
|
const lastMsg = messages[messages.length - 1];
|
||||||
if (prefixEnabled && messages.length > 0 && lastMsg?.role === 'assistant') {
|
if (prefixEnabled && messages.length > 0 && lastMsg?.role === 'assistant') {
|
||||||
lastMsg.prefix = true;
|
lastMsg.prefix = true;
|
||||||
|
@@ -132,7 +132,7 @@ export async function getPipeline(task, forceModel = '') {
|
|||||||
|
|
||||||
const cacheDir = path.join(globalThis.DATA_ROOT, '_cache');
|
const cacheDir = path.join(globalThis.DATA_ROOT, '_cache');
|
||||||
const model = forceModel || getModelForTask(task);
|
const model = forceModel || getModelForTask(task);
|
||||||
const localOnly = !getConfigValue('extensions.models.autoDownload', true);
|
const localOnly = !getConfigValue('extensions.models.autoDownload', true, 'boolean');
|
||||||
console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
|
console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
|
||||||
const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
|
const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
|
||||||
tasks[task].pipeline = instance;
|
tasks[task].pipeline = instance;
|
||||||
|
41
src/users.js
41
src/users.js
@@ -15,15 +15,15 @@ import _ from 'lodash';
|
|||||||
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
||||||
|
|
||||||
import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } from './constants.js';
|
import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } from './constants.js';
|
||||||
import { getConfigValue, color, delay, setConfigValue, generateTimestamp } from './util.js';
|
import { getConfigValue, color, delay, generateTimestamp } from './util.js';
|
||||||
import { readSecret, writeSecret } from './endpoints/secrets.js';
|
import { readSecret, writeSecret } from './endpoints/secrets.js';
|
||||||
import { getContentOfType } from './endpoints/content-manager.js';
|
import { getContentOfType } from './endpoints/content-manager.js';
|
||||||
|
|
||||||
export const KEY_PREFIX = 'user:';
|
export const KEY_PREFIX = 'user:';
|
||||||
const AVATAR_PREFIX = 'avatar:';
|
const AVATAR_PREFIX = 'avatar:';
|
||||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false, 'boolean');
|
||||||
const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false);
|
const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false, 'boolean');
|
||||||
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false);
|
const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false, 'boolean');
|
||||||
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,9 +32,13 @@ const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
|||||||
*/
|
*/
|
||||||
const DIRECTORIES_CACHE = new Map();
|
const DIRECTORIES_CACHE = new Map();
|
||||||
const PUBLIC_USER_AVATAR = '/img/default-user.png';
|
const PUBLIC_USER_AVATAR = '/img/default-user.png';
|
||||||
|
const COOKIE_SECRET_PATH = 'cookie-secret.txt';
|
||||||
|
|
||||||
const STORAGE_KEYS = {
|
const STORAGE_KEYS = {
|
||||||
csrfSecret: 'csrfSecret',
|
csrfSecret: 'csrfSecret',
|
||||||
|
/**
|
||||||
|
* @deprecated Read from COOKIE_SECRET_PATH in DATA_ROOT instead.
|
||||||
|
*/
|
||||||
cookieSecret: 'cookieSecret',
|
cookieSecret: 'cookieSecret',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -412,11 +416,10 @@ export function toAvatarKey(handle) {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function initUserStorage(dataRoot) {
|
export async function initUserStorage(dataRoot) {
|
||||||
globalThis.DATA_ROOT = dataRoot;
|
console.log('Using data root:', color.green(dataRoot));
|
||||||
console.log('Using data root:', color.green(globalThis.DATA_ROOT));
|
|
||||||
console.log();
|
console.log();
|
||||||
await storage.init({
|
await storage.init({
|
||||||
dir: path.join(globalThis.DATA_ROOT, '_storage'),
|
dir: path.join(dataRoot, '_storage'),
|
||||||
ttl: false, // Never expire
|
ttl: false, // Never expire
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -430,17 +433,29 @@ export async function initUserStorage(dataRoot) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the cookie secret from the config. If it doesn't exist, generate a new one.
|
* Get the cookie secret from the config. If it doesn't exist, generate a new one.
|
||||||
|
* @param {string} dataRoot The root directory for user data
|
||||||
* @returns {string} The cookie secret
|
* @returns {string} The cookie secret
|
||||||
*/
|
*/
|
||||||
export function getCookieSecret() {
|
export function getCookieSecret(dataRoot) {
|
||||||
let secret = getConfigValue(STORAGE_KEYS.cookieSecret);
|
const cookieSecretPath = path.join(dataRoot, COOKIE_SECRET_PATH);
|
||||||
|
|
||||||
if (!secret) {
|
if (fs.existsSync(cookieSecretPath)) {
|
||||||
console.warn(color.yellow('Cookie secret is missing from config.yaml. Generating a new one...'));
|
const stat = fs.statSync(cookieSecretPath);
|
||||||
secret = crypto.randomBytes(64).toString('base64');
|
if (stat.size > 0) {
|
||||||
setConfigValue(STORAGE_KEYS.cookieSecret, secret);
|
return fs.readFileSync(cookieSecretPath, 'utf8');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldSecret = getConfigValue(STORAGE_KEYS.cookieSecret);
|
||||||
|
if (oldSecret) {
|
||||||
|
console.log('Migrating cookie secret from config.yaml...');
|
||||||
|
writeFileAtomicSync(cookieSecretPath, oldSecret, { encoding: 'utf8' });
|
||||||
|
return oldSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(color.yellow('Cookie secret is missing from data root. Generating a new one...'));
|
||||||
|
const secret = crypto.randomBytes(64).toString('base64');
|
||||||
|
writeFileAtomicSync(cookieSecretPath, secret, { encoding: 'utf8' });
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
src/util.js
74
src/util.js
@@ -9,7 +9,6 @@ import { promises as dnsPromise } from 'node:dns';
|
|||||||
|
|
||||||
import yaml from 'yaml';
|
import yaml from 'yaml';
|
||||||
import { sync as commandExistsSync } from 'command-exists';
|
import { sync as commandExistsSync } from 'command-exists';
|
||||||
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import yauzl from 'yauzl';
|
import yauzl from 'yauzl';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
@@ -21,6 +20,14 @@ import { LOG_LEVELS } from './constants.js';
|
|||||||
*/
|
*/
|
||||||
let CACHED_CONFIG = null;
|
let CACHED_CONFIG = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a configuration key to an environment variable key.
|
||||||
|
* @param {string} key Configuration key
|
||||||
|
* @returns {string} Environment variable key
|
||||||
|
* @example keyToEnv('extensions.models.speechToText') // 'SILLYTAVERN_EXTENSIONS_MODELS_SPEECHTOTEXT'
|
||||||
|
*/
|
||||||
|
export const keyToEnv = (key) => 'SILLYTAVERN_' + String(key).toUpperCase().replace(/\./g, '_');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the config object from the config.yaml file.
|
* Returns the config object from the config.yaml file.
|
||||||
* @returns {object} Config object
|
* @returns {object} Config object
|
||||||
@@ -51,24 +58,40 @@ export function getConfig() {
|
|||||||
* Returns the value for the given key from the config object.
|
* Returns the value for the given key from the config object.
|
||||||
* @param {string} key - Key to get from the config object
|
* @param {string} key - Key to get from the config object
|
||||||
* @param {any} defaultValue - Default value to return if the key is not found
|
* @param {any} defaultValue - Default value to return if the key is not found
|
||||||
|
* @param {'number'|'boolean'|null} typeConverter - Type to convert the value to
|
||||||
* @returns {any} Value for the given key
|
* @returns {any} Value for the given key
|
||||||
*/
|
*/
|
||||||
export function getConfigValue(key, defaultValue = null) {
|
export function getConfigValue(key, defaultValue = null, typeConverter = null) {
|
||||||
|
function _getValue() {
|
||||||
|
const envKey = keyToEnv(key);
|
||||||
|
if (envKey in process.env) {
|
||||||
|
const needsJsonParse = defaultValue && typeof defaultValue === 'object';
|
||||||
|
const envValue = process.env[envKey];
|
||||||
|
return needsJsonParse ? (tryParse(envValue) ?? defaultValue) : envValue;
|
||||||
|
}
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
return _.get(config, key, defaultValue);
|
return _.get(config, key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = _getValue();
|
||||||
|
switch (typeConverter) {
|
||||||
|
case 'number':
|
||||||
|
return isNaN(parseFloat(value)) ? defaultValue : parseFloat(value);
|
||||||
|
case 'boolean':
|
||||||
|
return toBoolean(value);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a value for the given key in the config object and writes it to the config.yaml file.
|
* THIS FUNCTION IS DEPRECATED AND ONLY EXISTS FOR BACKWARDS COMPATIBILITY. DON'T USE IT.
|
||||||
* @param {string} key Key to set
|
* @param {any} _key Unused
|
||||||
* @param {any} value Value to set
|
* @param {any} _value Unused
|
||||||
|
* @deprecated Configs are read-only. Use environment variables instead.
|
||||||
*/
|
*/
|
||||||
export function setConfigValue(key, value) {
|
export function setConfigValue(_key, _value) {
|
||||||
// Reset cache so that the next getConfig call will read the updated config file
|
console.trace(color.yellow('setConfigValue is deprecated and should not be used.'));
|
||||||
CACHED_CONFIG = null;
|
|
||||||
const config = getConfig();
|
|
||||||
_.set(config, key, value);
|
|
||||||
writeFileAtomicSync('./config.yaml', yaml.stringify(config));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -394,7 +417,7 @@ export function generateTimestamp() {
|
|||||||
* @param {number?} limit Maximum number of backups to keep. If null, the limit is determined by the `backups.common.numberOfBackups` config value.
|
* @param {number?} limit Maximum number of backups to keep. If null, the limit is determined by the `backups.common.numberOfBackups` config value.
|
||||||
*/
|
*/
|
||||||
export function removeOldBackups(directory, prefix, limit = null) {
|
export function removeOldBackups(directory, prefix, limit = null) {
|
||||||
const MAX_BACKUPS = limit ?? Number(getConfigValue('backups.common.numberOfBackups', 50));
|
const MAX_BACKUPS = limit ?? Number(getConfigValue('backups.common.numberOfBackups', 50, 'number'));
|
||||||
|
|
||||||
let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix));
|
let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix));
|
||||||
if (files.length > MAX_BACKUPS) {
|
if (files.length > MAX_BACKUPS) {
|
||||||
@@ -747,6 +770,27 @@ export async function canResolve(name, useIPv6 = true, useIPv4 = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts various JavaScript primitives to boolean values.
|
||||||
|
* Handles special case for "true"/"false" strings (case-insensitive)
|
||||||
|
*
|
||||||
|
* @param {any} value - The value to convert to boolean
|
||||||
|
* @returns {boolean} - The boolean representation of the value
|
||||||
|
*/
|
||||||
|
export function toBoolean(value) {
|
||||||
|
// Handle string values case-insensitively
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// Trim and convert to lowercase for case-insensitive comparison
|
||||||
|
const trimmedLower = value.trim().toLowerCase();
|
||||||
|
|
||||||
|
// Handle explicit "true"/"false" strings
|
||||||
|
if (trimmedLower === 'true') return true;
|
||||||
|
if (trimmedLower === 'false') return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all other JavaScript values based on their "truthiness"
|
||||||
|
return Boolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts string to boolean accepts 'true' or 'false' else it returns the string put in
|
* converts string to boolean accepts 'true' or 'false' else it returns the string put in
|
||||||
@@ -754,8 +798,8 @@ export async function canResolve(name, useIPv6 = true, useIPv4 = true) {
|
|||||||
* @returns {boolean|string|null} boolean else original input string or null if input is
|
* @returns {boolean|string|null} boolean else original input string or null if input is
|
||||||
*/
|
*/
|
||||||
export function stringToBool(str) {
|
export function stringToBool(str) {
|
||||||
if (str === 'true') return true;
|
if (String(str).trim().toLowerCase() === 'true') return true;
|
||||||
if (str === 'false') return false;
|
if (String(str).trim().toLowerCase() === 'false') return false;
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,7 +807,7 @@ export function stringToBool(str) {
|
|||||||
* Setup the minimum log level
|
* Setup the minimum log level
|
||||||
*/
|
*/
|
||||||
export function setupLogLevel() {
|
export function setupLogLevel() {
|
||||||
const logLevel = getConfigValue('logging.minLogLevel', LOG_LEVELS.DEBUG);
|
const logLevel = getConfigValue('logging.minLogLevel', LOG_LEVELS.DEBUG, 'number');
|
||||||
|
|
||||||
globalThis.console.debug = logLevel <= LOG_LEVELS.DEBUG ? console.debug : () => {};
|
globalThis.console.debug = logLevel <= LOG_LEVELS.DEBUG ? console.debug : () => {};
|
||||||
globalThis.console.info = logLevel <= LOG_LEVELS.INFO ? console.info : () => {};
|
globalThis.console.info = logLevel <= LOG_LEVELS.INFO ? console.info : () => {};
|
||||||
|
Reference in New Issue
Block a user