Support all file formats for replace/update
| @@ -4796,7 +4796,7 @@ | ||
| 4796 | 4796 | <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar"> |
| 4797 | 4797 | <input id="character_import_file_type" name="file_type" class="text_pole" maxlength="999" size="2" value="" autocomplete="off"> |
| 4798 | 4798 | </form> |
| 4799 | 4799 | <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden> |
| 4800 | 4800 | </div> |
| 4801 | 4801 | <div name="Character List Panel" id="rm_characters_block" class="right_menu"> |
| 4802 | 4802 | <div id="charListFixedTop"> |
| @@ -8449,10 +8449,10 @@ async function connectAPISlash(_, text) { | ||
| 8449 | 8449 | /** |
| 8450 | 8450 | * Imports supported files dropped into the app window. |
| 8451 | 8451 | * @param {File[]} files Array of files to process |
| 8452 | 8452 | * @param {boolean?Map<File, string>} preserveFileNames[data] WhetherExtra data to preservepass originalto filethe namesimport function |
| 8453 | 8453 | * @returns {Promise<void>} |
| 8454 | 8454 | */ |
| 8455 | 8455 | export async function processDroppedFiles(files, preserveFileNamesdata = falsenew Map()) { |
| 8456 | 8456 | const allowedMimeTypes = [ |
| 8457 | 8457 | 'application/json', |
| 8458 | 8458 | 'image/png', |
| @@ -8469,7 +8469,8 @@ export async function processDroppedFiles(files, preserveFileNames = false) { | ||
| 8469 | 8469 | for (const file of files) { |
| 8470 | 8470 | const extension = file.name.split('.').pop().toLowerCase(); |
| 8471 | 8471 | if (allowedMimeTypes.includes(file.type) || allowedExtensions.includes(extension)) { |
| 8472 | - await importCharacter(file, preserveFileNames); | |
| 8472 | + const preservedName = data instanceof Map && data.get(file); | |
| 8473 | + await importCharacter(file, preservedName); | |
| 8473 | 8474 | } else { |
| 8474 | 8475 | toastr.warning('Unsupported file type: ' + file.name); |
| 8475 | 8476 | } |
| @@ -8479,10 +8480,10 @@ export async function processDroppedFiles(files, preserveFileNames = false) { | ||
| 8479 | 8480 | /** |
| 8480 | 8481 | * Imports a character from a file. |
| 8481 | 8482 | * @param {File} file File to import |
| 8482 | 8483 | * @param {booleanstring?} preserveFileName Whether to preserve original file name |
| 8483 | 8484 | * @returns {Promise<void>} |
| 8484 | 8485 | */ |
| 8485 | 8486 | async function importCharacter(file, preserveFileName = false'') { |
| 8486 | 8487 | if (is_group_generating || is_send_press) { |
| 8487 | 8488 | toastr.error('Cannot import characters while generating. Stop the request and try again.', 'Import aborted'); |
| 8488 | 8489 | throw new Error('Cannot import character while generating'); |
| @@ -8498,7 +8499,7 @@ async function importCharacter(file, preserveFileName = false) { | ||
| 8498 | 8499 | const formData = new FormData(); |
| 8499 | 8500 | formData.append('avatar', file); |
| 8500 | 8501 | formData.append('file_type', format); |
| 8501 | 8502 | if (preserveFileName) formData.append('preserve_file_namepreserved_name', String(preserveFileName)); |
| 8502 | 8503 | |
| 8503 | 8504 | const data = await jQuery.ajax({ |
| 8504 | 8505 | type: 'POST', |
| @@ -10637,10 +10638,12 @@ jQuery(async function () { | ||
| 10637 | 10638 | } |
| 10638 | 10639 | |
| 10639 | 10640 | try { |
| 10640 | - const cloneFile = new File([file], characters[this_chid].avatar, { type: file.type }); | |
| 10641 | 10641 | const chatFile = characters[this_chid]['chat']; |
| 10642 | - await processDroppedFiles([cloneFile], true); | |
| 10642 | + const data = new Map(); | |
| 10643 | + data.set(file, characters[this_chid].avatar); | |
| 10644 | + await processDroppedFiles([file], data); | |
| 10643 | 10645 | await openCharacterChat(chatFile); |
| 10646 | + await fetch(getThumbnailUrl('avatar', characters[this_chid].avatar), { cache: 'no-cache' }); | |
| 10644 | 10647 | } catch { |
| 10645 | 10648 | toastr.error('Failed to replace the character card.', 'Something went wrong'); |
| 10646 | 10649 | } |
| @@ -499,15 +499,16 @@ function convertWorldInfoToCharacterBook(name, entries) { | ||
| 499 | 499 | * Import a character from a YAML file. |
| 500 | 500 | * @param {string} uploadPath Path to the uploaded file |
| 501 | 501 | * @param {{ request: import('express').Request, response: import('express').Response }} context Express request and response objects |
| 502 | + * @param {string|undefined} preservedFileName Preserved file name | |
| 502 | 503 | * @returns {Promise<string>} Internal name of the character |
| 503 | 504 | */ |
| 504 | 505 | async function importFromYaml(uploadPath, context, preservedFileName) { |
| 505 | 506 | const fileText = fs.readFileSync(uploadPath, 'utf8'); |
| 506 | 507 | fs.rmSync(uploadPath); |
| 507 | 508 | const yamlData = yaml.parse(fileText); |
| 508 | 509 | console.log('Importing from YAML'); |
| 509 | 510 | yamlData.name = sanitize(yamlData.name); |
| 510 | 511 | const fileName = preservedFileName || getPngName(yamlData.name, context.request.user.directories); |
| 511 | 512 | let char = convertToV2({ |
| 512 | 513 | 'name': yamlData.name, |
| 513 | 514 | 'description': yamlData.context ?? '', |
| @@ -532,9 +533,10 @@ async function importFromYaml(uploadPath, context) { | ||
| 532 | 533 | * @param {string} uploadPath |
| 533 | 534 | * @param {object} params |
| 534 | 535 | * @param {import('express').Request} params.request |
| 536 | + * @param {string|undefined} preservedFileName Preserved file name | |
| 535 | 537 | * @returns {Promise<string>} Internal name of the character |
| 536 | 538 | */ |
| 537 | 539 | async function importFromCharX(uploadPath, { request }, preservedFileName) { |
| 538 | 540 | const data = fs.readFileSync(uploadPath); |
| 539 | 541 | fs.rmSync(uploadPath); |
| 540 | 542 | console.log('Importing from CharX'); |
| @@ -567,7 +569,7 @@ async function importFromCharX(uploadPath, { request }) { | ||
| 567 | 569 | unsetFavFlag(card); |
| 568 | 570 | card['create_date'] = humanizedISO8601DateTime(); |
| 569 | 571 | card.name = sanitize(card.name); |
| 570 | 572 | const fileName = preservedFileName || getPngName(card.name, request.user.directories); |
| 571 | 573 | const result = await writeCharacterData(avatar, JSON.stringify(card), fileName, request); |
| 572 | 574 | return result ? fileName : ''; |
| 573 | 575 | } |
| @@ -576,9 +578,10 @@ async function importFromCharX(uploadPath, { request }) { | ||
| 576 | 578 | * Import a character from a JSON file. |
| 577 | 579 | * @param {string} uploadPath Path to the uploaded file |
| 578 | 580 | * @param {{ request: import('express').Request, response: import('express').Response }} context Express request and response objects |
| 581 | + * @param {string|undefined} preservedFileName Preserved file name | |
| 579 | 582 | * @returns {Promise<string>} Internal name of the character |
| 580 | 583 | */ |
| 581 | 584 | async function importFromJson(uploadPath, { request }, preservedFileName) { |
| 582 | 585 | const data = fs.readFileSync(uploadPath, 'utf8'); |
| 583 | 586 | fs.unlinkSync(uploadPath); |
| 584 | 587 | |
| @@ -590,7 +593,7 @@ async function importFromJson(uploadPath, { request }) { | ||
| 590 | 593 | unsetFavFlag(jsonData); |
| 591 | 594 | jsonData = readFromV2(jsonData); |
| 592 | 595 | jsonData['create_date'] = humanizedISO8601DateTime(); |
| 593 | 596 | const pngName = preservedFileName || getPngName(jsonData.data?.name || jsonData.name, request.user.directories); |
| 594 | 597 | const char = JSON.stringify(jsonData); |
| 595 | 598 | const result = await writeCharacterData(defaultAvatarPath, char, pngName, request); |
| 596 | 599 | return result ? pngName : ''; |
| @@ -600,7 +603,7 @@ async function importFromJson(uploadPath, { request }) { | ||
| 600 | 603 | if (jsonData.creator_notes) { |
| 601 | 604 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); |
| 602 | 605 | } |
| 603 | 606 | const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories); |
| 604 | 607 | let char = { |
| 605 | 608 | 'name': jsonData.name, |
| 606 | 609 | 'description': jsonData.description ?? '', |
| @@ -626,7 +629,7 @@ async function importFromJson(uploadPath, { request }) { | ||
| 626 | 629 | if (jsonData.creator_notes) { |
| 627 | 630 | jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', ''); |
| 628 | 631 | } |
| 629 | 632 | const pngName = preservedFileName || getPngName(jsonData.char_name, request.user.directories); |
| 630 | 633 | let char = { |
| 631 | 634 | 'name': jsonData.char_name, |
| 632 | 635 | 'description': jsonData.char_persona ?? '', |
| @@ -1089,8 +1092,8 @@ function getPngName(file, directories) { | ||
| 1089 | 1092 | * @returns {string | undefined} - The preserved name if the request is valid, otherwise undefined |
| 1090 | 1093 | */ |
| 1091 | 1094 | function getPreservedName(request) { |
| 1092 | 1095 | return typeof request.body.file_typepreserved_name === 'pngstring' && request.body.preserve_file_name === 'true'preserved_name.length &&> request.file?.originalname0 |
| 1093 | 1096 | ? path.parse(request.filebody.originalnamepreserved_name).name |
| 1094 | 1097 | : undefined; |
| 1095 | 1098 | } |
| 1096 | 1099 | |
| @@ -1123,6 +1126,10 @@ router.post('/import', urlencodedParser, async function (request, response) { | ||
| 1123 | 1126 | return response.sendStatus(400); |
| 1124 | 1127 | } |
| 1125 | 1128 | |
| 1129 | + if (preservedFileName) { | |
| 1130 | + invalidateThumbnail(request.user.directories, 'avatar', `${preservedFileName}.png`); | |
| 1131 | + } | |
| 1132 | + | |
| 1126 | 1133 | response.send({ file_name: fileName }); |
| 1127 | 1134 | } catch (err) { |
| 1128 | 1135 | console.log(err); |