diff --git a/public/index.html b/public/index.html index 95de45cc3..655c60cfa 100644 --- a/public/index.html +++ b/public/index.html @@ -2408,6 +2408,9 @@ + diff --git a/public/script.js b/public/script.js index ef806ae94..8b88827c0 100644 --- a/public/script.js +++ b/public/script.js @@ -29,6 +29,7 @@ import { world_info_match_whole_words, world_names, world_info_character_strategy, + importEmbeddedWorldInfo, } from "./scripts/world-info.js"; import { @@ -4835,11 +4836,35 @@ export function select_selected_character(chid) { const world = characters[chid]?.data?.extensions?.world; const worldSet = world && world_names.includes(world); $('#set_character_world').toggleClass('world_set', worldSet); + checkEmbeddedWorld(chid); $("#form_create").attr("actiontype", "editcharacter"); saveSettingsDebounced(); } +function checkEmbeddedWorld(chid) { + $('#import_character_info').hide(); + + if (chid === undefined) { + return; + } + + if (characters[chid]?.data?.character_book) { + $('#import_character_info').data('chid', chid).show(); + + // Only show the alert once per character + const checkKey = `AlertWI_${characters[chid].avatar}`; + if (!localStorage.getItem(checkKey) && !(characters[chid]?.data?.extensions?.world)) { + toastr.info( + 'To import and use it, select "Import Embedded World Info" in the Options menu.', + `${characters[chid].name} has an embedded World/Lorebook`, + { timeOut: 10000, extendedTimeOut: 20000, positionClass: 'toast-top-center' }, + ); + localStorage.setItem(checkKey, 1); + } + } +} + function select_rm_create() { menu_type = "create"; @@ -4890,6 +4915,7 @@ function select_rm_create() { $('#set_character_world').data('chid', undefined); $('#set_character_world').toggleClass('world_set', !!create_save.world); updateFavButtonState(false); + checkEmbeddedWorld(); $("#form_create").attr("actiontype", "createcharacter"); } @@ -7492,7 +7518,7 @@ $(document).ready(function () { }); }); - $("#char-management-dropdown").on('change', (e) => { + $("#char-management-dropdown").on('change', async (e) => { let target = $(e.target.selectedOptions).attr('id'); switch (target) { case 'set_character_world': @@ -7511,6 +7537,10 @@ $(document).ready(function () { $('#export_format_popup').toggle(); exportPopper.update(); break; + case 'import_character_info': + await importEmbeddedWorldInfo(); + saveCharacterDebounced(); + break; case 'delete_button': popup_type = "del_ch"; callPopup(` diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 58b9e9c65..6f745e7d0 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -274,10 +274,7 @@ function appendWorldEntry(name, data, entry) { }); commentInput.val(entry.comment).trigger("input"); - commentToggle.prop("checked", entry.selective).trigger("input"); - commentToggle.siblings(".checkbox_fancy").click(function () { - $(this).siblings("input").click(); - }); + commentToggle.prop("checked", entry.addMemo).trigger("input"); // content const countTokensDebounced = debounce(function (that, value) { @@ -784,6 +781,58 @@ function matchKeys(haystack, needle) { return false; } +function convertCharacterBook(characterBook) { + const result = { entries: {} }; + + characterBook.entries.forEach((entry, index) => { + result.entries[index] = { + uid: entry.id || index, + key: entry.keys, + keysecondary: entry.secondary_keys || [], + comment: entry.comment || "", + content: entry.content, + constant: entry.constant || false, + selective: entry.selective || false, + order: entry.insertion_order, + position: entry.position === "before_char" ? world_info_position.before : world_info_position.after, + disable: !entry.enabled, + addMemo: entry.comment ? true : false, + }; + }); + + return result; +} + +export async function importEmbeddedWorldInfo() { + const chid = $('#import_character_info').data('chid'); + + if (chid === undefined) { + return; + } + + const bookName = characters[chid]?.data?.character_book?.name || `${characters[chid]?.name}'s Lorebook`; + const confirmationText = (`

Are you sure you want to import "${bookName}"?

`) + (world_names.includes(bookName) ? 'It will overwrite the World/Lorebook with the same name.' : ''); + + const confirmation = await callPopup(confirmationText, 'confirm'); + + if (!confirmation) { + return; + } + + const convertedBook = convertCharacterBook(characters[chid].data.character_book); + + await saveWorldInfo(bookName, convertedBook, true); + await updateWorldInfoList(); + $('#character_world').val(bookName).trigger('change'); + + toastr.success(`The world "${bookName}" has been imported and linked to the character successfully.`, 'World/Lorebook imported'); + + const newIndex = world_names.indexOf(bookName); + if (newIndex >= 0) { + $("#world_editor_select").val(newIndex).trigger('change'); + } +} + jQuery(() => { $("#world_info").on('change', async function () { const selectedWorld = $("#world_info").find(":selected").val();