Merge pull request #3307 from Eradev/staging-3071-v2 Added option to disable the chat logs in terminal v2

76d166176877779dfba11090d2849604e5fd242b

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
46 files changed, +564 -568Showing whitespace changes
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
@@ -69,6 +69,7 @@ const hash_derivations = {
6969 // DeepSeek R1
7070 'b6835114b7303ddd78919a82e4d9f7d8c26ed0d7dfc36beeb12d524f6144eab1':
7171 'DeepSeek-V2.5'
72+ ,
7273};
7374
7475const substr_derivations = {
@@ -97,6 +98,6 @@ export async function deriveTemplatesFromChatTemplate(chat_template, hash) {
9798 }
9899 }
99100
100101 console.logwarn(`Unknown chat template hash: ${hash} for [${chat_template}]`);
101102 return null;
102103}
public/scripts/extensions/quick-reply/src/QuickReplySet.js+6 -28
@@ -1,15 +1,14 @@
11import { getRequestHeaders, substituteParams } from '../../../../script.js';
22import { Popup, POPUP_RESULT, POPUP_TYPE } from '../../../popup.js';
33import { executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions } from '../../../slash-commands.js';
4-import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js';
54import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js';
65import { debounceAsync, log, warnSlashCommandParser } from '../index../../slash-commands/SlashCommandParser.js';
6+import { debounceAsync, warn } from '../index.js';
77import { QuickReply } from './QuickReply.js';
88
99export class QuickReplySet {
1010 /**@type {QuickReplySet[]}*/ static list = [];
1111
12-
1312 static from(props) {
1413 props.qrList = []; //props.qrList?.map(it=>QuickReply.from(it));
1514 const instance = Object.assign(new this(), props);
@@ -24,9 +23,6 @@ export class QuickReplySet {
2423 return this.list.find(it=>it.name == name);
2524 }
2625
27-
28-
29-
3026 /**@type {string}*/ name;
3127 /**@type {boolean}*/ disableSend = false;
3228 /**@type {boolean}*/ placeBeforeInput = false;
@@ -34,19 +30,12 @@ export class QuickReplySet {
3430 /**@type {string}*/ color = 'transparent';
3531 /**@type {boolean}*/ onlyBorderColor = false;
3632 /**@type {QuickReply[]}*/ qrList = [];
37-
3833 /**@type {number}*/ idIndex = 0;
39-
4034 /**@type {boolean}*/ isDeleted = false;
41-
4235 /**@type {function}*/ save;
43-
4436 /**@type {HTMLElement}*/ dom;
4537 /**@type {HTMLElement}*/ settingsDom;
4638
47-
48-
49-
5039 constructor() {
5140 this.save = debounceAsync(()=>this.performSave(), 200);
5241 }
@@ -55,9 +44,6 @@ export class QuickReplySet {
5544 this.qrList.forEach(qr=>this.hookQuickReply(qr));
5645 }
5746
58-
59-
60-
6147 unrender() {
6248 this.dom?.remove();
6349 this.dom = null;
@@ -100,9 +86,6 @@ export class QuickReplySet {
10086 }
10187 }
10288
103-
104-
105-
10689 renderSettings() {
10790 if (!this.settingsDom) {
10891 this.settingsDom = document.createElement('div'); {
@@ -123,9 +106,6 @@ export class QuickReplySet {
123106 this.settingsDom.append(qr.renderSettings(idx));
124107 }
125108
126-
127-
128-
129109 /**
130110 *
131111 * @param {QuickReply} qr
@@ -138,6 +118,7 @@ export class QuickReplySet {
138118 closure.scope.setMacro('arg::*', '');
139119 return (await closure.execute())?.pipe;
140120 }
121+
141122 /**
142123 *
143124 * @param {QuickReply} qr The QR to execute.
@@ -207,6 +188,7 @@ export class QuickReplySet {
207188 document.querySelector('#send_but').click();
208189 }
209190 }
191+
210192 /**
211193 * @param {QuickReply} qr
212194 * @param {string} [message] - optional altered message to be used
@@ -220,9 +202,6 @@ export class QuickReplySet {
220202 });
221203 }
222204
223-
224-
225-
226205 addQuickReply(data = {}) {
227206 const id = Math.max(this.idIndex, this.qrList.reduce((max,qr)=>Math.max(max,qr.id),0)) + 1;
228207 data.id =
@@ -239,6 +218,7 @@ export class QuickReplySet {
239218 this.save();
240219 return qr;
241220 }
221+
242222 addQuickReplyFromText(qrJson) {
243223 let data;
244224 if (qrJson) {
@@ -371,7 +351,6 @@ export class QuickReplySet {
371351 this.save();
372352 }
373353
374-
375354 toJSON() {
376355 return {
377356 version: 2,
@@ -386,7 +365,6 @@ export class QuickReplySet {
386365 };
387366 }
388367
389-
390368 async performSave() {
391369 const response = await fetch('/api/quick-replies/save', {
392370 method: 'POST',
public/scripts/extensions/vectors/index.js+2 -4
@@ -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),
public/scripts/openai.js+1 -3
@@ -1443,9 +1443,7 @@ async function sendWindowAIRequest(messages, signal, stream) {
14431443 }
14441444
14451445 const onStreamResult = (res, err) => {
14461446 if (err) {return;
1447- return;
1448- }
14491447
14501448 const thisContent = res?.message?.content;
14511449
server.js+6 -2
@@ -66,6 +66,7 @@ import {
6666 removeColorFormatting,
6767 getSeparator,
6868 safeReadFileSync,
69+ setupLogLevel,
6970} from './src/util.js';
7071import { UPLOADS_DIRECTORY } from './src/constants.js';
7172import { ensureThumbnailCache } from './src/endpoints/thumbnails.js';
@@ -698,8 +699,9 @@ const preSetupTasks = async function () {
698699function getAutorunHostname() {
699700 if (autorunHostname === 'auto') {
700701 if (enableIPv6 && enableIPv4) {
701- if (avoidLocalhost) return '[::1]';
702+ return avoidLocalhost
702- return 'localhost';
703+ ? '[::1]'
704+ : 'localhost';
703705 }
704706
705707 if (enableIPv6) {
@@ -765,6 +767,8 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
765767 }
766768 }
767769 }
770+
771+ setupLogLevel();
768772};
769773
770774/**
src/constants.js+7 -0
@@ -414,3 +414,10 @@ export const VLLM_KEYS = [
414414 'guided_decoding_backend',
415415 'guided_whitespace_pattern',
416416];
417+
418+export const LOG_LEVELS = {
419+ DEBUG: 0,
420+ INFO: 1,
421+ WARN: 2,
422+ ERROR: 3,
423+};
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.logwarn(`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+13 -12
@@ -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
@@ -241,13 +241,13 @@ router.post('/download', jsonParser, async (request, response) => {
241241 }
242242
243243 // Move into asset place
244244 console.debuginfo('Download finished, moving file from', temp_path, 'to', file_path);
245245 fs.copyFileSync(temp_path, file_path);
246246 fs.rmSync(temp_path);
247247 response.sendStatus(200);
248248 }
249249 catch (error) {
250250 console.logerror(error);
251251 response.sendStatus(500);
252252 }
253253});
@@ -270,7 +270,7 @@ router.post('/delete', jsonParser, async (request, response) => {
270270 category = i;
271271
272272 if (category === null) {
273273 console.debugerror('Bad request: unsupported asset category.');
274274 return response.sendStatus(400);
275275 }
276276
@@ -280,7 +280,7 @@ router.post('/delete', jsonParser, async (request, response) => {
280280 return response.status(400).send(validation.message);
281281
282282 const file_path = path.join(request.user.directories.assets, category, request.body.filename);
283283 console.debuginfo('Request received to delete', category, file_path);
284284
285285 try {
286286 // Delete if previous download failed
@@ -288,17 +288,17 @@ router.post('/delete', jsonParser, async (request, response) => {
288288 fs.unlink(file_path, (err) => {
289289 if (err) throw err;
290290 });
291291 console.debuginfo('Asset deleted.');
292292 }
293293 else {
294294 console.debugerror('Asset not found.');
295295 response.sendStatus(400);
296296 }
297297 // Move into asset place
298298 response.sendStatus(200);
299299 }
300300 catch (error) {
301301 console.logerror(error);
302302 response.sendStatus(500);
303303 }
304304});
@@ -314,6 +314,7 @@ router.post('/delete', jsonParser, async (request, response) => {
314314 */
315315router.post('/character', jsonParser, async (request, response) => {
316316 if (request.query.name === undefined) return response.sendStatus(400);
317+
317318 // For backwards compatibility, don't reject invalid character names, just sanitize them
318319 const name = sanitize(request.query.name.toString());
319320 const inputCategory = request.query.category;
@@ -325,7 +326,7 @@ router.post('/character', jsonParser, async (request, response) => {
325326 category = i;
326327
327328 if (category === null) {
328329 console.debugerror('Bad request: unsupported asset category.');
329330 return response.sendStatus(400);
330331 }
331332
@@ -364,7 +365,7 @@ router.post('/character', jsonParser, async (request, response) => {
364365 return response.send(output);
365366 }
366367 catch (err) {
367368 console.logerror(err);
368369 return response.sendStatus(500);
369370 }
370371});
src/endpoints/azure.js+6 -6
@@ -11,14 +11,14 @@ router.post('/list', jsonParser, async (req, res) => {
1111 const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS);
1212
1313 if (!key) {
1414 console.errorwarn('Azure TTS API Key not set');
1515 return res.sendStatus(403);
1616 }
1717
1818 const region = req.body.region;
1919
2020 if (!region) {
2121 console.errorwarn('Azure TTS region not set');
2222 return res.sendStatus(400);
2323 }
2424
@@ -32,7 +32,7 @@ router.post('/list', jsonParser, async (req, res) => {
3232 });
3333
3434 if (!response.ok) {
3535 console.errorwarn('Azure Request failed', response.status, response.statusText);
3636 return res.sendStatus(500);
3737 }
3838
@@ -49,13 +49,13 @@ router.post('/generate', jsonParser, async (req, res) => {
4949 const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS);
5050
5151 if (!key) {
5252 console.errorwarn('Azure TTS API Key not set');
5353 return res.sendStatus(403);
5454 }
5555
5656 const { text, voice, region } = req.body;
5757 if (!text || !voice || !region) {
5858 console.errorwarn('Missing required parameters');
5959 return res.sendStatus(400);
6060 }
6161
@@ -75,7 +75,7 @@ router.post('/generate', jsonParser, async (req, res) => {
7575 });
7676
7777 if (!response.ok) {
7878 console.errorwarn('Azure Request failed', response.status, response.statusText);
7979 return res.sendStatus(500);
8080 }
8181
src/endpoints/backends/chat-completions.js+48 -47
@@ -114,7 +114,7 @@ async function sendClaudeRequest(request, response) {
114114 }
115115
116116 if (!apiKey) {
117117 console.logwarn(color.red(`Claude API key is missing.\n${divider}`));
118118 return response.status(400).send({ error: true });
119119 }
120120
@@ -230,12 +230,12 @@ async function sendScaleRequest(request, response) {
230230 const apiKey = readSecret(request.user.directories, SECRET_KEYS.SCALE);
231231
232232 if (!apiKey) {
233233 console.logwarn('Scale API key is missing.');
234234 return response.status(400).send({ error: true });
235235 }
236236
237237 const requestPrompt = convertTextCompletionPrompt(request.body.messages);
238238 console.logdebug('Scale request:', requestPrompt);
239239
240240 try {
241241 const controller = new AbortController();
@@ -254,18 +254,18 @@ async function sendScaleRequest(request, response) {
254254 });
255255
256256 if (!generateResponse.ok) {
257257 console.logwarn(`Scale API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
258258 return response.status(500).send({ error: true });
259259 }
260260
261261 /** @type {any} */
262262 const generateResponseJson = await generateResponse.json();
263263 console.logdebug('Scale response:', generateResponseJson);
264264
265265 const reply = { choices: [{ 'message': { 'content': generateResponseJson.output } }] };
266266 return response.send(reply);
267267 } catch (error) {
268268 console.logerror(error);
269269 if (!response.headersSent) {
270270 return response.status(500).send({ error: true });
271271 }
@@ -282,7 +282,7 @@ async function sendMakerSuiteRequest(request, response) {
282282 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
283283
284284 if (!request.body.reverse_proxy && !apiKey) {
285285 console.logwarn('Google AI Studio API key is missing.');
286286 return response.status(400).send({ error: true });
287287 }
288288
@@ -340,7 +340,7 @@ async function sendMakerSuiteRequest(request, response) {
340340 }
341341
342342 const body = getGeminiBody();
343343 console.logdebug('Google AI Studio request:', body);
344344
345345 try {
346346 const controller = new AbortController();
@@ -366,14 +366,14 @@ async function sendMakerSuiteRequest(request, response) {
366366 // Pipe remote SSE stream to Express response
367367 forwardFetchResponse(generateResponse, response);
368368 } catch (error) {
369369 console.logerror('Error forwarding streaming response:', error);
370370 if (!response.headersSent) {
371371 return response.status(500).send({ error: true });
372372 }
373373 }
374374 } else {
375375 if (!generateResponse.ok) {
376376 console.logwarn(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
377377 return response.status(generateResponse.status).send({ error: true });
378378 }
379379
@@ -391,12 +391,12 @@ async function sendMakerSuiteRequest(request, response) {
391391 }
392392
393393 const responseContent = candidates[0].content ?? candidates[0].output;
394394 console.logwarn('Google AI Studio response:', responseContent);
395395
396396 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n');
397397 if (!responseText) {
398398 let message = 'Google AI Studio Candidate text empty';
399399 console.logwarn(message, generateResponseJson);
400400 return response.send({ error: { message } });
401401 }
402402
@@ -405,7 +405,7 @@ async function sendMakerSuiteRequest(request, response) {
405405 return response.send(reply);
406406 }
407407 } catch (error) {
408408 console.logerror('Error communicating with Google AI Studio API: ', error);
409409 if (!response.headersSent) {
410410 return response.status(500).send({ error: true });
411411 }
@@ -419,8 +419,9 @@ async function sendMakerSuiteRequest(request, response) {
419419 */
420420async function sendAI21Request(request, response) {
421421 if (!request.body) return response.sendStatus(400);
422+
422423 const controller = new AbortController();
423424 console.logdebug(request.body.messages);
424425 request.socket.removeAllListeners('close');
425426 request.socket.on('close', function () {
426427 controller.abort();
@@ -446,7 +447,7 @@ async function sendAI21Request(request, response) {
446447 signal: controller.signal,
447448 };
448449
449450 console.logdebug('AI21 request:', body);
450451
451452 try {
452453 const generateResponse = await fetch(API_AI21 + '/chat/completions', options);
@@ -455,16 +456,16 @@ async function sendAI21Request(request, response) {
455456 } else {
456457 if (!generateResponse.ok) {
457458 const errorText = await generateResponse.text();
458459 console.logwarn(`AI21 API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
459460 const errorJson = tryParse(errorText) ?? { error: true };
460461 return response.status(500).send(errorJson);
461462 }
462463 const generateResponseJson = await generateResponse.json();
463464 console.logdebug('AI21 response:', generateResponseJson);
464465 return response.send(generateResponseJson);
465466 }
466467 } catch (error) {
467468 console.logerror('Error communicating with AI21 API: ', error);
468469 if (!response.headersSent) {
469470 response.send({ error: true });
470471 } else {
@@ -483,7 +484,7 @@ async function sendMistralAIRequest(request, response) {
483484 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MISTRALAI);
484485
485486 if (!apiKey) {
486487 console.logwarn('MistralAI API key is missing.');
487488 return response.status(400).send({ error: true });
488489 }
489490
@@ -524,7 +525,7 @@ async function sendMistralAIRequest(request, response) {
524525 timeout: 0,
525526 };
526527
527528 console.logdebug('MisralAI request:', requestBody);
528529
529530 const generateResponse = await fetch(apiUrl + '/chat/completions', config);
530531 if (request.body.stream) {
@@ -532,16 +533,16 @@ async function sendMistralAIRequest(request, response) {
532533 } else {
533534 if (!generateResponse.ok) {
534535 const errorText = await generateResponse.text();
535536 console.logwarn(`MistralAI API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
536537 const errorJson = tryParse(errorText) ?? { error: true };
537538 return response.status(500).send(errorJson);
538539 }
539540 const generateResponseJson = await generateResponse.json();
540541 console.logdebug('MistralAI response:', generateResponseJson);
541542 return response.send(generateResponseJson);
542543 }
543544 } catch (error) {
544545 console.logerror('Error communicating with MistralAI API: ', error);
545546 if (!response.headersSent) {
546547 response.send({ error: true });
547548 } else {
@@ -564,7 +565,7 @@ async function sendCohereRequest(request, response) {
564565 });
565566
566567 if (!apiKey) {
567568 console.logwarn('Cohere API key is missing.');
568569 return response.status(400).send({ error: true });
569570 }
570571
@@ -603,7 +604,7 @@ async function sendCohereRequest(request, response) {
603604 requestBody.safety_mode = 'OFF';
604605 }
605606
606607 console.logdebug('Cohere request:', requestBody);
607608
608609 const config = {
609610 method: 'POST',
@@ -625,16 +626,16 @@ async function sendCohereRequest(request, response) {
625626 const generateResponse = await fetch(apiUrl, config);
626627 if (!generateResponse.ok) {
627628 const errorText = await generateResponse.text();
628629 console.logwarn(`Cohere API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
629630 const errorJson = tryParse(errorText) ?? { error: true };
630631 return response.status(500).send(errorJson);
631632 }
632633 const generateResponseJson = await generateResponse.json();
633634 console.logdebug('Cohere response:', generateResponseJson);
634635 return response.send(generateResponseJson);
635636 }
636637 } catch (error) {
637638 console.logerror('Error communicating with Cohere API: ', error);
638639 if (!response.headersSent) {
639640 response.send({ error: true });
640641 } else {
@@ -653,7 +654,7 @@ async function sendDeepSeekRequest(request, response) {
653654 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
654655
655656 if (!apiKey && !request.body.reverse_proxy) {
656657 console.logwarn('DeepSeek API key is missing.');
657658 return response.status(400).send({ error: true });
658659 }
659660
@@ -774,12 +775,12 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
774775 api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
775776 headers = {};
776777 } else {
777778 console.logwarn('This chat completion source is not supported yet.');
778779 return response_getstatus_openai.status(400).send({ error: true });
779780 }
780781
781782 if (!api_key_openai && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
782783 console.logwarn('Chat Completion API key is missing.');
783784 return response_getstatus_openai.status(400).send({ error: true });
784785 }
785786
@@ -814,23 +815,23 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
814815 };
815816 });
816817
817818 console.loginfo('Available OpenRouter models:', models);
818819 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) {
819820 const models = data?.data;
820821 console.loginfo(models);
821822 } else {
822823 const models = data?.data;
823824
824825 if (Array.isArray(models)) {
825826 const modelIds = models.filter(x => x && typeof x === 'object').map(x => x.id).sort();
826827 console.loginfo('Available models:', modelIds);
827828 } else {
828829 console.logwarn('Chat Completion endpoint did not return a list of models.');
829830 }
830831 }
831832 }
832833 else {
833834 console.logerror('Chat Completion status check failed. Either Access Token is incorrect or API endpoint is down.');
834835 response_getstatus_openai.send({ error: true, can_bypass: true, data: { data: [] } });
835836 }
836837 } catch (e) {
@@ -863,7 +864,7 @@ router.post('/bias', jsonParser, async function (request, response) {
863864 const tokenizer = getSentencepiceTokenizer(model);
864865 const instance = await tokenizer?.get();
865866 if (!instance) {
866867 console.warnerror('Tokenizer not initialized:', model);
867868 return response.send({});
868869 }
869870 encodeFunction = (text) => new Uint32Array(instance.encodeIds(text));
@@ -1025,7 +1026,7 @@ router.post('/generate', jsonParser, function (request, response) {
10251026 mergeObjectWithYaml(headers, request.body.custom_include_headers);
10261027
10271028 if (request.body.custom_prompt_post_processing) {
10281029 console.loginfo('Applying custom prompt post-processing of type', request.body.custom_prompt_post_processing);
10291030 request.body.messages = postProcessPrompt(
10301031 request.body.messages,
10311032 request.body.custom_prompt_post_processing,
@@ -1058,12 +1059,12 @@ router.post('/generate', jsonParser, function (request, response) {
10581059 headers = {};
10591060 bodyParams = {};
10601061 } else {
10611062 console.logwarn('This chat completion source is not supported yet.');
10621063 return response.status(400).send({ error: true });
10631064 }
10641065
10651066 if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
10661067 console.logwarn('OpenAI API key is missing.');
10671068 return response.status(400).send({ error: true });
10681069 }
10691070
@@ -1123,7 +1124,7 @@ router.post('/generate', jsonParser, function (request, response) {
11231124 signal: controller.signal,
11241125 };
11251126
11261127 console.logdebug(requestBody);
11271128
11281129 makeRequest(config, response, request);
11291130
@@ -1140,7 +1141,7 @@ router.post('/generate', jsonParser, function (request, response) {
11401141 const fetchResponse = await fetch(endpointUrl, config);
11411142
11421143 if (request.body.stream) {
11431144 console.loginfo('Streaming request in progress');
11441145 forwardFetchResponse(fetchResponse, response);
11451146 return;
11461147 }
@@ -1149,10 +1150,10 @@ router.post('/generate', jsonParser, function (request, response) {
11491150 /** @type {any} */
11501151 let json = await fetchResponse.json();
11511152 response.send(json);
11521153 console.logdebug(json);
11531154 console.logdebug(json?.choices?.[0]?.message);
11541155 } else if (fetchResponse.status === 429 && retries > 0) {
11551156 console.logwarn(`Out of quota, retrying in ${Math.round(timeout / 1000)}s`);
11561157 setTimeout(() => {
11571158 timeout *= 2;
11581159 makeRequest(config, response, request, retries - 1, timeout);
@@ -1161,7 +1162,7 @@ router.post('/generate', jsonParser, function (request, response) {
11611162 await handleErrorResponse(fetchResponse);
11621163 }
11631164 } catch (error) {
11641165 console.logerror('Generation failed', error);
11651166 const message = error.code === 'ECONNREFUSED'
11661167 ? `Connection refused: ${error.message}`
11671168 : error.message || 'Unknown error occurred';
@@ -1183,7 +1184,7 @@ router.post('/generate', jsonParser, function (request, response) {
11831184
11841185 const message = errorResponse.statusText || 'Unknown error occurred';
11851186 const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota';
11861187 console.logerror('Chat completion request error: ', message, responseText);
11871188
11881189 if (!response.headersSent) {
11891190 response.send({ error: { message }, quota_error: quota_error });
src/endpoints/backends/kobold.js+14 -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.logwarn(`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,16 @@ 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.logdebug('Transcribing audio with KoboldCpp', server);
206206
207207 const fileBase64 = fs.readFileSync(request.file.path).toString('base64');
208208 fs.rmSync(request.file.path);
@@ -226,12 +226,12 @@ router.post('/transcribe-audio', urlencodedParser, async function (request, resp
226226
227227 if (!result.ok) {
228228 const text = await result.text();
229229 console.logerror('KoboldCpp request failed', result.statusText, text);
230230 return response.status(500).send(text);
231231 }
232232
233233 const data = await result.json();
234234 console.logdebug('KoboldCpp transcription response', data);
235235 return response.json(data);
236236 } catch (error) {
237237 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+39 -45
@@ -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,7 @@ 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.logdebug('Trying to connect to API:', request.body);
105105 const baseUrl = trimV1(request.body.api_server);
106106
107107 const args = {
@@ -123,6 +123,7 @@ router.post('/status', jsonParser, async function (request, response) {
123123 case TEXTGEN_TYPES.LLAMACPP:
124124 case TEXTGEN_TYPES.INFERMATICAI:
125125 case TEXTGEN_TYPES.OPENROUTER:
126+ case TEXTGEN_TYPES.FEATHERLESS:
126127 url += '/v1/models';
127128 break;
128129 case TEXTGEN_TYPES.DREAMGEN:
@@ -140,9 +141,6 @@ router.post('/status', jsonParser, async function (request, response) {
140141 case TEXTGEN_TYPES.OLLAMA:
141142 url += '/api/tags';
142143 break;
143- case TEXTGEN_TYPES.FEATHERLESS:
144- url += '/v1/models';
145- break;
146144 case TEXTGEN_TYPES.HUGGINGFACE:
147145 url += '/info';
148146 break;
@@ -152,7 +150,7 @@ router.post('/status', jsonParser, async function (request, response) {
152150 const isPossiblyLmStudio = modelsReply.headers.get('x-powered-by') === 'Express';
153151
154152 if (!modelsReply.ok) {
155153 console.logerror('Models endpoint is offline.');
156154 return response.sendStatus(400);
157155 }
158156
@@ -173,12 +171,12 @@ router.post('/status', jsonParser, async function (request, response) {
173171 }
174172
175173 if (!Array.isArray(data.data)) {
176174 console.logerror('Models response is not an array.');
177175 return response.sendStatus(400);
178176 }
179177
180178 const modelIds = data.data.map(x => x.id);
181179 console.loginfo('Models available:', modelIds);
182180
183181 // Set result to the first model ID
184182 result = modelIds[0] || 'Valid';
@@ -191,7 +189,7 @@ router.post('/status', jsonParser, async function (request, response) {
191189 if (modelInfoReply.ok) {
192190 /** @type {any} */
193191 const modelInfo = await modelInfoReply.json();
194192 console.logdebug('Ooba model info:', modelInfo);
195193
196194 const modelName = modelInfo?.model_name;
197195 result = modelName || result;
@@ -208,7 +206,7 @@ router.post('/status', jsonParser, async function (request, response) {
208206 if (modelInfoReply.ok) {
209207 /** @type {any} */
210208 const modelInfo = await modelInfoReply.json();
211209 console.logdebug('Tabby model info:', modelInfo);
212210
213211 const modelName = modelInfo?.id;
214212 result = modelName || result;
@@ -255,7 +253,7 @@ router.post('/props', jsonParser, async function (request, response) {
255253 props['chat_template'] = props['chat_template'].slice(0, -1) + '\n';
256254 }
257255 props['chat_template_hash'] = createHash('sha256').update(props['chat_template']).digest('hex');
258256 console.logdebug(`Model properties: ${JSON.stringify(props)}`);
259257 return response.send(props);
260258 } catch (error) {
261259 console.error(error);
@@ -273,7 +271,7 @@ router.post('/generate', jsonParser, async function (request, response) {
273271
274272 const apiType = request.body.api_type;
275273 const baseUrl = request.body.api_server;
276274 console.logdebug(request.body);
277275
278276 const controller = new AbortController();
279277 request.socket.removeAllListeners('close');
@@ -399,7 +397,7 @@ router.post('/generate', jsonParser, async function (request, response) {
399397 if (completionsReply.ok) {
400398 /** @type {any} */
401399 const data = await completionsReply.json();
402400 console.logdebug('Endpoint response:', data);
403401
404402 // Map InfermaticAI response to OAI completions format
405403 if (apiType === TEXTGEN_TYPES.INFERMATICAI) {
@@ -411,24 +409,20 @@ router.post('/generate', jsonParser, async function (request, response) {
411409 const text = await completionsReply.text();
412410 const errorBody = { error: true, status: completionsReply.status, response: text };
413411
414412 ifreturn (!response.headersSent) {
415413 return? response.send(errorBody);
416- }
414+ : response.end();
417-
418- return response.end();
419415 }
420416 }
421417 } catch (error) {
422418 const status = error?.status ?? error?.code ?? 'UNKNOWN';
423419 const text = error?.error ?? error?.statusText ?? error?.message ?? 'Unknown error on /generate endpoint';
424420 let value = { error: true, status: status, response: text };
425421 console.logerror('Endpoint error:', error);
426-
427- if (!response.headersSent) {
428- return response.send(value);
429- }
430422
431423 return !response.end();headersSent
424+ ? response.send(value)
425+ : response.end();
432426 }
433427});
434428
@@ -451,7 +445,7 @@ ollama.post('/download', jsonParser, async function (request, response) {
451445 });
452446
453447 if (!fetchResponse.ok) {
454448 console.logerror('Download error:', fetchResponse.status, fetchResponse.statusText);
455449 return response.status(fetchResponse.status).send({ error: true });
456450 }
457451
@@ -468,7 +462,7 @@ ollama.post('/caption-image', jsonParser, async function (request, response) {
468462 return response.sendStatus(400);
469463 }
470464
471465 console.logdebug('Ollama caption request:', request.body);
472466 const baseUrl = trimV1(request.body.server_url);
473467
474468 const fetchResponse = await fetch(`${baseUrl}/api/generate`, {
@@ -483,18 +477,18 @@ ollama.post('/caption-image', jsonParser, async function (request, response) {
483477 });
484478
485479 if (!fetchResponse.ok) {
486480 console.logerror('Ollama caption error:', fetchResponse.status, fetchResponse.statusText);
487481 return response.status(500).send({ error: true });
488482 }
489483
490484 /** @type {any} */
491485 const data = await fetchResponse.json();
492486 console.logdebug('Ollama caption response:', data);
493487
494488 const caption = data?.response || '';
495489
496490 if (!caption) {
497491 console.logerror('Ollama caption is empty.');
498492 return response.status(500).send({ error: true });
499493 }
500494
@@ -513,7 +507,7 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) {
513507 return response.sendStatus(400);
514508 }
515509
516510 console.logdebug('LlamaCpp caption request:', request.body);
517511 const baseUrl = trimV1(request.body.server_url);
518512
519513 const fetchResponse = await fetch(`${baseUrl}/completion`, {
@@ -529,18 +523,18 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) {
529523 });
530524
531525 if (!fetchResponse.ok) {
532526 console.logerror('LlamaCpp caption error:', fetchResponse.status, fetchResponse.statusText);
533527 return response.status(500).send({ error: true });
534528 }
535529
536530 /** @type {any} */
537531 const data = await fetchResponse.json();
538532 console.logdebug('LlamaCpp caption response:', data);
539533
540534 const caption = data?.content || '';
541535
542536 if (!caption) {
543537 console.logerror('LlamaCpp caption is empty.');
544538 return response.status(500).send({ error: true });
545539 }
546540
@@ -558,7 +552,7 @@ llamacpp.post('/props', jsonParser, async function (request, response) {
558552 return response.sendStatus(400);
559553 }
560554
561555 console.logdebug('LlamaCpp props request:', request.body);
562556 const baseUrl = trimV1(request.body.server_url);
563557
564558 const fetchResponse = await fetch(`${baseUrl}/props`, {
@@ -566,12 +560,12 @@ llamacpp.post('/props', jsonParser, async function (request, response) {
566560 });
567561
568562 if (!fetchResponse.ok) {
569563 console.logerror('LlamaCpp props error:', fetchResponse.status, fetchResponse.statusText);
570564 return response.status(500).send({ error: true });
571565 }
572566
573567 const data = await fetchResponse.json();
574568 console.logdebug('LlamaCpp props response:', data);
575569
576570 return response.send(data);
577571
@@ -590,7 +584,7 @@ llamacpp.post('/slots', jsonParser, async function (request, response) {
590584 return response.sendStatus(400);
591585 }
592586
593587 console.logdebug('LlamaCpp slots request:', request.body);
594588 const baseUrl = trimV1(request.body.server_url);
595589
596590 let fetchResponse;
@@ -616,12 +610,12 @@ llamacpp.post('/slots', jsonParser, async function (request, response) {
616610 }
617611
618612 if (!fetchResponse.ok) {
619613 console.logerror('LlamaCpp slots error:', fetchResponse.status, fetchResponse.statusText);
620614 return response.status(500).send({ error: true });
621615 }
622616
623617 const data = await fetchResponse.json();
624618 console.logdebug('LlamaCpp slots response:', data);
625619
626620 return response.send(data);
627621
@@ -659,14 +653,14 @@ tabby.post('/download', jsonParser, async function (request, response) {
659653 return response.status(403).send({ error: true });
660654 }
661655 } else {
662656 console.logerror('API Permission error:', permissionResponse.status, permissionResponse.statusText);
663657 return response.status(permissionResponse.status).send({ error: true });
664658 }
665659
666660 const fetchResponse = await fetch(`${baseUrl}/v1/download`, args);
667661
668662 if (!fetchResponse.ok) {
669663 console.logerror('Download error:', fetchResponse.status, fetchResponse.statusText);
670664 return response.status(fetchResponse.status).send({ error: true });
671665 }
672666
src/endpoints/backgrounds.js+3 -3
@@ -27,7 +27,7 @@ router.post('/delete', jsonParser, getFileNameValidationFunction('bg'), function
2727 const fileName = path.join(request.user.directories.backgrounds, sanitize(request.body.bg));
2828
2929 if (!fs.existsSync(fileName)) {
3030 console.logerror('BG file not found');
3131 return response.sendStatus(400);
3232 }
3333
@@ -43,12 +43,12 @@ router.post('/rename', jsonParser, function (request, response) {
4343 const newFileName = path.join(request.user.directories.backgrounds, sanitize(request.body.new_bg));
4444
4545 if (!fs.existsSync(oldFileName)) {
4646 console.logerror('BG file not found');
4747 return response.sendStatus(400);
4848 }
4949
5050 if (fs.existsSync(newFileName)) {
5151 console.logerror('New BG file already exists');
5252 return response.sendStatus(400);
5353 }
5454
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.logwarn('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.loginfo('Image caption:', text);
2424
2525 return res.json({ caption: text });
2626 } catch (error) {
src/endpoints/characters.js+34 -35
@@ -97,7 +97,7 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
9797 writeFileAtomicSync(outputImagePath, outputImage);
9898 return true;
9999 } catch (err) {
100100 console.logerror(err);
101101 return false;
102102 }
103103}
@@ -166,7 +166,7 @@ async function tryReadImage(imgPath, crop) {
166166 }
167167 // If it's an unsupported type of image (APNG) - just read the file as buffer
168168 catch (error) {
169169 console.logerror(`Failed to read image: ${imgPath}`, error);
170170 return fs.readFileSync(imgPath);
171171 }
172172}
@@ -229,12 +229,12 @@ const processCharacter = async (item, directories) => {
229229 return character;
230230 }
231231 catch (err) {
232232 console.logerror(`Could not process character: ${item}`);
233233
234234 if (err instanceof SyntaxError) {
235235 console.logerror(`${item} does not contain a valid JSON object.`);
236236 } else {
237237 console.logerror('An unexpected error occurred: ', err);
238238 }
239239
240240 return {
@@ -322,7 +322,7 @@ function readFromV2(char) {
322322 };
323323
324324 _.forEach(fieldMappings, (v2Path, charField) => {
325325 //console.loginfo(`Migrating field: ${charField} from ${v2Path}`);
326326 const v2Value = _.get(char.data, v2Path);
327327 if (_.isUndefined(v2Value)) {
328328 let defaultValue = undefined;
@@ -337,15 +337,15 @@ function readFromV2(char) {
337337 }
338338
339339 if (!_.isUndefined(defaultValue)) {
340340 //console.debugwarn(`Spec v2 extension data missing for field: ${charField}, using default value: ${defaultValue}`);
341341 char[charField] = defaultValue;
342342 } else {
343343 console.debugwarn(`Char ${char['name']} has Spec v2 data missing for unknown field: ${charField}`);
344344 return;
345345 }
346346 }
347347 if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) {
348348 console.debugwarn(`Char ${char['name']} has Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value);
349349 }
350350 char[charField] = v2Value;
351351 });
@@ -442,7 +442,7 @@ function charaFormatData(data, directories) {
442442 }
443443
444444 } catch {
445445 console.debugwarn(`Failed to read world info file: ${data.world}. Character book will not be available.`);
446446 }
447447 }
448448
@@ -452,7 +452,7 @@ function charaFormatData(data, directories) {
452452 // Deep merge the extensions object
453453 _.set(char, 'data.extensions', deepMerge(char.data.extensions, extensions));
454454 } catch {
455455 console.debugwarn(`Failed to parse extensions JSON: ${data.extensions}`);
456456 }
457457 }
458458
@@ -526,7 +526,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) {
526526 const fileText = fs.readFileSync(uploadPath, 'utf8');
527527 fs.rmSync(uploadPath);
528528 const yamlData = yaml.parse(fileText);
529529 console.loginfo('Importing from YAML');
530530 yamlData.name = sanitize(yamlData.name);
531531 const fileName = preservedFileName || getPngName(yamlData.name, context.request.user.directories);
532532 let char = convertToV2({
@@ -559,7 +559,7 @@ async function importFromYaml(uploadPath, context, preservedFileName) {
559559async function importFromCharX(uploadPath, { request }, preservedFileName) {
560560 const data = fs.readFileSync(uploadPath).buffer;
561561 fs.rmSync(uploadPath);
562562 console.loginfo('Importing from CharX');
563563 const cardBuffer = await extractFileFromZipBuffer(data, 'card.json');
564564
565565 if (!cardBuffer) {
@@ -608,7 +608,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
608608 let jsonData = JSON.parse(data);
609609
610610 if (jsonData.spec !== undefined) {
611611 console.loginfo(`Importing from ${jsonData.spec} json`);
612612 importRisuSprites(request.user.directories, jsonData);
613613 unsetFavFlag(jsonData);
614614 jsonData = readFromV2(jsonData);
@@ -618,7 +618,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
618618 const result = await writeCharacterData(defaultAvatarPath, char, pngName, request);
619619 return result ? pngName : '';
620620 } else if (jsonData.name !== undefined) {
621621 console.loginfo('Importing from v1 json');
622622 jsonData.name = sanitize(jsonData.name);
623623 if (jsonData.creator_notes) {
624624 jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
@@ -644,7 +644,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
644644 const result = await writeCharacterData(defaultAvatarPath, charJSON, pngName, request);
645645 return result ? pngName : '';
646646 } else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
647647 console.loginfo('Importing from gradio json');
648648 jsonData.char_name = sanitize(jsonData.char_name);
649649 if (jsonData.creator_notes) {
650650 jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
@@ -691,7 +691,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) {
691691 const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories);
692692
693693 if (jsonData.spec !== undefined) {
694694 console.loginfo(`Found a ${jsonData.spec} character file.`);
695695 importRisuSprites(request.user.directories, jsonData);
696696 unsetFavFlag(jsonData);
697697 jsonData = readFromV2(jsonData);
@@ -701,7 +701,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) {
701701 fs.unlinkSync(uploadPath);
702702 return result ? pngName : '';
703703 } else if (jsonData.name !== undefined) {
704704 console.loginfo('Found a v1 character file.');
705705
706706 if (jsonData.creator_notes) {
707707 jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
@@ -812,13 +812,13 @@ router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (
812812
813813router.post('/edit', urlencodedParser, validateAvatarUrlMiddleware, async function (request, response) {
814814 if (!request.body) {
815815 console.errorwarn('Error: no response body detected');
816816 response.status(400).send('Error: no response body detected');
817817 return;
818818 }
819819
820820 if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') {
821821 console.errorwarn('Error: invalid name.');
822822 response.status(400).send('Error: invalid name.');
823823 return;
824824 }
@@ -860,14 +860,14 @@ router.post('/edit', urlencodedParser, validateAvatarUrlMiddleware, async functi
860860 * @returns {void}
861861 */
862862router.post('/edit-attribute', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
863863 console.logdebug(request.body);
864864 if (!request.body) {
865865 console.errorwarn('Error: no response body detected');
866866 return response.status(400).send('Error: no response body detected');
867867 }
868868
869869 if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') {
870870 console.errorwarn('Error: invalid name.');
871871 return response.status(400).send('Error: invalid name.');
872872 }
873873
@@ -879,7 +879,7 @@ router.post('/edit-attribute', jsonParser, validateAvatarUrlMiddleware, async fu
879879 const char = JSON.parse(charJSON);
880880 //check if the field exists
881881 if (char[request.body.field] === undefined && char.data[request.body.field] === undefined) {
882882 console.errorwarn('Error: invalid field.');
883883 response.status(400).send('Error: invalid field.');
884884 return;
885885 }
@@ -928,7 +928,7 @@ router.post('/merge-attributes', jsonParser, getFileNameValidationFunction('avat
928928 await writeCharacterData(avatarPath, JSON.stringify(character), targetImg, request);
929929 response.sendStatus(200);
930930 } else {
931931 console.logwarn(validator.lastValidationError);
932932 response.status(400).send({ message: `Validation failed for ${character.name}`, error: validator.lastValidationError });
933933 }
934934 } catch (exception) {
@@ -1050,7 +1050,7 @@ router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (r
10501050 const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`;
10511051
10521052 if (stats.size === 0) {
10531053 console.logwarn(`Found an empty chat file: ${pathToFile}`);
10541054 res({});
10551055 return;
10561056 }
@@ -1082,7 +1082,7 @@ router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (r
10821082
10831083 res(chatData);
10841084 } else {
10851085 console.logwarn('Found an invalid or corrupted chat file:', pathToFile);
10861086 res({});
10871087 }
10881088 }
@@ -1095,7 +1095,7 @@ router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (r
10951095
10961096 return response.send(validFiles);
10971097 } catch (error) {
10981098 console.logerror(error);
10991099 return response.send({ error: true });
11001100 }
11011101});
@@ -1152,7 +1152,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
11521152 const fileName = await importFunction(uploadPath, { request, response }, preservedFileName);
11531153
11541154 if (!fileName) {
11551155 console.errorwarn('Failed to import character');
11561156 return response.sendStatus(400);
11571157 }
11581158
@@ -1162,7 +1162,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
11621162
11631163 response.send({ file_name: fileName });
11641164 } catch (err) {
11651165 console.logerror(err);
11661166 response.send({ error: true });
11671167 }
11681168});
@@ -1170,14 +1170,13 @@ router.post('/import', urlencodedParser, async function (request, response) {
11701170router.post('/duplicate', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
11711171 try {
11721172 if (!request.body.avatar_url) {
11731173 console.logwarn('avatar URL not found in request body');
11741174 console.logdebug(request.body);
11751175 return response.sendStatus(400);
11761176 }
11771177 let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url));
11781178 if (!fs.existsSync(filename)) {
11791179 console.logerror('file for dupe not found', filename);
1180- console.log(filename);
11811180 return response.sendStatus(404);
11821181 }
11831182 let suffix = 1;
@@ -1205,7 +1204,7 @@ router.post('/duplicate', jsonParser, validateAvatarUrlMiddleware, async functio
12051204 }
12061205
12071206 fs.copyFileSync(filename, newFilename);
12081207 console.loginfo(`${filename} was copied to ${newFilename}`);
12091208 response.send({ path: path.parse(newFilename).base });
12101209 }
12111210 catch (error) {
src/endpoints/chats.js+17 -18
@@ -50,7 +50,7 @@ function backupChat(directory, name, chat) {
5050
5151 removeOldBackups(directory, 'chat_', maxTotalChatBackups);
5252 } catch (err) {
5353 console.logerror(`Could not backup chat for ${name}`, err);
5454 }
5555}
5656
@@ -306,8 +306,8 @@ router.post('/save', jsonParser, validateAvatarUrlMiddleware, function (request,
306306 getBackupFunction(request.user.profile.handle)(request.user.directories.backups, directoryName, jsonlData);
307307 return response.send({ result: 'ok' });
308308 } catch (error) {
309309 responseconsole.senderror(error);
310310 return consoleresponse.logsend(error);
311311 }
312312});
313313
@@ -359,17 +359,17 @@ router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (
359359 const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file));
360360 const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file));
361361 const sanitizedFileName = path.parse(pathToRenamedFile).name;
362362 console.loginfo('Old chat name', pathToOriginalFile);
363363 console.loginfo('New chat name', pathToRenamedFile);
364364
365365 if (!fs.existsSync(pathToOriginalFile) || fs.existsSync(pathToRenamedFile)) {
366366 console.logerror('Either Source or Destination files are not available');
367367 return response.status(400).send({ error: true });
368368 }
369369
370370 fs.copyFileSync(pathToOriginalFile, pathToRenamedFile);
371371 fs.rmSync(pathToOriginalFile);
372372 console.loginfo('Successfully renamed.');
373373 return response.send({ ok: true, sanitizedFileName });
374374});
375375
@@ -380,12 +380,12 @@ router.post('/delete', jsonParser, validateAvatarUrlMiddleware, function (reques
380380 const chatFileExists = fs.existsSync(filePath);
381381
382382 if (!chatFileExists) {
383383 console.logerror(`Chat file not found '${filePath}'`);
384384 return response.sendStatus(400);
385385 }
386386
387387 fs.rmSync(filePath);
388388 console.loginfo('`Deleted chat file: ' + ${filePath}`);
389389 return response.send('ok');
390390});
391391
@@ -402,7 +402,7 @@ router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (
402402 const errorMessage = {
403403 message: `Could not find JSONL file to export. Source chat file: ${filename}.`,
404404 };
405405 console.logerror(errorMessage.message);
406406 return response.status(404).json(errorMessage);
407407 }
408408 try {
@@ -415,14 +415,14 @@ router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (
415415 result: rawFile,
416416 };
417417
418418 console.loginfo(`Chat exported as ${exportfilename}`);
419419 return response.status(200).json(successMessage);
420420 } catch (err) {
421421 console.error(err);
422422 const errorMessage = {
423423 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,
424424 };
425425 console.logerror(errorMessage.message);
426426 return response.status(500).json(errorMessage);
427427 }
428428 }
@@ -449,12 +449,11 @@ router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (
449449 message: `Chat saved to ${exportfilename}`,
450450 result: buffer,
451451 };
452452 console.loginfo(`Chat exported as ${exportfilename}`);
453453 return response.status(200).json(successMessage);
454454 });
455455 } catch (err) {
456456 console.logerror('chat export failed.', err);
457- console.log(err);
458457 return response.sendStatus(400);
459458 }
460459});
@@ -513,7 +512,7 @@ router.post('/import', urlencodedParser, validateAvatarUrlMiddleware, function (
513512 } else if (jsonData.type === 'risuChat') { // RisuAI format
514513 importFunc = importRisuChat;
515514 } else { // Unknown format
516515 console.logerror('Incorrect chat format .json');
517516 return response.send({ error: true });
518517 }
519518
@@ -541,7 +540,7 @@ router.post('/import', urlencodedParser, validateAvatarUrlMiddleware, function (
541540 const jsonData = JSON.parse(header);
542541
543542 if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) {
544543 console.logerror('Incorrect chat format .jsonl');
545544 return response.send({ error: true });
546545 }
547546
@@ -647,7 +646,7 @@ router.post('/search', jsonParser, validateAvatarUrlMiddleware, function (reques
647646 break;
648647 }
649648 } catch (error) {
650649 console.errorwarn(groupFile, 'group file is corrupted:', error);
651650 }
652651 }
653652
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.loginfo('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+15 -15
@@ -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
@@ -360,16 +360,16 @@ router.post('/generate-image', jsonParser, async (request, response) => {
360360 { token: api_key_horde });
361361
362362 if (!generation.id) {
363363 console.errorwarn('Image generation request is not satisfyable:', generation.message || 'unknown error');
364364 return response.sendStatus(400);
365365 }
366366
367367 console.loginfo('Horde image generation request:', generation);
368368
369369 const controller = new AbortController();
370370 request.socket.removeAllListeners('close');
371371 request.socket.on('close', function () {
372372 console.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn(`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.loginfo('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.logwarn('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.logwarn('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.logwarn('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.logdebug('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.logwarn('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.loginfo('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.logwarn('No OpenAI key found');
188188 return response.sendStatus(400);
189189 }
190190
191191 if (!request.file) {
192192 console.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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
@@ -96,25 +96,25 @@ router.post('/serpapi', jsonParser, async (request, response) => {
9696 const key = readSecret(request.user.directories, SECRET_KEYS.SERPAPI);
9797
9898 if (!key) {
9999 console.logerror('No SerpApi key found');
100100 return response.sendStatus(400);
101101 }
102102
103103 const { query } = request.body;
104104 const result = await fetch(`https://serpapi.com/search.json?q=${encodeURIComponent(query)}&api_key=${key}`);
105105
106106 console.logdebug('SerpApi query', query);
107107
108108 if (!result.ok) {
109109 const text = await result.text();
110110 console.logerror('SerpApi request failed', result.statusText, text);
111111 return response.status(500).send(text);
112112 }
113113
114114 const data = await result.json();
115115 return response.json(data);
116116 } catch (error) {
117117 console.logerror(error);
118118 return response.sendStatus(500);
119119 }
120120});
@@ -130,7 +130,7 @@ router.post('/transcript', jsonParser, async (request, response) => {
130130 const json = request.body.json;
131131
132132 if (!id) {
133133 console.logerror('Id is required for /transcript');
134134 return response.sendStatus(400);
135135 }
136136
@@ -155,7 +155,7 @@ router.post('/transcript', jsonParser, async (request, response) => {
155155 throw error;
156156 }
157157 } catch (error) {
158158 console.logerror(error);
159159 return response.sendStatus(500);
160160 }
161161});
@@ -165,17 +165,17 @@ router.post('/searxng', jsonParser, async (request, response) => {
165165 const { baseUrl, query, preferences, categories } = request.body;
166166
167167 if (!baseUrl || !query) {
168168 console.logerror('Missing required parameters for /searxng');
169169 return response.sendStatus(400);
170170 }
171171
172172 console.logdebug('SearXNG query', baseUrl, query);
173173
174174 const mainPageUrl = new URL(baseUrl);
175175 const mainPageRequest = await fetch(mainPageUrl, { headers: visitHeaders });
176176
177177 if (!mainPageRequest.ok) {
178178 console.logerror('SearXNG request failed', mainPageRequest.statusText);
179179 return response.sendStatus(500);
180180 }
181181
@@ -202,14 +202,14 @@ router.post('/searxng', jsonParser, async (request, response) => {
202202
203203 if (!searchResult.ok) {
204204 const text = await searchResult.text();
205205 console.logerror('SearXNG request failed', searchResult.statusText, text);
206206 return response.sendStatus(500);
207207 }
208208
209209 const data = await searchResult.text();
210210 return response.send(data);
211211 } catch (error) {
212212 console.logerror('SearXNG request failed', error);
213213 return response.sendStatus(500);
214214 }
215215});
@@ -219,7 +219,7 @@ router.post('/tavily', jsonParser, async (request, response) => {
219219 const apiKey = readSecret(request.user.directories, SECRET_KEYS.TAVILY);
220220
221221 if (!apiKey) {
222222 console.logerror('No Tavily key found');
223223 return response.sendStatus(400);
224224 }
225225
@@ -246,18 +246,18 @@ router.post('/tavily', jsonParser, async (request, response) => {
246246 body: JSON.stringify(body),
247247 });
248248
249249 console.logdebug('Tavily query', query);
250250
251251 if (!result.ok) {
252252 const text = await result.text();
253253 console.logerror('Tavily request failed', result.statusText, text);
254254 return response.status(500).send(text);
255255 }
256256
257257 const data = await result.json();
258258 return response.json(data);
259259 } catch (error) {
260260 console.logerror(error);
261261 return response.sendStatus(500);
262262 }
263263});
@@ -303,7 +303,7 @@ router.post('/visit', jsonParser, async (request, response) => {
303303 const html = Boolean(request.body.html ?? true);
304304
305305 if (!url) {
306306 console.logerror('No url provided for /visit');
307307 return response.sendStatus(400);
308308 }
309309
@@ -330,16 +330,16 @@ router.post('/visit', jsonParser, async (request, response) => {
330330 throw new Error('Invalid hostname');
331331 }
332332 } catch (error) {
333333 console.logerror('Invalid url provided for /visit', url);
334334 return response.sendStatus(400);
335335 }
336336
337337 console.loginfo('Visiting web URL', url);
338338
339339 const result = await fetch(url, { headers: visitHeaders });
340340
341341 if (!result.ok) {
342342 console.logerror(`Visit failed ${result.status} ${result.statusText}`);
343343 return response.sendStatus(500);
344344 }
345345
@@ -347,7 +347,7 @@ router.post('/visit', jsonParser, async (request, response) => {
347347
348348 if (html) {
349349 if (!contentType.includes('text/html')) {
350350 console.logerror(`Visit failed, content-type is ${contentType}, expected text/html`);
351351 return response.sendStatus(500);
352352 }
353353
@@ -359,7 +359,7 @@ router.post('/visit', jsonParser, async (request, response) => {
359359 const buffer = await result.arrayBuffer();
360360 return response.send(Buffer.from(buffer));
361361 } catch (error) {
362362 console.logerror(error);
363363 return response.sendStatus(500);
364364 }
365365});
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
@@ -105,7 +105,7 @@ function readPresetsFromDirectory(directoryPath, options = {}) {
105105 fileNames.push(removeFileExtension ? item.replace(/\.[^/.]+$/, '') : item);
106106 } catch {
107107 // skip
108108 console.logwarn(`${item} is not a valid JSON`);
109109 }
110110 });
111111
@@ -120,7 +120,7 @@ async function backupSettings() {
120120 backupUserSettings(handle, true);
121121 }
122122 } catch (err) {
123123 console.logerror('Could not backup settings file', err);
124124 }
125125}
126126
@@ -202,7 +202,7 @@ router.post('/save', jsonParser, function (request, response) {
202202 triggerAutoSave(request.user.profile.handle);
203203 response.send({ result: 'ok' });
204204 } catch (err) {
205205 console.logerror(err);
206206 response.send(err);
207207 }
208208});
@@ -292,7 +292,7 @@ router.post('/get-snapshots', jsonParser, async (request, response) => {
292292
293293 response.json(result);
294294 } catch (error) {
295295 console.logerror(error);
296296 response.sendStatus(500);
297297 }
298298});
@@ -316,7 +316,7 @@ router.post('/load-snapshot', jsonParser, getFileNameValidationFunction('name'),
316316
317317 response.send(content);
318318 } catch (error) {
319319 console.logerror(error);
320320 response.sendStatus(500);
321321 }
322322});
@@ -326,7 +326,7 @@ router.post('/make-snapshot', jsonParser, async (request, response) => {
326326 backupUserSettings(request.user.profile.handle, false);
327327 response.sendStatus(204);
328328 } catch (error) {
329329 console.logerror(error);
330330 response.sendStatus(500);
331331 }
332332});
@@ -352,7 +352,7 @@ router.post('/restore-snapshot', jsonParser, getFileNameValidationFunction('name
352352
353353 response.sendStatus(204);
354354 } catch (error) {
355355 console.logerror(error);
356356 response.sendStatus(500);
357357 }
358358});
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.loginfo(`Execution duration: ${(end - start) / 1000} seconds`);
4747 console.loginfo('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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);
@@ -484,7 +484,7 @@ export function getTiktokenTokenizer(model) {
484484 }
485485
486486 const tokenizer = tiktoken.encoding_for_model(model);
487487 console.loginfo('Instantiated the tokenizer for', model);
488488 tokenizersCache[model] = tokenizer;
489489 return tokenizer;
490490}
@@ -531,7 +531,7 @@ function createSentencepieceEncodingHandler(tokenizer) {
531531 const chunks = instance?.encodePieces(text);
532532 return response.send({ ids, count, chunks });
533533 } catch (error) {
534534 console.logerror(error);
535535 return response.send({ ids: [], count: 0, chunks: [] });
536536 }
537537 };
@@ -562,7 +562,7 @@ function createSentencepieceDecodingHandler(tokenizer) {
562562 const text = chunks.join('');
563563 return response.send({ text, chunks });
564564 } catch (error) {
565565 console.logerror(error);
566566 return response.send({ text: '', chunks: [] });
567567 }
568568 };
@@ -591,7 +591,7 @@ function createTiktokenEncodingHandler(modelId) {
591591 const chunks = await getTiktokenChunks(tokenizer, tokens);
592592 return response.send({ ids: tokens, count: tokens.length, chunks });
593593 } catch (error) {
594594 console.logerror(error);
595595 return response.send({ ids: [], count: 0, chunks: [] });
596596 }
597597 };
@@ -620,7 +620,7 @@ function createTiktokenDecodingHandler(modelId) {
620620 const text = new TextDecoder().decode(textBytes);
621621 return response.send({ text });
622622 } catch (error) {
623623 console.logerror(error);
624624 return response.send({ text: '' });
625625 }
626626 };
@@ -650,7 +650,7 @@ function createWebTokenizerEncodingHandler(tokenizer) {
650650 const chunks = getWebTokenizersChunks(instance, tokens);
651651 return response.send({ ids: tokens, count: tokens.length, chunks });
652652 } catch (error) {
653653 console.logerror(error);
654654 return response.send({ ids: [], count: 0, chunks: [] });
655655 }
656656 };
@@ -681,7 +681,7 @@ function createWebTokenizerDecodingHandler(tokenizer) {
681681 const text = instance.decode(new Int32Array(ids));
682682 return response.send({ text, chunks });
683683 } catch (error) {
684684 console.logerror(error);
685685 return response.send({ text: '', chunks: [] });
686686 }
687687 };
@@ -781,7 +781,7 @@ router.post('/openai/encode', jsonParser, async function (req, res) {
781781 const handler = createTiktokenEncodingHandler(model);
782782 return handler(req, res);
783783 } catch (error) {
784784 console.logerror(error);
785785 return res.send({ ids: [], count: 0, chunks: [] });
786786 }
787787});
@@ -849,7 +849,7 @@ router.post('/openai/decode', jsonParser, async function (req, res) {
849849 const handler = createTiktokenDecodingHandler(model);
850850 return handler(req, res);
851851 } catch (error) {
852852 console.logerror(error);
853853 return res.send({ text: '' });
854854 }
855855});
@@ -988,7 +988,7 @@ router.post('/remote/kobold/count', jsonParser, async function (request, respons
988988 const result = await fetch(url, args);
989989
990990 if (!result.ok) {
991991 console.logwarn(`API returned error: ${result.status} ${result.statusText}`);
992992 return response.send({ error: true });
993993 }
994994
@@ -997,7 +997,7 @@ router.post('/remote/kobold/count', jsonParser, async function (request, respons
997997 const ids = data['ids'] ?? [];
998998 return response.send({ count, ids });
999999 } catch (error) {
10001000 console.logerror(error);
10011001 return response.send({ error: true });
10021002 }
10031003});
@@ -1052,7 +1052,7 @@ router.post('/remote/textgenerationwebui/encode', jsonParser, async function (re
10521052 const result = await fetch(url, args);
10531053
10541054 if (!result.ok) {
10551055 console.logwarn(`API returned error: ${result.status} ${result.statusText}`);
10561056 return response.send({ error: true });
10571057 }
10581058
@@ -1062,7 +1062,7 @@ router.post('/remote/textgenerationwebui/encode', jsonParser, async function (re
10621062
10631063 return response.send({ count, ids });
10641064 } catch (error) {
10651065 console.logerror(error);
10661066 return response.send({ error: true });
10671067 }
10681068});
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.logerror('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('Slugify failed: Missing required fields');
242242 return response.status(400).json({ error: 'Missing required fields' });
243243 }
244244
src/endpoints/users-private.js+22 -22
@@ -58,18 +58,18 @@ router.get('/me', async (request, response) => {
5858router.post('/change-avatar', jsonParser, async (request, response) => {
5959 try {
6060 if (!request.body.handle) {
6161 console.logwarn('Change avatar failed: Missing required fields');
6262 return response.status(400).json({ error: 'Missing required fields' });
6363 }
6464
6565 if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
6666 console.logerror('Change avatar failed: Unauthorized');
6767 return response.status(403).json({ error: 'Unauthorized' });
6868 }
6969
7070 // Avatar is not a data URL or not an empty string
7171 if (!request.body.avatar.startsWith('data:image/') && request.body.avatar !== '') {
7272 console.logwarn('Change avatar failed: Invalid data URL');
7373 return response.status(400).json({ error: 'Invalid data URL' });
7474 }
7575
@@ -77,7 +77,7 @@ router.post('/change-avatar', jsonParser, async (request, response) => {
7777 const user = await storage.getItem(toKey(request.body.handle));
7878
7979 if (!user) {
8080 console.logerror('Change avatar failed: User not found');
8181 return response.status(404).json({ error: 'User not found' });
8282 }
8383
@@ -93,12 +93,12 @@ router.post('/change-avatar', jsonParser, async (request, response) => {
9393router.post('/change-password', jsonParser, async (request, response) => {
9494 try {
9595 if (!request.body.handle) {
9696 console.logwarn('Change password failed: Missing required fields');
9797 return response.status(400).json({ error: 'Missing required fields' });
9898 }
9999
100100 if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
101101 console.logerror('Change password failed: Unauthorized');
102102 return response.status(403).json({ error: 'Unauthorized' });
103103 }
104104
@@ -106,17 +106,17 @@ router.post('/change-password', jsonParser, async (request, response) => {
106106 const user = await storage.getItem(toKey(request.body.handle));
107107
108108 if (!user) {
109109 console.logerror('Change password failed: User not found');
110110 return response.status(404).json({ error: 'User not found' });
111111 }
112112
113113 if (!user.enabled) {
114114 console.logerror('Change password failed: User is disabled');
115115 return response.status(403).json({ error: 'User is disabled' });
116116 }
117117
118118 if (!request.user.profile.admin && user.password && user.password !== getPasswordHash(request.body.oldPassword, user.salt)) {
119119 console.logerror('Change password failed: Incorrect password');
120120 return response.status(403).json({ error: 'Incorrect password' });
121121 }
122122
@@ -142,12 +142,12 @@ router.post('/backup', jsonParser, async (request, response) => {
142142 const handle = request.body.handle;
143143
144144 if (!handle) {
145145 console.logwarn('Backup failed: Missing required fields');
146146 return response.status(400).json({ error: 'Missing required fields' });
147147 }
148148
149149 if (handle !== request.user.profile.handle && !request.user.profile.admin) {
150150 console.logerror('Backup failed: Unauthorized');
151151 return response.status(403).json({ error: 'Unauthorized' });
152152 }
153153
@@ -163,7 +163,7 @@ router.post('/reset-settings', jsonParser, async (request, response) => {
163163 const password = request.body.password;
164164
165165 if (request.user.profile.password && request.user.profile.password !== getPasswordHash(password, request.user.profile.salt)) {
166166 console.logwarn('Reset settings failed: Incorrect password');
167167 return response.status(403).json({ error: 'Incorrect password' });
168168 }
169169
@@ -181,12 +181,12 @@ router.post('/reset-settings', jsonParser, async (request, response) => {
181181router.post('/change-name', jsonParser, async (request, response) => {
182182 try {
183183 if (!request.body.name || !request.body.handle) {
184184 console.logwarn('Change name failed: Missing required fields');
185185 return response.status(400).json({ error: 'Missing required fields' });
186186 }
187187
188188 if (request.body.handle !== request.user.profile.handle && !request.user.profile.admin) {
189189 console.logerror('Change name failed: Unauthorized');
190190 return response.status(403).json({ error: 'Unauthorized' });
191191 }
192192
@@ -194,7 +194,7 @@ router.post('/change-name', jsonParser, async (request, response) => {
194194 const user = await storage.getItem(toKey(request.body.handle));
195195
196196 if (!user) {
197197 console.logwarn('Change name failed: User not found');
198198 return response.status(404).json({ error: 'User not found' });
199199 }
200200
@@ -211,9 +211,9 @@ router.post('/change-name', jsonParser, async (request, response) => {
211211router.post('/reset-step1', jsonParser, async (request, response) => {
212212 try {
213213 const resetCode = String(crypto.randomInt(1000, 9999));
214214 console.loginfo();
215215 console.loginfo(color.magenta(`${request.user.profile.name}, your account reset code is: `) + color.red(resetCode));
216216 console.loginfo();
217217 RESET_CACHE.set(request.user.profile.handle, resetCode);
218218 return response.sendStatus(204);
219219 } catch (error) {
@@ -225,23 +225,23 @@ router.post('/reset-step1', jsonParser, async (request, response) => {
225225router.post('/reset-step2', jsonParser, async (request, response) => {
226226 try {
227227 if (!request.body.code) {
228228 console.logwarn('Recover step 2 failed: Missing required fields');
229229 return response.status(400).json({ error: 'Missing required fields' });
230230 }
231231
232232 if (request.user.profile.password && request.user.profile.password !== getPasswordHash(request.body.password, request.user.profile.salt)) {
233233 console.logwarn('Recover step 2 failed: Incorrect password');
234234 return response.status(400).json({ error: 'Incorrect password' });
235235 }
236236
237237 const code = RESET_CACHE.get(request.user.profile.handle);
238238
239239 if (!code || code !== request.body.code) {
240240 console.logwarn('Recover step 2 failed: Incorrect code');
241241 return response.status(400).json({ error: 'Incorrect code' });
242242 }
243243
244244 console.loginfo('Resetting account data:', request.user.profile.handle);
245245 await fsPromises.rm(request.user.directories.root, { recursive: true, force: true });
246246
247247 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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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.logwarn('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+20 -5
@@ -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));
@@ -163,9 +165,7 @@ export function formatBytes(bytes) {
163165 */
164166export async function extractFileFromZipBuffer(archiveBuffer, fileExtension) {
165167 return await new Promise((resolve, reject) => yauzl.fromBuffer(Buffer.from(archiveBuffer), { lazyEntries: true }, (err, zipfile) => {
166168 if (err) {reject(err);
167- reject(err);
168- }
169169
170170 zipfile.readEntry();
171171 zipfile.on('entry', (entry) => {
@@ -285,10 +285,11 @@ export function deepMerge(target, source) {
285285 if (isObject(target) && isObject(source)) {
286286 Object.keys(source).forEach(key => {
287287 if (isObject(source[key])) {
288288 if (!(key in target)) {
289289 Object.assign(output, { [key]: source[key] });
290290 } else {
291291 output[key] = deepMerge(target[key], source[key]);
292+ }
292293 } else {
293294 Object.assign(output, { [key]: source[key] });
294295 }
@@ -467,6 +468,7 @@ export function forwardFetchResponse(from, to) {
467468
468469 to.socket.on('close', function () {
469470 if (from.body instanceof Readable) from.body.destroy(); // Close the remote stream
471+
470472 to.end(); // End the Express response
471473 });
472474
@@ -693,6 +695,19 @@ export function isValidUrl(url) {
693695}
694696
695697/**
698+ * Setup the minimum log level
699+ */
700+export function setupLogLevel() {
701+ const logLevel = getConfigValue('minLogLevel', LOG_LEVELS.DEBUG);
702+
703+ globalThis.console.debug = logLevel <= LOG_LEVELS.DEBUG ? console.debug : () => {};
704+ globalThis.console.log = logLevel <= LOG_LEVELS.INFO ? console.log : () => {};
705+ globalThis.console.info = logLevel <= LOG_LEVELS.INFO ? console.info : () => {};
706+ globalThis.console.warn = logLevel <= LOG_LEVELS.WARN ? console.warn : () => {};
707+ globalThis.console.error = logLevel <= LOG_LEVELS.ERROR ? console.error : () => {};
708+}
709+
710+/**
696711 * MemoryLimitedMap class that limits the memory usage of string values.
697712 */
698713export class MemoryLimitedMap {