Rewritten minLogLevel feature

1f9fa747865e3ad9f4160c16f4e2eb3443cd1baf

Eradev <admin@eradev.com>

44 files changed, +580 -561Ignore whitespace
default/config.yaml+2 -0
@@ -65,6 +65,8 @@ autheliaAuth: false
6565# the username and passwords for basic auth are the same as those
6666# for the individual accounts
6767perUserBasicAuth: false
68+# Minimum log level to display in the terminal (DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3)
69+minLogLevel: 0
6870
6971# User session timeout *in seconds* (defaults to 24 hours).
7072## Set to a positive number to expire session after a certain time of inactivity
public/scripts/chat-templates.js+2 -1
@@ -59,6 +59,7 @@ const hash_derivations = {
5959 // Tulu-3-8B
6060 // Tulu-3-70B
6161 'Tulu'
62+ ,
6263};
6364
6465const substr_derivations = {
@@ -87,6 +88,6 @@ export async function deriveTemplatesFromChatTemplate(chat_template, hash) {
8788 }
8889 }
8990
9091 console.logwarn(`Unknown chat template hash: ${hash} for [${chat_template}]`);
9192 return null;
9293}
public/scripts/extensions/quick-reply/src/QuickReplySet.js+5 -28
@@ -1,15 +1,13 @@
11import { getRequestHeaders, substituteParams } from '../../../../script.js';
22import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js';
33import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
44import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
55import { SlashCommandScopedebounceAsync, warn } from '../../../slash-commands/SlashCommandScopeindex.js';
6-import { debounceAsync, log, warn } from '../index.js';
76import { QuickReply } from './QuickReply.js';
87
98export class QuickReplySet {
109 /**@type {QuickReplySet[]}*/ static list = [];
1110
12-
1311 static from(props) {
1412 props.qrList = []; //props.qrList?.map(it=>QuickReply.from(it));
1513 const instance = Object.assign(new this(), props);
@@ -24,9 +22,6 @@ export class QuickReplySet {
2422 return this.list.find(it=>it.name == name);
2523 }
2624
27-
28-
29-
3025 /**@type {string}*/ name;
3126 /**@type {boolean}*/ disableSend = false;
3227 /**@type {boolean}*/ placeBeforeInput = false;
@@ -34,19 +29,12 @@ export class QuickReplySet {
3429 /**@type {string}*/ color = 'transparent';
3530 /**@type {boolean}*/ onlyBorderColor = false;
3631 /**@type {QuickReply[]}*/ qrList = [];
37-
3832 /**@type {number}*/ idIndex = 0;
39-
4033 /**@type {boolean}*/ isDeleted = false;
41-
4234 /**@type {function}*/ save;
43-
4435 /**@type {HTMLElement}*/ dom;
4536 /**@type {HTMLElement}*/ settingsDom;
4637
47-
48-
49-
5038 constructor() {
5139 this.save = debounceAsync(()=>this.performSave(), 200);
5240 }
@@ -55,9 +43,6 @@ export class QuickReplySet {
5543 this.qrList.forEach(qr=>this.hookQuickReply(qr));
5644 }
5745
58-
59-
60-
6146 unrender() {
6247 this.dom?.remove();
6348 this.dom = null;
@@ -100,9 +85,6 @@ export class QuickReplySet {
10085 }
10186 }
10287
103-
104-
105-
10688 renderSettings() {
10789 if (!this.settingsDom) {
10890 this.settingsDom = document.createElement('div'); {
@@ -123,9 +105,6 @@ export class QuickReplySet {
123105 this.settingsDom.append(qr.renderSettings(idx));
124106 }
125107
126-
127-
128-
129108 /**
130109 *
131110 * @param {QuickReply} qr
@@ -138,6 +117,7 @@ export class QuickReplySet {
138117 closure.scope.setMacro('arg::*', '');
139118 return (await closure.execute())?.pipe;
140119 }
120+
141121 /**
142122 *
143123 * @param {QuickReply} qr The QR to execute.
@@ -207,6 +187,7 @@ export class QuickReplySet {
207187 document.querySelector('#send_but').click();
208188 }
209189 }
190+
210191 /**
211192 * @param {QuickReply} qr
212193 * @param {string} [message] - optional altered message to be used
@@ -220,9 +201,6 @@ export class QuickReplySet {
220201 });
221202 }
222203
223-
224-
225-
226204 addQuickReply(data = {}) {
227205 const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1;
228206 data.id =
@@ -239,6 +217,7 @@ export class QuickReplySet {
239217 this.save();
240218 return qr;
241219 }
220+
242221 addQuickReplyFromText(qrJson) {
243222 let data;
244223 if (qrJson) {
@@ -371,7 +350,6 @@ export class QuickReplySet {
371350 this.save();
372351 }
373352
374-
375353 toJSON() {
376354 return {
377355 version: 2,
@@ -386,7 +364,6 @@ export class QuickReplySet {
386364 };
387365 }
388366
389-
390367 async performSave() {
391368 const response = await fetch('/api/quick-replies/save', {
392369 method: 'POST',
public/scripts/extensions/vectors/index.js+4 -6
@@ -1621,14 +1621,14 @@ jQuery(async () => {
16211621 const attachments = source ? getDataBankAttachmentsForSource(source, false) : getDataBankAttachments(false);
16221622 const collectionIds = await ingestDataBankAttachments(String(source));
16231623 const queryResults = await queryMultipleCollections(collectionIds, String(query), count, threshold);
1624-
1624+
16251625 // Get URLs
16261626 const urls = Object
16271627 .keys(queryResults)
16281628 .map(x => attachments.find(y => getFileCollectionId(y.url) === x))
16291629 .filter(x => x)
16301630 .map(x => x.url);
1631-
1631+
16321632 // Gets the actual text content of chunks
16331633 const getChunksText = () => {
16341634 let textResult = '';
@@ -1638,14 +1638,12 @@ jQuery(async () => {
16381638 }
16391639 return textResult;
16401640 };
1641-
16421641 if (args.return === 'chunks') {
16431642 return getChunksText();
16441643 }
16451644
16461645 // @ts-ignore
16471646 return slashCommandReturnHelper.doReturn(args.return ?? 'object', urls, { objectToStringFunc: list => list.join('\n') });
1648-
16491647 },
16501648 aliases: ['databank-search', 'data-bank-search'],
16511649 helpString: 'Search the Data Bank for a specific query using vector similarity. Returns a list of file URLs with the most relevant content.',
@@ -1660,10 +1658,10 @@ jQuery(async () => {
16601658 defaultValue: 'object',
16611659 enumList: [
16621660 new SlashCommandEnumValue('chunks', 'Return the actual content chunks', enumTypes.enum, '{}'),
16631661 ...slashCommandReturnHelper.enumList({ allowObject: true }),
16641662 ],
16651663 forceEnum: true,
16661664 }),
16671665 ],
16681666 unnamedArgumentList: [
16691667 new SlashCommandArgument('Query to search by.', ARGUMENT_TYPE.STRING, true, false),
server.js+6 -2
@@ -67,6 +67,7 @@ import {
6767 forwardFetchResponse,
6868 removeColorFormatting,
6969 getSeparator,
70+ setupLogLevel,
7071} from './src/util.js';
7172import { UPLOADS_DIRECTORY } from './src/constants.js';
7273import { ensureThumbnailCache } from './src/endpoints/thumbnails.js';
@@ -688,8 +689,9 @@ const preSetupTasks = async function () {
688689function getAutorunHostname() {
689690 if (autorunHostname === 'auto') {
690691 if (enableIPv6 && enableIPv4) {
691- if (avoidLocalhost) return '[::1]';
692+ return avoidLocalhost
692- return 'localhost';
693+ ? '[::1]'
694+ : 'localhost';
693695 }
694696
695697 if (enableIPv6) {
@@ -755,6 +757,8 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
755757 }
756758 }
757759 }
760+
761+ setupLogLevel();
758762};
759763
760764/**
src/constants.js+7 -0
@@ -413,3 +413,10 @@ export const VLLM_KEYS = [
413413 'guided_decoding_backend',
414414 'guided_whitespace_pattern',
415415];
416+
417+export const LOG_LEVELS = {
418+ DEBUG: 0,
419+ INFO: 1,
420+ WARN: 2,
421+ ERROR: 3,
422+};
src/endpoints/anthropic.js+3 -3
@@ -32,7 +32,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
3232 max_tokens: 4096,
3333 };
3434
3535 console.logdebug('Multimodal captioning request', body);
3636
3737 const result = await fetch(url, {
3838 body: JSON.stringify(body),
@@ -46,14 +46,14 @@ router.post('/caption-image', jsonParser, async (request, response) => {
4646
4747 if (!result.ok) {
4848 const text = await result.text();
4949 console.logerror(`Claude API returned error: ${result.status} ${result.statusText}`, text);
5050 return response.status(result.status).send({ error: true });
5151 }
5252
5353 /** @type {any} */
5454 const generateResponseJson = await result.json();
5555 const caption = generateResponseJson.content[0].text;
5656 console.logdebug('Claude response:', generateResponseJson);
5757
5858 if (!caption) {
5959 return response.status(500).send('No caption found');
src/endpoints/assets.js+29 -18
@@ -176,7 +176,7 @@ router.post('/get', jsonParser, async (request, response) => {
176176 }
177177 }
178178 catch (err) {
179179 console.logerror(err);
180180 }
181181 return response.send(output);
182182});
@@ -200,7 +200,7 @@ router.post('/download', jsonParser, async (request, response) => {
200200 category = i;
201201
202202 if (category === null) {
203203 console.debugerror('Bad request: unsupported asset category.');
204204 return response.sendStatus(400);
205205 }
206206
@@ -212,7 +212,7 @@ router.post('/download', jsonParser, async (request, response) => {
212212
213213 const temp_path = path.join(request.user.directories.assets, 'temp', request.body.filename);
214214 const file_path = path.join(request.user.directories.assets, category, request.body.filename);
215215 console.debuginfo('Request received to download', url, 'to', file_path);
216216
217217 try {
218218 // Download to temp
@@ -224,7 +224,9 @@ router.post('/download', jsonParser, async (request, response) => {
224224 // Delete if previous download failed
225225 if (fs.existsSync(temp_path)) {
226226 fs.unlink(temp_path, (err) => {
227227 if (err) throw err;{
228+ throw err;
229+ }
228230 });
229231 }
230232 const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
@@ -241,13 +243,13 @@ router.post('/download', jsonParser, async (request, response) => {
241243 }
242244
243245 // Move into asset place
244246 console.debuginfo('Download finished, moving file from', temp_path, 'to', file_path);
245247 fs.copyFileSync(temp_path, file_path);
246248 fs.rmSync(temp_path);
247249 response.sendStatus(200);
248250 }
249251 catch (error) {
250252 console.logerror(error);
251253 response.sendStatus(500);
252254 }
253255});
@@ -270,7 +272,7 @@ router.post('/delete', jsonParser, async (request, response) => {
270272 category = i;
271273
272274 if (category === null) {
273275 console.debugerror('Bad request: unsupported asset category.');
274276 return response.sendStatus(400);
275277 }
276278
@@ -280,25 +282,27 @@ router.post('/delete', jsonParser, async (request, response) => {
280282 return response.status(400).send(validation.message);
281283
282284 const file_path = path.join(request.user.directories.assets, category, request.body.filename);
283285 console.debuginfo('Request received to delete', category, file_path);
284286
285287 try {
286288 // Delete if previous download failed
287289 if (fs.existsSync(file_path)) {
288290 fs.unlink(file_path, (err) => {
289291 if (err) throw err;{
292+ throw err;
293+ }
290294 });
291295 console.debuginfo('Asset deleted.');
292296 }
293297 else {
294298 console.debugerror('Asset not found.');
295299 response.sendStatus(400);
296300 }
297301 // Move into asset place
298302 response.sendStatus(200);
299303 }
300304 catch (error) {
301305 console.logerror(error);
302306 response.sendStatus(500);
303307 }
304308});
@@ -313,19 +317,24 @@ router.post('/delete', jsonParser, async (request, response) => {
313317 * @returns {void}
314318 */
315319router.post('/character', jsonParser, async (request, response) => {
316320 if (request.query.name === undefined) return response.sendStatus(400);{
321+ return response.sendStatus(400);
322+ }
323+
317324 // For backwards compatibility, don't reject invalid character names, just sanitize them
318325 const name = sanitize(request.query.name.toString());
319326 const inputCategory = request.query.category;
320327
321328 // Check category
322329 let category = null;
323330 for (let i of VALID_CATEGORIES) {
324331 if (i == inputCategory) {
325332 category = i;
333+ }
334+ }
326335
327336 if (category === null) {
328337 console.debugerror('Bad request: unsupported asset category.');
329338 return response.sendStatus(400);
330339 }
331340
@@ -339,7 +348,9 @@ router.post('/character', jsonParser, async (request, response) => {
339348 if (category == 'live2d') {
340349 const folders = fs.readdirSync(folderPath, { withFileTypes: true });
341350 for (const folderInfo of folders) {
342351 if (!folderInfo.isDirectory()) continue;{
352+ continue;
353+ }
343354
344355 const modelFolder = folderInfo.name;
345356 const live2dModelPath = path.join(folderPath, modelFolder);
@@ -364,7 +375,7 @@ router.post('/character', jsonParser, async (request, response) => {
364375 return response.send(output);
365376 }
366377 catch (err) {
367378 console.logerror(err);
368379 return response.sendStatus(500);
369380 }
370381});
src/endpoints/backends/chat-completions.js+49 -47
@@ -230,7 +230,7 @@ async function sendScaleRequest(request, response) {
230230 }
231231
232232 const requestPrompt = convertTextCompletionPrompt(request.body.messages);
233233 console.logdebug('Scale request:', requestPrompt);
234234
235235 try {
236236 const controller = new AbortController();
@@ -249,18 +249,18 @@ async function sendScaleRequest(request, response) {
249249 });
250250
251251 if (!generateResponse.ok) {
252252 console.logerror(`Scale API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
253253 return response.status(500).send({ error: true });
254254 }
255255
256256 /** @type {any} */
257257 const generateResponseJson = await generateResponse.json();
258258 console.logdebug('Scale response:', generateResponseJson);
259259
260260 const reply = { choices: [{ 'message': { 'content': generateResponseJson.output } }] };
261261 return response.send(reply);
262262 } catch (error) {
263263 console.logerror(error);
264264 if (!response.headersSent) {
265265 return response.status(500).send({ error: true });
266266 }
@@ -277,7 +277,7 @@ async function sendMakerSuiteRequest(request, response) {
277277 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
278278
279279 if (!request.body.reverse_proxy && !apiKey) {
280280 console.logerror('Google AI Studio API key is missing.');
281281 return response.status(400).send({ error: true });
282282 }
283283
@@ -328,7 +328,7 @@ async function sendMakerSuiteRequest(request, response) {
328328 }
329329
330330 const body = getGeminiBody();
331331 console.logdebug('Google AI Studio request:', body);
332332
333333 try {
334334 const controller = new AbortController();
@@ -355,14 +355,14 @@ async function sendMakerSuiteRequest(request, response) {
355355 // Pipe remote SSE stream to Express response
356356 forwardFetchResponse(generateResponse, response);
357357 } catch (error) {
358358 console.logerror('Error forwarding streaming response:', error);
359359 if (!response.headersSent) {
360360 return response.status(500).send({ error: true });
361361 }
362362 }
363363 } else {
364364 if (!generateResponse.ok) {
365365 console.logerror(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
366366 return response.status(generateResponse.status).send({ error: true });
367367 }
368368
@@ -380,7 +380,7 @@ async function sendMakerSuiteRequest(request, response) {
380380 }
381381
382382 const responseContent = candidates[0].content ?? candidates[0].output;
383383 console.logerror('Google AI Studio response:', responseContent);
384384
385385 if (Array.isArray(responseContent?.parts) && isThinking && !showThoughts) {
386386 responseContent.parts = responseContent.parts.filter(part => !part.thought);
@@ -389,7 +389,7 @@ async function sendMakerSuiteRequest(request, response) {
389389 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join('\n\n');
390390 if (!responseText) {
391391 let message = 'Google AI Studio Candidate text empty';
392392 console.logerror(message, generateResponseJson);
393393 return response.send({ error: { message } });
394394 }
395395
@@ -398,7 +398,7 @@ async function sendMakerSuiteRequest(request, response) {
398398 return response.send(reply);
399399 }
400400 } catch (error) {
401401 console.logerror('Error communicating with Google AI Studio API: ', error);
402402 if (!response.headersSent) {
403403 return response.status(500).send({ error: true });
404404 }
@@ -411,9 +411,12 @@ async function sendMakerSuiteRequest(request, response) {
411411 * @param {express.Response} response Express response
412412 */
413413async function sendAI21Request(request, response) {
414414 if (!request.body) return response.sendStatus(400);{
415+ return response.sendStatus(400);
416+ }
417+
415418 const controller = new AbortController();
416419 console.logdebug(request.body.messages);
417420 request.socket.removeAllListeners('close');
418421 request.socket.on('close', function () {
419422 controller.abort();
@@ -439,7 +442,7 @@ async function sendAI21Request(request, response) {
439442 signal: controller.signal,
440443 };
441444
442445 console.logdebug('AI21 request:', body);
443446
444447 try {
445448 const generateResponse = await fetch(API_AI21 + '/chat/completions', options);
@@ -448,16 +451,16 @@ async function sendAI21Request(request, response) {
448451 } else {
449452 if (!generateResponse.ok) {
450453 const errorText = await generateResponse.text();
451454 console.logerror(`AI21 API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
452455 const errorJson = tryParse(errorText) ?? { error: true };
453456 return response.status(500).send(errorJson);
454457 }
455458 const generateResponseJson = await generateResponse.json();
456459 console.logdebug('AI21 response:', generateResponseJson);
457460 return response.send(generateResponseJson);
458461 }
459462 } catch (error) {
460463 console.logerror('Error communicating with AI21 API: ', error);
461464 if (!response.headersSent) {
462465 response.send({ error: true });
463466 } else {
@@ -476,7 +479,7 @@ async function sendMistralAIRequest(request, response) {
476479 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI);
477480
478481 if (!apiKey) {
479482 console.logerror('MistralAI API key is missing.');
480483 return response.status(400).send({ error: true });
481484 }
482485
@@ -517,7 +520,7 @@ async function sendMistralAIRequest(request, response) {
517520 timeout: 0,
518521 };
519522
520523 console.logdebug('MisralAI request:', requestBody);
521524
522525 const generateResponse = await fetch(apiUrl + '/chat/completions', config);
523526 if (request.body.stream) {
@@ -525,16 +528,16 @@ async function sendMistralAIRequest(request, response) {
525528 } else {
526529 if (!generateResponse.ok) {
527530 const errorText = await generateResponse.text();
528531 console.logerror(`MistralAI API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
529532 const errorJson = tryParse(errorText) ?? { error: true };
530533 return response.status(500).send(errorJson);
531534 }
532535 const generateResponseJson = await generateResponse.json();
533536 console.logdebug('MistralAI response:', generateResponseJson);
534537 return response.send(generateResponseJson);
535538 }
536539 } catch (error) {
537540 console.logerror('Error communicating with MistralAI API: ', error);
538541 if (!response.headersSent) {
539542 response.send({ error: true });
540543 } else {
@@ -557,7 +560,7 @@ async function sendCohereRequest(request, response) {
557560 });
558561
559562 if (!apiKey) {
560563 console.logerror('Cohere API key is missing.');
561564 return response.status(400).send({ error: true });
562565 }
563566
@@ -596,7 +599,7 @@ async function sendCohereRequest(request, response) {
596599 requestBody.safety_mode = 'OFF';
597600 }
598601
599602 console.logdebug('Cohere request:', requestBody);
600603
601604 const config = {
602605 method: 'POST',
@@ -618,16 +621,16 @@ async function sendCohereRequest(request, response) {
618621 const generateResponse = await fetch(apiUrl, config);
619622 if (!generateResponse.ok) {
620623 const errorText = await generateResponse.text();
621624 console.logerror(`Cohere API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
622625 const errorJson = tryParse(errorText) ?? { error: true };
623626 return response.status(500).send(errorJson);
624627 }
625628 const generateResponseJson = await generateResponse.json();
626629 console.logdebug('Cohere response:', generateResponseJson);
627630 return response.send(generateResponseJson);
628631 }
629632 } catch (error) {
630633 console.logerror('Error communicating with Cohere API: ', error);
631634 if (!response.headersSent) {
632635 response.send({ error: true });
633636 } else {
@@ -684,12 +687,12 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
684687 api_key_openai = readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
685688 headers = {};
686689 } else {
687690 console.logerror('This chat completion source is not supported yet.');
688691 return response_getstatus_openai.status(400).send({ error: true });
689692 }
690693
691694 if (!api_key_openai && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
692695 console.logerror('Chat Completion API key is missing.');
693696 return response_getstatus_openai.status(400).send({ error: true });
694697 }
695698
@@ -724,23 +727,23 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
724727 };
725728 });
726729
727730 console.loginfo('Available OpenRouter models:', models);
728731 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) {
729732 const models = data?.data;
730733 console.loginfo(models);
731734 } else {
732735 const models = data?.data;
733736
734737 if (Array.isArray(models)) {
735738 const modelIds = models.filter(x => x && typeof x === 'object').map(x => x.id).sort();
736739 console.loginfo('Available models:', modelIds);
737740 } else {
738741 console.logwarn('Chat Completion endpoint did not return a list of models.');
739742 }
740743 }
741744 }
742745 else {
743746 console.logerror('Chat Completion status check failed. Either Access Token is incorrect or API endpoint is down.');
744747 response_getstatus_openai.send({ error: true, can_bypass: true, data: { data: [] } });
745748 }
746749 } catch (e) {
@@ -755,8 +758,7 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
755758});
756759
757760router.post('/bias', jsonParser, async function (request, response) {
758761 if (!request.body || !Array.isArray(request.body)) return response.sendStatus(400);
759- return response.sendStatus(400);
760762
761763 try {
762764 const result = {};
@@ -773,7 +775,7 @@ router.post('/bias', jsonParser, async function (request, response) {
773775 const tokenizer = getSentencepiceTokenizer(model);
774776 const instance = await tokenizer?.get();
775777 if (!instance) {
776778 console.warnerror('Tokenizer not initialized:', model);
777779 return response.send({});
778780 }
779781 encodeFunction = (text) => new Uint32Array(instance.encodeIds(text));
@@ -922,7 +924,7 @@ router.post('/generate', jsonParser, function (request, response) {
922924 mergeObjectWithYaml(headers, request.body.custom_include_headers);
923925
924926 if (request.body.custom_prompt_post_processing) {
925927 console.loginfo('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing);
926928 request.body.messages = postProcessPrompt(
927929 request.body.messages,
928930 request.body.custom_prompt_post_processing,
@@ -967,12 +969,12 @@ router.post('/generate', jsonParser, function (request, response) {
967969
968970 request.body.messages = postProcessPrompt(request.body.messages, 'deepseek', getPromptNames(request));
969971 } else {
970972 console.logerror('This chat completion source is not supported yet.');
971973 return response.status(400).send({ error: true });
972974 }
973975
974976 if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
975977 console.logerror('OpenAI API key is missing.');
976978 return response.status(400).send({ error: true });
977979 }
978980
@@ -1032,7 +1034,7 @@ router.post('/generate', jsonParser, function (request, response) {
10321034 signal: controller.signal,
10331035 };
10341036
10351037 console.logdebug(requestBody);
10361038
10371039 makeRequest(config, response, request);
10381040
@@ -1049,7 +1051,7 @@ router.post('/generate', jsonParser, function (request, response) {
10491051 const fetchResponse = await fetch(endpointUrl, config);
10501052
10511053 if (request.body.stream) {
10521054 console.loginfo('Streaming request in progress');
10531055 forwardFetchResponse(fetchResponse, response);
10541056 return;
10551057 }
@@ -1058,10 +1060,10 @@ router.post('/generate', jsonParser, function (request, response) {
10581060 /** @type {any} */
10591061 let json = await fetchResponse.json();
10601062 response.send(json);
10611063 console.logdebug(json);
10621064 console.logdebug(json?.choices?.[0]?.message);
10631065 } else if (fetchResponse.status === 429 && retries > 0) {
10641066 console.logwarn(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`);
10651067 setTimeout(() => {
10661068 timeout *= 2;
10671069 makeRequest(config, response, request, retries - 1, timeout);
@@ -1070,7 +1072,7 @@ router.post('/generate', jsonParser, function (request, response) {
10701072 await handleErrorResponse(fetchResponse);
10711073 }
10721074 } catch (error) {
10731075 console.logerror('Generation failed', error);
10741076 const message = error.code === 'ECONNREFUSED'
10751077 ? `Connection refused: ${error.message}`
10761078 : error.message || 'Unknown error occurred';
@@ -1092,7 +1094,7 @@ router.post('/generate', jsonParser, function (request, response) {
10921094
10931095 const message = errorResponse.statusText || 'Unknown error occurred';
10941096 const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota';
10951097 console.logerror('Chat completion request error: ', message, responseText);
10961098
10971099 if (!response.headersSent) {
10981100 response.send({ error: { message }, quota_error: quota_error });
src/endpoints/backends/kobold.js+15 -14
@@ -22,17 +22,17 @@ router.post('/generate', jsonParser, async function (request, response_generate)
2222 request.socket.on('close', async function () {
2323 if (request.body.can_abort && !response_generate.writableEnded) {
2424 try {
2525 console.loginfo('Aborting Kobold generation...');
2626 // send abort signal to koboldcpp
2727 const abortResponse = await fetch(`${request.body.api_server}/extra/abort`, {
2828 method: 'POST',
2929 });
3030
3131 if (!abortResponse.ok) {
3232 console.logerror('Error sending abort request to Kobold:', abortResponse.status);
3333 }
3434 } catch (error) {
3535 console.logerror(error);
3636 }
3737 }
3838 controller.abort();
@@ -81,7 +81,7 @@ router.post('/generate', jsonParser, async function (request, response_generate)
8181 }
8282 }
8383
8484 console.logdebug(this_settings);
8585 const args = {
8686 body: JSON.stringify(this_settings),
8787 headers: Object.assign(
@@ -105,7 +105,7 @@ router.post('/generate', jsonParser, async function (request, response_generate)
105105 } else {
106106 if (!response.ok) {
107107 const errorText = await response.text();
108108 console.logerror(`Kobold returned error: ${response.status} ${response.statusText} ${errorText}`);
109109
110110 try {
111111 const errorJson = JSON.parse(errorText);
@@ -117,7 +117,7 @@ router.post('/generate', jsonParser, async function (request, response_generate)
117117 }
118118
119119 const data = await response.json();
120120 console.logdebug('Endpoint response:', data);
121121 return response_generate.send(data);
122122 }
123123 } catch (error) {
@@ -125,19 +125,19 @@ router.post('/generate', jsonParser, async function (request, response_generate)
125125 switch (error?.status) {
126126 case 403:
127127 case 503: // retry in case of temporary service issue, possibly caused by a queue failure?
128128 console.debugwarn(`KoboldAI is busy. Retry attempt ${i + 1} of ${MAX_RETRIES}...`);
129129 await delay(delayAmount);
130130 break;
131131 default:
132132 if ('status' in error) {
133133 console.logerror('Status Code from Kobold:', error.status);
134134 }
135135 return response_generate.send({ error: true });
136136 }
137137 }
138138 }
139139
140140 console.logerror('Max retries exceeded. Giving up.');
141141 return response_generate.send({ error: true });
142142});
143143
@@ -193,16 +193,17 @@ router.post('/transcribe-audio', urlencodedParser, async function (request, resp
193193 const server = request.body.server;
194194
195195 if (!server) {
196196 console.logerror('Server is not set');
197197 return response.sendStatus(400);
198198 }
199199
200200 if (!request.file) {
201201 console.logerror('No audio file found');
202202 return response.sendStatus(400);
203203 }
204204
205205 console.loginfo('Transcribing audio with KoboldCpp', server);
206+ console.debug(server);
206207
207208 const fileBase64 = fs.readFileSync(request.file.path).toString('base64');
208209 fs.rmSync(request.file.path);
@@ -226,12 +227,12 @@ router.post('/transcribe-audio', urlencodedParser, async function (request, resp
226227
227228 if (!result.ok) {
228229 const text = await result.text();
229230 console.logerror('KoboldCpp request failed', result.statusText, text);
230231 return response.status(500).send(text);
231232 }
232233
233234 const data = await result.json();
234235 console.logdebug('KoboldCpp transcription response', data);
235236 return response.json(data);
236237 } catch (error) {
237238 console.error('KoboldCpp transcription failed', error);
src/endpoints/backends/scale-alt.js+5 -5
@@ -13,7 +13,7 @@ router.post('/generate', jsonParser, async function (request, response) {
1313 const cookie = readSecret(request.user.directories, SECRET_KEYS.SCALE_COOKIE);
1414
1515 if (!cookie) {
1616 console.logerror('No Scale cookie found');
1717 return response.sendStatus(400);
1818 }
1919
@@ -62,7 +62,7 @@ router.post('/generate', jsonParser, async function (request, response) {
6262 },
6363 };
6464
6565 console.logdebug('Scale request:', body);
6666
6767 const result = await fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', {
6868 method: 'POST',
@@ -75,7 +75,7 @@ router.post('/generate', jsonParser, async function (request, response) {
7575
7676 if (!result.ok) {
7777 const text = await result.text();
7878 console.logerror('Scale request failed', result.statusText, text);
7979 return response.status(500).send({ error: { message: result.statusText } });
8080 }
8181
@@ -83,7 +83,7 @@ router.post('/generate', jsonParser, async function (request, response) {
8383 const data = await result.json();
8484 const output = data?.result?.data?.json?.outputs?.[0] || '';
8585
8686 console.logdebug('Scale response:', data);
8787
8888 if (!output) {
8989 console.warn('Scale response is empty');
@@ -92,7 +92,7 @@ router.post('/generate', jsonParser, async function (request, response) {
9292
9393 return response.json({ output });
9494 } catch (error) {
9595 console.logerror(error);
9696 return response.sendStatus(500);
9797 }
9898});
src/endpoints/backends/text-completions.js+49 -48
@@ -58,12 +58,12 @@ async function parseOllamaStream(jsonStream, request, response) {
5858 });
5959
6060 jsonStream.body.on('end', () => {
6161 console.loginfo('Streaming request finished');
6262 response.write('data: [DONE]\n\n');
6363 response.end();
6464 });
6565 } catch (error) {
6666 console.logerror('Error forwarding streaming response:', error);
6767 if (!response.headersSent) {
6868 return response.status(500).send({ error: true });
6969 } else {
@@ -79,16 +79,16 @@ async function parseOllamaStream(jsonStream, request, response) {
7979 */
8080async function abortKoboldCppRequest(url) {
8181 try {
8282 console.loginfo('Aborting Kobold generation...');
8383 const abortResponse = await fetch(`${url}/api/extra/abort`, {
8484 method: 'POST',
8585 });
8686
8787 if (!abortResponse.ok) {
8888 console.logerror('Error sending abort request to Kobold:', abortResponse.status, abortResponse.statusText);
8989 }
9090 } catch (error) {
9191 console.logerror(error);
9292 }
9393}
9494
@@ -101,7 +101,8 @@ router.post('/status', jsonParser, async function (request, response) {
101101 request.body.api_server = request.body.api_server.replace('localhost', '127.0.0.1');
102102 }
103103
104104 console.loginfo('Trying to connect to API:', request.body);
105+ console.debug(request.body);
105106 const baseUrl = trimV1(request.body.api_server);
106107
107108 const args = {
@@ -123,6 +124,7 @@ router.post('/status', jsonParser, async function (request, response) {
123124 case TEXTGEN_TYPES.LLAMACPP:
124125 case TEXTGEN_TYPES.INFERMATICAI:
125126 case TEXTGEN_TYPES.OPENROUTER:
127+ case TEXTGEN_TYPES.FEATHERLESS:
126128 url += '/v1/models';
127129 break;
128130 case TEXTGEN_TYPES.DREAMGEN:
@@ -140,9 +142,6 @@ router.post('/status', jsonParser, async function (request, response) {
140142 case TEXTGEN_TYPES.OLLAMA:
141143 url += '/api/tags';
142144 break;
143- case TEXTGEN_TYPES.FEATHERLESS:
144- url += '/v1/models';
145- break;
146145 case TEXTGEN_TYPES.HUGGINGFACE:
147146 url += '/info';
148147 break;
@@ -152,7 +151,7 @@ router.post('/status', jsonParser, async function (request, response) {
152151 const isPossiblyLmStudio = modelsReply.headers.get('x-powered-by') === 'Express';
153152
154153 if (!modelsReply.ok) {
155154 console.logerror('Models endpoint is offline.');
156155 return response.sendStatus(400);
157156 }
158157
@@ -173,12 +172,12 @@ router.post('/status', jsonParser, async function (request, response) {
173172 }
174173
175174 if (!Array.isArray(data.data)) {
176175 console.logerror('Models response is not an array.');
177176 return response.sendStatus(400);
178177 }
179178
180179 const modelIds = data.data.map(x => x.id);
181180 console.loginfo('Models available:', modelIds);
182181
183182 // Set result to the first model ID
184183 result = modelIds[0] || 'Valid';
@@ -191,7 +190,7 @@ router.post('/status', jsonParser, async function (request, response) {
191190 if (modelInfoReply.ok) {
192191 /** @type {any} */
193192 const modelInfo = await modelInfoReply.json();
194193 console.logdebug('Ooba model info:', modelInfo);
195194
196195 const modelName = modelInfo?.model_name;
197196 result = modelName || result;
@@ -208,7 +207,7 @@ router.post('/status', jsonParser, async function (request, response) {
208207 if (modelInfoReply.ok) {
209208 /** @type {any} */
210209 const modelInfo = await modelInfoReply.json();
211210 console.logdebug('Tabby model info:', modelInfo);
212211
213212 const modelName = modelInfo?.id;
214213 result = modelName || result;
@@ -230,7 +229,9 @@ router.post('/status', jsonParser, async function (request, response) {
230229});
231230
232231router.post('/props', jsonParser, async function (request, response) {
233232 if (!request.body.api_server) return response.sendStatus(400);{
233+ return response.sendStatus(400);
234+ }
234235
235236 try {
236237 const baseUrl = trimV1(request.body.api_server);
@@ -255,7 +256,7 @@ router.post('/props', jsonParser, async function (request, response) {
255256 props['chat_template'] = props['chat_template'].slice(0, -1) + '\n';
256257 }
257258 props['chat_template_hash'] = createHash('sha256').update(props['chat_template']).digest('hex');
258259 console.logdebug(`Model properties: ${JSON.stringify(props)}`);
259260 return response.send(props);
260261 } catch (error) {
261262 console.error(error);
@@ -264,7 +265,9 @@ router.post('/props', jsonParser, async function (request, response) {
264265});
265266
266267router.post('/generate', jsonParser, async function (request, response) {
267268 if (!request.body) return response.sendStatus(400);{
269+ return response.sendStatus(400);
270+ }
268271
269272 try {
270273 if (request.body.api_server.indexOf('localhost') !== -1) {
@@ -273,7 +276,7 @@ router.post('/generate', jsonParser, async function (request, response) {
273276
274277 const apiType = request.body.api_type;
275278 const baseUrl = request.body.api_server;
276279 console.logdebug(request.body);
277280
278281 const controller = new AbortController();
279282 request.socket.removeAllListeners('close');
@@ -399,7 +402,7 @@ router.post('/generate', jsonParser, async function (request, response) {
399402 if (completionsReply.ok) {
400403 /** @type {any} */
401404 const data = await completionsReply.json();
402405 console.logdebug('Endpoint response:', data);
403406
404407 // Map InfermaticAI response to OAI completions format
405408 if (apiType === TEXTGEN_TYPES.INFERMATICAI) {
@@ -411,24 +414,20 @@ router.post('/generate', jsonParser, async function (request, response) {
411414 const text = await completionsReply.text();
412415 const errorBody = { error: true, status: completionsReply.status, response: text };
413416
414417 ifreturn (!response.headersSent) {
415418 return? response.send(errorBody);
416- }
419+ : response.end();
417-
418- return response.end();
419420 }
420421 }
421422 } catch (error) {
422423 const status = error?.status ?? error?.code ?? 'UNKNOWN';
423424 const text = error?.error ?? error?.statusText ?? error?.message ?? 'Unknown error on /generate endpoint';
424425 let value = { error: true, status: status, response: text };
425426 console.logerror('Endpoint error:', error);
426427
427428 ifreturn (!response.headersSent) {
428429 return? response.send(value);
429- }
430+ : response.end();
430-
431- return response.end();
432431 }
433432});
434433
@@ -436,7 +435,9 @@ const ollama = express.Router();
436435
437436ollama.post('/download', jsonParser, async function (request, response) {
438437 try {
439438 if (!request.body.name || !request.body.api_server) return response.sendStatus(400);{
439+ return response.sendStatus(400);
440+ }
440441
441442 const name = request.body.name;
442443 const url = String(request.body.api_server).replace(/\/$/, '');
@@ -451,7 +452,7 @@ ollama.post('/download', jsonParser, async function (request, response) {
451452 });
452453
453454 if (!fetchResponse.ok) {
454455 console.logerror('Download error:', fetchResponse.status, fetchResponse.statusText);
455456 return response.status(fetchResponse.status).send({ error: true });
456457 }
457458
@@ -468,7 +469,7 @@ ollama.post('/caption-image', jsonParser, async function (request, response) {
468469 return response.sendStatus(400);
469470 }
470471
471472 console.logdebug('Ollama caption request:', request.body);
472473 const baseUrl = trimV1(request.body.server_url);
473474
474475 const fetchResponse = await fetch(`${baseUrl}/api/generate`, {
@@ -483,18 +484,18 @@ ollama.post('/caption-image', jsonParser, async function (request, response) {
483484 });
484485
485486 if (!fetchResponse.ok) {
486487 console.logerror('Ollama caption error:', fetchResponse.status, fetchResponse.statusText);
487488 return response.status(500).send({ error: true });
488489 }
489490
490491 /** @type {any} */
491492 const data = await fetchResponse.json();
492493 console.logdebug('Ollama caption response:', data);
493494
494495 const caption = data?.response || '';
495496
496497 if (!caption) {
497498 console.logerror('Ollama caption is empty.');
498499 return response.status(500).send({ error: true });
499500 }
500501
@@ -513,7 +514,7 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) {
513514 return response.sendStatus(400);
514515 }
515516
516517 console.logdebug('LlamaCpp caption request:', request.body);
517518 const baseUrl = trimV1(request.body.server_url);
518519
519520 const fetchResponse = await fetch(`${baseUrl}/completion`, {
@@ -529,18 +530,18 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) {
529530 });
530531
531532 if (!fetchResponse.ok) {
532533 console.logerror('LlamaCpp caption error:', fetchResponse.status, fetchResponse.statusText);
533534 return response.status(500).send({ error: true });
534535 }
535536
536537 /** @type {any} */
537538 const data = await fetchResponse.json();
538539 console.logdebug('LlamaCpp caption response:', data);
539540
540541 const caption = data?.content || '';
541542
542543 if (!caption) {
543544 console.logerror('LlamaCpp caption is empty.');
544545 return response.status(500).send({ error: true });
545546 }
546547
@@ -558,7 +559,7 @@ llamacpp.post('/props', jsonParser, async function (request, response) {
558559 return response.sendStatus(400);
559560 }
560561
561562 console.logdebug('LlamaCpp props request:', request.body);
562563 const baseUrl = trimV1(request.body.server_url);
563564
564565 const fetchResponse = await fetch(`${baseUrl}/props`, {
@@ -566,12 +567,12 @@ llamacpp.post('/props', jsonParser, async function (request, response) {
566567 });
567568
568569 if (!fetchResponse.ok) {
569570 console.logerror('LlamaCpp props error:', fetchResponse.status, fetchResponse.statusText);
570571 return response.status(500).send({ error: true });
571572 }
572573
573574 const data = await fetchResponse.json();
574575 console.logdebug('LlamaCpp props response:', data);
575576
576577 return response.send(data);
577578
@@ -590,7 +591,7 @@ llamacpp.post('/slots', jsonParser, async function (request, response) {
590591 return response.sendStatus(400);
591592 }
592593
593594 console.logdebug('LlamaCpp slots request:', request.body);
594595 const baseUrl = trimV1(request.body.server_url);
595596
596597 let fetchResponse;
@@ -616,12 +617,12 @@ llamacpp.post('/slots', jsonParser, async function (request, response) {
616617 }
617618
618619 if (!fetchResponse.ok) {
619620 console.logerror('LlamaCpp slots error:', fetchResponse.status, fetchResponse.statusText);
620621 return response.status(500).send({ error: true });
621622 }
622623
623624 const data = await fetchResponse.json();
624625 console.logdebug('LlamaCpp slots response:', data);
625626
626627 return response.send(data);
627628
@@ -659,14 +660,14 @@ tabby.post('/download', jsonParser, async function (request, response) {
659660 return response.status(403).send({ error: true });
660661 }
661662 } else {
662663 console.logerror('API Permission error:', permissionResponse.status, permissionResponse.statusText);
663664 return response.status(permissionResponse.status).send({ error: true });
664665 }
665666
666667 const fetchResponse = await fetch(`${baseUrl}/v1/download`, args);
667668
668669 if (!fetchResponse.ok) {
669670 console.logerror('Download error:', fetchResponse.status, fetchResponse.statusText);
670671 return response.status(fetchResponse.status).send({ error: true });
671672 }
672673
src/endpoints/backgrounds.js+3 -3
@@ -26,7 +26,7 @@ router.post('/delete', jsonParser, function (request, response) {
2626 const fileName = path.join(request.user.directories.backgrounds, sanitize(request.body.bg));
2727
2828 if (!fs.existsSync(fileName)) {
2929 console.logerror('BG file not found');
3030 return response.sendStatus(400);
3131 }
3232
@@ -42,12 +42,12 @@ router.post('/rename', jsonParser, function (request, response) {
4242 const newFileName = path.join(request.user.directories.backgrounds, sanitize(request.body.new_bg));
4343
4444 if (!fs.existsSync(oldFileName)) {
4545 console.logerror('BG file not found');
4646 return response.sendStatus(400);
4747 }
4848
4949 if (fs.existsSync(newFileName)) {
5050 console.logerror('New BG file already exists');
5151 return response.sendStatus(400);
5252 }
5353
src/endpoints/caption.js+4 -4
@@ -2,10 +2,10 @@ import express from 'express';
22import { jsonParser } from '../express-common.js';
33import { getPipeline, getRawImage } from '../transformers.js';
44
5-const TASK = 'image-to-text';
6-
75export const router = express.Router();
86
7+const TASK = 'image-to-text';
8+
99router.post('/', jsonParser, async (req, res) => {
1010 try {
1111 const { image } = req.body;
@@ -13,14 +13,14 @@ router.post('/', jsonParser, async (req, res) => {
1313 const rawImage = await getRawImage(image);
1414
1515 if (!rawImage) {
1616 console.logerror('Failed to parse captioned image');
1717 return res.sendStatus(400);
1818 }
1919
2020 const pipe = await getPipeline(TASK);
2121 const result = await pipe(rawImage);
2222 const text = result[0].generated_text;
2323 console.logdebug('Image caption:', text);
2424
2525 return res.json({ caption: text });
2626 } catch (error) {
src/endpoints/characters.js+28 -29
@@ -90,7 +90,7 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
9090 writeFileAtomicSync(outputImagePath, outputImage);
9191 return true;
9292 } catch (err) {
9393 console.logerror(err);
9494 return false;
9595 }
9696}
@@ -159,7 +159,7 @@ async function tryReadImage(imgPath, crop) {
159159 }
160160 // If it's an unsupported type of image (APNG) - just read the file as buffer
161161 catch (error) {
162162 console.logerror(`Failed to read image: ${imgPath}`, error);
163163 return fs.readFileSync(imgPath);
164164 }
165165}
@@ -222,12 +222,12 @@ const processCharacter = async (item, directories) => {
222222 return character;
223223 }
224224 catch (err) {
225225 console.logerror(`Could not process character: ${item}`);
226226
227227 if (err instanceof SyntaxError) {
228228 console.logerror(`${item} does not contain a valid JSON object.`);
229229 } else {
230230 console.logerror('An unexpected error occurred: ', err);
231231 }
232232
233233 return {
@@ -315,7 +315,7 @@ function readFromV2(char) {
315315 };
316316
317317 _.forEach(fieldMappings, (v2Path, charField) => {
318318 //console.loginfo(`Migrating field: ${charField} from ${v2Path}`);
319319 const v2Value = _.get(char.data, v2Path);
320320 if (_.isUndefined(v2Value)) {
321321 let defaultValue = undefined;
@@ -330,15 +330,15 @@ function readFromV2(char) {
330330 }
331331
332332 if (!_.isUndefined(defaultValue)) {
333333 //console.debugwarn(`Spec v2 extension data missing for field: ${charField}, using default value: ${defaultValue}`);
334334 char[charField] = defaultValue;
335335 } else {
336336 console.debugwarn(`Char ${char['name']} has Spec v2 data missing for unknown field: ${charField}`);
337337 return;
338338 }
339339 }
340340 if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) {
341341 console.debugwarn(`Char ${char['name']} has Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value);
342342 }
343343 char[charField] = v2Value;
344344 });
@@ -435,7 +435,7 @@ function charaFormatData(data, directories) {
435435 }
436436
437437 } catch {
438438 console.debugwarn(`Failed to read world info file: ${data.world}. Character book will not be available.`);
439439 }
440440 }
441441
@@ -445,7 +445,7 @@ function charaFormatData(data, directories) {
445445 // Deep merge the extensions object
446446 _.set(char, 'data.extensions', deepMerge(char.data.extensions, extensions));
447447 } catch {
448448 console.debugwarn(`Failed to parse extensions JSON: ${data.extensions}`);
449449 }
450450 }
451451
@@ -519,7 +519,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) {
519519 const fileText = fs.readFileSync(uploadPath, 'utf8');
520520 fs.rmSync(uploadPath);
521521 const yamlData = yaml.parse(fileText);
522522 console.loginfo('Importing from YAML');
523523 yamlData.name = sanitize(yamlData.name);
524524 const fileName = preservedFileName || getPngName(yamlData.name, context.request.user.directories);
525525 let char = convertToV2({
@@ -552,7 +552,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) {
552552async function importFromCharX(uploadPath, { request }, preservedFileName) {
553553 const data = fs.readFileSync(uploadPath).buffer;
554554 fs.rmSync(uploadPath);
555555 console.loginfo('Importing from CharX');
556556 const cardBuffer = await extractFileFromZipBuffer(data, 'card.json');
557557
558558 if (!cardBuffer) {
@@ -601,7 +601,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
601601 let jsonData = JSON.parse(data);
602602
603603 if (jsonData.spec !== undefined) {
604604 console.loginfo(`Importing from ${jsonData.spec} json`);
605605 importRisuSprites(request.user.directories, jsonData);
606606 unsetFavFlag(jsonData);
607607 jsonData = readFromV2(jsonData);
@@ -611,7 +611,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
611611 const result = await writeCharacterData(defaultAvatarPath, char, pngName, request);
612612 return result ? pngName : '';
613613 } else if (jsonData.name !== undefined) {
614614 console.loginfo('Importing from v1 json');
615615 jsonData.name = sanitize(jsonData.name);
616616 if (jsonData.creator_notes) {
617617 jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
@@ -637,7 +637,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
637637 const result = await writeCharacterData(defaultAvatarPath, charJSON, pngName, request);
638638 return result ? pngName : '';
639639 } else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
640640 console.loginfo('Importing from gradio json');
641641 jsonData.char_name = sanitize(jsonData.char_name);
642642 if (jsonData.creator_notes) {
643643 jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
@@ -684,7 +684,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) {
684684 const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories);
685685
686686 if (jsonData.spec !== undefined) {
687687 console.loginfo(`Found a ${jsonData.spec} character file.`);
688688 importRisuSprites(request.user.directories, jsonData);
689689 unsetFavFlag(jsonData);
690690 jsonData = readFromV2(jsonData);
@@ -694,7 +694,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) {
694694 fs.unlinkSync(uploadPath);
695695 return result ? pngName : '';
696696 } else if (jsonData.name !== undefined) {
697697 console.loginfo('Found a v1 character file.');
698698
699699 if (jsonData.creator_notes) {
700700 jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
@@ -853,7 +853,7 @@ router.post('/edit', urlencodedParser, async function (request, response) {
853853 * @returns {void}
854854 */
855855router.post('/edit-attribute', jsonParser, async function (request, response) {
856856 console.logdebug(request.body);
857857 if (!request.body) {
858858 console.error('Error: no response body detected');
859859 return response.status(400).send('Error: no response body detected');
@@ -921,7 +921,7 @@ router.post('/merge-attributes', jsonParser, async function (request, response)
921921 await writeCharacterData(avatarPath, JSON.stringify(character), targetImg, request);
922922 response.sendStatus(200);
923923 } else {
924924 console.logerror(validator.lastValidationError);
925925 response.status(400).send({ message: `Validation failed for ${character.name}`, error: validator.lastValidationError });
926926 }
927927 } catch (exception) {
@@ -1043,7 +1043,7 @@ router.post('/chats', jsonParser, async function (request, response) {
10431043 const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`;
10441044
10451045 if (stats.size === 0) {
10461046 console.logwarn(`Found an empty chat file: ${pathToFile}`);
10471047 res({});
10481048 return;
10491049 }
@@ -1075,7 +1075,7 @@ router.post('/chats', jsonParser, async function (request, response) {
10751075
10761076 res(chatData);
10771077 } else {
10781078 console.logwarn('Found an invalid or corrupted chat file:', pathToFile);
10791079 res({});
10801080 }
10811081 }
@@ -1088,7 +1088,7 @@ router.post('/chats', jsonParser, async function (request, response) {
10881088
10891089 return response.send(validFiles);
10901090 } catch (error) {
10911091 console.logerror(error);
10921092 return response.send({ error: true });
10931093 }
10941094});
@@ -1155,7 +1155,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
11551155
11561156 response.send({ file_name: fileName });
11571157 } catch (err) {
11581158 console.logerror(err);
11591159 response.send({ error: true });
11601160 }
11611161});
@@ -1163,14 +1163,13 @@ router.post('/import', urlencodedParser, async function (request, response) {
11631163router.post('/duplicate', jsonParser, async function (request, response) {
11641164 try {
11651165 if (!request.body.avatar_url) {
11661166 console.logerror('avatar URL not found in request body');
11671167 console.logdebug(request.body);
11681168 return response.sendStatus(400);
11691169 }
11701170 let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url));
11711171 if (!fs.existsSync(filename)) {
11721172 console.logerror('file for dupe not found', filename);
1173- console.log(filename);
11741173 return response.sendStatus(404);
11751174 }
11761175 let suffix = 1;
@@ -1198,7 +1197,7 @@ router.post('/duplicate', jsonParser, async function (request, response) {
11981197 }
11991198
12001199 fs.copyFileSync(filename, newFilename);
12011200 console.loginfo(`${filename} was copied to ${newFilename}`);
12021201 response.send({ path: path.parse(newFilename).base });
12031202 }
12041203 catch (error) {
src/endpoints/chats.js+17 -18
@@ -49,7 +49,7 @@ function backupChat(directory, name, chat) {
4949
5050 removeOldBackups(directory, 'chat_', maxTotalChatBackups);
5151 } catch (err) {
5252 console.logerror(`Could not backup chat for ${name}`, err);
5353 }
5454}
5555
@@ -277,8 +277,8 @@ router.post('/save', jsonParser, function (request, response) {
277277 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);
278278 return response.send({ result: 'ok' });
279279 } catch (error) {
280280 responseconsole.senderror(error);
281281 return consoleresponse.logsend(error);
282282 }
283283});
284284
@@ -330,17 +330,17 @@ router.post('/rename', jsonParser, async function (request, response) {
330330 const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file));
331331 const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file));
332332 const sanitizedFileName = path.parse(pathToRenamedFile).name;
333333 console.loginfo('Old chat name', pathToOriginalFile);
334334 console.loginfo('New chat name', pathToRenamedFile);
335335
336336 if (!fs.existsSync(pathToOriginalFile) || fs.existsSync(pathToRenamedFile)) {
337337 console.logerror('Either Source or Destination files are not available');
338338 return response.status(400).send({ error: true });
339339 }
340340
341341 fs.copyFileSync(pathToOriginalFile, pathToRenamedFile);
342342 fs.rmSync(pathToOriginalFile);
343343 console.loginfo('Successfully renamed.');
344344 return response.send({ ok: true, sanitizedFileName });
345345});
346346
@@ -351,12 +351,12 @@ router.post('/delete', jsonParser, function (request, response) {
351351 const chatFileExists = fs.existsSync(filePath);
352352
353353 if (!chatFileExists) {
354354 console.logerror(`Chat file not found '${filePath}'`);
355355 return response.sendStatus(400);
356356 }
357357
358358 fs.rmSync(filePath);
359359 console.loginfo('`Deleted chat file: ' + ${filePath}`);
360360 return response.send('ok');
361361});
362362
@@ -373,7 +373,7 @@ router.post('/export', jsonParser, async function (request, response) {
373373 const errorMessage = {
374374 message: `Could not find JSONL file to export. Source chat file: ${filename}.`,
375375 };
376376 console.logerror(errorMessage.message);
377377 return response.status(404).json(errorMessage);
378378 }
379379 try {
@@ -386,14 +386,14 @@ router.post('/export', jsonParser, async function (request, response) {
386386 result: rawFile,
387387 };
388388
389389 console.loginfo(`Chat exported as ${exportfilename}`);
390390 return response.status(200).json(successMessage);
391391 } catch (err) {
392392 console.error(err);
393393 const errorMessage = {
394394 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,
395395 };
396396 console.logerror(errorMessage.message);
397397 return response.status(500).json(errorMessage);
398398 }
399399 }
@@ -420,12 +420,11 @@ router.post('/export', jsonParser, async function (request, response) {
420420 message: `Chat saved to ${exportfilename}`,
421421 result: buffer,
422422 };
423423 console.loginfo(`Chat exported as ${exportfilename}`);
424424 return response.status(200).json(successMessage);
425425 });
426426 } catch (err) {
427427 console.logerror('chat export failed.', err);
428- console.log(err);
429428 return response.sendStatus(400);
430429 }
431430});
@@ -482,7 +481,7 @@ router.post('/import', urlencodedParser, function (request, response) {
482481 } else if (Array.isArray(jsonData.messages)) { // Agnai's format
483482 importFunc = importAgnaiChat;
484483 } else { // Unknown format
485484 console.logerror('Incorrect chat format .json');
486485 return response.send({ error: true });
487486 }
488487
@@ -510,7 +509,7 @@ router.post('/import', urlencodedParser, function (request, response) {
510509 const jsonData = JSON.parse(header);
511510
512511 if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) {
513512 console.logerror('Incorrect chat format .jsonl');
514513 return response.send({ error: true });
515514 }
516515
@@ -616,7 +615,7 @@ router.post('/search', jsonParser, function (request, response) {
616615 break;
617616 }
618617 } catch (error) {
619618 console.errorwarn(groupFile, 'group file is corrupted:', error);
620619 }
621620 }
622621
src/endpoints/classify.js+2 -2
@@ -44,9 +44,9 @@ router.post('/', jsonParser, async (req, res) => {
4444 }
4545 }
4646
4747 console.logdebug('Classify input:', text);
4848 const result = await getResult(text);
4949 console.logdebug('Classify output:', result);
5050
5151 return res.json({ classification: result });
5252 } catch (error) {
src/endpoints/content-manager.js+26 -26
@@ -71,7 +71,7 @@ export function getDefaultPresets(directories) {
7171
7272 return presets;
7373 } catch (err) {
7474 console.logwarn('Failed to get default presets', err);
7575 return [];
7676 }
7777}
@@ -92,7 +92,7 @@ export function getDefaultPresetFile(filename) {
9292 const fileContent = fs.readFileSync(contentPath, 'utf8');
9393 return JSON.parse(fileContent);
9494 } catch (err) {
9595 console.logwarn(`Failed to get default file ${filename}`, err);
9696 return null;
9797 }
9898}
@@ -121,21 +121,21 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
121121 }
122122
123123 if (!contentItem.folder) {
124124 console.logwarn(`Content file ${contentItem.filename} has no parent folder`);
125125 continue;
126126 }
127127
128128 const contentPath = path.join(contentItem.folder, contentItem.filename);
129129
130130 if (!fs.existsSync(contentPath)) {
131131 console.logwarn(`Content file ${contentItem.filename} is missing`);
132132 continue;
133133 }
134134
135135 const contentTarget = getTargetByType(contentItem.type, directories);
136136
137137 if (!contentTarget) {
138138 console.logwarn(`Content file ${contentItem.filename} has unknown type ${contentItem.type}`);
139139 continue;
140140 }
141141
@@ -144,12 +144,12 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
144144 contentLog.push(contentItem.filename);
145145
146146 if (fs.existsSync(targetPath)) {
147147 console.logwarn(`Content file ${contentItem.filename} already exists in ${contentTarget}`);
148148 continue;
149149 }
150150
151151 fs.cpSync(contentPath, targetPath, { recursive: true, force: false });
152152 console.loginfo(`Content file ${contentItem.filename} copied to ${contentTarget}`);
153153 anyContentAdded = true;
154154 }
155155
@@ -182,12 +182,12 @@ export async function checkForNewContent(directoriesList, forceCategories = [])
182182 }
183183
184184 if (anyContentAdded && !contentCheckSkip && forceCategories?.length === 0) {
185185 console.loginfo();
186186 console.loginfo(`${color.blue('If you don\'t want to receive content updates in the future, set')} ${color.yellow('skipContentCheck')} ${color.blue('to true in the config.yaml file.')}`);
187187 console.loginfo();
188188 }
189189 } catch (err) {
190190 console.logerror('Content check failed', err);
191191 }
192192}
193193
@@ -331,7 +331,7 @@ async function downloadChubLorebook(id) {
331331
332332 if (!result.ok) {
333333 const text = await result.text();
334334 console.logerror('Chub returned error', result.statusText, text);
335335 throw new Error('Failed to download lorebook');
336336 }
337337
@@ -355,7 +355,7 @@ async function downloadChubCharacter(id) {
355355
356356 if (!result.ok) {
357357 const text = await result.text();
358358 console.logerror('Chub returned error', result.statusText, text);
359359 throw new Error('Failed to download character');
360360 }
361361
@@ -376,7 +376,7 @@ async function downloadPygmalionCharacter(id) {
376376
377377 if (!result.ok) {
378378 const text = await result.text();
379379 console.logerror('Pygsite returned error', result.status, text);
380380 throw new Error('Failed to download character');
381381 }
382382
@@ -485,7 +485,7 @@ async function downloadJannyCharacter(uuid) {
485485 }
486486 }
487487
488488 console.logerror('Janny returned error', result.statusText, await result.text());
489489 throw new Error('Failed to download character');
490490}
491491
@@ -577,7 +577,7 @@ async function downloadRisuCharacter(uuid) {
577577
578578 if (!result.ok) {
579579 const text = await result.text();
580580 console.logerror('RisuAI returned error', result.statusText, text);
581581 throw new Error('Failed to download character');
582582 }
583583
@@ -673,11 +673,11 @@ router.post('/importURL', jsonParser, async (request, response) => {
673673 type = chubParsed?.type;
674674
675675 if (chubParsed?.type === 'character') {
676676 console.loginfo('Downloading chub character:', chubParsed.id);
677677 result = await downloadChubCharacter(chubParsed.id);
678678 }
679679 else if (chubParsed?.type === 'lorebook') {
680680 console.loginfo('Downloading chub lorebook:', chubParsed.id);
681681 result = await downloadChubLorebook(chubParsed.id);
682682 }
683683 else {
@@ -692,7 +692,7 @@ router.post('/importURL', jsonParser, async (request, response) => {
692692 type = 'character';
693693 result = await downloadRisuCharacter(uuid);
694694 } else if (isGeneric) {
695695 console.loginfo('Downloading from generic url.');
696696 type = 'character';
697697 result = await downloadGenericPng(url);
698698 } else {
@@ -708,7 +708,7 @@ router.post('/importURL', jsonParser, async (request, response) => {
708708 response.set('X-Custom-Content-Type', type);
709709 return response.send(result.buffer);
710710 } catch (error) {
711711 console.logerror('Importing custom content failed', error);
712712 return response.sendStatus(500);
713713 }
714714});
@@ -728,22 +728,22 @@ router.post('/importUUID', jsonParser, async (request, response) => {
728728 const uuidType = uuid.includes('lorebook') ? 'lorebook' : 'character';
729729
730730 if (isPygmalion) {
731731 console.loginfo('Downloading Pygmalion character:', uuid);
732732 result = await downloadPygmalionCharacter(uuid);
733733 } else if (isJannny) {
734734 console.loginfo('Downloading Janitor character:', uuid.split('_')[0]);
735735 result = await downloadJannyCharacter(uuid.split('_')[0]);
736736 } else if (isAICC) {
737737 const [, author, card] = uuid.split('/');
738738 console.loginfo('Downloading AICC character:', `${author}/${card}`);
739739 result = await downloadAICCCharacter(`${author}/${card}`);
740740 } else {
741741 if (uuidType === 'character') {
742742 console.loginfo('Downloading chub character:', uuid);
743743 result = await downloadChubCharacter(uuid);
744744 }
745745 else if (uuidType === 'lorebook') {
746746 console.loginfo('Downloading chub lorebook:', uuid);
747747 result = await downloadChubLorebook(uuid);
748748 }
749749 else {
@@ -756,7 +756,7 @@ router.post('/importUUID', jsonParser, async (request, response) => {
756756 response.set('X-Custom-Content-Type', uuidType);
757757 return response.send(result.buffer);
758758 } catch (error) {
759759 console.logerror('Importing custom content failed', error);
760760 return response.sendStatus(500);
761761 }
762762});
src/endpoints/extensions.js+16 -16
@@ -80,7 +80,7 @@ router.post('/install', jsonParser, async (request, response) => {
8080 const { url, global } = request.body;
8181
8282 if (global && !request.user.profile.admin) {
8383 console.warnerror(`User ${request.user.profile.handle} does not have permission to install global extensions.`);
8484 return response.status(403).send('Forbidden: No permission to install global extensions.');
8585 }
8686
@@ -92,13 +92,13 @@ router.post('/install', jsonParser, async (request, response) => {
9292 }
9393
9494 await git.clone(url, extensionPath, { '--depth': 1 });
9595 console.loginfo(`Extension has been cloned at ${extensionPath}`);
9696
9797 const { version, author, display_name } = await getManifest(extensionPath);
9898
9999 return response.send({ version, author, display_name, extensionPath });
100100 } catch (error) {
101101 console.logerror('Importing custom content failed', error);
102102 return response.status(500).send(`Server Error: ${error.message}`);
103103 }
104104});
@@ -124,7 +124,7 @@ router.post('/update', jsonParser, async (request, response) => {
124124 const { extensionName, global } = request.body;
125125
126126 if (global && !request.user.profile.admin) {
127127 console.warnerror(`User ${request.user.profile.handle} does not have permission to update global extensions.`);
128128 return response.status(403).send('Forbidden: No permission to update global extensions.');
129129 }
130130
@@ -139,9 +139,9 @@ router.post('/update', jsonParser, async (request, response) => {
139139 const currentBranch = await git.cwd(extensionPath).branch();
140140 if (!isUpToDate) {
141141 await git.cwd(extensionPath).pull('origin', currentBranch.current);
142142 console.loginfo(`Extension has been updated at ${extensionPath}`);
143143 } else {
144144 console.loginfo(`Extension is up to date at ${extensionPath}`);
145145 }
146146 await git.cwd(extensionPath).fetch('origin');
147147 const fullCommitHash = await git.cwd(extensionPath).revparse(['HEAD']);
@@ -150,7 +150,7 @@ router.post('/update', jsonParser, async (request, response) => {
150150 return response.send({ shortCommitHash, extensionPath, isUpToDate, remoteUrl });
151151
152152 } catch (error) {
153153 console.logerror('Updating custom content failed', error);
154154 return response.status(500).send(`Server Error: ${error.message}`);
155155 }
156156});
@@ -164,7 +164,7 @@ router.post('/move', jsonParser, async (request, response) => {
164164 }
165165
166166 if (!request.user.profile.admin) {
167167 console.warnerror(`User ${request.user.profile.handle} does not have permission to move extensions.`);
168168 return response.status(403).send('Forbidden: No permission to move extensions.');
169169 }
170170
@@ -190,11 +190,11 @@ router.post('/move', jsonParser, async (request, response) => {
190190
191191 fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true });
192192 fs.rmSync(sourcePath, { recursive: true, force: true });
193193 console.loginfo(`Extension has been moved from ${sourcePath} to ${destinationPath}`);
194194
195195 return response.sendStatus(204);
196196 } catch (error) {
197197 console.logerror('Moving extension failed', error);
198198 return response.status(500).send('Internal Server Error. Try again later.');
199199 }
200200});
@@ -237,13 +237,13 @@ router.post('/version', jsonParser, async (request, response) => {
237237 // get only the working branch
238238 const currentBranchName = currentBranch.current;
239239 await git.cwd(extensionPath).fetch('origin');
240240 console.logdebug(extensionName, currentBranchName, currentCommitHash);
241241 const { isUpToDate, remoteUrl } = await checkIfRepoIsUpToDate(extensionPath);
242242
243243 return response.send({ currentBranchName, currentCommitHash, isUpToDate, remoteUrl });
244244
245245 } catch (error) {
246246 console.logerror('Getting extension version failed', error);
247247 return response.status(500).send(`Server Error: ${error.message}`);
248248 }
249249});
@@ -265,7 +265,7 @@ router.post('/delete', jsonParser, async (request, response) => {
265265 const { extensionName, global } = request.body;
266266
267267 if (global && !request.user.profile.admin) {
268268 console.warnerror(`User ${request.user.profile.handle} does not have permission to delete global extensions.`);
269269 return response.status(403).send('Forbidden: No permission to delete global extensions.');
270270 }
271271
@@ -277,12 +277,12 @@ router.post('/delete', jsonParser, async (request, response) => {
277277 }
278278
279279 await fs.promises.rm(extensionPath, { recursive: true });
280280 console.loginfo(`Extension has been deleted at ${extensionPath}`);
281281
282282 return response.send(`Extension has been deleted at ${extensionPath}`);
283283
284284 } catch (error) {
285285 console.logerror('Deleting custom content failed', error);
286286 return response.status(500).send(`Server Error: ${error.message}`);
287287 }
288288});
@@ -323,7 +323,7 @@ router.get('/discover', jsonParser, function (request, response) {
323323
324324 // Combine all extensions
325325 const allExtensions = [...builtInExtensions, ...userExtensions, ...globalExtensions];
326326 console.loginfo('Extensions available for', request.user.profile.handle, allExtensions);
327327
328328 return response.send(allExtensions);
329329});
src/endpoints/files.js+7 -7
@@ -21,7 +21,7 @@ router.post('/sanitize-filename', jsonParser, async (request, response) => {
2121 const sanitizedFilename = sanitize(fileName);
2222 return response.send({ fileName: sanitizedFilename });
2323 } catch (error) {
2424 console.logerror(error);
2525 return response.sendStatus(500);
2626 }
2727});
@@ -44,10 +44,10 @@ router.post('/upload', jsonParser, async (request, response) => {
4444 const pathToUpload = path.join(request.user.directories.files, request.body.name);
4545 writeFileSyncAtomic(pathToUpload, request.body.data, 'base64');
4646 const url = clientRelativePath(request.user.directories.root, pathToUpload);
4747 console.loginfo(`Uploaded file: ${url} from ${request.user.profile.handle}`);
4848 return response.send({ path: url });
4949 } catch (error) {
5050 console.logerror(error);
5151 return response.sendStatus(500);
5252 }
5353});
@@ -68,10 +68,10 @@ router.post('/delete', jsonParser, async (request, response) => {
6868 }
6969
7070 fs.rmSync(pathToDelete);
7171 console.loginfo(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`);
7272 return response.sendStatus(200);
7373 } catch (error) {
7474 console.logerror(error);
7575 return response.sendStatus(500);
7676 }
7777});
@@ -87,7 +87,7 @@ router.post('/verify', jsonParser, async (request, response) => {
8787 for (const url of request.body.urls) {
8888 const pathToVerify = path.join(request.user.directories.root, url);
8989 if (!pathToVerify.startsWith(request.user.directories.files)) {
9090 console.debugwarn(`File verification: Invalid path: ${pathToVerify}`);
9191 continue;
9292 }
9393 const fileExists = fs.existsSync(pathToVerify);
@@ -96,7 +96,7 @@ router.post('/verify', jsonParser, async (request, response) => {
9696
9797 return response.send(verified);
9898 } catch (error) {
9999 console.logerror(error);
100100 return response.sendStatus(500);
101101 }
102102});
src/endpoints/google.js+3 -3
@@ -34,7 +34,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
3434 generationConfig: { maxOutputTokens: 1000 },
3535 };
3636
3737 console.logdebug('Multimodal captioning request', model, body);
3838
3939 const result = await fetch(url, {
4040 body: JSON.stringify(body),
@@ -46,13 +46,13 @@ router.post('/caption-image', jsonParser, async (request, response) => {
4646
4747 if (!result.ok) {
4848 const error = await result.json();
4949 console.logerror(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error);
5050 return response.status(result.status).send({ error: true });
5151 }
5252
5353 /** @type {any} */
5454 const data = await result.json();
5555 console.logdebug('Multimodal captioning response', data);
5656
5757 const candidates = data?.candidates;
5858 if (!candidates) {
src/endpoints/groups.js+1 -1
@@ -114,7 +114,7 @@ router.post('/delete', jsonParser, async (request, response) => {
114114
115115 if (group && Array.isArray(group.chats)) {
116116 for (const chat of group.chats) {
117117 console.loginfo('Deleting group chat', chat);
118118 const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`);
119119
120120 if (fs.existsSync(pathToFile)) {
src/endpoints/horde.js+14 -14
@@ -155,7 +155,7 @@ router.post('/cancel-task', jsonParser, async (request, response) => {
155155 });
156156
157157 const data = await fetchResult.json();
158158 console.loginfo(`Cancelled Horde task ${taskId}`);
159159 return response.send(data);
160160 } catch (error) {
161161 console.error(error);
@@ -174,7 +174,7 @@ router.post('/task-status', jsonParser, async (request, response) => {
174174 });
175175
176176 const data = await fetchResult.json();
177177 console.loginfo(`Horde task ${taskId} status:`, data);
178178 return response.send(data);
179179 } catch (error) {
180180 console.error(error);
@@ -187,7 +187,7 @@ router.post('/generate-text', jsonParser, async (request, response) => {
187187 const url = 'https://aihorde.net/api/v2/generate/text/async';
188188 const agent = await getClientAgent();
189189
190190 console.logdebug(request.body);
191191 try {
192192 const result = await fetch(url, {
193193 method: 'POST',
@@ -201,14 +201,14 @@ router.post('/generate-text', jsonParser, async (request, response) => {
201201
202202 if (!result.ok) {
203203 const message = await result.text();
204204 console.logerror('Horde returned an error:', message);
205205 return response.send({ error: { message } });
206206 }
207207
208208 const data = await result.json();
209209 return response.send(data);
210210 } catch (error) {
211211 console.logerror(error);
212212 return response.send({ error: true });
213213 }
214214});
@@ -254,7 +254,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
254254 for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
255255 await delay(CHECK_INTERVAL);
256256 const status = await ai_horde.getInterrogationStatus(result.id);
257257 console.loginfo(status);
258258
259259 if (status.state === HordeAsyncRequestStates.done) {
260260
@@ -263,7 +263,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
263263 return response.sendStatus(500);
264264 }
265265
266266 console.logdebug('Image interrogation result:', status);
267267 const caption = status?.forms[0]?.result?.caption || '';
268268
269269 if (!caption) {
@@ -275,7 +275,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
275275 }
276276
277277 if (status.state === HordeAsyncRequestStates.faulted || status.state === HordeAsyncRequestStates.cancelled) {
278278 console.logerror('Image interrogation request is not successful.');
279279 return response.sendStatus(503);
280280 }
281281 }
@@ -315,7 +315,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
315315 try {
316316 const maxLength = PROMPT_THRESHOLD - String(request.body.negative_prompt).length - 5;
317317 if (String(request.body.prompt).length > maxLength) {
318318 console.logwarn('Stable Horde prompt is too long, truncating...');
319319 request.body.prompt = String(request.body.prompt).substring(0, maxLength);
320320 }
321321
@@ -324,14 +324,14 @@ router.post('/generate-image', jsonParser, async (request, response) => {
324324 const sanitized = sanitizeHordeImagePrompt(request.body.prompt);
325325
326326 if (request.body.prompt !== sanitized) {
327327 console.loginfo('Stable Horde prompt was sanitized.');
328328 }
329329
330330 request.body.prompt = sanitized;
331331 }
332332
333333 const api_key_horde = readSecret(request.user.directories, SECRET_KEYS.HORDE) || ANONYMOUS_KEY;
334334 console.logdebug('Stable Horde request:', request.body);
335335
336336 const ai_horde = await getHordeClient();
337337 // noinspection JSCheckFunctionSignatures -- see @ts-ignore - use_gfpgan
@@ -364,12 +364,12 @@ router.post('/generate-image', jsonParser, async (request, response) => {
364364 return response.sendStatus(400);
365365 }
366366
367367 console.logdebug('Horde image generation request:', generation);
368368
369369 const controller = new AbortController();
370370 request.socket.removeAllListeners('close');
371371 request.socket.on('close', function () {
372372 console.loginfo('Horde image generation request aborted.');
373373 controller.abort();
374374 if (generation.id) ai_horde.deleteImageGenerationRequest(generation.id);
375375 });
@@ -378,7 +378,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
378378 controller.signal.throwIfAborted();
379379 await delay(CHECK_INTERVAL);
380380 const check = await ai_horde.getImageGenerationCheck(generation.id);
381381 console.loginfo(check);
382382
383383 if (check.done) {
384384 const result = await ai_horde.getImageGenerationStatus(generation.id);
src/endpoints/images.js+1 -1
@@ -71,7 +71,7 @@ router.post('/upload', jsonParser, async (request, response) => {
7171 await fs.promises.writeFile(pathToNewFile, new Uint8Array(imageBuffer));
7272 response.send({ path: clientRelativePath(request.user.directories.root, pathToNewFile) });
7373 } catch (error) {
7474 console.logerror(error);
7575 response.status(500).send({ error: 'Failed to save the image' });
7676 }
7777});
src/endpoints/novelai.js+16 -16
@@ -120,7 +120,7 @@ router.post('/status', jsonParser, async function (req, res) {
120120 const api_key_novel = readSecret(req.user.directories, SECRET_KEYS.NOVEL);
121121
122122 if (!api_key_novel) {
123123 console.logerror('NovelAI Access Token is missing.');
124124 return res.sendStatus(400);
125125 }
126126
@@ -137,15 +137,15 @@ router.post('/status', jsonParser, async function (req, res) {
137137 const data = await response.json();
138138 return res.send(data);
139139 } else if (response.status == 401) {
140140 console.logerror('NovelAI Access Token is incorrect.');
141141 return res.send({ error: true });
142142 }
143143 else {
144144 console.logerror('NovelAI returned an error:', response.statusText);
145145 return res.send({ error: true });
146146 }
147147 } catch (error) {
148148 console.logerror(error);
149149 return res.send({ error: true });
150150 }
151151});
@@ -156,7 +156,7 @@ router.post('/generate', jsonParser, async function (req, res) {
156156 const api_key_novel = readSecret(req.user.directories, SECRET_KEYS.NOVEL);
157157
158158 if (!api_key_novel) {
159159 console.logerror('NovelAI Access Token is missing.');
160160 return res.sendStatus(400);
161161 }
162162
@@ -241,7 +241,7 @@ router.post('/generate', jsonParser, async function (req, res) {
241241 }
242242 }
243243
244244 console.logdebug(util.inspect(data, { depth: 4 }));
245245
246246 const args = {
247247 body: JSON.stringify(data),
@@ -261,7 +261,7 @@ router.post('/generate', jsonParser, async function (req, res) {
261261 if (!response.ok) {
262262 const text = await response.text();
263263 let message = text;
264264 console.logerror(`Novel API returned error: ${response.status} ${response.statusText} ${text}`);
265265
266266 try {
267267 const data = JSON.parse(text);
@@ -276,7 +276,7 @@ router.post('/generate', jsonParser, async function (req, res) {
276276
277277 /** @type {any} */
278278 const data = await response.json();
279279 console.logdebug('NovelAI Output', data?.output);
280280 return res.send(data);
281281 }
282282 } catch (error) {
@@ -292,12 +292,12 @@ router.post('/generate-image', jsonParser, async (request, response) => {
292292 const key = readSecret(request.user.directories, SECRET_KEYS.NOVEL);
293293
294294 if (!key) {
295295 console.logerror('NovelAI Access Token is missing.');
296296 return response.sendStatus(400);
297297 }
298298
299299 try {
300300 console.logdebug('NAI Diffusion request:', request.body);
301301 const generateUrl = `${IMAGE_NOVELAI}/ai/generate-image`;
302302 const generateResult = await fetch(generateUrl, {
303303 method: 'POST',
@@ -358,7 +358,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
358358
359359 if (!generateResult.ok) {
360360 const text = await generateResult.text();
361361 console.logerror('NovelAI returned an error.', generateResult.statusText, text);
362362 return response.sendStatus(500);
363363 }
364364
@@ -366,7 +366,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
366366 const imageBuffer = await extractFileFromZipBuffer(archiveBuffer, '.png');
367367
368368 if (!imageBuffer) {
369369 console.warnerror('NovelAI generated an image, but the PNG file was not found.');
370370 return response.sendStatus(500);
371371 }
372372
@@ -378,7 +378,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
378378 }
379379
380380 try {
381381 console.debuginfo('Upscaling image...');
382382 const upscaleUrl = `${API_NOVELAI}/ai/upscale`;
383383 const upscaleResult = await fetch(upscaleUrl, {
384384 method: 'POST',
@@ -413,7 +413,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
413413 return response.send(originalBase64);
414414 }
415415 } catch (error) {
416416 console.logerror(error);
417417 return response.sendStatus(500);
418418 }
419419});
@@ -422,7 +422,7 @@ router.post('/generate-voice', jsonParser, async (request, response) => {
422422 const token = readSecret(request.user.directories, SECRET_KEYS.NOVEL);
423423
424424 if (!token) {
425425 console.logerror('NovelAI Access Token is missing.');
426426 return response.sendStatus(400);
427427 }
428428
@@ -445,7 +445,7 @@ router.post('/generate-voice', jsonParser, async (request, response) => {
445445
446446 if (!result.ok) {
447447 const errorText = await result.text();
448448 console.logerror('NovelAI returned an error.', result.statusText, errorText);
449449 return response.sendStatus(500);
450450 }
451451
src/endpoints/openai.js+16 -16
@@ -63,7 +63,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
6363 }
6464
6565 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {
6666 console.logerror('No key found for API', request.body.api);
6767 return response.sendStatus(400);
6868 }
6969
@@ -93,7 +93,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
9393 excludeKeysByYaml(body, request.body.custom_exclude_body);
9494 }
9595
9696 console.logerror('Multimodal captioning request', body);
9797
9898 let apiUrl = '';
9999
@@ -158,13 +158,13 @@ router.post('/caption-image', jsonParser, async (request, response) => {
158158
159159 if (!result.ok) {
160160 const text = await result.text();
161161 console.logerror('Multimodal captioning request failed', result.statusText, text);
162162 return response.status(500).send(text);
163163 }
164164
165165 /** @type {any} */
166166 const data = await result.json();
167167 console.logdebug('Multimodal captioning response', data);
168168 const caption = data?.choices[0]?.message?.content;
169169
170170 if (!caption) {
@@ -184,17 +184,17 @@ router.post('/transcribe-audio', urlencodedParser, async (request, response) =>
184184 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
185185
186186 if (!key) {
187187 console.logerror('No OpenAI key found');
188188 return response.sendStatus(400);
189189 }
190190
191191 if (!request.file) {
192192 console.logerror('No audio file found');
193193 return response.sendStatus(400);
194194 }
195195
196196 const formData = new FormData();
197197 console.loginfo('Processing audio file', request.file.path);
198198 formData.append('file', fs.createReadStream(request.file.path), { filename: 'audio.wav', contentType: 'audio/wav' });
199199 formData.append('model', request.body.model);
200200
@@ -213,13 +213,13 @@ router.post('/transcribe-audio', urlencodedParser, async (request, response) =>
213213
214214 if (!result.ok) {
215215 const text = await result.text();
216216 console.logerror('OpenAI request failed', result.statusText, text);
217217 return response.status(500).send(text);
218218 }
219219
220220 fs.rmSync(request.file.path);
221221 const data = await result.json();
222222 console.logdebug('OpenAI transcription response', data);
223223 return response.json(data);
224224 } catch (error) {
225225 console.error('OpenAI transcription failed', error);
@@ -232,7 +232,7 @@ router.post('/generate-voice', jsonParser, async (request, response) => {
232232 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
233233
234234 if (!key) {
235235 console.logerror('No OpenAI key found');
236236 return response.sendStatus(400);
237237 }
238238
@@ -253,7 +253,7 @@ router.post('/generate-voice', jsonParser, async (request, response) => {
253253
254254 if (!result.ok) {
255255 const text = await result.text();
256256 console.logerror('OpenAI request failed', result.statusText, text);
257257 return response.status(500).send(text);
258258 }
259259
@@ -271,11 +271,11 @@ router.post('/generate-image', jsonParser, async (request, response) => {
271271 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
272272
273273 if (!key) {
274274 console.logerror('No OpenAI key found');
275275 return response.sendStatus(400);
276276 }
277277
278278 console.logdebug('OpenAI request', request.body);
279279
280280 const result = await fetch('https://api.openai.com/v1/images/generations', {
281281 method: 'POST',
@@ -288,7 +288,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
288288
289289 if (!result.ok) {
290290 const text = await result.text();
291291 console.logerror('OpenAI request failed', result.statusText, text);
292292 return response.status(500).send(text);
293293 }
294294
@@ -308,7 +308,7 @@ custom.post('/generate-voice', jsonParser, async (request, response) => {
308308 const { input, provider_endpoint, response_format, voice, speed, model } = request.body;
309309
310310 if (!provider_endpoint) {
311311 console.logerror('No OpenAI-compatible TTS provider endpoint provided');
312312 return response.sendStatus(400);
313313 }
314314
@@ -329,7 +329,7 @@ custom.post('/generate-voice', jsonParser, async (request, response) => {
329329
330330 if (!result.ok) {
331331 const text = await result.text();
332332 console.logerror('OpenAI request failed', result.statusText, text);
333333 return response.status(500).send(text);
334334 }
335335
src/endpoints/presets.js+1 -1
@@ -96,7 +96,7 @@ router.post('/restore', jsonParser, function (request, response) {
9696
9797 return response.send(result);
9898 } catch (error) {
9999 console.logerror(error);
100100 return response.sendStatus(500);
101101 }
102102});
src/endpoints/search.js+21 -21
@@ -94,25 +94,25 @@ router.post('/serpapi', jsonParser, async (request, response) => {
9494 const key = readSecret(request.user.directories, SECRET_KEYS.SERPAPI);
9595
9696 if (!key) {
9797 console.logerror('No SerpApi key found');
9898 return response.sendStatus(400);
9999 }
100100
101101 const { query } = request.body;
102102 const result = await fetch(`https://serpapi.com/search.json?q=${encodeURIComponent(query)}&api_key=${key}`);
103103
104104 console.logdebug('SerpApi query', query);
105105
106106 if (!result.ok) {
107107 const text = await result.text();
108108 console.logerror('SerpApi request failed', result.statusText, text);
109109 return response.status(500).send(text);
110110 }
111111
112112 const data = await result.json();
113113 return response.json(data);
114114 } catch (error) {
115115 console.logerror(error);
116116 return response.sendStatus(500);
117117 }
118118});
@@ -128,7 +128,7 @@ router.post('/transcript', jsonParser, async (request, response) => {
128128 const json = request.body.json;
129129
130130 if (!id) {
131131 console.logerror('Id is required for /transcript');
132132 return response.sendStatus(400);
133133 }
134134
@@ -153,7 +153,7 @@ router.post('/transcript', jsonParser, async (request, response) => {
153153 throw error;
154154 }
155155 } catch (error) {
156156 console.logerror(error);
157157 return response.sendStatus(500);
158158 }
159159});
@@ -163,17 +163,17 @@ router.post('/searxng', jsonParser, async (request, response) => {
163163 const { baseUrl, query, preferences } = request.body;
164164
165165 if (!baseUrl || !query) {
166166 console.logerror('Missing required parameters for /searxng');
167167 return response.sendStatus(400);
168168 }
169169
170170 console.logdebug('SearXNG query', baseUrl, query);
171171
172172 const mainPageUrl = new URL(baseUrl);
173173 const mainPageRequest = await fetch(mainPageUrl, { headers: visitHeaders });
174174
175175 if (!mainPageRequest.ok) {
176176 console.logerror('SearXNG request failed', mainPageRequest.statusText);
177177 return response.sendStatus(500);
178178 }
179179
@@ -197,14 +197,14 @@ router.post('/searxng', jsonParser, async (request, response) => {
197197
198198 if (!searchResult.ok) {
199199 const text = await searchResult.text();
200200 console.logerror('SearXNG request failed', searchResult.statusText, text);
201201 return response.sendStatus(500);
202202 }
203203
204204 const data = await searchResult.text();
205205 return response.send(data);
206206 } catch (error) {
207207 console.logerror('SearXNG request failed', error);
208208 return response.sendStatus(500);
209209 }
210210});
@@ -214,7 +214,7 @@ router.post('/tavily', jsonParser, async (request, response) => {
214214 const apiKey = readSecret(request.user.directories, SECRET_KEYS.TAVILY);
215215
216216 if (!apiKey) {
217217 console.logerror('No Tavily key found');
218218 return response.sendStatus(400);
219219 }
220220
@@ -241,18 +241,18 @@ router.post('/tavily', jsonParser, async (request, response) => {
241241 body: JSON.stringify(body),
242242 });
243243
244244 console.logdebug('Tavily query', query);
245245
246246 if (!result.ok) {
247247 const text = await result.text();
248248 console.logerror('Tavily request failed', result.statusText, text);
249249 return response.status(500).send(text);
250250 }
251251
252252 const data = await result.json();
253253 return response.json(data);
254254 } catch (error) {
255255 console.logerror(error);
256256 return response.sendStatus(500);
257257 }
258258});
@@ -262,7 +262,7 @@ router.post('/visit', jsonParser, async (request, response) => {
262262 const url = request.body.url;
263263
264264 if (!url) {
265265 console.logerror('No url provided for /visit');
266266 return response.sendStatus(400);
267267 }
268268
@@ -289,29 +289,29 @@ router.post('/visit', jsonParser, async (request, response) => {
289289 throw new Error('Invalid hostname');
290290 }
291291 } catch (error) {
292292 console.logerror('Invalid url provided for /visit', url);
293293 return response.sendStatus(400);
294294 }
295295
296296 console.loginfo('Visiting web URL', url);
297297
298298 const result = await fetch(url, { headers: visitHeaders });
299299
300300 if (!result.ok) {
301301 console.logerror(`Visit failed ${result.status} ${result.statusText}`);
302302 return response.sendStatus(500);
303303 }
304304
305305 const contentType = String(result.headers.get('content-type'));
306306 if (!contentType.includes('text/html')) {
307307 console.logerror(`Visit failed, content-type is ${contentType}, expected text/html`);
308308 return response.sendStatus(500);
309309 }
310310
311311 const text = await result.text();
312312 return response.send(text);
313313 } catch (error) {
314314 console.logerror(error);
315315 return response.sendStatus(500);
316316 }
317317});
src/endpoints/secrets.js+1 -1
@@ -151,7 +151,7 @@ export function getAllSecrets(directories) {
151151 const filePath = path.join(directories.root, SECRETS_FILE);
152152
153153 if (!fs.existsSync(filePath)) {
154154 console.logerror('Secrets file does not exist');
155155 return undefined;
156156 }
157157
src/endpoints/settings.js+7 -7
@@ -104,7 +104,7 @@ function readPresetsFromDirectory(directoryPath, options = {}) {
104104 fileNames.push(removeFileExtension ? item.replace(/\.[^/.]+$/, '') : item);
105105 } catch {
106106 // skip
107107 console.logwarn(`${item} is not a valid JSON`);
108108 }
109109 });
110110
@@ -119,7 +119,7 @@ async function backupSettings() {
119119 backupUserSettings(handle, true);
120120 }
121121 } catch (err) {
122122 console.logerror('Could not backup settings file', err);
123123 }
124124}
125125
@@ -201,7 +201,7 @@ router.post('/save', jsonParser, function (request, response) {
201201 triggerAutoSave(request.user.profile.handle);
202202 response.send({ result: 'ok' });
203203 } catch (err) {
204204 console.logerror(err);
205205 response.send(err);
206206 }
207207});
@@ -291,7 +291,7 @@ router.post('/get-snapshots', jsonParser, async (request, response) => {
291291
292292 response.json(result);
293293 } catch (error) {
294294 console.logerror(error);
295295 response.sendStatus(500);
296296 }
297297});
@@ -315,7 +315,7 @@ router.post('/load-snapshot', jsonParser, async (request, response) => {
315315
316316 response.send(content);
317317 } catch (error) {
318318 console.logerror(error);
319319 response.sendStatus(500);
320320 }
321321});
@@ -325,7 +325,7 @@ router.post('/make-snapshot', jsonParser, async (request, response) => {
325325 backupUserSettings(request.user.profile.handle, false);
326326 response.sendStatus(204);
327327 } catch (error) {
328328 console.logerror(error);
329329 response.sendStatus(500);
330330 }
331331});
@@ -351,7 +351,7 @@ router.post('/restore-snapshot', jsonParser, async (request, response) => {
351351
352352 response.sendStatus(204);
353353 } catch (error) {
354354 console.logerror(error);
355355 response.sendStatus(500);
356356 }
357357});
src/endpoints/speech.js+3 -3
@@ -43,8 +43,8 @@ router.post('/recognize', jsonParser, async (req, res) => {
4343 const start = performance.now();
4444 const result = await pipe(wav, { language: lang || null, task: 'transcribe' });
4545 const end = performance.now();
4646 console.logdebug(`Execution duration: ${(end - start) / 1000} seconds`);
4747 console.logdebug('Transcribed audio:', result.text);
4848
4949 return res.json({ text: result.text });
5050 } catch (error) {
@@ -64,7 +64,7 @@ router.post('/synthesize', jsonParser, async (req, res) => {
6464 const start = performance.now();
6565 const result = await pipe(text, { speaker_embeddings: speaker_embeddings });
6666 const end = performance.now();
6767 console.logdebug(`Execution duration: ${(end - start) / 1000} seconds`);
6868
6969 const wav = new wavefile.WaveFile();
7070 wav.fromScratch(1, result.sampling_rate, '32f', result.audio);
src/endpoints/sprites.js+3 -3
@@ -82,14 +82,14 @@ export function importRisuSprites(directories, data) {
8282 return;
8383 }
8484
8585 console.loginfo(`RisuAI: Found ${images.length} sprites for ${name}. Writing to disk.`);
8686 const files = fs.readdirSync(spritesPath);
8787
8888 outer: for (const [label, fileBase64] of images) {
8989 // Remove existing sprite with the same label
9090 for (const file of files) {
9191 if (path.parse(file).name === label) {
9292 console.logwarn(`RisuAI: The sprite ${label} for ${name} already exists. Skipping.`);
9393 continue outer;
9494 }
9595 }
@@ -133,7 +133,7 @@ router.get('/get', jsonParser, function (request, response) {
133133 }
134134 }
135135 catch (err) {
136136 console.logerror(err);
137137 }
138138 return response.send(sprites);
139139});
src/endpoints/stable-diffusion.js+74 -74
@@ -45,7 +45,7 @@ router.post('/ping', jsonParser, async (request, response) => {
4545
4646 return response.sendStatus(200);
4747 } catch (error) {
4848 console.logerror(error);
4949 return response.sendStatus(500);
5050 }
5151});
@@ -99,7 +99,7 @@ router.post('/upscalers', jsonParser, async (request, response) => {
9999
100100 return response.send(upscalers);
101101 } catch (error) {
102102 console.logerror(error);
103103 return response.sendStatus(500);
104104 }
105105});
@@ -131,7 +131,7 @@ router.post('/vaes', jsonParser, async (request, response) => {
131131 const names = data.map(x => x.model_name);
132132 return response.send(names);
133133 } catch (error) {
134134 console.logerror(error);
135135 return response.sendStatus(500);
136136 }
137137});
@@ -158,7 +158,7 @@ router.post('/samplers', jsonParser, async (request, response) => {
158158 return response.send(names);
159159
160160 } catch (error) {
161161 console.logerror(error);
162162 return response.sendStatus(500);
163163 }
164164});
@@ -184,7 +184,7 @@ router.post('/schedulers', jsonParser, async (request, response) => {
184184 const names = data.map(x => x.name);
185185 return response.send(names);
186186 } catch (error) {
187187 console.logerror(error);
188188 return response.sendStatus(500);
189189 }
190190});
@@ -210,7 +210,7 @@ router.post('/models', jsonParser, async (request, response) => {
210210 const models = data.map(x => ({ value: x.title, text: x.title }));
211211 return response.send(models);
212212 } catch (error) {
213213 console.logerror(error);
214214 return response.sendStatus(500);
215215 }
216216});
@@ -230,7 +230,7 @@ router.post('/get-model', jsonParser, async (request, response) => {
230230 const data = await result.json();
231231 return response.send(data['sd_model_checkpoint']);
232232 } catch (error) {
233233 console.logerror(error);
234234 return response.sendStatus(500);
235235 }
236236});
@@ -283,13 +283,13 @@ router.post('/set-model', jsonParser, async (request, response) => {
283283 break;
284284 }
285285
286286 console.loginfo(`Waiting for SD WebUI to finish model loading... Progress: ${progress}; Job count: ${jobCount}`);
287287 await delay(CHECK_INTERVAL);
288288 }
289289
290290 return response.sendStatus(200);
291291 } catch (error) {
292292 console.logerror(error);
293293 return response.sendStatus(500);
294294 }
295295});
@@ -309,7 +309,7 @@ router.post('/generate', jsonParser, async (request, response) => {
309309 }
310310 }
311311 } catch (error) {
312312 console.logerror('SD WebUI failed to get options:', error);
313313 }
314314
315315 const controller = new AbortController();
@@ -323,7 +323,7 @@ router.post('/generate', jsonParser, async (request, response) => {
323323 controller.abort();
324324 });
325325
326326 console.logdebug('SD WebUI request:', request.body);
327327 const txt2imgUrl = new URL(request.body.url);
328328 txt2imgUrl.pathname = '/sdapi/v1/txt2img';
329329 const result = await fetch(txt2imgUrl, {
@@ -344,7 +344,7 @@ router.post('/generate', jsonParser, async (request, response) => {
344344 const data = await result.json();
345345 return response.send(data);
346346 } catch (error) {
347347 console.logerror(error);
348348 return response.sendStatus(500);
349349 }
350350});
@@ -377,7 +377,7 @@ router.post('/sd-next/upscalers', jsonParser, async (request, response) => {
377377
378378 return response.send(names);
379379 } catch (error) {
380380 console.logerror(error);
381381 return response.sendStatus(500);
382382 }
383383});
@@ -395,7 +395,7 @@ comfy.post('/ping', jsonParser, async (request, response) => {
395395
396396 return response.sendStatus(200);
397397 } catch (error) {
398398 console.logerror(error);
399399 return response.sendStatus(500);
400400 }
401401});
@@ -413,7 +413,7 @@ comfy.post('/samplers', jsonParser, async (request, response) => {
413413 const data = await result.json();
414414 return response.send(data.KSampler.input.required.sampler_name[0]);
415415 } catch (error) {
416416 console.logerror(error);
417417 return response.sendStatus(500);
418418 }
419419});
@@ -441,7 +441,7 @@ comfy.post('/models', jsonParser, async (request, response) => {
441441
442442 return response.send(models);
443443 } catch (error) {
444444 console.logerror(error);
445445 return response.sendStatus(500);
446446 }
447447});
@@ -459,7 +459,7 @@ comfy.post('/schedulers', jsonParser, async (request, response) => {
459459 const data = await result.json();
460460 return response.send(data.KSampler.input.required.scheduler[0]);
461461 } catch (error) {
462462 console.logerror(error);
463463 return response.sendStatus(500);
464464 }
465465});
@@ -477,7 +477,7 @@ comfy.post('/vaes', jsonParser, async (request, response) => {
477477 const data = await result.json();
478478 return response.send(data.VAELoader.input.required.vae_name[0]);
479479 } catch (error) {
480480 console.logerror(error);
481481 return response.sendStatus(500);
482482 }
483483});
@@ -487,7 +487,7 @@ comfy.post('/workflows', jsonParser, async (request, response) => {
487487 const data = getComfyWorkflows(request.user.directories);
488488 return response.send(data);
489489 } catch (error) {
490490 console.logerror(error);
491491 return response.sendStatus(500);
492492 }
493493});
@@ -501,7 +501,7 @@ comfy.post('/workflow', jsonParser, async (request, response) => {
501501 const data = fs.readFileSync(filePath, { encoding: 'utf-8' });
502502 return response.send(JSON.stringify(data));
503503 } catch (error) {
504504 console.logerror(error);
505505 return response.sendStatus(500);
506506 }
507507});
@@ -513,7 +513,7 @@ comfy.post('/save-workflow', jsonParser, async (request, response) => {
513513 const data = getComfyWorkflows(request.user.directories);
514514 return response.send(data);
515515 } catch (error) {
516516 console.logerror(error);
517517 return response.sendStatus(500);
518518 }
519519});
@@ -526,7 +526,7 @@ comfy.post('/delete-workflow', jsonParser, async (request, response) => {
526526 }
527527 return response.sendStatus(200);
528528 } catch (error) {
529529 console.logerror(error);
530530 return response.sendStatus(500);
531531 }
532532});
@@ -591,7 +591,7 @@ comfy.post('/generate', jsonParser, async (request, response) => {
591591 const imgBuffer = await imgResponse.arrayBuffer();
592592 return response.send(Buffer.from(imgBuffer).toString('base64'));
593593 } catch (error) {
594594 console.logerror('ComfyUI error:', error);
595595 response.status(500).send(error.message);
596596 return response;
597597 }
@@ -604,7 +604,7 @@ together.post('/models', jsonParser, async (request, response) => {
604604 const key = readSecret(request.user.directories, SECRET_KEYS.TOGETHERAI);
605605
606606 if (!key) {
607607 console.logerror('TogetherAI key not found.');
608608 return response.sendStatus(400);
609609 }
610610
@@ -616,14 +616,14 @@ together.post('/models', jsonParser, async (request, response) => {
616616 });
617617
618618 if (!modelsResponse.ok) {
619619 console.logerror('TogetherAI returned an error.');
620620 return response.sendStatus(500);
621621 }
622622
623623 const data = await modelsResponse.json();
624624
625625 if (!Array.isArray(data)) {
626626 console.logerror('TogetherAI returned invalid data.');
627627 return response.sendStatus(500);
628628 }
629629
@@ -633,7 +633,7 @@ together.post('/models', jsonParser, async (request, response) => {
633633
634634 return response.send(models);
635635 } catch (error) {
636636 console.logerror(error);
637637 return response.sendStatus(500);
638638 }
639639});
@@ -643,11 +643,11 @@ together.post('/generate', jsonParser, async (request, response) => {
643643 const key = readSecret(request.user.directories, SECRET_KEYS.TOGETHERAI);
644644
645645 if (!key) {
646646 console.logerror('TogetherAI key not found.');
647647 return response.sendStatus(400);
648648 }
649649
650650 console.logdebug('TogetherAI request:', request.body);
651651
652652 const result = await fetch('https://api.together.xyz/v1/images/generations', {
653653 method: 'POST',
@@ -669,13 +669,13 @@ together.post('/generate', jsonParser, async (request, response) => {
669669 });
670670
671671 if (!result.ok) {
672672 console.logerror('TogetherAI returned an error.', { body: await result.text() });
673673 return response.sendStatus(500);
674674 }
675675
676676 /** @type {any} */
677677 const data = await result.json();
678678 console.logdebug('TogetherAI response:', data);
679679
680680 const choice = data?.data?.[0];
681681 let b64_json = choice.b64_json;
@@ -687,7 +687,7 @@ together.post('/generate', jsonParser, async (request, response) => {
687687
688688 return response.send({ format: 'jpg', data: b64_json });
689689 } catch (error) {
690690 console.logerror(error);
691691 return response.sendStatus(500);
692692 }
693693});
@@ -709,7 +709,7 @@ drawthings.post('/ping', jsonParser, async (request, response) => {
709709
710710 return response.sendStatus(200);
711711 } catch (error) {
712712 console.logerror(error);
713713 return response.sendStatus(500);
714714 }
715715});
@@ -728,7 +728,7 @@ drawthings.post('/get-model', jsonParser, async (request, response) => {
728728
729729 return response.send(data['model']);
730730 } catch (error) {
731731 console.logerror(error);
732732 return response.sendStatus(500);
733733 }
734734});
@@ -747,14 +747,14 @@ drawthings.post('/get-upscaler', jsonParser, async (request, response) => {
747747
748748 return response.send(data['upscaler']);
749749 } catch (error) {
750750 console.logerror(error);
751751 return response.sendStatus(500);
752752 }
753753});
754754
755755drawthings.post('/generate', jsonParser, async (request, response) => {
756756 try {
757757 console.logdebug('SD DrawThings API request:', request.body);
758758
759759 const url = new URL(request.body.url);
760760 url.pathname = '/sdapi/v1/txt2img';
@@ -781,7 +781,7 @@ drawthings.post('/generate', jsonParser, async (request, response) => {
781781 const data = await result.json();
782782 return response.send(data);
783783 } catch (error) {
784784 console.logerror(error);
785785 return response.sendStatus(500);
786786 }
787787});
@@ -794,21 +794,21 @@ pollinations.post('/models', jsonParser, async (_request, response) => {
794794 const result = await fetch(modelsUrl);
795795
796796 if (!result.ok) {
797797 console.logerror('Pollinations returned an error.', result.status, result.statusText);
798798 throw new Error('Pollinations request failed.');
799799 }
800800
801801 const data = await result.json();
802802
803803 if (!Array.isArray(data)) {
804804 console.logerror('Pollinations returned invalid data.');
805805 throw new Error('Pollinations request failed.');
806806 }
807807
808808 const models = data.map(x => ({ value: x, text: x }));
809809 return response.send(models);
810810 } catch (error) {
811811 console.logerror(error);
812812 return response.sendStatus(500);
813813 }
814814});
@@ -829,12 +829,12 @@ pollinations.post('/generate', jsonParser, async (request, response) => {
829829 });
830830 promptUrl.search = params.toString();
831831
832832 console.loginfo('Pollinations request URL:', promptUrl.toString());
833833
834834 const result = await fetch(promptUrl);
835835
836836 if (!result.ok) {
837837 console.logerror('Pollinations returned an error.', result.status, result.statusText);
838838 throw new Error('Pollinations request failed.');
839839 }
840840
@@ -843,7 +843,7 @@ pollinations.post('/generate', jsonParser, async (request, response) => {
843843
844844 return response.send({ image: base64 });
845845 } catch (error) {
846846 console.logerror(error);
847847 return response.sendStatus(500);
848848 }
849849});
@@ -855,13 +855,13 @@ stability.post('/generate', jsonParser, async (request, response) => {
855855 const key = readSecret(request.user.directories, SECRET_KEYS.STABILITY);
856856
857857 if (!key) {
858858 console.logerror('Stability AI key not found.');
859859 return response.sendStatus(400);
860860 }
861861
862862 const { payload, model } = request.body;
863863
864864 console.logdebug('Stability AI request:', model, payload);
865865
866866 const formData = new FormData();
867867 for (const [key, value] of Object.entries(payload)) {
@@ -896,14 +896,14 @@ stability.post('/generate', jsonParser, async (request, response) => {
896896
897897 if (!result.ok) {
898898 const text = await result.text();
899899 console.logerror('Stability AI returned an error.', result.status, result.statusText, text);
900900 return response.sendStatus(500);
901901 }
902902
903903 const buffer = await result.arrayBuffer();
904904 return response.send(Buffer.from(buffer).toString('base64'));
905905 } catch (error) {
906906 console.logerror(error);
907907 return response.sendStatus(500);
908908 }
909909});
@@ -915,7 +915,7 @@ blockentropy.post('/models', jsonParser, async (request, response) => {
915915 const key = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY);
916916
917917 if (!key) {
918918 console.logerror('Block Entropy key not found.');
919919 return response.sendStatus(400);
920920 }
921921
@@ -927,21 +927,21 @@ blockentropy.post('/models', jsonParser, async (request, response) => {
927927 });
928928
929929 if (!modelsResponse.ok) {
930930 console.logerror('Block Entropy returned an error.');
931931 return response.sendStatus(500);
932932 }
933933
934934 const data = await modelsResponse.json();
935935
936936 if (!Array.isArray(data)) {
937937 console.logerror('Block Entropy returned invalid data.');
938938 return response.sendStatus(500);
939939 }
940940 const models = data.map(x => ({ value: x.name, text: x.name }));
941941 return response.send(models);
942942
943943 } catch (error) {
944944 console.logerror(error);
945945 return response.sendStatus(500);
946946 }
947947});
@@ -951,11 +951,11 @@ blockentropy.post('/generate', jsonParser, async (request, response) => {
951951 const key = readSecret(request.user.directories, SECRET_KEYS.BLOCKENTROPY);
952952
953953 if (!key) {
954954 console.logerror('Block Entropy key not found.');
955955 return response.sendStatus(400);
956956 }
957957
958958 console.logdebug('Block Entropy request:', request.body);
959959
960960 const result = await fetch('https://api.blockentropy.ai/sdapi/v1/txt2img', {
961961 method: 'POST',
@@ -976,16 +976,16 @@ blockentropy.post('/generate', jsonParser, async (request, response) => {
976976 });
977977
978978 if (!result.ok) {
979979 console.logerror('Block Entropy returned an error.');
980980 return response.sendStatus(500);
981981 }
982982
983983 const data = await result.json();
984984 console.logdebug('Block Entropy response:', data);
985985
986986 return response.send(data);
987987 } catch (error) {
988988 console.logerror(error);
989989 return response.sendStatus(500);
990990 }
991991});
@@ -998,11 +998,11 @@ huggingface.post('/generate', jsonParser, async (request, response) => {
998998 const key = readSecret(request.user.directories, SECRET_KEYS.HUGGINGFACE);
999999
10001000 if (!key) {
10011001 console.logerror('Hugging Face key not found.');
10021002 return response.sendStatus(400);
10031003 }
10041004
10051005 console.logdebug('Hugging Face request:', request.body);
10061006
10071007 const result = await fetch(`https://api-inference.huggingface.co/models/${request.body.model}`, {
10081008 method: 'POST',
@@ -1016,7 +1016,7 @@ huggingface.post('/generate', jsonParser, async (request, response) => {
10161016 });
10171017
10181018 if (!result.ok) {
10191019 console.logerror('Hugging Face returned an error.');
10201020 return response.sendStatus(500);
10211021 }
10221022
@@ -1025,7 +1025,7 @@ huggingface.post('/generate', jsonParser, async (request, response) => {
10251025 image: Buffer.from(buffer).toString('base64'),
10261026 });
10271027 } catch (error) {
10281028 console.logerror(error);
10291029 return response.sendStatus(500);
10301030 }
10311031});
@@ -1037,7 +1037,7 @@ nanogpt.post('/models', jsonParser, async (request, response) => {
10371037 const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
10381038
10391039 if (!key) {
10401040 console.logerror('NanoGPT key not found.');
10411041 return response.sendStatus(400);
10421042 }
10431043
@@ -1050,7 +1050,7 @@ nanogpt.post('/models', jsonParser, async (request, response) => {
10501050 });
10511051
10521052 if (!modelsResponse.ok) {
10531053 console.logerror('NanoGPT returned an error.');
10541054 return response.sendStatus(500);
10551055 }
10561056
@@ -1059,7 +1059,7 @@ nanogpt.post('/models', jsonParser, async (request, response) => {
10591059 const imageModels = data?.models?.image;
10601060
10611061 if (!imageModels || typeof imageModels !== 'object') {
10621062 console.logerror('NanoGPT returned invalid data.');
10631063 return response.sendStatus(500);
10641064 }
10651065
@@ -1067,7 +1067,7 @@ nanogpt.post('/models', jsonParser, async (request, response) => {
10671067 return response.send(models);
10681068 }
10691069 catch (error) {
10701070 console.logerror(error);
10711071 return response.sendStatus(500);
10721072 }
10731073});
@@ -1077,11 +1077,11 @@ nanogpt.post('/generate', jsonParser, async (request, response) => {
10771077 const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
10781078
10791079 if (!key) {
10801080 console.logerror('NanoGPT key not found.');
10811081 return response.sendStatus(400);
10821082 }
10831083
10841084 console.logdebug('NanoGPT request:', request.body);
10851085
10861086 const result = await fetch('https://nano-gpt.com/api/generate-image', {
10871087 method: 'POST',
@@ -1093,7 +1093,7 @@ nanogpt.post('/generate', jsonParser, async (request, response) => {
10931093 });
10941094
10951095 if (!result.ok) {
10961096 console.logerror('NanoGPT returned an error.');
10971097 return response.sendStatus(500);
10981098 }
10991099
@@ -1102,14 +1102,14 @@ nanogpt.post('/generate', jsonParser, async (request, response) => {
11021102
11031103 const image = data?.data?.[0]?.b64_json;
11041104 if (!image) {
11051105 console.logerror('NanoGPT returned invalid data.');
11061106 return response.sendStatus(500);
11071107 }
11081108
11091109 return response.send({ image });
11101110 }
11111111 catch (error) {
11121112 console.logerror(error);
11131113 return response.sendStatus(500);
11141114 }
11151115});
@@ -1121,7 +1121,7 @@ bfl.post('/generate', jsonParser, async (request, response) => {
11211121 const key = readSecret(request.user.directories, SECRET_KEYS.BFL);
11221122
11231123 if (!key) {
11241124 console.logerror('BFL key not found.');
11251125 return response.sendStatus(400);
11261126 }
11271127
@@ -1173,7 +1173,7 @@ bfl.post('/generate', jsonParser, async (request, response) => {
11731173 delete requestBody.guidance;
11741174 }
11751175
11761176 console.logdebug('BFL request:', requestBody);
11771177
11781178 const result = await fetch(`https://api.bfl.ml/v1/${request.body.model}`, {
11791179 method: 'POST',
@@ -1185,7 +1185,7 @@ bfl.post('/generate', jsonParser, async (request, response) => {
11851185 });
11861186
11871187 if (!result.ok) {
11881188 console.logerror('BFL returned an error.');
11891189 return response.sendStatus(500);
11901190 }
11911191
@@ -1201,7 +1201,7 @@ bfl.post('/generate', jsonParser, async (request, response) => {
12011201
12021202 if (!statusResult.ok) {
12031203 const text = await statusResult.text();
12041204 console.logerror('BFL returned an error.', text);
12051205 return response.sendStatus(500);
12061206 }
12071207
@@ -1223,7 +1223,7 @@ bfl.post('/generate', jsonParser, async (request, response) => {
12231223 throw new Error('BFL failed to generate image.', { cause: statusData });
12241224 }
12251225 } catch (error) {
12261226 console.logerror(error);
12271227 return response.sendStatus(500);
12281228 }
12291229});
src/endpoints/stats.js+2 -2
@@ -148,7 +148,7 @@ async function collectAndCreateStats(chatsPath, charactersPath) {
148148 * @param {string} charactersPath Path to the directory containing the character files.
149149 */
150150export async function recreateStats(handle, chatsPath, charactersPath) {
151151 console.loginfo('Collecting and creating stats for user:', handle);
152152 const stats = await collectAndCreateStats(chatsPath, charactersPath);
153153 STATS.set(handle, stats);
154154 await saveStatsToFile();
@@ -200,7 +200,7 @@ async function saveStatsToFile() {
200200 await writeFileAtomic(statsFilePath, JSON.stringify(charStats));
201201 TIMESTAMPS.set(handle, Date.now());
202202 } catch (error) {
203203 console.logerror('Failed to save stats to file.', error);
204204 }
205205 }
206206 }
src/endpoints/thumbnails.js+3 -4
@@ -89,7 +89,6 @@ async function generateThumbnail(directories, type, file) {
8989 let thumbnailFolder = getThumbnailFolder(directories, type);
9090 let originalFolder = getOriginalFolder(directories, type);
9191 if (thumbnailFolder === undefined || originalFolder === undefined) throw new Error('Invalid thumbnail type');
92-
9392 const pathToCachedFile = path.join(thumbnailFolder, file);
9493 const pathToOriginalFile = path.join(originalFolder, file);
9594
@@ -104,7 +103,7 @@ async function generateThumbnail(directories, type, file) {
104103 const cachedStat = fs.statSync(pathToCachedFile);
105104
106105 if (originalStat.mtimeMs > cachedStat.ctimeMs) {
107106 //console.logwarn('Original file changed. Regenerating thumbnail...');
108107 shouldRegenerate = true;
109108 }
110109 }
@@ -157,7 +156,7 @@ export async function ensureThumbnailCache() {
157156 return;
158157 }
159158
160159 console.loginfo('Generating thumbnails cache. Please wait...');
161160
162161 const bgFiles = fs.readdirSync(directories.backgrounds);
163162 const tasks = [];
@@ -167,7 +166,7 @@ export async function ensureThumbnailCache() {
167166 }
168167
169168 await Promise.all(tasks);
170169 console.loginfo(`Done! Generated: ${bgFiles.length} preview images`);
171170 }
172171}
173172
src/endpoints/tokenizers.js+17 -17
@@ -96,7 +96,7 @@ async function getPathToTokenizer(model, fallbackModel) {
9696 throw new Error('Downloading tokenizers is disabled, the model is not cached');
9797 }
9898
9999 console.loginfo('Downloading tokenizer model:', model);
100100 const response = await fetch(model);
101101 if (!response.ok) {
102102 throw new Error(`Failed to fetch the model: ${response.status} ${response.statusText}`);
@@ -108,7 +108,7 @@ async function getPathToTokenizer(model, fallbackModel) {
108108 } catch (error) {
109109 const getLastSegment = str => str?.split('/')?.pop() || '';
110110 if (fallbackModel) {
111111 console.logerror(`Could not get a tokenizer from ${getLastSegment(model)}. Reason: ${error.message}. Using a fallback model: ${getLastSegment(fallbackModel)}.`);
112112 return fallbackModel;
113113 }
114114
@@ -156,7 +156,7 @@ class SentencePieceTokenizer {
156156 const pathToModel = await getPathToTokenizer(this.#model, this.#fallbackModel);
157157 this.#instance = new SentencePieceProcessor();
158158 await this.#instance.load(pathToModel);
159159 console.loginfo('Instantiated the tokenizer for', path.parse(pathToModel).name);
160160 return this.#instance;
161161 } catch (error) {
162162 console.error('Sentencepiece tokenizer failed to load: ' + this.#model, error);
@@ -205,7 +205,7 @@ class WebTokenizer {
205205 const pathToModel = await getPathToTokenizer(this.#model, this.#fallbackModel);
206206 const arrayBuffer = fs.readFileSync(pathToModel).buffer;
207207 this.#instance = await Tokenizer.fromJSON(arrayBuffer);
208208 console.loginfo('Instantiated the tokenizer for', path.parse(pathToModel).name);
209209 return this.#instance;
210210 } catch (error) {
211211 console.error('Web tokenizer failed to load: ' + this.#model, error);
@@ -442,7 +442,7 @@ export function getTiktokenTokenizer(model) {
442442 }
443443
444444 const tokenizer = tiktoken.encoding_for_model(model);
445445 console.loginfo('Instantiated the tokenizer for', model);
446446 tokenizersCache[model] = tokenizer;
447447 return tokenizer;
448448}
@@ -489,7 +489,7 @@ function createSentencepieceEncodingHandler(tokenizer) {
489489 const chunks = instance?.encodePieces(text);
490490 return response.send({ ids, count, chunks });
491491 } catch (error) {
492492 console.logerror(error);
493493 return response.send({ ids: [], count: 0, chunks: [] });
494494 }
495495 };
@@ -520,7 +520,7 @@ function createSentencepieceDecodingHandler(tokenizer) {
520520 const text = chunks.join('');
521521 return response.send({ text, chunks });
522522 } catch (error) {
523523 console.logerror(error);
524524 return response.send({ text: '', chunks: [] });
525525 }
526526 };
@@ -549,7 +549,7 @@ function createTiktokenEncodingHandler(modelId) {
549549 const chunks = await getTiktokenChunks(tokenizer, tokens);
550550 return response.send({ ids: tokens, count: tokens.length, chunks });
551551 } catch (error) {
552552 console.logerror(error);
553553 return response.send({ ids: [], count: 0, chunks: [] });
554554 }
555555 };
@@ -578,7 +578,7 @@ function createTiktokenDecodingHandler(modelId) {
578578 const text = new TextDecoder().decode(textBytes);
579579 return response.send({ text });
580580 } catch (error) {
581581 console.logerror(error);
582582 return response.send({ text: '' });
583583 }
584584 };
@@ -608,7 +608,7 @@ function createWebTokenizerEncodingHandler(tokenizer) {
608608 const chunks = getWebTokenizersChunks(instance, tokens);
609609 return response.send({ ids: tokens, count: tokens.length, chunks });
610610 } catch (error) {
611611 console.logerror(error);
612612 return response.send({ ids: [], count: 0, chunks: [] });
613613 }
614614 };
@@ -639,7 +639,7 @@ function createWebTokenizerDecodingHandler(tokenizer) {
639639 const text = instance.decode(new Int32Array(ids));
640640 return response.send({ text, chunks });
641641 } catch (error) {
642642 console.logerror(error);
643643 return response.send({ text: '', chunks: [] });
644644 }
645645 };
@@ -739,7 +739,7 @@ router.post('/openai/encode', jsonParser, async function (req, res) {
739739 const handler = createTiktokenEncodingHandler(model);
740740 return handler(req, res);
741741 } catch (error) {
742742 console.logerror(error);
743743 return res.send({ ids: [], count: 0, chunks: [] });
744744 }
745745});
@@ -807,7 +807,7 @@ router.post('/openai/decode', jsonParser, async function (req, res) {
807807 const handler = createTiktokenDecodingHandler(model);
808808 return handler(req, res);
809809 } catch (error) {
810810 console.logerror(error);
811811 return res.send({ text: '' });
812812 }
813813});
@@ -946,7 +946,7 @@ router.post('/remote/kobold/count', jsonParser, async function (request, respons
946946 const result = await fetch(url, args);
947947
948948 if (!result.ok) {
949949 console.logerror(`API returned error: ${result.status} ${result.statusText}`);
950950 return response.send({ error: true });
951951 }
952952
@@ -955,7 +955,7 @@ router.post('/remote/kobold/count', jsonParser, async function (request, respons
955955 const ids = data['ids'] ?? [];
956956 return response.send({ count, ids });
957957 } catch (error) {
958958 console.logerror(error);
959959 return response.send({ error: true });
960960 }
961961});
@@ -1010,7 +1010,7 @@ router.post('/remote/textgenerationwebui/encode', jsonParser, async function (re
10101010 const result = await fetch(url, args);
10111011
10121012 if (!result.ok) {
10131013 console.logerror(`API returned error: ${result.status} ${result.statusText}`);
10141014 return response.send({ error: true });
10151015 }
10161016
@@ -1020,7 +1020,7 @@ router.post('/remote/textgenerationwebui/encode', jsonParser, async function (re
10201020
10211021 return response.send({ count, ids });
10221022 } catch (error) {
10231023 console.logerror(error);
10241024 return response.send({ error: true });
10251025 }
10261026});
src/endpoints/translate.js+31 -31
@@ -35,7 +35,7 @@ function decodeBuffer(buffer) {
3535 try {
3636 return iconv.decode(Buffer.from(buffer), 'utf-8');
3737 } catch (error) {
3838 console.logwarn('Failed to decode buffer:', error);
3939 return Buffer.from(buffer).toString('utf-8');
4040 }
4141}
@@ -46,7 +46,7 @@ router.post('/libre', jsonParser, async (request, response) => {
4646 const url = readSecret(request.user.directories, SECRET_KEYS.LIBRE_URL);
4747
4848 if (!url) {
4949 console.logerror('LibreTranslate URL is not configured.');
5050 return response.sendStatus(400);
5151 }
5252
@@ -69,7 +69,7 @@ router.post('/libre', jsonParser, async (request, response) => {
6969 return response.sendStatus(400);
7070 }
7171
7272 console.logdebug('Input text: ' + text);
7373
7474 const result = await fetch(url, {
7575 method: 'POST',
@@ -85,17 +85,17 @@ router.post('/libre', jsonParser, async (request, response) => {
8585
8686 if (!result.ok) {
8787 const error = await result.text();
8888 console.logerror('LibreTranslate error: ', result.statusText, error);
8989 return response.sendStatus(500);
9090 }
9191
9292 /** @type {any} */
9393 const json = await result.json();
9494 console.logdebug('Translated text: ' + json.translatedText);
9595
9696 return response.send(json.translatedText);
9797 } catch (error) {
9898 console.logerror('Translation error: ' + error.message);
9999 return response.sendStatus(500);
100100 }
101101});
@@ -109,14 +109,14 @@ router.post('/google', jsonParser, async (request, response) => {
109109 return response.sendStatus(400);
110110 }
111111
112112 console.logdebug('Input text: ' + text);
113113
114114 const { generateRequestUrl, normaliseResponse } = getGoogleTranslateClient();
115115 const requestUrl = generateRequestUrl(text, { to: lang });
116116 const result = await fetch(requestUrl);
117117
118118 if (!result.ok) {
119119 console.logerror('Google Translate error: ', result.statusText);
120120 return response.sendStatus(500);
121121 }
122122
@@ -125,10 +125,10 @@ router.post('/google', jsonParser, async (request, response) => {
125125 const translatedText = translateResponse.text;
126126
127127 response.setHeader('Content-Type', 'text/plain; charset=utf-8');
128128 console.logdebug('Translated text: ' + translatedText);
129129 return response.send(translatedText);
130130 } catch (error) {
131131 console.logerror('Translation error', error);
132132 return response.sendStatus(500);
133133 }
134134});
@@ -161,7 +161,7 @@ router.post('/yandex', jsonParser, async (request, response) => {
161161 params.append('lang', lang);
162162 const ucid = uuidv4().replaceAll('-', '');
163163
164164 console.logdebug('Input text: ' + inputText);
165165
166166 const result = await fetch(`https://translate.yandex.net/api/v1/tr.json/translate?ucid=${ucid}&srv=android&format=text`, {
167167 method: 'POST',
@@ -173,18 +173,18 @@ router.post('/yandex', jsonParser, async (request, response) => {
173173
174174 if (!result.ok) {
175175 const error = await result.text();
176176 console.logerror('Yandex error: ', result.statusText, error);
177177 return response.sendStatus(500);
178178 }
179179
180180 /** @type {any} */
181181 const json = await result.json();
182182 const translated = json.text.join();
183183 console.logdebug('Translated text: ' + translated);
184184
185185 return response.send(translated);
186186 } catch (error) {
187187 console.logerror('Translation error: ' + error.message);
188188 return response.sendStatus(500);
189189 }
190190});
@@ -195,7 +195,7 @@ router.post('/lingva', jsonParser, async (request, response) => {
195195 const baseUrl = secretUrl || LINGVA_DEFAULT;
196196
197197 if (!secretUrl && baseUrl === LINGVA_DEFAULT) {
198198 console.logwarn('Lingva URL is using default value.', LINGVA_DEFAULT);
199199 }
200200
201201 if (request.body.lang === 'zh-CN' || request.body.lang === 'zh-TW') {
@@ -213,22 +213,22 @@ router.post('/lingva', jsonParser, async (request, response) => {
213213 return response.sendStatus(400);
214214 }
215215
216216 console.logdebug('Input text: ' + text);
217217
218218 const url = urlJoin(baseUrl, 'auto', lang, encodeURIComponent(text));
219219 const result = await fetch(url);
220220
221221 if (!result.ok) {
222222 const error = await result.text();
223223 console.logerror('Lingva error: ', result.statusText, error);
224224 }
225225
226226 /** @type {any} */
227227 const data = await result.json();
228228 console.logdebug('Translated text: ' + data.translation);
229229 return response.send(data.translation);
230230 } catch (error) {
231231 console.logerror('Translation error', error);
232232 return response.sendStatus(500);
233233 }
234234});
@@ -238,7 +238,7 @@ router.post('/deepl', jsonParser, async (request, response) => {
238238 const key = readSecret(request.user.directories, SECRET_KEYS.DEEPL);
239239
240240 if (!key) {
241241 console.logerror('DeepL key is not configured.');
242242 return response.sendStatus(400);
243243 }
244244
@@ -254,7 +254,7 @@ router.post('/deepl', jsonParser, async (request, response) => {
254254 return response.sendStatus(400);
255255 }
256256
257257 console.logdebug('Input text: ' + text);
258258
259259 const params = new URLSearchParams();
260260 params.append('text', text);
@@ -280,17 +280,17 @@ router.post('/deepl', jsonParser, async (request, response) => {
280280
281281 if (!result.ok) {
282282 const error = await result.text();
283283 console.logerror('DeepL error: ', result.statusText, error);
284284 return response.sendStatus(500);
285285 }
286286
287287 /** @type {any} */
288288 const json = await result.json();
289289 console.logdebug('Translated text: ' + json.translations[0].text);
290290
291291 return response.send(json.translations[0].text);
292292 } catch (error) {
293293 console.logerror('Translation error: ' + error.message);
294294 return response.sendStatus(500);
295295 }
296296});
@@ -376,7 +376,7 @@ router.post('/deeplx', jsonParser, async (request, response) => {
376376 return response.sendStatus(400);
377377 }
378378
379379 console.logdebug('Input text: ' + text);
380380
381381 const result = await fetch(url, {
382382 method: 'POST',
@@ -393,17 +393,17 @@ router.post('/deeplx', jsonParser, async (request, response) => {
393393
394394 if (!result.ok) {
395395 const error = await result.text();
396396 console.logerror('DeepLX error: ', result.statusText, error);
397397 return response.sendStatus(500);
398398 }
399399
400400 /** @type {any} */
401401 const json = await result.json();
402402 console.logdebug('Translated text: ' + json.data);
403403
404404 return response.send(json.data);
405405 } catch (error) {
406406 console.logerror('DeepLX translation error: ' + error.message);
407407 return response.sendStatus(500);
408408 }
409409});
@@ -429,14 +429,14 @@ router.post('/bing', jsonParser, async (request, response) => {
429429 return response.sendStatus(400);
430430 }
431431
432432 console.logdebug('Input text: ' + text);
433433
434434 const result = await bingTranslate(text, null, lang);
435435 const translatedText = result?.translation;
436436 console.logdebug('Translated text: ' + translatedText);
437437 return response.send(translatedText);
438438 } catch (error) {
439439 console.logerror('Translation error', error);
440440 return response.sendStatus(500);
441441 }
442442});
src/endpoints/users-admin.js+19 -19
@@ -53,12 +53,12 @@ router.post('/get', requireAdminMiddleware, jsonParser, async (_request, respons
5353router.post('/disable', requireAdminMiddleware, jsonParser, async (request, response) => {
5454 try {
5555 if (!request.body.handle) {
5656 console.logerror('Disable user failed: Missing required fields');
5757 return response.status(400).json({ error: 'Missing required fields' });
5858 }
5959
6060 if (request.body.handle === request.user.profile.handle) {
6161 console.logerror('Disable user failed: Cannot disable yourself');
6262 return response.status(400).json({ error: 'Cannot disable yourself' });
6363 }
6464
@@ -66,7 +66,7 @@ router.post('/disable', requireAdminMiddleware, jsonParser, async (request, resp
6666 const user = await storage.getItem(toKey(request.body.handle));
6767
6868 if (!user) {
6969 console.logerror('Disable user failed: User not found');
7070 return response.status(404).json({ error: 'User not found' });
7171 }
7272
@@ -82,7 +82,7 @@ router.post('/disable', requireAdminMiddleware, jsonParser, async (request, resp
8282router.post('/enable', requireAdminMiddleware, jsonParser, async (request, response) => {
8383 try {
8484 if (!request.body.handle) {
8585 console.logerror('Enable user failed: Missing required fields');
8686 return response.status(400).json({ error: 'Missing required fields' });
8787 }
8888
@@ -90,7 +90,7 @@ router.post('/enable', requireAdminMiddleware, jsonParser, async (request, respo
9090 const user = await storage.getItem(toKey(request.body.handle));
9191
9292 if (!user) {
9393 console.logerror('Enable user failed: User not found');
9494 return response.status(404).json({ error: 'User not found' });
9595 }
9696
@@ -106,7 +106,7 @@ router.post('/enable', requireAdminMiddleware, jsonParser, async (request, respo
106106router.post('/promote', requireAdminMiddleware, jsonParser, async (request, response) => {
107107 try {
108108 if (!request.body.handle) {
109109 console.logerror('Promote user failed: Missing required fields');
110110 return response.status(400).json({ error: 'Missing required fields' });
111111 }
112112
@@ -114,7 +114,7 @@ router.post('/promote', requireAdminMiddleware, jsonParser, async (request, resp
114114 const user = await storage.getItem(toKey(request.body.handle));
115115
116116 if (!user) {
117117 console.logerror('Promote user failed: User not found');
118118 return response.status(404).json({ error: 'User not found' });
119119 }
120120
@@ -130,12 +130,12 @@ router.post('/promote', requireAdminMiddleware, jsonParser, async (request, resp
130130router.post('/demote', requireAdminMiddleware, jsonParser, async (request, response) => {
131131 try {
132132 if (!request.body.handle) {
133133 console.logerror('Demote user failed: Missing required fields');
134134 return response.status(400).json({ error: 'Missing required fields' });
135135 }
136136
137137 if (request.body.handle === request.user.profile.handle) {
138138 console.logerror('Demote user failed: Cannot demote yourself');
139139 return response.status(400).json({ error: 'Cannot demote yourself' });
140140 }
141141
@@ -143,7 +143,7 @@ router.post('/demote', requireAdminMiddleware, jsonParser, async (request, respo
143143 const user = await storage.getItem(toKey(request.body.handle));
144144
145145 if (!user) {
146146 console.logerror('Demote user failed: User not found');
147147 return response.status(404).json({ error: 'User not found' });
148148 }
149149
@@ -159,7 +159,7 @@ router.post('/demote', requireAdminMiddleware, jsonParser, async (request, respo
159159router.post('/create', requireAdminMiddleware, jsonParser, async (request, response) => {
160160 try {
161161 if (!request.body.handle || !request.body.name) {
162162 console.logerror('Create user failed: Missing required fields');
163163 return response.status(400).json({ error: 'Missing required fields' });
164164 }
165165
@@ -167,12 +167,12 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo
167167 const handle = lodash.kebabCase(String(request.body.handle).toLowerCase().trim());
168168
169169 if (!handle) {
170170 console.logerror('Create user failed: Invalid handle');
171171 return response.status(400).json({ error: 'Invalid handle' });
172172 }
173173
174174 if (handles.some(x => x === handle)) {
175175 console.logerror('Create user failed: User with that handle already exists');
176176 return response.status(409).json({ error: 'User already exists' });
177177 }
178178
@@ -192,7 +192,7 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo
192192 await storage.setItem(toKey(handle), newUser);
193193
194194 // Create user directories
195195 console.loginfo('Creating data directories for', newUser.handle);
196196 await ensurePublicDirectoriesExist();
197197 const directories = getUserDirectories(newUser.handle);
198198 await checkForNewContent([directories]);
@@ -206,17 +206,17 @@ router.post('/create', requireAdminMiddleware, jsonParser, async (request, respo
206206router.post('/delete', requireAdminMiddleware, jsonParser, async (request, response) => {
207207 try {
208208 if (!request.body.handle) {
209209 console.logerror('Delete user failed: Missing required fields');
210210 return response.status(400).json({ error: 'Missing required fields' });
211211 }
212212
213213 if (request.body.handle === request.user.profile.handle) {
214214 console.logerror('Delete user failed: Cannot delete yourself');
215215 return response.status(400).json({ error: 'Cannot delete yourself' });
216216 }
217217
218218 if (request.body.handle === DEFAULT_USER.handle) {
219219 console.logerror('Delete user failed: Cannot delete default user');
220220 return response.status(400).json({ error: 'Sorry, but the default user cannot be deleted. It is required as a fallback.' });
221221 }
222222
@@ -224,7 +224,7 @@ router.post('/delete', requireAdminMiddleware, jsonParser, async (request, respo
224224
225225 if (request.body.purge) {
226226 const directories = getUserDirectories(request.body.handle);
227227 console.loginfo('Deleting data directories for', request.body.handle);
228228 await fsPromises.rm(directories.root, { recursive: true, force: true });
229229 }
230230
@@ -238,7 +238,7 @@ router.post('/delete', requireAdminMiddleware, jsonParser, async (request, respo
238238router.post('/slugify', requireAdminMiddleware, jsonParser, async (request, response) => {
239239 try {
240240 if (!request.body.text) {
241241 console.logerror('Slugify failed: Missing required fields');
242242 return response.status(400).json({ error: 'Missing required fields' });
243243 }
244244
src/endpoints/users-private.js+22 -22
@@ -57,18 +57,18 @@ router.get('/me', async (request, response) => {
5757router.post('/change-avatar', jsonParser, async (request, response) => {
5858 try {
5959 if (!request.body.handle) {
6060 console.logerror('Change avatar failed: Missing required fields');
6161 return response.status(400).json({ error: 'Missing required fields' });
6262 }
6363
6464 if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
6565 console.logerror('Change avatar failed: Unauthorized');
6666 return response.status(403).json({ error: 'Unauthorized' });
6767 }
6868
6969 // Avatar is not a data URL or not an empty string
7070 if (!request.body.avatar.startsWith('data:image/') && request.body.avatar !== '') {
7171 console.logerror('Change avatar failed: Invalid data URL');
7272 return response.status(400).json({ error: 'Invalid data URL' });
7373 }
7474
@@ -76,7 +76,7 @@ router.post('/change-avatar', jsonParser, async (request, response) => {
7676 const user = await storage.getItem(toKey(request.body.handle));
7777
7878 if (!user) {
7979 console.logerror('Change avatar failed: User not found');
8080 return response.status(404).json({ error: 'User not found' });
8181 }
8282
@@ -92,12 +92,12 @@ router.post('/change-avatar', jsonParser, async (request, response) => {
9292router.post('/change-password', jsonParser, async (request, response) => {
9393 try {
9494 if (!request.body.handle) {
9595 console.logerror('Change password failed: Missing required fields');
9696 return response.status(400).json({ error: 'Missing required fields' });
9797 }
9898
9999 if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
100100 console.logerror('Change password failed: Unauthorized');
101101 return response.status(403).json({ error: 'Unauthorized' });
102102 }
103103
@@ -105,17 +105,17 @@ router.post('/change-password', jsonParser, async (request, response) => {
105105 const user = await storage.getItem(toKey(request.body.handle));
106106
107107 if (!user) {
108108 console.logerror('Change password failed: User not found');
109109 return response.status(404).json({ error: 'User not found' });
110110 }
111111
112112 if (!user.enabled) {
113113 console.logerror('Change password failed: User is disabled');
114114 return response.status(403).json({ error: 'User is disabled' });
115115 }
116116
117117 if (!request.user.profile.admin && user.password && user.password !== getPasswordHash(request.body.oldPassword, user.salt)) {
118118 console.logerror('Change password failed: Incorrect password');
119119 return response.status(403).json({ error: 'Incorrect password' });
120120 }
121121
@@ -141,12 +141,12 @@ router.post('/backup', jsonParser, async (request, response) => {
141141 const handle = request.body.handle;
142142
143143 if (!handle) {
144144 console.logerror('Backup failed: Missing required fields');
145145 return response.status(400).json({ error: 'Missing required fields' });
146146 }
147147
148148 if (handle !== request.user.profile.handle && !request.user.profile.admin) {
149149 console.logerror('Backup failed: Unauthorized');
150150 return response.status(403).json({ error: 'Unauthorized' });
151151 }
152152
@@ -162,7 +162,7 @@ router.post('/reset-settings', jsonParser, async (request, response) => {
162162 const password = request.body.password;
163163
164164 if (request.user.profile.password && request.user.profile.password !== getPasswordHash(password, request.user.profile.salt)) {
165165 console.logerror('Reset settings failed: Incorrect password');
166166 return response.status(403).json({ error: 'Incorrect password' });
167167 }
168168
@@ -180,12 +180,12 @@ router.post('/reset-settings', jsonParser, async (request, response) => {
180180router.post('/change-name', jsonParser, async (request, response) => {
181181 try {
182182 if (!request.body.name || !request.body.handle) {
183183 console.logerror('Change name failed: Missing required fields');
184184 return response.status(400).json({ error: 'Missing required fields' });
185185 }
186186
187187 if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
188188 console.logerror('Change name failed: Unauthorized');
189189 return response.status(403).json({ error: 'Unauthorized' });
190190 }
191191
@@ -193,7 +193,7 @@ router.post('/change-name', jsonParser, async (request, response) => {
193193 const user = await storage.getItem(toKey(request.body.handle));
194194
195195 if (!user) {
196196 console.logerror('Change name failed: User not found');
197197 return response.status(404).json({ error: 'User not found' });
198198 }
199199
@@ -210,9 +210,9 @@ router.post('/change-name', jsonParser, async (request, response) => {
210210router.post('/reset-step1', jsonParser, async (request, response) => {
211211 try {
212212 const resetCode = String(crypto.randomInt(1000, 9999));
213213 console.loginfo();
214214 console.loginfo(color.magenta(`${request.user.profile.name}, your account reset code is: `) + color.red(resetCode));
215215 console.loginfo();
216216 RESET_CACHE.set(request.user.profile.handle, resetCode);
217217 return response.sendStatus(204);
218218 } catch (error) {
@@ -224,23 +224,23 @@ router.post('/reset-step1', jsonParser, async (request, response) => {
224224router.post('/reset-step2', jsonParser, async (request, response) => {
225225 try {
226226 if (!request.body.code) {
227227 console.logerror('Recover step 2 failed: Missing required fields');
228228 return response.status(400).json({ error: 'Missing required fields' });
229229 }
230230
231231 if (request.user.profile.password && request.user.profile.password !== getPasswordHash(request.body.password, request.user.profile.salt)) {
232232 console.logerror('Recover step 2 failed: Incorrect password');
233233 return response.status(400).json({ error: 'Incorrect password' });
234234 }
235235
236236 const code = RESET_CACHE.get(request.user.profile.handle);
237237
238238 if (!code || code !== request.body.code) {
239239 console.logerror('Recover step 2 failed: Incorrect code');
240240 return response.status(400).json({ error: 'Incorrect code' });
241241 }
242242
243243 console.loginfo('Resetting account data:', request.user.profile.handle);
244244 await fsPromises.rm(request.user.directories.root, { recursive: true, force: true });
245245
246246 await ensurePublicDirectoriesExist();
src/endpoints/users-public.js+18 -18
@@ -56,7 +56,7 @@ router.post('/list', async (_request, response) => {
5656router.post('/login', jsonParser, async (request, response) => {
5757 try {
5858 if (!request.body.handle) {
5959 console.logerror('Login failed: Missing required fields');
6060 return response.status(400).json({ error: 'Missing required fields' });
6161 }
6262
@@ -67,17 +67,17 @@ router.post('/login', jsonParser, async (request, response) => {
6767 const user = await storage.getItem(toKey(request.body.handle));
6868
6969 if (!user) {
7070 console.logerror('Login failed: User', request.body.handle, 'not found');
7171 return response.status(403).json({ error: 'Incorrect credentials' });
7272 }
7373
7474 if (!user.enabled) {
7575 console.logerror('Login failed: User', user.handle, 'is disabled');
7676 return response.status(403).json({ error: 'User is disabled' });
7777 }
7878
7979 if (user.password && user.password !== getPasswordHash(request.body.password, user.salt)) {
8080 console.logerror('Login failed: Incorrect password for', user.handle);
8181 return response.status(403).json({ error: 'Incorrect credentials' });
8282 }
8383
@@ -88,11 +88,11 @@ router.post('/login', jsonParser, async (request, response) => {
8888
8989 await loginLimiter.delete(ip);
9090 request.session.handle = user.handle;
9191 console.loginfo('Login successful:', user.handle, 'from', ip, 'at', new Date().toLocaleString());
9292 return response.json({ handle: user.handle });
9393 } catch (error) {
9494 if (error instanceof RateLimiterRes) {
9595 console.logerror('Login failed: Rate limited from', getIpFromRequest(request));
9696 return response.status(429).send({ error: 'Too many attempts. Try again later or recover your password.' });
9797 }
9898
@@ -104,7 +104,7 @@ router.post('/login', jsonParser, async (request, response) => {
104104router.post('/recover-step1', jsonParser, async (request, response) => {
105105 try {
106106 if (!request.body.handle) {
107107 console.logerror('Recover step 1 failed: Missing required fields');
108108 return response.status(400).json({ error: 'Missing required fields' });
109109 }
110110
@@ -115,24 +115,24 @@ router.post('/recover-step1', jsonParser, async (request, response) => {
115115 const user = await storage.getItem(toKey(request.body.handle));
116116
117117 if (!user) {
118118 console.logerror('Recover step 1 failed: User', request.body.handle, 'not found');
119119 return response.status(404).json({ error: 'User not found' });
120120 }
121121
122122 if (!user.enabled) {
123123 console.logerror('Recover step 1 failed: User', user.handle, 'is disabled');
124124 return response.status(403).json({ error: 'User is disabled' });
125125 }
126126
127127 const mfaCode = String(crypto.randomInt(1000, 9999));
128128 console.loginfo();
129129 console.loginfo(color.blue(`${user.name}, your password recovery code is: `) + color.magenta(mfaCode));
130130 console.loginfo();
131131 MFA_CACHE.set(user.handle, mfaCode);
132132 return response.sendStatus(204);
133133 } catch (error) {
134134 if (error instanceof RateLimiterRes) {
135135 console.logerror('Recover step 1 failed: Rate limited from', getIpFromRequest(request));
136136 return response.status(429).send({ error: 'Too many attempts. Try again later or contact your admin.' });
137137 }
138138
@@ -144,7 +144,7 @@ router.post('/recover-step1', jsonParser, async (request, response) => {
144144router.post('/recover-step2', jsonParser, async (request, response) => {
145145 try {
146146 if (!request.body.handle || !request.body.code) {
147147 console.logerror('Recover step 2 failed: Missing required fields');
148148 return response.status(400).json({ error: 'Missing required fields' });
149149 }
150150
@@ -153,12 +153,12 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
153153 const ip = getIpFromRequest(request);
154154
155155 if (!user) {
156156 console.logerror('Recover step 2 failed: User', request.body.handle, 'not found');
157157 return response.status(404).json({ error: 'User not found' });
158158 }
159159
160160 if (!user.enabled) {
161161 console.logerror('Recover step 2 failed: User', user.handle, 'is disabled');
162162 return response.status(403).json({ error: 'User is disabled' });
163163 }
164164
@@ -166,7 +166,7 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
166166
167167 if (request.body.code !== mfaCode) {
168168 await recoverLimiter.consume(ip);
169169 console.logerror('Recover step 2 failed: Incorrect code');
170170 return response.status(403).json({ error: 'Incorrect code' });
171171 }
172172
@@ -186,7 +186,7 @@ router.post('/recover-step2', jsonParser, async (request, response) => {
186186 return response.sendStatus(204);
187187 } catch (error) {
188188 if (error instanceof RateLimiterRes) {
189189 console.logerror('Recover step 2 failed: Rate limited from', getIpFromRequest(request));
190190 return response.status(429).send({ error: 'Too many attempts. Try again later or contact your admin.' });
191191 }
192192
src/endpoints/vectors.js+3 -3
@@ -360,7 +360,7 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) {
360360
361361 if (exists) {
362362 const path = index.folderPath;
363363 console.errorwarn(`Corrupted index detected at ${path}, regenerating...`);
364364 await index.deleteIndex();
365365 return res.redirect(307, req.originalUrl + '?regenerated=true');
366366 }
@@ -474,7 +474,7 @@ router.post('/purge-all', jsonParser, async (req, res) => {
474474 continue;
475475 }
476476 await fs.promises.rm(sourcePath, { recursive: true });
477477 console.loginfo(`Deleted vector source store at ${sourcePath}`);
478478 }
479479
480480 return res.sendStatus(200);
@@ -498,7 +498,7 @@ router.post('/purge', jsonParser, async (req, res) => {
498498 continue;
499499 }
500500 await fs.promises.rm(sourcePath, { recursive: true });
501501 console.loginfo(`Deleted vector index at ${sourcePath}`);
502502 }
503503
504504 return res.sendStatus(200);
src/endpoints/worldinfo.js+1 -1
@@ -25,7 +25,7 @@ export function readWorldInfoFile(directories, worldInfoName, allowDummy) {
2525 const pathToWorldInfo = path.join(directories.worlds, filename);
2626
2727 if (!fs.existsSync(pathToWorldInfo)) {
2828 console.logerror(`World info file ${filename} doesn't exist.`);
2929 return dummyObject;
3030 }
3131
src/request-proxy.js+3 -3
@@ -43,9 +43,9 @@ export default function initRequestProxy({ enabled, url, bypass }) {
4343 http.globalAgent = proxyAgent;
4444 https.globalAgent = proxyAgent;
4545
4646 console.loginfo();
4747 console.loginfo(color.green(LOG_HEADER), 'Proxy URL is used:', color.blue(url));
4848 console.loginfo();
4949 } catch (error) {
5050 console.error(color.red(LOG_HEADER), 'Failed to initialize request proxy:', error);
5151 }
src/util.js+21 -3
@@ -13,6 +13,7 @@ import _ from 'lodash';
1313import yauzl from 'yauzl';
1414import mime from 'mime-types';
1515import { default as simpleGit } from 'simple-git';
16+import { LOG_LEVELS } from './constants.js';
1617
1718/**
1819 * Parsed config object.
@@ -149,6 +150,7 @@ export function getHexString(length) {
149150 */
150151export function formatBytes(bytes) {
151152 if (bytes === 0) return '0 B';
153+
152154 const k = 1024;
153155 const sizes = ['B', 'KB', 'MB', 'GB'];
154156 const i = Math.floor(Math.log(bytes) / Math.log(k));
@@ -285,10 +287,11 @@ export function deepMerge(target, source) {
285287 if (isObject(target) && isObject(source)) {
286288 Object.keys(source).forEach(key => {
287289 if (isObject(source[key])) {
288290 if (!(key in target)) {
289291 Object.assign(output, { [key]: source[key] });
290292 } else {
291293 output[key] = deepMerge(target[key], source[key]);
294+ }
292295 } else {
293296 Object.assign(output, { [key]: source[key] });
294297 }
@@ -466,7 +469,9 @@ export function forwardFetchResponse(from, to) {
466469 from.body.pipe(to);
467470
468471 to.socket.on('close', function () {
469472 if (from.body instanceof Readable) from.body.destroy(); // Close the remote stream{
473+ from.body.destroy(); // Close the remote stream
474+ }
470475 to.end(); // End the Express response
471476 });
472477
@@ -693,6 +698,19 @@ export function isValidUrl(url) {
693698}
694699
695700/**
701+ * Setup the minimum log level
702+ */
703+export function setupLogLevel() {
704+ const logLevel = getConfigValue('minLogLevel', LOG_LEVELS.DEBUG);
705+
706+ globalThis.console.debug = logLevel <= LOG_LEVELS.DEBUG ? console.debug : () => {};
707+ globalThis.console.log = logLevel <= LOG_LEVELS.INFO ? console.log : () => {};
708+ globalThis.console.info = logLevel <= LOG_LEVELS.INFO ? console.info : () => {};
709+ globalThis.console.warn = logLevel <= LOG_LEVELS.WARN ? console.warn : () => {};
710+ globalThis.console.error = logLevel <= LOG_LEVELS.ERROR ? console.error : () => {};
711+}
712+
713+/**
696714 * MemoryLimitedMap class that limits the memory usage of string values.
697715 */
698716export class MemoryLimitedMap {