mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
#1579 Add ooba character yaml import
This commit is contained in:
@@ -3838,7 +3838,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="rm_character_import" class="right_menu" style="display: none;">
|
<div id="rm_character_import" class="right_menu" style="display: none;">
|
||||||
<form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
<form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||||
<input multiple type="file" id="character_import_file" accept=".json, image/png" name="avatar">
|
<input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml" name="avatar">
|
||||||
<input id="character_import_file_type" name="file_type" class="text_pole" maxlength="999" size="2" value="" autocomplete="off">
|
<input id="character_import_file_type" name="file_type" class="text_pole" maxlength="999" size="2" value="" autocomplete="off">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -7649,6 +7649,10 @@ export async function processDroppedFiles(files) {
|
|||||||
const allowedMimeTypes = [
|
const allowedMimeTypes = [
|
||||||
'application/json',
|
'application/json',
|
||||||
'image/png',
|
'image/png',
|
||||||
|
'application/yaml',
|
||||||
|
'application/x-yaml',
|
||||||
|
'text/yaml',
|
||||||
|
'text/x-yaml',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
@@ -7662,10 +7666,7 @@ export async function processDroppedFiles(files) {
|
|||||||
|
|
||||||
async function importCharacter(file) {
|
async function importCharacter(file) {
|
||||||
const ext = file.name.match(/\.(\w+)$/);
|
const ext = file.name.match(/\.(\w+)$/);
|
||||||
if (
|
if (!ext || !(['json', 'png', 'yaml', 'yml'].includes(ext[1].toLowerCase()))) {
|
||||||
!ext ||
|
|
||||||
(ext[1].toLowerCase() != 'json' && ext[1].toLowerCase() != 'png')
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ const readline = require('readline');
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
|
const yaml = require('yaml');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const encode = require('png-chunks-encode');
|
const encode = require('png-chunks-encode');
|
||||||
@@ -19,6 +20,7 @@ const characterCardParser = require('../character-card-parser.js');
|
|||||||
const { readWorldInfoFile } = require('./worldinfo');
|
const { readWorldInfoFile } = require('./worldinfo');
|
||||||
const { invalidateThumbnail } = require('./thumbnails');
|
const { invalidateThumbnail } = require('./thumbnails');
|
||||||
const { importRisuSprites } = require('./sprites');
|
const { importRisuSprites } = require('./sprites');
|
||||||
|
const defaultAvatarPath = './public/img/ai4.png';
|
||||||
|
|
||||||
let characters = {};
|
let characters = {};
|
||||||
|
|
||||||
@@ -394,6 +396,36 @@ function convertWorldInfoToCharacterBook(name, entries) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a character from a YAML file.
|
||||||
|
* @param {string} uploadPath Path to the uploaded file
|
||||||
|
* @param {import('express').Response} response Express response object
|
||||||
|
*/
|
||||||
|
function importFromYaml(uploadPath, response) {
|
||||||
|
const fileText = fs.readFileSync(uploadPath, 'utf8');
|
||||||
|
fs.rmSync(uploadPath);
|
||||||
|
const yamlData = yaml.parse(fileText);
|
||||||
|
console.log('importing from yaml');
|
||||||
|
yamlData.name = sanitize(yamlData.name);
|
||||||
|
const fileName = getPngName(yamlData.name);
|
||||||
|
let char = convertToV2({
|
||||||
|
'name': yamlData.name,
|
||||||
|
'description': yamlData.context ?? '',
|
||||||
|
'first_mes': yamlData.greeting ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'chat': `${yamlData.name} - ${humanizedISO8601DateTime()}`,
|
||||||
|
'personality': '',
|
||||||
|
'creatorcomment': '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'mes_example': '',
|
||||||
|
'scenario': '',
|
||||||
|
'talkativeness': 0.5,
|
||||||
|
'creator': '',
|
||||||
|
'tags': '',
|
||||||
|
});
|
||||||
|
charaWrite(defaultAvatarPath, JSON.stringify(char), fileName, response, { file_name: fileName });
|
||||||
|
}
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/create', urlencodedParser, async function (request, response) {
|
router.post('/create', urlencodedParser, async function (request, response) {
|
||||||
@@ -760,144 +792,147 @@ function getPngName(file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.post('/import', urlencodedParser, async function (request, response) {
|
router.post('/import', urlencodedParser, async function (request, response) {
|
||||||
|
if (!request.body || !request.file) return response.sendStatus(400);
|
||||||
if (!request.body || request.file === undefined) return response.sendStatus(400);
|
|
||||||
|
|
||||||
let png_name = '';
|
let png_name = '';
|
||||||
let filedata = request.file;
|
let filedata = request.file;
|
||||||
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
||||||
var format = request.body.file_type;
|
let format = request.body.file_type;
|
||||||
const defaultAvatarPath = './public/img/ai4.png';
|
|
||||||
//console.log(format);
|
|
||||||
if (filedata) {
|
|
||||||
if (format == 'json') {
|
|
||||||
fs.readFile(uploadPath, 'utf8', async (err, data) => {
|
|
||||||
fs.unlinkSync(uploadPath);
|
|
||||||
|
|
||||||
if (err) {
|
if (format == 'yaml' || format == 'yml') {
|
||||||
console.log(err);
|
try {
|
||||||
response.send({ error: true });
|
importFromYaml(uploadPath, response);
|
||||||
}
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
response.send({ error: true });
|
||||||
|
}
|
||||||
|
} else if (format == 'json') {
|
||||||
|
fs.readFile(uploadPath, 'utf8', async (err, data) => {
|
||||||
|
fs.unlinkSync(uploadPath);
|
||||||
|
|
||||||
let jsonData = JSON.parse(data);
|
if (err) {
|
||||||
|
|
||||||
if (jsonData.spec !== undefined) {
|
|
||||||
console.log('importing from v2 json');
|
|
||||||
importRisuSprites(jsonData);
|
|
||||||
unsetFavFlag(jsonData);
|
|
||||||
jsonData = readFromV2(jsonData);
|
|
||||||
jsonData['create_date'] = humanizedISO8601DateTime();
|
|
||||||
png_name = getPngName(jsonData.data?.name || jsonData.name);
|
|
||||||
let char = JSON.stringify(jsonData);
|
|
||||||
charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name });
|
|
||||||
} else if (jsonData.name !== undefined) {
|
|
||||||
console.log('importing from v1 json');
|
|
||||||
jsonData.name = sanitize(jsonData.name);
|
|
||||||
if (jsonData.creator_notes) {
|
|
||||||
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
|
||||||
}
|
|
||||||
png_name = getPngName(jsonData.name);
|
|
||||||
let char = {
|
|
||||||
'name': jsonData.name,
|
|
||||||
'description': jsonData.description ?? '',
|
|
||||||
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
|
||||||
'personality': jsonData.personality ?? '',
|
|
||||||
'first_mes': jsonData.first_mes ?? '',
|
|
||||||
'avatar': 'none',
|
|
||||||
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
|
||||||
'mes_example': jsonData.mes_example ?? '',
|
|
||||||
'scenario': jsonData.scenario ?? '',
|
|
||||||
'create_date': humanizedISO8601DateTime(),
|
|
||||||
'talkativeness': jsonData.talkativeness ?? 0.5,
|
|
||||||
'creator': jsonData.creator ?? '',
|
|
||||||
'tags': jsonData.tags ?? '',
|
|
||||||
};
|
|
||||||
char = convertToV2(char);
|
|
||||||
let charJSON = JSON.stringify(char);
|
|
||||||
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
|
||||||
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
|
|
||||||
console.log('importing from gradio json');
|
|
||||||
jsonData.char_name = sanitize(jsonData.char_name);
|
|
||||||
if (jsonData.creator_notes) {
|
|
||||||
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
|
||||||
}
|
|
||||||
png_name = getPngName(jsonData.char_name);
|
|
||||||
let char = {
|
|
||||||
'name': jsonData.char_name,
|
|
||||||
'description': jsonData.char_persona ?? '',
|
|
||||||
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
|
||||||
'personality': '',
|
|
||||||
'first_mes': jsonData.char_greeting ?? '',
|
|
||||||
'avatar': 'none',
|
|
||||||
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
|
||||||
'mes_example': jsonData.example_dialogue ?? '',
|
|
||||||
'scenario': jsonData.world_scenario ?? '',
|
|
||||||
'create_date': humanizedISO8601DateTime(),
|
|
||||||
'talkativeness': jsonData.talkativeness ?? 0.5,
|
|
||||||
'creator': jsonData.creator ?? '',
|
|
||||||
'tags': jsonData.tags ?? '',
|
|
||||||
};
|
|
||||||
char = convertToV2(char);
|
|
||||||
let charJSON = JSON.stringify(char);
|
|
||||||
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
|
||||||
} else {
|
|
||||||
console.log('Incorrect character format .json');
|
|
||||||
response.send({ error: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
var img_data = await charaRead(uploadPath, format);
|
|
||||||
if (img_data === undefined) throw new Error('Failed to read character data');
|
|
||||||
|
|
||||||
let jsonData = JSON.parse(img_data);
|
|
||||||
|
|
||||||
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
|
||||||
png_name = getPngName(jsonData.name);
|
|
||||||
|
|
||||||
if (jsonData.spec !== undefined) {
|
|
||||||
console.log('Found a v2 character file.');
|
|
||||||
importRisuSprites(jsonData);
|
|
||||||
unsetFavFlag(jsonData);
|
|
||||||
jsonData = readFromV2(jsonData);
|
|
||||||
jsonData['create_date'] = humanizedISO8601DateTime();
|
|
||||||
const char = JSON.stringify(jsonData);
|
|
||||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
|
||||||
fs.unlinkSync(uploadPath);
|
|
||||||
} else if (jsonData.name !== undefined) {
|
|
||||||
console.log('Found a v1 character file.');
|
|
||||||
|
|
||||||
if (jsonData.creator_notes) {
|
|
||||||
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
let char = {
|
|
||||||
'name': jsonData.name,
|
|
||||||
'description': jsonData.description ?? '',
|
|
||||||
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
|
||||||
'personality': jsonData.personality ?? '',
|
|
||||||
'first_mes': jsonData.first_mes ?? '',
|
|
||||||
'avatar': 'none',
|
|
||||||
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
|
||||||
'mes_example': jsonData.mes_example ?? '',
|
|
||||||
'scenario': jsonData.scenario ?? '',
|
|
||||||
'create_date': humanizedISO8601DateTime(),
|
|
||||||
'talkativeness': jsonData.talkativeness ?? 0.5,
|
|
||||||
'creator': jsonData.creator ?? '',
|
|
||||||
'tags': jsonData.tags ?? '',
|
|
||||||
};
|
|
||||||
char = convertToV2(char);
|
|
||||||
const charJSON = JSON.stringify(char);
|
|
||||||
await charaWrite(uploadPath, charJSON, png_name, response, { file_name: png_name });
|
|
||||||
fs.unlinkSync(uploadPath);
|
|
||||||
} else {
|
|
||||||
console.log('Unknown character card format');
|
|
||||||
response.send({ error: true });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
response.send({ error: true });
|
response.send({ error: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let jsonData = JSON.parse(data);
|
||||||
|
|
||||||
|
if (jsonData.spec !== undefined) {
|
||||||
|
console.log('importing from v2 json');
|
||||||
|
importRisuSprites(jsonData);
|
||||||
|
unsetFavFlag(jsonData);
|
||||||
|
jsonData = readFromV2(jsonData);
|
||||||
|
jsonData['create_date'] = humanizedISO8601DateTime();
|
||||||
|
png_name = getPngName(jsonData.data?.name || jsonData.name);
|
||||||
|
let char = JSON.stringify(jsonData);
|
||||||
|
charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name });
|
||||||
|
} else if (jsonData.name !== undefined) {
|
||||||
|
console.log('importing from v1 json');
|
||||||
|
jsonData.name = sanitize(jsonData.name);
|
||||||
|
if (jsonData.creator_notes) {
|
||||||
|
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
||||||
|
}
|
||||||
|
png_name = getPngName(jsonData.name);
|
||||||
|
let char = {
|
||||||
|
'name': jsonData.name,
|
||||||
|
'description': jsonData.description ?? '',
|
||||||
|
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||||
|
'personality': jsonData.personality ?? '',
|
||||||
|
'first_mes': jsonData.first_mes ?? '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
||||||
|
'mes_example': jsonData.mes_example ?? '',
|
||||||
|
'scenario': jsonData.scenario ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'talkativeness': jsonData.talkativeness ?? 0.5,
|
||||||
|
'creator': jsonData.creator ?? '',
|
||||||
|
'tags': jsonData.tags ?? '',
|
||||||
|
};
|
||||||
|
char = convertToV2(char);
|
||||||
|
let charJSON = JSON.stringify(char);
|
||||||
|
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
||||||
|
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
|
||||||
|
console.log('importing from gradio json');
|
||||||
|
jsonData.char_name = sanitize(jsonData.char_name);
|
||||||
|
if (jsonData.creator_notes) {
|
||||||
|
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
||||||
|
}
|
||||||
|
png_name = getPngName(jsonData.char_name);
|
||||||
|
let char = {
|
||||||
|
'name': jsonData.char_name,
|
||||||
|
'description': jsonData.char_persona ?? '',
|
||||||
|
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||||
|
'personality': '',
|
||||||
|
'first_mes': jsonData.char_greeting ?? '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
||||||
|
'mes_example': jsonData.example_dialogue ?? '',
|
||||||
|
'scenario': jsonData.world_scenario ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'talkativeness': jsonData.talkativeness ?? 0.5,
|
||||||
|
'creator': jsonData.creator ?? '',
|
||||||
|
'tags': jsonData.tags ?? '',
|
||||||
|
};
|
||||||
|
char = convertToV2(char);
|
||||||
|
let charJSON = JSON.stringify(char);
|
||||||
|
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
||||||
|
} else {
|
||||||
|
console.log('Incorrect character format .json');
|
||||||
|
response.send({ error: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
var img_data = await charaRead(uploadPath, format);
|
||||||
|
if (img_data === undefined) throw new Error('Failed to read character data');
|
||||||
|
|
||||||
|
let jsonData = JSON.parse(img_data);
|
||||||
|
|
||||||
|
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
||||||
|
png_name = getPngName(jsonData.name);
|
||||||
|
|
||||||
|
if (jsonData.spec !== undefined) {
|
||||||
|
console.log('Found a v2 character file.');
|
||||||
|
importRisuSprites(jsonData);
|
||||||
|
unsetFavFlag(jsonData);
|
||||||
|
jsonData = readFromV2(jsonData);
|
||||||
|
jsonData['create_date'] = humanizedISO8601DateTime();
|
||||||
|
const char = JSON.stringify(jsonData);
|
||||||
|
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||||
|
fs.unlinkSync(uploadPath);
|
||||||
|
} else if (jsonData.name !== undefined) {
|
||||||
|
console.log('Found a v1 character file.');
|
||||||
|
|
||||||
|
if (jsonData.creator_notes) {
|
||||||
|
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
let char = {
|
||||||
|
'name': jsonData.name,
|
||||||
|
'description': jsonData.description ?? '',
|
||||||
|
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||||
|
'personality': jsonData.personality ?? '',
|
||||||
|
'first_mes': jsonData.first_mes ?? '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
||||||
|
'mes_example': jsonData.mes_example ?? '',
|
||||||
|
'scenario': jsonData.scenario ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'talkativeness': jsonData.talkativeness ?? 0.5,
|
||||||
|
'creator': jsonData.creator ?? '',
|
||||||
|
'tags': jsonData.tags ?? '',
|
||||||
|
};
|
||||||
|
char = convertToV2(char);
|
||||||
|
const charJSON = JSON.stringify(char);
|
||||||
|
await charaWrite(uploadPath, charJSON, png_name, response, { file_name: png_name });
|
||||||
|
fs.unlinkSync(uploadPath);
|
||||||
|
} else {
|
||||||
|
console.log('Unknown character card format');
|
||||||
|
response.send({ error: true });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
response.send({ error: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user