Move worldinfo endpoints into separate file

This commit is contained in:
valadaptive 2023-12-06 17:14:10 -05:00
parent c746e6a426
commit bf2b59279b
4 changed files with 164 additions and 158 deletions

View File

@ -51,7 +51,6 @@ const { delay, getVersion, getConfigValue, color, uuidv4, tryParse, clientRelati
const { invalidateThumbnail, ensureThumbnailCache } = require('./src/endpoints/thumbnails');
const { getTokenizerModel, getTiktokenTokenizer, loadTokenizers, TEXT_COMPLETION_MODELS, getSentencepiceTokenizer, sentencepieceTokenizers } = require('./src/endpoints/tokenizers');
const { convertClaudePrompt } = require('./src/chat-completion');
const { readWorldInfoFile } = require('./src/worldinfo');
// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
@ -1060,34 +1059,6 @@ app.post('/getsettings', jsonParser, (request, response) => {
});
});
app.post('/api/worldinfo/get', jsonParser, (request, response) => {
if (!request.body?.name) {
return response.sendStatus(400);
}
const file = readWorldInfoFile(request.body.name);
return response.send(file);
});
app.post('/api/worldinfo/delete', jsonParser, (request, response) => {
if (!request.body?.name) {
return response.sendStatus(400);
}
const worldInfoName = request.body.name;
const filename = sanitize(`${worldInfoName}.json`);
const pathToWorldInfo = path.join(DIRECTORIES.worlds, filename);
if (!fs.existsSync(pathToWorldInfo)) {
throw new Error(`World info file ${filename} doesn't exist.`);
}
fs.rmSync(pathToWorldInfo);
return response.sendStatus(200);
});
app.post('/savetheme', jsonParser, (request, response) => {
if (!request.body || !request.body.name) {
return response.sendStatus(400);
@ -1132,66 +1103,6 @@ function getImages(path) {
.sort(Intl.Collator().compare);
}
app.post('/api/worldinfo/import', urlencodedParser, (request, response) => {
if (!request.file) return response.sendStatus(400);
const filename = `${path.parse(sanitize(request.file.originalname)).name}.json`;
let fileContents = null;
if (request.body.convertedData) {
fileContents = request.body.convertedData;
} else {
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
fileContents = fs.readFileSync(pathToUpload, 'utf8');
fs.unlinkSync(pathToUpload);
}
try {
const worldContent = JSON.parse(fileContents);
if (!('entries' in worldContent)) {
throw new Error('File must contain a world info entries list');
}
} catch (err) {
return response.status(400).send('Is not a valid world info file');
}
const pathToNewFile = path.join(DIRECTORIES.worlds, filename);
const worldName = path.parse(pathToNewFile).name;
if (!worldName) {
return response.status(400).send('World file must have a name');
}
writeFileAtomicSync(pathToNewFile, fileContents);
return response.send({ name: worldName });
});
app.post('/api/worldinfo/edit', jsonParser, (request, response) => {
if (!request.body) {
return response.sendStatus(400);
}
if (!request.body.name) {
return response.status(400).send('World file must have a name');
}
try {
if (!('entries' in request.body.data)) {
throw new Error('World info must contain an entries list');
}
} catch (err) {
return response.status(400).send('Is not a valid world info file');
}
const filename = `${sanitize(request.body.name)}.json`;
const pathToFile = path.join(DIRECTORIES.worlds, filename);
writeFileAtomicSync(pathToFile, JSON.stringify(request.body.data, null, 4));
return response.send({ ok: true });
});
app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
if (!request.file) return response.sendStatus(400);
@ -2197,6 +2108,9 @@ app.use('/api/chats', require('./src/endpoints/chats').router);
// Group management
app.use('/api/groups', require('./src/endpoints/groups').router);
// World info management
app.use('/api/worldinfo', require('./src/endpoints/worldinfo').router);
// Character sprite management
app.use('/api/sprites', require('./src/endpoints/sprites').router);

View File

@ -16,7 +16,7 @@ const { jsonParser, urlencodedParser } = require('../express-common');
const { deepMerge, humanizedISO8601DateTime, tryParse } = require('../util');
const { TavernCardValidator } = require('../validator/TavernCardValidator');
const characterCardParser = require('../character-card-parser.js');
const { readWorldInfoFile, convertWorldInfoToCharacterBook } = require('../worldinfo');
const { readWorldInfoFile } = require('./worldinfo');
const { invalidateThumbnail } = require('./thumbnails');
const { importRisuSprites } = require('./sprites');
@ -330,6 +330,46 @@ function charaFormatData(data) {
return char;
}
/**
* @param {string} name Name of World Info file
* @param {object} entries Entries object
*/
function convertWorldInfoToCharacterBook(name, entries) {
/** @type {{ entries: object[]; name: string }} */
const result = { entries: [], name };
for (const index in entries) {
const entry = entries[index];
const originalEntry = {
id: entry.uid,
keys: entry.key,
secondary_keys: entry.keysecondary,
comment: entry.comment,
content: entry.content,
constant: entry.constant,
selective: entry.selective,
insertion_order: entry.order,
enabled: !entry.disable,
position: entry.position == 0 ? 'before_char' : 'after_char',
extensions: {
position: entry.position,
exclude_recursion: entry.excludeRecursion,
display_index: entry.displayIndex,
probability: entry.probability ?? null,
useProbability: entry.useProbability ?? false,
depth: entry.depth ?? 4,
selectiveLogic: entry.selectiveLogic ?? 0,
group: entry.group ?? '',
},
};
result.entries.push(originalEntry);
}
return result;
}
const router = express.Router();
router.post('/create', urlencodedParser, async function (request, response) {

120
src/endpoints/worldinfo.js Normal file
View File

@ -0,0 +1,120 @@
const fs = require('fs');
const path = require('path');
const express = require('express');
const sanitize = require('sanitize-filename');
const writeFileAtomicSync = require('write-file-atomic').sync;
const { jsonParser, urlencodedParser } = require('../express-common');
const { DIRECTORIES, UPLOADS_PATH } = require('../constants');
function readWorldInfoFile(worldInfoName) {
const dummyObject = { entries: {} };
if (!worldInfoName) {
return dummyObject;
}
const filename = `${worldInfoName}.json`;
const pathToWorldInfo = path.join(DIRECTORIES.worlds, filename);
if (!fs.existsSync(pathToWorldInfo)) {
console.log(`World info file ${filename} doesn't exist.`);
return dummyObject;
}
const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8');
const worldInfo = JSON.parse(worldInfoText);
return worldInfo;
}
const router = express.Router();
router.post('/get', jsonParser, (request, response) => {
if (!request.body?.name) {
return response.sendStatus(400);
}
const file = readWorldInfoFile(request.body.name);
return response.send(file);
});
router.post('/delete', jsonParser, (request, response) => {
if (!request.body?.name) {
return response.sendStatus(400);
}
const worldInfoName = request.body.name;
const filename = sanitize(`${worldInfoName}.json`);
const pathToWorldInfo = path.join(DIRECTORIES.worlds, filename);
if (!fs.existsSync(pathToWorldInfo)) {
throw new Error(`World info file ${filename} doesn't exist.`);
}
fs.rmSync(pathToWorldInfo);
return response.sendStatus(200);
});
router.post('/import', urlencodedParser, (request, response) => {
if (!request.file) return response.sendStatus(400);
const filename = `${path.parse(sanitize(request.file.originalname)).name}.json`;
let fileContents = null;
if (request.body.convertedData) {
fileContents = request.body.convertedData;
} else {
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
fileContents = fs.readFileSync(pathToUpload, 'utf8');
fs.unlinkSync(pathToUpload);
}
try {
const worldContent = JSON.parse(fileContents);
if (!('entries' in worldContent)) {
throw new Error('File must contain a world info entries list');
}
} catch (err) {
return response.status(400).send('Is not a valid world info file');
}
const pathToNewFile = path.join(DIRECTORIES.worlds, filename);
const worldName = path.parse(pathToNewFile).name;
if (!worldName) {
return response.status(400).send('World file must have a name');
}
writeFileAtomicSync(pathToNewFile, fileContents);
return response.send({ name: worldName });
});
router.post('/edit', jsonParser, (request, response) => {
if (!request.body) {
return response.sendStatus(400);
}
if (!request.body.name) {
return response.status(400).send('World file must have a name');
}
try {
if (!('entries' in request.body.data)) {
throw new Error('World info must contain an entries list');
}
} catch (err) {
return response.status(400).send('Is not a valid world info file');
}
const filename = `${sanitize(request.body.name)}.json`;
const pathToFile = path.join(DIRECTORIES.worlds, filename);
writeFileAtomicSync(pathToFile, JSON.stringify(request.body.data, null, 4));
return response.send({ ok: true });
});
module.exports = { router, readWorldInfoFile };

View File

@ -1,68 +0,0 @@
const path = require('path');
const fs = require('fs');
const { DIRECTORIES } = require('./constants');
function readWorldInfoFile(worldInfoName) {
const dummyObject = { entries: {} };
if (!worldInfoName) {
return dummyObject;
}
const filename = `${worldInfoName}.json`;
const pathToWorldInfo = path.join(DIRECTORIES.worlds, filename);
if (!fs.existsSync(pathToWorldInfo)) {
console.log(`World info file ${filename} doesn't exist.`);
return dummyObject;
}
const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8');
const worldInfo = JSON.parse(worldInfoText);
return worldInfo;
}
/**
* @param {string} name Name of World Info file
* @param {object} entries Entries object
*/
function convertWorldInfoToCharacterBook(name, entries) {
/** @type {{ entries: object[]; name: string }} */
const result = { entries: [], name };
for (const index in entries) {
const entry = entries[index];
const originalEntry = {
id: entry.uid,
keys: entry.key,
secondary_keys: entry.keysecondary,
comment: entry.comment,
content: entry.content,
constant: entry.constant,
selective: entry.selective,
insertion_order: entry.order,
enabled: !entry.disable,
position: entry.position == 0 ? 'before_char' : 'after_char',
extensions: {
position: entry.position,
exclude_recursion: entry.excludeRecursion,
display_index: entry.displayIndex,
probability: entry.probability ?? null,
useProbability: entry.useProbability ?? false,
depth: entry.depth ?? 4,
selectiveLogic: entry.selectiveLogic ?? 0,
group: entry.group ?? '',
},
};
result.entries.push(originalEntry);
}
return result;
}
module.exports = {
readWorldInfoFile,
convertWorldInfoToCharacterBook,
};