Support all file formats for replace/update

This commit is contained in:
Cohee 2024-07-01 21:48:56 +03:00
parent e1e0ef8730
commit 1315f0968b
3 changed files with 29 additions and 19 deletions

View File

@ -4796,7 +4796,7 @@
<input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar"> <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" 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>
<input type="file" id="character_replace_file" accept="image/png" name="replace_avatar" hidden> <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden>
</div> </div>
<div name="Character List Panel" id="rm_characters_block" class="right_menu"> <div name="Character List Panel" id="rm_characters_block" class="right_menu">
<div id="charListFixedTop"> <div id="charListFixedTop">

View File

@ -8449,10 +8449,10 @@ async function connectAPISlash(_, text) {
/** /**
* Imports supported files dropped into the app window. * Imports supported files dropped into the app window.
* @param {File[]} files Array of files to process * @param {File[]} files Array of files to process
* @param {boolean?} preserveFileNames Whether to preserve original file names * @param {Map<File, string>} [data] Extra data to pass to the import function
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
export async function processDroppedFiles(files, preserveFileNames = false) { export async function processDroppedFiles(files, data = new Map()) {
const allowedMimeTypes = [ const allowedMimeTypes = [
'application/json', 'application/json',
'image/png', 'image/png',
@ -8469,7 +8469,8 @@ export async function processDroppedFiles(files, preserveFileNames = false) {
for (const file of files) { for (const file of files) {
const extension = file.name.split('.').pop().toLowerCase(); const extension = file.name.split('.').pop().toLowerCase();
if (allowedMimeTypes.includes(file.type) || allowedExtensions.includes(extension)) { if (allowedMimeTypes.includes(file.type) || allowedExtensions.includes(extension)) {
await importCharacter(file, preserveFileNames); const preservedName = data instanceof Map && data.get(file);
await importCharacter(file, preservedName);
} else { } else {
toastr.warning('Unsupported file type: ' + file.name); toastr.warning('Unsupported file type: ' + file.name);
} }
@ -8479,10 +8480,10 @@ export async function processDroppedFiles(files, preserveFileNames = false) {
/** /**
* Imports a character from a file. * Imports a character from a file.
* @param {File} file File to import * @param {File} file File to import
* @param {boolean?} preserveFileName Whether to preserve original file name * @param {string?} preserveFileName Whether to preserve original file name
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function importCharacter(file, preserveFileName = false) { async function importCharacter(file, preserveFileName = '') {
if (is_group_generating || is_send_press) { if (is_group_generating || is_send_press) {
toastr.error('Cannot import characters while generating. Stop the request and try again.', 'Import aborted'); toastr.error('Cannot import characters while generating. Stop the request and try again.', 'Import aborted');
throw new Error('Cannot import character while generating'); throw new Error('Cannot import character while generating');
@ -8498,7 +8499,7 @@ async function importCharacter(file, preserveFileName = false) {
const formData = new FormData(); const formData = new FormData();
formData.append('avatar', file); formData.append('avatar', file);
formData.append('file_type', format); formData.append('file_type', format);
formData.append('preserve_file_name', String(preserveFileName)); if (preserveFileName) formData.append('preserved_name', preserveFileName);
const data = await jQuery.ajax({ const data = await jQuery.ajax({
type: 'POST', type: 'POST',
@ -10637,10 +10638,12 @@ jQuery(async function () {
} }
try { try {
const cloneFile = new File([file], characters[this_chid].avatar, { type: file.type });
const chatFile = characters[this_chid]['chat']; const chatFile = characters[this_chid]['chat'];
await processDroppedFiles([cloneFile], true); const data = new Map();
data.set(file, characters[this_chid].avatar);
await processDroppedFiles([file], data);
await openCharacterChat(chatFile); await openCharacterChat(chatFile);
await fetch(getThumbnailUrl('avatar', characters[this_chid].avatar), { cache: 'no-cache' });
} catch { } catch {
toastr.error('Failed to replace the character card.', 'Something went wrong'); toastr.error('Failed to replace the character card.', 'Something went wrong');
} }

View File

@ -499,15 +499,16 @@ function convertWorldInfoToCharacterBook(name, entries) {
* Import a character from a YAML file. * Import a character from a YAML file.
* @param {string} uploadPath Path to the uploaded file * @param {string} uploadPath Path to the uploaded file
* @param {{ request: import('express').Request, response: import('express').Response }} context Express request and response objects * @param {{ request: import('express').Request, response: import('express').Response }} context Express request and response objects
* @param {string|undefined} preservedFileName Preserved file name
* @returns {Promise<string>} Internal name of the character * @returns {Promise<string>} Internal name of the character
*/ */
async function importFromYaml(uploadPath, context) { async function importFromYaml(uploadPath, context, preservedFileName) {
const fileText = fs.readFileSync(uploadPath, 'utf8'); const fileText = fs.readFileSync(uploadPath, 'utf8');
fs.rmSync(uploadPath); fs.rmSync(uploadPath);
const yamlData = yaml.parse(fileText); const yamlData = yaml.parse(fileText);
console.log('Importing from YAML'); console.log('Importing from YAML');
yamlData.name = sanitize(yamlData.name); yamlData.name = sanitize(yamlData.name);
const fileName = getPngName(yamlData.name, context.request.user.directories); const fileName = preservedFileName || getPngName(yamlData.name, context.request.user.directories);
let char = convertToV2({ let char = convertToV2({
'name': yamlData.name, 'name': yamlData.name,
'description': yamlData.context ?? '', 'description': yamlData.context ?? '',
@ -532,9 +533,10 @@ async function importFromYaml(uploadPath, context) {
* @param {string} uploadPath * @param {string} uploadPath
* @param {object} params * @param {object} params
* @param {import('express').Request} params.request * @param {import('express').Request} params.request
* @param {string|undefined} preservedFileName Preserved file name
* @returns {Promise<string>} Internal name of the character * @returns {Promise<string>} Internal name of the character
*/ */
async function importFromCharX(uploadPath, { request }) { async function importFromCharX(uploadPath, { request }, preservedFileName) {
const data = fs.readFileSync(uploadPath); const data = fs.readFileSync(uploadPath);
fs.rmSync(uploadPath); fs.rmSync(uploadPath);
console.log('Importing from CharX'); console.log('Importing from CharX');
@ -567,7 +569,7 @@ async function importFromCharX(uploadPath, { request }) {
unsetFavFlag(card); unsetFavFlag(card);
card['create_date'] = humanizedISO8601DateTime(); card['create_date'] = humanizedISO8601DateTime();
card.name = sanitize(card.name); card.name = sanitize(card.name);
const fileName = getPngName(card.name, request.user.directories); const fileName = preservedFileName || getPngName(card.name, request.user.directories);
const result = await writeCharacterData(avatar, JSON.stringify(card), fileName, request); const result = await writeCharacterData(avatar, JSON.stringify(card), fileName, request);
return result ? fileName : ''; return result ? fileName : '';
} }
@ -576,9 +578,10 @@ async function importFromCharX(uploadPath, { request }) {
* Import a character from a JSON file. * Import a character from a JSON file.
* @param {string} uploadPath Path to the uploaded file * @param {string} uploadPath Path to the uploaded file
* @param {{ request: import('express').Request, response: import('express').Response }} context Express request and response objects * @param {{ request: import('express').Request, response: import('express').Response }} context Express request and response objects
* @param {string|undefined} preservedFileName Preserved file name
* @returns {Promise<string>} Internal name of the character * @returns {Promise<string>} Internal name of the character
*/ */
async function importFromJson(uploadPath, { request }) { async function importFromJson(uploadPath, { request }, preservedFileName) {
const data = fs.readFileSync(uploadPath, 'utf8'); const data = fs.readFileSync(uploadPath, 'utf8');
fs.unlinkSync(uploadPath); fs.unlinkSync(uploadPath);
@ -590,7 +593,7 @@ async function importFromJson(uploadPath, { request }) {
unsetFavFlag(jsonData); unsetFavFlag(jsonData);
jsonData = readFromV2(jsonData); jsonData = readFromV2(jsonData);
jsonData['create_date'] = humanizedISO8601DateTime(); jsonData['create_date'] = humanizedISO8601DateTime();
const pngName = getPngName(jsonData.data?.name || jsonData.name, request.user.directories); const pngName = preservedFileName || getPngName(jsonData.data?.name || jsonData.name, request.user.directories);
const char = JSON.stringify(jsonData); const char = JSON.stringify(jsonData);
const result = await writeCharacterData(defaultAvatarPath, char, pngName, request); const result = await writeCharacterData(defaultAvatarPath, char, pngName, request);
return result ? pngName : ''; return result ? pngName : '';
@ -600,7 +603,7 @@ async function importFromJson(uploadPath, { request }) {
if (jsonData.creator_notes) { if (jsonData.creator_notes) {
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
} }
const pngName = getPngName(jsonData.name, request.user.directories); const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories);
let char = { let char = {
'name': jsonData.name, 'name': jsonData.name,
'description': jsonData.description ?? '', 'description': jsonData.description ?? '',
@ -626,7 +629,7 @@ async function importFromJson(uploadPath, { request }) {
if (jsonData.creator_notes) { if (jsonData.creator_notes) {
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
} }
const pngName = getPngName(jsonData.char_name, request.user.directories); const pngName = preservedFileName || getPngName(jsonData.char_name, request.user.directories);
let char = { let char = {
'name': jsonData.char_name, 'name': jsonData.char_name,
'description': jsonData.char_persona ?? '', 'description': jsonData.char_persona ?? '',
@ -1089,8 +1092,8 @@ function getPngName(file, directories) {
* @returns {string | undefined} - The preserved name if the request is valid, otherwise undefined * @returns {string | undefined} - The preserved name if the request is valid, otherwise undefined
*/ */
function getPreservedName(request) { function getPreservedName(request) {
return request.body.file_type === 'png' && request.body.preserve_file_name === 'true' && request.file?.originalname return typeof request.body.preserved_name === 'string' && request.body.preserved_name.length > 0
? path.parse(request.file.originalname).name ? path.parse(request.body.preserved_name).name
: undefined; : undefined;
} }
@ -1123,6 +1126,10 @@ router.post('/import', urlencodedParser, async function (request, response) {
return response.sendStatus(400); return response.sendStatus(400);
} }
if (preservedFileName) {
invalidateThumbnail(request.user.directories, 'avatar', `${preservedFileName}.png`);
}
response.send({ file_name: fileName }); response.send({ file_name: fileName });
} catch (err) { } catch (err) {
console.log(err); console.log(err);