[wip] Persona lorebook
| @@ -4813,6 +4813,8 @@ | |||
| 4813 | </div> | 4813 | </div> |
| 4814 | <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"> | 4814 | <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"> |
| 4815 | </div> | 4815 | </div> |
| 4816 | <div id="persona_lore_button" class="menu_button fa-solid fa-globe" title="Persona Lore" data-i18n="[title]Persona Lore"> | ||
| 4817 | </div> | ||
| 4816 | </div> | 4818 | </div> |
| 4817 | <div> | 4819 | <div> |
| 4818 | <h4 data-i18n="Persona Description">Persona Description</h4> | 4820 | <h4 data-i18n="Persona Description">Persona Description</h4> |
| @@ -5539,26 +5541,6 @@ | |||
| 5539 | </div> | 5541 | </div> |
| 5540 | </div> | 5542 | </div> |
| 5541 | <!-- templates for JS to reuse when needed --> | 5543 | <!-- templates for JS to reuse when needed --> |
| 5542 | <div id="chat_world_template" class="template_element"> | ||
| 5543 | <div class="chat_world range-block flexFlowColumn flex-container"> | ||
| 5544 | <div class="range-block-title"> | ||
| 5545 | <h4 data-i18n="Chat Lorebook"><!-- This data-i18n attribute is kept for backward compatibility, use the ones below when translating --> | ||
| 5546 | <span data-i18n="Chat Lorebook for">Chat Lorebook for</span> <span class="chat_name"></span> | ||
| 5547 | </h4> | ||
| 5548 | </div> | ||
| 5549 | <div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px"> | ||
| 5550 | <span data-i18n="chat_world_template_txt"> | ||
| 5551 | A selected World Info will be bound to this chat. When generating an AI reply, | ||
| 5552 | it will be combined with the entries from global and character lorebooks. | ||
| 5553 | </span> | ||
| 5554 | </div> | ||
| 5555 | <div class="range-block-range wide100p"> | ||
| 5556 | <select class="chat_world_info_selector wide100p"> | ||
| 5557 | <option value="">--- None ---</option> | ||
| 5558 | </select> | ||
| 5559 | </div> | ||
| 5560 | </div> | ||
| 5561 | </div> | ||
| 5562 | <div id="character_world_template" class="template_element"> | 5544 | <div id="character_world_template" class="template_element"> |
| 5563 | <div class="character_world range-block flexFlowColumn flex-container"> | 5545 | <div class="character_world range-block flexFlowColumn flex-container"> |
| 5564 | <div class="range-block-title"> | 5546 | <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 { 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,43 @@ function onPersonaDescriptionDepthRoleInput() { | |||
| 766 | saveSettingsDebounced(); | 783 | saveSettingsDebounced(); |
| 767 | } | 784 | } |
| 768 | 785 | ||
| 786 | async function onPersonaLoreButtonClick() { | ||
| 787 | const personaName = power_user.personas[user_avatar]; | ||
| 788 | const selectedLorebook = power_user.persona_description_lorebook; | ||
| 789 | |||
| 790 | if (!personaName) { | ||
| 791 | toastr.warning(t`You must bind a name to this persona before you can set a lorebook.`, t`Persona name not set`); | ||
| 792 | return; | ||
| 793 | } | ||
| 794 | |||
| 795 | const template = $(await renderTemplateAsync('personaLorebook')); | ||
| 796 | |||
| 797 | const worldSelect = template.find('select'); | ||
| 798 | template.find('.persona_name').text(personaName); | ||
| 799 | |||
| 800 | for (const worldName of world_names) { | ||
| 801 | const option = document.createElement('option'); | ||
| 802 | option.value = worldName; | ||
| 803 | option.innerText = worldName; | ||
| 804 | option.selected = selectedLorebook === worldName; | ||
| 805 | worldSelect.append(option); | ||
| 806 | } | ||
| 807 | |||
| 808 | worldSelect.on('change', function () { | ||
| 809 | power_user.persona_description_lorebook = String($(this).val()); | ||
| 810 | |||
| 811 | if (power_user.personas[user_avatar]) { | ||
| 812 | const object = getOrCreatePersonaDescriptor(); | ||
| 813 | object.lorebook = power_user.persona_description_lorebook; | ||
| 814 | } | ||
| 815 | |||
| 816 | $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook); | ||
| 817 | saveSettingsDebounced(); | ||
| 818 | }); | ||
| 819 | |||
| 820 | await callGenericPopup(template, POPUP_TYPE.TEXT); | ||
| 821 | } | ||
| 822 | |||
| 769 | function onPersonaDescriptionPositionInput() { | 823 | function onPersonaDescriptionPositionInput() { |
| 770 | power_user.persona_description_position = Number( | 824 | power_user.persona_description_position = Number( |
| 771 | $('#persona_description_position').find(':selected').val(), | 825 | $('#persona_description_position').find(':selected').val(), |
| @@ -789,6 +843,7 @@ function getOrCreatePersonaDescriptor() { | |||
| 789 | position: power_user.persona_description_position, | 843 | position: power_user.persona_description_position, |
| 790 | depth: power_user.persona_description_depth, | 844 | depth: power_user.persona_description_depth, |
| 791 | role: power_user.persona_description_role, | 845 | role: power_user.persona_description_role, |
| 846 | lorebook: power_user.persona_description_lorebook, | ||
| 792 | }; | 847 | }; |
| 793 | power_user.persona_descriptions[user_avatar] = object; | 848 | power_user.persona_descriptions[user_avatar] = object; |
| 794 | } | 849 | } |
| @@ -1038,6 +1093,7 @@ async function duplicatePersona(avatarId) { | |||
| 1038 | position: descriptor?.position ?? persona_description_positions.IN_PROMPT, | 1093 | position: descriptor?.position ?? persona_description_positions.IN_PROMPT, |
| 1039 | depth: descriptor?.depth ?? DEFAULT_DEPTH, | 1094 | depth: descriptor?.depth ?? DEFAULT_DEPTH, |
| 1040 | role: descriptor?.role ?? DEFAULT_ROLE, | 1095 | role: descriptor?.role ?? DEFAULT_ROLE, |
| 1096 | lorebook: descriptor?.lorebook ?? '', | ||
| 1041 | }; | 1097 | }; |
| 1042 | 1098 | ||
| 1043 | await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); | 1099 | await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); |
| @@ -1055,6 +1111,7 @@ export function initPersonas() { | |||
| 1055 | $('#persona_description_position').on('input', onPersonaDescriptionPositionInput); | 1111 | $('#persona_description_position').on('input', onPersonaDescriptionPositionInput); |
| 1056 | $('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput); | 1112 | $('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput); |
| 1057 | $('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput); | 1113 | $('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput); |
| 1114 | $('#persona_lore_button').on('click', onPersonaLoreButtonClick); | ||
| 1058 | $('#personas_backup').on('click', onBackupPersonas); | 1115 | $('#personas_backup').on('click', onBackupPersonas); |
| 1059 | $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); | 1116 | $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); |
| 1060 | $('#personas_restore_input').on('change', onPersonasRestoreInput); | 1117 | $('#personas_restore_input').on('change', onPersonasRestoreInput); |
| @@ -262,6 +262,7 @@ let power_user = { | |||
| 262 | persona_description_position: persona_description_positions.IN_PROMPT, | 262 | persona_description_position: persona_description_positions.IN_PROMPT, |
| 263 | persona_description_role: 0, | 263 | persona_description_role: 0, |
| 264 | persona_description_depth: 2, | 264 | persona_description_depth: 2, |
| 265 | persona_description_lorebook: '', | ||
| 265 | persona_show_notifications: true, | 266 | persona_show_notifications: true, |
| 266 | persona_sort_order: 'asc', | 267 | persona_sort_order: 'asc', |
| 267 | 268 | ||
| @@ -1925,7 +1926,7 @@ export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) | |||
| 1925 | const mappedData = data.map(x => ({ | 1926 | const mappedData = data.map(x => ({ |
| 1926 | key: x, | 1927 | key: x, |
| 1927 | name: power_user.personas[x] ?? '', | 1928 | name: power_user.personas[x] ?? '', |
| 1928 | description: power_user.persona_descriptions[x]?.description ?? '' | 1929 | description: power_user.persona_descriptions[x]?.description ?? '', |
| 1929 | })); | 1930 | })); |
| 1930 | 1931 | ||
| 1931 | const keys = [ | 1932 | const keys = [ |
| @@ -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,9 @@ export async function importWorldInfo(file) { | |||
| 4816 | }); | 4855 | }); |
| 4817 | } | 4856 | } |
| 4818 | 4857 | ||
| 4819 | export function assignLorebookToChat() { | 4858 | export async function assignLorebookToChat() { |
| 4820 | const selectedName = chat_metadata[METADATA_KEY]; | 4859 | const selectedName = chat_metadata[METADATA_KEY]; |
| 4821 | const template = $('#chat_world_template .chat_world').clone(); | 4860 | const template = $(await renderTemplateAsync('chatLorebook')); |
| 4822 | 4861 | ||
| 4823 | const worldSelect = template.find('select'); | 4862 | const worldSelect = template.find('select'); |
| 4824 | const chatName = template.find('.chat_name'); | 4863 | const chatName = template.find('.chat_name'); |
| @@ -4846,7 +4885,7 @@ export function assignLorebookToChat() { | |||
| 4846 | saveMetadata(); | 4885 | saveMetadata(); |
| 4847 | }); | 4886 | }); |
| 4848 | 4887 | ||
| 4849 | callPopup(template, 'text'); | 4888 | callGenericPopup(template, POPUP_TYPE.TEXT); |
| 4850 | } | 4889 | } |
| 4851 | 4890 | ||
| 4852 | jQuery(() => { | 4891 | jQuery(() => { |