Merge pull request #3146 from SillyTavern/persona-lorebook Persona lorebook
Signed| @@ -4846,6 +4846,8 @@ | |||
| 4846 | </div> | 4846 | </div> |
| 4847 | <div id="sync_name_button" class="menu_button fa-solid fa-sync" title="Click to set user name for all messages" data-i18n="[title]Click to set user name for all messages"> | 4847 | <div id="sync_name_button" class="menu_button fa-solid fa-sync" title="Click to set user name for all messages" data-i18n="[title]Click to set user name for all messages"> |
| 4848 | </div> | 4848 | </div> |
| 4849 | <div id="persona_lore_button" class="menu_button fa-solid fa-globe" title="Persona Lore Alt+Click to open the lorebook" data-i18n="[title]Persona Lore Alt+Click to open the lorebook"> | ||
| 4850 | </div> | ||
| 4849 | </div> | 4851 | </div> |
| 4850 | <div> | 4852 | <div> |
| 4851 | <h4 data-i18n="Persona Description">Persona Description</h4> | 4853 | <h4 data-i18n="Persona Description">Persona Description</h4> |
| @@ -4962,7 +4964,7 @@ | |||
| 4962 | <input type="hidden" id="fav_checkbox" name="fav" /> | 4964 | <input type="hidden" id="fav_checkbox" name="fav" /> |
| 4963 | <div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div> | 4965 | <div id="advanced_div" class="menu_button fa-solid fa-book " title="Advanced Definitions" data-i18n="[title]Advanced Definition"></div> |
| 4964 | <div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore Click to load Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div> | 4966 | <div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore Click to load Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div> |
| 4965 | <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div> | 4967 | <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore Alt+Click to open the lorebook" data-i18n="[title]Chat Lore Alt+Click to open the lorebook"></div> |
| 4966 | <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div> | 4968 | <div id="export_button" class="menu_button fa-solid fa-file-export " title="Export and Download" data-i18n="[title]Export and Download"></div> |
| 4967 | <!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> --> | 4969 | <!-- <div id="set_chat_scenario" class="menu_button fa-solid fa-scroll" title="Set a chat scenario override"></div> --> |
| 4968 | <!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> --> | 4970 | <!-- <div id="set_character_world" class="menu_button fa-solid fa-globe" title="Set a character World Info / Lorebook"></div> --> |
| @@ -5572,26 +5574,6 @@ | |||
| 5572 | </div> | 5574 | </div> |
| 5573 | </div> | 5575 | </div> |
| 5574 | <!-- templates for JS to reuse when needed --> | 5576 | <!-- templates for JS to reuse when needed --> |
| 5575 | <div id="chat_world_template" class="template_element"> | ||
| 5576 | <div class="chat_world range-block flexFlowColumn flex-container"> | ||
| 5577 | <div class="range-block-title"> | ||
| 5578 | <h4 data-i18n="Chat Lorebook"><!-- This data-i18n attribute is kept for backward compatibility, use the ones below when translating --> | ||
| 5579 | <span data-i18n="Chat Lorebook for">Chat Lorebook for</span> <span class="chat_name"></span> | ||
| 5580 | </h4> | ||
| 5581 | </div> | ||
| 5582 | <div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px"> | ||
| 5583 | <span data-i18n="chat_world_template_txt"> | ||
| 5584 | A selected World Info will be bound to this chat. When generating an AI reply, | ||
| 5585 | it will be combined with the entries from global and character lorebooks. | ||
| 5586 | </span> | ||
| 5587 | </div> | ||
| 5588 | <div class="range-block-range wide100p"> | ||
| 5589 | <select class="chat_world_info_selector wide100p"> | ||
| 5590 | <option value="">--- None ---</option> | ||
| 5591 | </select> | ||
| 5592 | </div> | ||
| 5593 | </div> | ||
| 5594 | </div> | ||
| 5595 | <div id="character_world_template" class="template_element"> | 5577 | <div id="character_world_template" class="template_element"> |
| 5596 | <div class="character_world range-block flexFlowColumn flex-container"> | 5578 | <div class="character_world range-block flexFlowColumn flex-container"> |
| 5597 | <div class="range-block-title"> | 5579 | <div class="range-block-title"> |
| @@ -21,8 +21,10 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor | |||
| 21 | import { debounce_timeout } from './constants.js'; | 21 | import { debounce_timeout } from './constants.js'; |
| 22 | import { FILTER_TYPES, FilterHelper } from './filters.js'; | 22 | import { FILTER_TYPES, FilterHelper } from './filters.js'; |
| 23 | import { selected_group } from './group-chats.js'; | 23 | import { selected_group } from './group-chats.js'; |
| 24 | import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js'; | 24 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| 25 | import { t } from './i18n.js'; | 25 | import { t } from './i18n.js'; |
| 26 | import { openWorldInfoEditor, world_names } from './world-info.js'; | ||
| 27 | import { renderTemplateAsync } from './templates.js'; | ||
| 26 | 28 | ||
| 27 | let savePersonasPage = 0; | 29 | let savePersonasPage = 0; |
| 28 | const GRID_STORAGE_KEY = 'Personas_GridView'; | 30 | const GRID_STORAGE_KEY = 'Personas_GridView'; |
| @@ -375,6 +377,7 @@ export function initPersona(avatarId, personaName, personaDescription) { | |||
| 375 | position: persona_description_positions.IN_PROMPT, | 377 | position: persona_description_positions.IN_PROMPT, |
| 376 | depth: DEFAULT_DEPTH, | 378 | depth: DEFAULT_DEPTH, |
| 377 | role: DEFAULT_ROLE, | 379 | role: DEFAULT_ROLE, |
| 380 | lorebook: '', | ||
| 378 | }; | 381 | }; |
| 379 | 382 | ||
| 380 | saveSettingsDebounced(); | 383 | saveSettingsDebounced(); |
| @@ -418,6 +421,7 @@ export async function convertCharacterToPersona(characterId = null) { | |||
| 418 | position: persona_description_positions.IN_PROMPT, | 421 | position: persona_description_positions.IN_PROMPT, |
| 419 | depth: DEFAULT_DEPTH, | 422 | depth: DEFAULT_DEPTH, |
| 420 | role: DEFAULT_ROLE, | 423 | role: DEFAULT_ROLE, |
| 424 | lorebook: '', | ||
| 421 | }; | 425 | }; |
| 422 | 426 | ||
| 423 | // If the user is currently using this persona, update the description | 427 | // If the user is currently using this persona, update the description |
| @@ -461,6 +465,7 @@ export function setPersonaDescription() { | |||
| 461 | .val(power_user.persona_description_role) | 465 | .val(power_user.persona_description_role) |
| 462 | .find(`option[value="${power_user.persona_description_role}"]`) | 466 | .find(`option[value="${power_user.persona_description_role}"]`) |
| 463 | .prop('selected', String(true)); | 467 | .prop('selected', String(true)); |
| 468 | $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); | ||
| 464 | countPersonaDescriptionTokens(); | 469 | countPersonaDescriptionTokens(); |
| 465 | } | 470 | } |
| 466 | 471 | ||
| @@ -490,6 +495,7 @@ async function updatePersonaNameIfExists(avatarId, newName) { | |||
| 490 | position: persona_description_positions.IN_PROMPT, | 495 | position: persona_description_positions.IN_PROMPT, |
| 491 | depth: DEFAULT_DEPTH, | 496 | depth: DEFAULT_DEPTH, |
| 492 | role: DEFAULT_ROLE, | 497 | role: DEFAULT_ROLE, |
| 498 | lorebook: '', | ||
| 493 | }; | 499 | }; |
| 494 | console.log(`Created persona name for ${avatarId} as ${newName}`); | 500 | console.log(`Created persona name for ${avatarId} as ${newName}`); |
| 495 | } | 501 | } |
| @@ -535,6 +541,7 @@ async function bindUserNameToPersona(e) { | |||
| 535 | position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.IN_PROMPT, | 541 | position: isCurrentPersona ? power_user.persona_description_position : persona_description_positions.IN_PROMPT, |
| 536 | depth: isCurrentPersona ? power_user.persona_description_depth : DEFAULT_DEPTH, | 542 | depth: isCurrentPersona ? power_user.persona_description_depth : DEFAULT_DEPTH, |
| 537 | role: isCurrentPersona ? power_user.persona_description_role : DEFAULT_ROLE, | 543 | role: isCurrentPersona ? power_user.persona_description_role : DEFAULT_ROLE, |
| 544 | lorebook: isCurrentPersona ? power_user.persona_description_lorebook : '', | ||
| 538 | }; | 545 | }; |
| 539 | } | 546 | } |
| 540 | 547 | ||
| @@ -579,12 +586,20 @@ function selectCurrentPersona() { | |||
| 579 | power_user.persona_description_position = descriptor.position ?? persona_description_positions.IN_PROMPT; | 586 | power_user.persona_description_position = descriptor.position ?? persona_description_positions.IN_PROMPT; |
| 580 | power_user.persona_description_depth = descriptor.depth ?? DEFAULT_DEPTH; | 587 | power_user.persona_description_depth = descriptor.depth ?? DEFAULT_DEPTH; |
| 581 | power_user.persona_description_role = descriptor.role ?? DEFAULT_ROLE; | 588 | power_user.persona_description_role = descriptor.role ?? DEFAULT_ROLE; |
| 589 | power_user.persona_description_lorebook = descriptor.lorebook ?? ''; | ||
| 582 | } else { | 590 | } else { |
| 583 | power_user.persona_description = ''; | 591 | power_user.persona_description = ''; |
| 584 | power_user.persona_description_position = persona_description_positions.IN_PROMPT; | 592 | power_user.persona_description_position = persona_description_positions.IN_PROMPT; |
| 585 | power_user.persona_description_depth = DEFAULT_DEPTH; | 593 | power_user.persona_description_depth = DEFAULT_DEPTH; |
| 586 | power_user.persona_description_role = DEFAULT_ROLE; | 594 | power_user.persona_description_role = DEFAULT_ROLE; |
| 587 | power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT, depth: DEFAULT_DEPTH, role: DEFAULT_ROLE }; | 595 | power_user.persona_description_lorebook = ''; |
| 596 | power_user.persona_descriptions[user_avatar] = { | ||
| 597 | description: '', | ||
| 598 | position: persona_description_positions.IN_PROMPT, | ||
| 599 | depth: DEFAULT_DEPTH, | ||
| 600 | role: DEFAULT_ROLE, | ||
| 601 | lorebook: '', | ||
| 602 | }; | ||
| 588 | } | 603 | } |
| 589 | 604 | ||
| 590 | setPersonaDescription(); | 605 | setPersonaDescription(); |
| @@ -652,6 +667,7 @@ async function lockPersona() { | |||
| 652 | position: persona_description_positions.IN_PROMPT, | 667 | position: persona_description_positions.IN_PROMPT, |
| 653 | depth: DEFAULT_DEPTH, | 668 | depth: DEFAULT_DEPTH, |
| 654 | role: DEFAULT_ROLE, | 669 | role: DEFAULT_ROLE, |
| 670 | lorebook: '', | ||
| 655 | }; | 671 | }; |
| 656 | } | 672 | } |
| 657 | 673 | ||
| @@ -731,6 +747,7 @@ function onPersonaDescriptionInput() { | |||
| 731 | position: Number($('#persona_description_position').find(':selected').val()), | 747 | position: Number($('#persona_description_position').find(':selected').val()), |
| 732 | depth: Number($('#persona_depth_value').val()), | 748 | depth: Number($('#persona_depth_value').val()), |
| 733 | role: Number($('#persona_depth_role').find(':selected').val()), | 749 | role: Number($('#persona_depth_role').find(':selected').val()), |
| 750 | lorebook: '', | ||
| 734 | }; | 751 | }; |
| 735 | power_user.persona_descriptions[user_avatar] = object; | 752 | power_user.persona_descriptions[user_avatar] = object; |
| 736 | } | 753 | } |
| @@ -766,6 +783,52 @@ function onPersonaDescriptionDepthRoleInput() { | |||
| 766 | saveSettingsDebounced(); | 783 | saveSettingsDebounced(); |
| 767 | } | 784 | } |
| 768 | 785 | ||
| 786 | /** | ||
| 787 | * Opens a popup to set the lorebook for the current persona. | ||
| 788 | * @param {PointerEvent} event Click event | ||
| 789 | */ | ||
| 790 | async function onPersonaLoreButtonClick(event) { | ||
| 791 | const personaName = power_user.personas[user_avatar]; | ||
| 792 | const selectedLorebook = power_user.persona_description_lorebook; | ||
| 793 | |||
| 794 | if (!personaName) { | ||
| 795 | toastr.warning(t`You must bind a name to this persona before you can set a lorebook.`, t`Persona name not set`); | ||
| 796 | return; | ||
| 797 | } | ||
| 798 | |||
| 799 | if (event.altKey && selectedLorebook) { | ||
| 800 | openWorldInfoEditor(selectedLorebook); | ||
| 801 | return; | ||
| 802 | } | ||
| 803 | |||
| 804 | const template = $(await renderTemplateAsync('personaLorebook')); | ||
| 805 | |||
| 806 | const worldSelect = template.find('select'); | ||
| 807 | template.find('.persona_name').text(personaName); | ||
| 808 | |||
| 809 | for (const worldName of world_names) { | ||
| 810 | const option = document.createElement('option'); | ||
| 811 | option.value = worldName; | ||
| 812 | option.innerText = worldName; | ||
| 813 | option.selected = selectedLorebook === worldName; | ||
| 814 | worldSelect.append(option); | ||
| 815 | } | ||
| 816 | |||
| 817 | worldSelect.on('change', function () { | ||
| 818 | power_user.persona_description_lorebook = String($(this).val()); | ||
| 819 | |||
| 820 | if (power_user.personas[user_avatar]) { | ||
| 821 | const object = getOrCreatePersonaDescriptor(); | ||
| 822 | object.lorebook = power_user.persona_description_lorebook; | ||
| 823 | } | ||
| 824 | |||
| 825 | $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); | ||
| 826 | saveSettingsDebounced(); | ||
| 827 | }); | ||
| 828 | |||
| 829 | await callGenericPopup(template, POPUP_TYPE.TEXT); | ||
| 830 | } | ||
| 831 | |||
| 769 | function onPersonaDescriptionPositionInput() { | 832 | function onPersonaDescriptionPositionInput() { |
| 770 | power_user.persona_description_position = Number( | 833 | power_user.persona_description_position = Number( |
| 771 | $('#persona_description_position').find(':selected').val(), | 834 | $('#persona_description_position').find(':selected').val(), |
| @@ -789,6 +852,7 @@ function getOrCreatePersonaDescriptor() { | |||
| 789 | position: power_user.persona_description_position, | 852 | position: power_user.persona_description_position, |
| 790 | depth: power_user.persona_description_depth, | 853 | depth: power_user.persona_description_depth, |
| 791 | role: power_user.persona_description_role, | 854 | role: power_user.persona_description_role, |
| 855 | lorebook: power_user.persona_description_lorebook, | ||
| 792 | }; | 856 | }; |
| 793 | power_user.persona_descriptions[user_avatar] = object; | 857 | power_user.persona_descriptions[user_avatar] = object; |
| 794 | } | 858 | } |
| @@ -1038,6 +1102,7 @@ async function duplicatePersona(avatarId) { | |||
| 1038 | position: descriptor?.position ?? persona_description_positions.IN_PROMPT, | 1102 | position: descriptor?.position ?? persona_description_positions.IN_PROMPT, |
| 1039 | depth: descriptor?.depth ?? DEFAULT_DEPTH, | 1103 | depth: descriptor?.depth ?? DEFAULT_DEPTH, |
| 1040 | role: descriptor?.role ?? DEFAULT_ROLE, | 1104 | role: descriptor?.role ?? DEFAULT_ROLE, |
| 1105 | lorebook: descriptor?.lorebook ?? '', | ||
| 1041 | }; | 1106 | }; |
| 1042 | 1107 | ||
| 1043 | await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); | 1108 | await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); |
| @@ -1055,6 +1120,7 @@ export function initPersonas() { | |||
| 1055 | $('#persona_description_position').on('input', onPersonaDescriptionPositionInput); | 1120 | $('#persona_description_position').on('input', onPersonaDescriptionPositionInput); |
| 1056 | $('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput); | 1121 | $('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput); |
| 1057 | $('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput); | 1122 | $('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput); |
| 1123 | $('#persona_lore_button').on('click', onPersonaLoreButtonClick); | ||
| 1058 | $('#personas_backup').on('click', onBackupPersonas); | 1124 | $('#personas_backup').on('click', onBackupPersonas); |
| 1059 | $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); | 1125 | $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); |
| 1060 | $('#personas_restore_input').on('change', onPersonasRestoreInput); | 1126 | $('#personas_restore_input').on('change', onPersonasRestoreInput); |
| @@ -261,6 +261,7 @@ let power_user = { | |||
| 261 | persona_description_position: persona_description_positions.IN_PROMPT, | 261 | persona_description_position: persona_description_positions.IN_PROMPT, |
| 262 | persona_description_role: 0, | 262 | persona_description_role: 0, |
| 263 | persona_description_depth: 2, | 263 | persona_description_depth: 2, |
| 264 | persona_description_lorebook: '', | ||
| 264 | persona_show_notifications: true, | 265 | persona_show_notifications: true, |
| 265 | persona_sort_order: 'asc', | 266 | persona_sort_order: 'asc', |
| 266 | 267 | ||
| @@ -0,0 +1,18 @@ | |||
| 1 | <div class="chat_world range-block flexFlowColumn flex-container"> | ||
| 2 | <div class="range-block-title"> | ||
| 3 | <h4 data-i18n="Chat Lorebook"><!-- This data-i18n attribute is kept for backward compatibility, use the ones below when translating --> | ||
| 4 | <span data-i18n="Chat Lorebook for">Chat Lorebook for</span> <span class="chat_name"></span> | ||
| 5 | </h4> | ||
| 6 | </div> | ||
| 7 | <div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px"> | ||
| 8 | <span data-i18n="chat_world_template_txt"> | ||
| 9 | A selected World Info will be bound to this chat. When generating an AI reply, | ||
| 10 | it will be combined with the entries from global and character lorebooks. | ||
| 11 | </span> | ||
| 12 | </div> | ||
| 13 | <div class="range-block-range wide100p"> | ||
| 14 | <select class="chat_world_info_selector wide100p"> | ||
| 15 | <option value="">--- None ---</option> | ||
| 16 | </select> | ||
| 17 | </div> | ||
| 18 | </div> | ||
| @@ -0,0 +1,18 @@ | |||
| 1 | <div class="persona_world range-block flexFlowColumn flex-container"> | ||
| 2 | <div class="range-block-title"> | ||
| 3 | <h4> | ||
| 4 | <span data-i18n="PErsona Lorebook for">Persona Lorebook for</span> <span class="persona_name"></span> | ||
| 5 | </h4> | ||
| 6 | </div> | ||
| 7 | <div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px"> | ||
| 8 | <span data-i18n="persona_world_template_txt"> | ||
| 9 | A selected World Info will be bound to this persona. When generating an AI reply, | ||
| 10 | it will be combined with the entries from global, character and chat lorebooks. | ||
| 11 | </span> | ||
| 12 | </div> | ||
| 13 | <div class="range-block-range wide100p"> | ||
| 14 | <select class="persona_world_info_selector wide100p"> | ||
| 15 | <option value="">--- None ---</option> | ||
| 16 | </select> | ||
| 17 | </div> | ||
| 18 | </div> | ||
| @@ -3548,6 +3548,11 @@ async function getCharacterLore() { | |||
| 3548 | continue; | 3548 | continue; |
| 3549 | } | 3549 | } |
| 3550 | 3550 | ||
| 3551 | if (power_user.persona_description_lorebook === worldName) { | ||
| 3552 | console.debug(`[WI] Character ${name}'s world ${worldName} is already activated in persona lore! Skipping...`); | ||
| 3553 | continue; | ||
| 3554 | } | ||
| 3555 | |||
| 3551 | const data = await loadWorldInfo(worldName); | 3556 | const data = await loadWorldInfo(worldName); |
| 3552 | const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : []; | 3557 | const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : []; |
| 3553 | entries = entries.concat(newEntries); | 3558 | entries = entries.concat(newEntries); |
| @@ -3598,11 +3603,45 @@ async function getChatLore() { | |||
| 3598 | return entries; | 3603 | return entries; |
| 3599 | } | 3604 | } |
| 3600 | 3605 | ||
| 3606 | async function getPersonaLore() { | ||
| 3607 | const chatWorld = chat_metadata[METADATA_KEY]; | ||
| 3608 | const personaWorld = power_user.persona_description_lorebook; | ||
| 3609 | |||
| 3610 | if (!personaWorld) { | ||
| 3611 | return []; | ||
| 3612 | } | ||
| 3613 | |||
| 3614 | if (chatWorld === personaWorld) { | ||
| 3615 | console.debug(`[WI] Persona world ${personaWorld} is already activated in chat world! Skipping...`); | ||
| 3616 | return []; | ||
| 3617 | } | ||
| 3618 | |||
| 3619 | if (selected_world_info.includes(personaWorld)) { | ||
| 3620 | console.debug(`[WI] Persona world ${personaWorld} is already activated in global world info! Skipping...`); | ||
| 3621 | return []; | ||
| 3622 | } | ||
| 3623 | |||
| 3624 | const data = await loadWorldInfo(personaWorld); | ||
| 3625 | const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: personaWorld, ...rest })) : []; | ||
| 3626 | |||
| 3627 | console.debug(`[WI] Persona lore has ${entries.length} entries`, [personaWorld]); | ||
| 3628 | |||
| 3629 | return entries; | ||
| 3630 | } | ||
| 3631 | |||
| 3601 | export async function getSortedEntries() { | 3632 | export async function getSortedEntries() { |
| 3602 | try { | 3633 | try { |
| 3603 | const globalLore = await getGlobalLore(); | 3634 | const [ |
| 3604 | const characterLore = await getCharacterLore(); | 3635 | globalLore, |
| 3605 | const chatLore = await getChatLore(); | 3636 | characterLore, |
| 3637 | chatLore, | ||
| 3638 | personaLore, | ||
| 3639 | ] = await Promise.all([ | ||
| 3640 | getGlobalLore(), | ||
| 3641 | getCharacterLore(), | ||
| 3642 | getChatLore(), | ||
| 3643 | getPersonaLore(), | ||
| 3644 | ]); | ||
| 3606 | 3645 | ||
| 3607 | let entries; | 3646 | let entries; |
| 3608 | 3647 | ||
| @@ -3622,8 +3661,8 @@ export async function getSortedEntries() { | |||
| 3622 | break; | 3661 | break; |
| 3623 | } | 3662 | } |
| 3624 | 3663 | ||
| 3625 | // Chat lore always goes first | 3664 | // Chat lore always goes first, then persona lore, then the rest |
| 3626 | entries = [...chatLore.sort(sortFn), ...entries]; | 3665 | entries = [...chatLore.sort(sortFn), ...personaLore.sort(sortFn), ...entries]; |
| 3627 | 3666 | ||
| 3628 | // Calculate hash and parse decorators. Split maps to preserve old hashes. | 3667 | // Calculate hash and parse decorators. Split maps to preserve old hashes. |
| 3629 | entries = entries.map((entry) => { | 3668 | entries = entries.map((entry) => { |
| @@ -4816,9 +4855,33 @@ export async function importWorldInfo(file) { | |||
| 4816 | }); | 4855 | }); |
| 4817 | } | 4856 | } |
| 4818 | 4857 | ||
| 4819 | export function assignLorebookToChat() { | 4858 | /** |
| 4859 | * Forces the world info editor to open on a specific world. | ||
| 4860 | * @param {string} worldName The name of the world to open | ||
| 4861 | */ | ||
| 4862 | export function openWorldInfoEditor(worldName) { | ||
| 4863 | console.log(`Opening lorebook for ${worldName}`); | ||
| 4864 | if (!$('#WorldInfo').is(':visible')) { | ||
| 4865 | $('#WIDrawerIcon').trigger('click'); | ||
| 4866 | } | ||
| 4867 | const index = world_names.indexOf(worldName); | ||
| 4868 | $('#world_editor_select').val(index).trigger('change'); | ||
| 4869 | } | ||
| 4870 | |||
| 4871 | /** | ||
| 4872 | * Assigns a lorebook to the current chat. | ||
| 4873 | * @param {PointerEvent} event Pointer event | ||
| 4874 | * @returns {Promise<void>} | ||
| 4875 | */ | ||
| 4876 | export async function assignLorebookToChat(event) { | ||
| 4820 | const selectedName = chat_metadata[METADATA_KEY]; | 4877 | const selectedName = chat_metadata[METADATA_KEY]; |
| 4821 | const template = $('#chat_world_template .chat_world').clone(); | 4878 | |
| 4879 | if (selectedName && event.altKey) { | ||
| 4880 | openWorldInfoEditor(selectedName); | ||
| 4881 | return; | ||
| 4882 | } | ||
| 4883 | |||
| 4884 | const template = $(await renderTemplateAsync('chatLorebook')); | ||
| 4822 | 4885 | ||
| 4823 | const worldSelect = template.find('select'); | 4886 | const worldSelect = template.find('select'); |
| 4824 | const chatName = template.find('.chat_name'); | 4887 | const chatName = template.find('.chat_name'); |
| @@ -4846,7 +4909,7 @@ export function assignLorebookToChat() { | |||
| 4846 | saveMetadata(); | 4909 | saveMetadata(); |
| 4847 | }); | 4910 | }); |
| 4848 | 4911 | ||
| 4849 | callPopup(template, 'text'); | 4912 | return callGenericPopup(template, POPUP_TYPE.TEXT); |
| 4850 | } | 4913 | } |
| 4851 | 4914 | ||
| 4852 | jQuery(() => { | 4915 | jQuery(() => { |
| @@ -4997,11 +5060,7 @@ jQuery(() => { | |||
| 4997 | const worldName = characters[chid]?.data?.extensions?.world; | 5060 | const worldName = characters[chid]?.data?.extensions?.world; |
| 4998 | const hasEmbed = checkEmbeddedWorld(chid); | 5061 | const hasEmbed = checkEmbeddedWorld(chid); |
| 4999 | if (worldName && world_names.includes(worldName) && !event.shiftKey) { | 5062 | if (worldName && world_names.includes(worldName) && !event.shiftKey) { |
| 5000 | if (!$('#WorldInfo').is(':visible')) { | 5063 | openWorldInfoEditor(worldName); |
| 5001 | $('#WIDrawerIcon').trigger('click'); | ||
| 5002 | } | ||
| 5003 | const index = world_names.indexOf(worldName); | ||
| 5004 | $('#world_editor_select').val(index).trigger('change'); | ||
| 5005 | } else if (hasEmbed && !event.shiftKey) { | 5064 | } else if (hasEmbed && !event.shiftKey) { |
| 5006 | await importEmbeddedWorldInfo(); | 5065 | await importEmbeddedWorldInfo(); |
| 5007 | saveCharacterDebounced(); | 5066 | saveCharacterDebounced(); |
| @@ -2788,6 +2788,10 @@ select option:not(:checked) { | |||
| 2788 | width: max-content; | 2788 | width: max-content; |
| 2789 | } | 2789 | } |
| 2790 | 2790 | ||
| 2791 | #persona-management-block .menu_button { | ||
| 2792 | filter: grayscale(0.5); | ||
| 2793 | } | ||
| 2794 | |||
| 2791 | input[type=search]::-webkit-search-cancel-button { | 2795 | input[type=search]::-webkit-search-cancel-button { |
| 2792 | -webkit-appearance: none; | 2796 | -webkit-appearance: none; |
| 2793 | height: 1em; | 2797 | height: 1em; |