mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Replace implementation of world info
This commit is contained in:
287
server.js
287
server.js
@@ -61,7 +61,7 @@ if (is_colab && process.env.googledrive == 2){
|
||||
const jsonParser = express.json({limit: '100mb'});
|
||||
const urlencodedParser = express.urlencoded({extended: true, limit: '100mb'});
|
||||
const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
|
||||
const directories = { worlds: 'public/KoboldAI Worlds/' };
|
||||
const directories = { worlds: 'public/worlds/' };
|
||||
|
||||
// CSRF Protection //
|
||||
const doubleCsrf = require('csrf-csrf').doubleCsrf;
|
||||
@@ -753,7 +753,7 @@ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's cod
|
||||
.readdirSync(directories.worlds)
|
||||
.filter(file => path.extname(file).toLowerCase() === '.json')
|
||||
.sort((a, b) => a < b);
|
||||
const koboldai_world_names = worldFiles.map(item => path.parse(item).name);
|
||||
const world_names = worldFiles.map(item => path.parse(item).name);
|
||||
|
||||
files.forEach(item => {
|
||||
const file = fs.readFileSync(
|
||||
@@ -797,37 +797,12 @@ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's cod
|
||||
settings,
|
||||
koboldai_settings,
|
||||
koboldai_setting_names,
|
||||
koboldai_world_names,
|
||||
world_names,
|
||||
novelai_settings,
|
||||
novelai_setting_names
|
||||
});
|
||||
});
|
||||
|
||||
// Work around to disable parallel requests to endpoint
|
||||
let kobold_world_sync_busy = false;
|
||||
|
||||
app.post('/synckoboldworld', jsonParser, async (request, response) => {
|
||||
if(!request.body) return response.sendStatus(400);
|
||||
|
||||
if (!api_server || kobold_world_sync_busy) {
|
||||
response.send({ busy: true });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
kobold_world_sync_busy = true;
|
||||
const worldName = request.body.name;
|
||||
await synchronizeKoboldWorldInfo(worldName);
|
||||
response.send({ ok: true });
|
||||
} catch (err) {
|
||||
var message = JSON.stringify(err);
|
||||
console.error(`Error during world synchronization: ${message}`);
|
||||
response.status(500).send(message);
|
||||
} finally {
|
||||
kobold_world_sync_busy = false;
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/getworldinfo', jsonParser, (request, response) => {
|
||||
if (!request.body?.name) {
|
||||
return response.sendStatus(400);
|
||||
@@ -835,7 +810,7 @@ app.post('/getworldinfo', jsonParser, (request, response) => {
|
||||
|
||||
const file = readWorldInfoFile(request.body.name);
|
||||
|
||||
return response.send(file.tavernWorldInfo);
|
||||
return response.send(file);
|
||||
});
|
||||
|
||||
app.post('/deleteworldinfo', jsonParser, (request, response) => {
|
||||
@@ -856,6 +831,23 @@ app.post('/deleteworldinfo', jsonParser, (request, response) => {
|
||||
return response.sendStatus(200);
|
||||
});
|
||||
|
||||
function readWorldInfoFile(worldInfoName) {
|
||||
if (!worldInfoName) {
|
||||
return { entries: {} };
|
||||
}
|
||||
|
||||
const filename = `${worldInfoName}.json`;
|
||||
const pathToWorldInfo = path.join(directories.worlds, filename);
|
||||
|
||||
if (!fs.existsSync(pathToWorldInfo)) {
|
||||
throw new Error(`World info file ${filename} doesn't exist.`);
|
||||
}
|
||||
|
||||
const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8');
|
||||
const worldInfo = JSON.parse(worldInfoText);
|
||||
return worldInfo;
|
||||
}
|
||||
|
||||
function getCharaterFile(directories,response,i){ //old need del
|
||||
if(directories.length > i){
|
||||
|
||||
@@ -1286,234 +1278,6 @@ app.post('/editworldinfo', jsonParser, (request, response) => {
|
||||
return response.send({ ok: true });
|
||||
});
|
||||
|
||||
function findTavernWorldEntry(info, key, content) {
|
||||
for (const entryId in info.entries) {
|
||||
const entry = info.entries[entryId];
|
||||
const keyString = entry.key.join(',');
|
||||
|
||||
if (keyString === key && entry.content === content) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function synchronizeKoboldWorldInfo(worldInfoName) {
|
||||
const { koboldFolderName, tavernWorldInfo } = readWorldInfoFile(worldInfoName);
|
||||
|
||||
// Get existing world info
|
||||
const koboldWorldInfo = await getAsync(`${api_server}/v1/world_info`, baseRequestArgs);
|
||||
|
||||
// Validate kobold world info
|
||||
let {
|
||||
shouldCreateWorld,
|
||||
koboldWorldUid,
|
||||
tavernEntriesToCreate,
|
||||
koboldEntriesToDelete,
|
||||
koboldFoldersToDelete,
|
||||
} = await validateKoboldWorldInfo(koboldFolderName, koboldWorldInfo, tavernWorldInfo);
|
||||
|
||||
// Create folder if not already exists
|
||||
if (koboldFolderName && shouldCreateWorld) {
|
||||
koboldWorldUid = await createKoboldFolder(koboldFolderName, tavernEntriesToCreate, tavernWorldInfo);
|
||||
}
|
||||
|
||||
await deleteKoboldFolders(koboldFoldersToDelete);
|
||||
await deleteKoboldEntries(koboldEntriesToDelete);
|
||||
await createTavernEntries(tavernEntriesToCreate, koboldWorldUid, tavernWorldInfo);
|
||||
}
|
||||
|
||||
function readWorldInfoFile(worldInfoName) {
|
||||
if (!worldInfoName) {
|
||||
return { koboldFolderName: null, tavernWorldInfo: { entries: {}, folders: {} }};
|
||||
}
|
||||
|
||||
const koboldFolderName = getKoboldWorldInfoName(worldInfoName);
|
||||
const filename = `${worldInfoName}.json`;
|
||||
const pathToWorldInfo = path.join(directories.worlds, filename);
|
||||
|
||||
if (!fs.existsSync(pathToWorldInfo)) {
|
||||
throw new Error(`World info file ${filename} doesn't exist.`);
|
||||
}
|
||||
|
||||
const tavernWorldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8');
|
||||
const tavernWorldInfo = JSON.parse(tavernWorldInfoText);
|
||||
return { koboldFolderName, tavernWorldInfo };
|
||||
}
|
||||
|
||||
async function createKoboldFolder(koboldFolderName, tavernEntriesToCreate, tavernWorldInfo) {
|
||||
const createdFolder = await postAsync(`${api_server}/v1/world_info/folders`, { data: {}, ...baseRequestArgs });
|
||||
const koboldWorldUid = createdFolder.uid;
|
||||
|
||||
// Set a name so we could find the folder later
|
||||
const setNameArgs = { data: { value: koboldFolderName }, ...baseRequestArgs };
|
||||
await putAsync(`${api_server}/v1/world_info/folders/${koboldWorldUid}/name`, setNameArgs);
|
||||
|
||||
// Create all world info entries
|
||||
tavernEntriesToCreate.push(...Object.keys(tavernWorldInfo.entries));
|
||||
return koboldWorldUid;
|
||||
}
|
||||
|
||||
async function createTavernEntries(tavernEntriesToCreate, koboldWorldUid, tavernWorldInfo) {
|
||||
if (tavernEntriesToCreate.length && koboldWorldUid) {
|
||||
for (const tavernUid of tavernEntriesToCreate) {
|
||||
try {
|
||||
const tavernEntry = tavernWorldInfo.entries[tavernUid];
|
||||
const koboldEntry = await postAsync(`${api_server}/v1/world_info/folders/${koboldWorldUid}`, { data: {}, ...baseRequestArgs });
|
||||
await setKoboldEntryData(tavernEntry, koboldEntry);
|
||||
} catch (err) {
|
||||
console.error(`Couldn't create Kobold world info entry, tavernUid=${tavernUid}. Skipping...`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteKoboldEntries(koboldEntriesToDelete) {
|
||||
if (koboldEntriesToDelete.length) {
|
||||
for (const uid of koboldEntriesToDelete) {
|
||||
try {
|
||||
await deleteAsync(`${api_server}/v1/world_info/${uid}`);
|
||||
} catch (err) {
|
||||
console.error(`Couldn't delete Kobold world info entry, uid=${uid}. Skipping...`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteKoboldFolders(koboldFoldersToDelete) {
|
||||
if (koboldFoldersToDelete.length) {
|
||||
for (const uid of koboldFoldersToDelete) {
|
||||
try {
|
||||
await deleteAsync(api_server + `/v1/world_info/folders/${uid}`, baseRequestArgs);
|
||||
} catch (err) {
|
||||
console.error(`Couldn't delete Kobold world info folder, uid=${uid}. Skipping...`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setKoboldEntryData(tavernEntry, koboldEntry) {
|
||||
// 1. Set primary key
|
||||
if (tavernEntry.key?.length) {
|
||||
const keyArgs = { data: { value: tavernEntry.key.join(',') }, ...baseRequestArgs };
|
||||
await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/key`, keyArgs);
|
||||
}
|
||||
|
||||
// 2. Set secondary key
|
||||
if (tavernEntry.keysecondary?.length) {
|
||||
const keySecondaryArgs = { data: { value: tavernEntry.keysecondary.join(',') }, ...baseRequestArgs };
|
||||
await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/keysecondary`, keySecondaryArgs);
|
||||
}
|
||||
|
||||
// 3. Set content
|
||||
if (tavernEntry.content) {
|
||||
const contentArgs = { data: { value: tavernEntry.content }, ...baseRequestArgs };
|
||||
await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/content`, contentArgs);
|
||||
}
|
||||
|
||||
// 4. Set comment
|
||||
if (tavernEntry.comment) {
|
||||
const commentArgs = { data: { value: tavernEntry.comment }, ...baseRequestArgs };
|
||||
await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/comment`, commentArgs);
|
||||
};
|
||||
|
||||
/* Fixed in Kobold United only: https://github.com/henk717/KoboldAI/pull/280 */
|
||||
try {
|
||||
// 5. Set constant flag
|
||||
if (tavernEntry.constant) {
|
||||
const constantArgs = { data: { value: tavernEntry.constant.toString() }, ...baseRequestArgs };
|
||||
await putToPromise(`${api_server}/v1/world_info/${koboldEntry.uid}/constant`, constantArgs);
|
||||
}
|
||||
|
||||
// 6. Set selective flag
|
||||
if (tavernEntry.selective) {
|
||||
const selectiveArgs = { data: { value: tavernEntry.selective.toString() }, ...baseRequestArgs };
|
||||
await putToPromise(`${api_server}/v1/world_info/${koboldEntry.uid}/selective`, selectiveArgs);
|
||||
}
|
||||
} catch {
|
||||
// couldn't set fields = ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function validateKoboldWorldInfo(koboldFolderName, koboldWorldInfo, tavernWorldInfo) {
|
||||
let shouldCreateWorld = true;
|
||||
let koboldWorldUid = null;
|
||||
|
||||
const koboldEntriesToDelete = []; // KoboldUIDs
|
||||
const koboldFoldersToDelete = []; // KoboldUIDs
|
||||
const tavernEntriesToCreate = []; // TavernUIDs
|
||||
|
||||
if (koboldWorldInfo?.folders?.length) {
|
||||
let existingFolderAlreadyFound = false;
|
||||
|
||||
for (const folder of koboldWorldInfo.folders) {
|
||||
// Don't care about non-Tavern folders
|
||||
if (!isTavernKoboldWorldInfo(folder.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Other Tavern folders should be deleted (including dupes). If folder name selected is null, then delete anyway to clean-up
|
||||
if (!koboldFolderName || folder.name !== koboldFolderName || existingFolderAlreadyFound) {
|
||||
koboldFoldersToDelete.push(folder.uid);
|
||||
// Should also delete all entries in folder otherwise they will be detached
|
||||
if (Array.isArray(folder.entries)) {
|
||||
koboldEntriesToDelete.push(...folder.entries.map(entry => entry.uid));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate existing entries in Kobold world
|
||||
if (folder.name === koboldFolderName) {
|
||||
existingFolderAlreadyFound = true;
|
||||
shouldCreateWorld = false;
|
||||
koboldWorldUid = folder.uid;
|
||||
if (folder.entries?.length) {
|
||||
const foundTavernEntries = [];
|
||||
for (const koboldEntry of folder.entries) {
|
||||
const tavernEntry = findTavernWorldEntry(tavernWorldInfo, koboldEntry.key, koboldEntry.content);
|
||||
|
||||
if (tavernEntry) {
|
||||
foundTavernEntries.push(tavernEntry.uid);
|
||||
if (isEntryOutOfSync(tavernEntry, koboldEntry)) {
|
||||
// Entry is out of sync. Should be recreated
|
||||
koboldEntriesToDelete.push(koboldEntry.uid);
|
||||
tavernEntriesToCreate.push(tavernEntry.uid);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We don't have that entry in our world. It should be deleted
|
||||
koboldEntriesToDelete.push(koboldEntry.uid);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if every tavern entry was found in kobold world
|
||||
// BTW. Entries is an object, not an array!
|
||||
for (const tavernEntryUid in tavernWorldInfo.entries) {
|
||||
const tavernEntry = tavernWorldInfo.entries[tavernEntryUid];
|
||||
if (!foundTavernEntries.includes(tavernEntry.uid)) {
|
||||
tavernEntriesToCreate.push(tavernEntry.uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { shouldCreateWorld, koboldWorldUid, tavernEntriesToCreate, koboldEntriesToDelete, koboldFoldersToDelete };
|
||||
}
|
||||
|
||||
function isEntryOutOfSync(tavernEntry, koboldEntry) {
|
||||
return tavernEntry.content !== koboldEntry.content ||
|
||||
tavernEntry.comment !== koboldEntry.comment ||
|
||||
tavernEntry.selective !== koboldEntry.selective ||
|
||||
tavernEntry.constant !== koboldEntry.constant ||
|
||||
tavernEntry.key.join(',') !== koboldEntry.key ||
|
||||
(koboldEntry.selective ? tavernEntry.keysecondary.join(',') !== koboldEntry.keysecondary : false);
|
||||
}
|
||||
|
||||
// ** REST CLIENT ASYNC WRAPPERS **
|
||||
function deleteAsync(url, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -1560,15 +1324,6 @@ function getAsync(url, args) {
|
||||
}
|
||||
// ** END **
|
||||
|
||||
function getKoboldWorldInfoName(worldInfoName) {
|
||||
return worldInfoName ? `TavernAI_${worldInfoName}_WI` : null;
|
||||
}
|
||||
|
||||
function isTavernKoboldWorldInfo(folderName) {
|
||||
return /^TavernAI_(.*)_WI$/.test(folderName);
|
||||
}
|
||||
|
||||
|
||||
app.listen(server_port, function() {
|
||||
if(process.env.colab !== undefined){
|
||||
if(process.env.colab == 2){
|
||||
|
Reference in New Issue
Block a user