Merge pull request #1480 from valadaptive/worldinfo-endpoint
Move worldinfo endpoints into their own module
This commit is contained in:
commit
969c1ffefb
|
@ -424,7 +424,7 @@ async function loadWorldInfoData(name) {
|
||||||
return worldInfoCache[name];
|
return worldInfoCache[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('/getworldinfo', {
|
const response = await fetch('/api/worldinfo/get', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders(),
|
||||||
body: JSON.stringify({ name: name }),
|
body: JSON.stringify({ name: name }),
|
||||||
|
@ -1402,7 +1402,7 @@ function createWorldInfoEntry(name, data, fromSlashCommand = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _save(name, data) {
|
async function _save(name, data) {
|
||||||
await fetch('/editworldinfo', {
|
await fetch('/api/worldinfo/edit', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders(),
|
||||||
body: JSON.stringify({ name: name, data: data }),
|
body: JSON.stringify({ name: name, data: data }),
|
||||||
|
@ -1464,7 +1464,7 @@ async function deleteWorldInfo(worldInfoName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch('/deleteworldinfo', {
|
const response = await fetch('/api/worldinfo/delete', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders(),
|
||||||
body: JSON.stringify({ name: worldInfoName }),
|
body: JSON.stringify({ name: worldInfoName }),
|
||||||
|
@ -2269,7 +2269,7 @@ export async function importWorldInfo(file) {
|
||||||
|
|
||||||
jQuery.ajax({
|
jQuery.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/importworldinfo',
|
url: '/api/worldinfo/import',
|
||||||
data: formData,
|
data: formData,
|
||||||
beforeSend: () => { },
|
beforeSend: () => { },
|
||||||
cache: false,
|
cache: false,
|
||||||
|
|
98
server.js
98
server.js
|
@ -51,7 +51,6 @@ const { delay, getVersion, getConfigValue, color, uuidv4, tryParse, clientRelati
|
||||||
const { invalidateThumbnail, ensureThumbnailCache } = require('./src/endpoints/thumbnails');
|
const { invalidateThumbnail, ensureThumbnailCache } = require('./src/endpoints/thumbnails');
|
||||||
const { getTokenizerModel, getTiktokenTokenizer, loadTokenizers, TEXT_COMPLETION_MODELS, getSentencepiceTokenizer, sentencepieceTokenizers } = require('./src/endpoints/tokenizers');
|
const { getTokenizerModel, getTiktokenTokenizer, loadTokenizers, TEXT_COMPLETION_MODELS, getSentencepiceTokenizer, sentencepieceTokenizers } = require('./src/endpoints/tokenizers');
|
||||||
const { convertClaudePrompt } = require('./src/chat-completion');
|
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.
|
// 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
|
// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
|
||||||
|
@ -1060,34 +1059,6 @@ app.post('/getsettings', jsonParser, (request, response) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/getworldinfo', jsonParser, (request, response) => {
|
|
||||||
if (!request.body?.name) {
|
|
||||||
return response.sendStatus(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = readWorldInfoFile(request.body.name);
|
|
||||||
|
|
||||||
return response.send(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/deleteworldinfo', 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) => {
|
app.post('/savetheme', jsonParser, (request, response) => {
|
||||||
if (!request.body || !request.body.name) {
|
if (!request.body || !request.body.name) {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
|
@ -1132,66 +1103,6 @@ function getImages(path) {
|
||||||
.sort(Intl.Collator().compare);
|
.sort(Intl.Collator().compare);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post('/importworldinfo', 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('/editworldinfo', 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) => {
|
app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||||
if (!request.file) return response.sendStatus(400);
|
if (!request.file) return response.sendStatus(400);
|
||||||
|
|
||||||
|
@ -2134,6 +2045,12 @@ redirect('/creategroup', '/api/groups/create');
|
||||||
redirect('/editgroup', '/api/groups/edit');
|
redirect('/editgroup', '/api/groups/edit');
|
||||||
redirect('/deletegroup', '/api/groups/delete');
|
redirect('/deletegroup', '/api/groups/delete');
|
||||||
|
|
||||||
|
// Redirect deprecated worldinfo API endpoints
|
||||||
|
redirect('/getworldinfo', '/api/worldinfo/get');
|
||||||
|
redirect('/deleteworldinfo', '/api/worldinfo/delete');
|
||||||
|
redirect('/importworldinfo', '/api/worldinfo/import');
|
||||||
|
redirect('/editworldinfo', '/api/worldinfo/edit');
|
||||||
|
|
||||||
// ** REST CLIENT ASYNC WRAPPERS **
|
// ** REST CLIENT ASYNC WRAPPERS **
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2191,6 +2108,9 @@ app.use('/api/chats', require('./src/endpoints/chats').router);
|
||||||
// Group management
|
// Group management
|
||||||
app.use('/api/groups', require('./src/endpoints/groups').router);
|
app.use('/api/groups', require('./src/endpoints/groups').router);
|
||||||
|
|
||||||
|
// World info management
|
||||||
|
app.use('/api/worldinfo', require('./src/endpoints/worldinfo').router);
|
||||||
|
|
||||||
// Character sprite management
|
// Character sprite management
|
||||||
app.use('/api/sprites', require('./src/endpoints/sprites').router);
|
app.use('/api/sprites', require('./src/endpoints/sprites').router);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ const { jsonParser, urlencodedParser } = require('../express-common');
|
||||||
const { deepMerge, humanizedISO8601DateTime, tryParse } = require('../util');
|
const { deepMerge, humanizedISO8601DateTime, tryParse } = require('../util');
|
||||||
const { TavernCardValidator } = require('../validator/TavernCardValidator');
|
const { TavernCardValidator } = require('../validator/TavernCardValidator');
|
||||||
const characterCardParser = require('../character-card-parser.js');
|
const characterCardParser = require('../character-card-parser.js');
|
||||||
const { readWorldInfoFile, convertWorldInfoToCharacterBook } = require('../worldinfo');
|
const { readWorldInfoFile } = require('./worldinfo');
|
||||||
const { invalidateThumbnail } = require('./thumbnails');
|
const { invalidateThumbnail } = require('./thumbnails');
|
||||||
const { importRisuSprites } = require('./sprites');
|
const { importRisuSprites } = require('./sprites');
|
||||||
|
|
||||||
|
@ -330,6 +330,46 @@ function charaFormatData(data) {
|
||||||
return char;
|
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();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/create', urlencodedParser, async function (request, response) {
|
router.post('/create', urlencodedParser, async function (request, response) {
|
||||||
|
|
|
@ -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 };
|
|
@ -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,
|
|
||||||
};
|
|
Loading…
Reference in New Issue