From 6a1b230c7eb261c0cdbc28a05e9cf800f1b58fa8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 16 Oct 2023 23:03:42 +0300 Subject: [PATCH] #1226 Add chat-bound lorebooks --- public/index.html | 27 +++++++++++++-- public/script.js | 2 ++ public/scripts/group-chats.js | 2 ++ public/scripts/world-info.js | 62 ++++++++++++++++++++++++++++++++++- public/style.css | 8 ++--- server.js | 7 ++-- 6 files changed, 99 insertions(+), 9 deletions(-) diff --git a/public/index.html b/public/index.html index bf9e71f33..7a9a7451d 100644 --- a/public/index.html +++ b/public/index.html @@ -3428,6 +3428,7 @@ + @@ -3543,8 +3544,11 @@
-
- +
+
+ + +
@@ -3896,6 +3900,25 @@
+
+
+
+

Chat Lorebook for

+
+
+ + A selected World Info will be bound to this chat. When generating an AI reply, + it will be combined with the entries from global and character lorebooks. + +
+
+ +
+
+
+
diff --git a/public/script.js b/public/script.js index 72bc8e4f2..bfe9fe57d 100644 --- a/public/script.js +++ b/public/script.js @@ -5729,6 +5729,7 @@ export function select_selected_character(chid) { checkEmbeddedWorld(chid); $("#form_create").attr("actiontype", "editcharacter"); + $('.form_create_bottom_buttons_block .chat_lorebook_button').show(); saveSettingsDebounced(); } @@ -5787,6 +5788,7 @@ function select_rm_create() { checkEmbeddedWorld(); $("#form_create").attr("actiontype", "createcharacter"); + $('.form_create_bottom_buttons_block .chat_lorebook_button').hide(); } function select_rm_characters() { diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 22bca164e..2d4f2bbbf 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -1110,6 +1110,7 @@ function select_group_chats(groupId, skipAnimation) { $("#rm_group_submit").hide(); $("#rm_group_delete").show(); $("#rm_group_scenario").show(); + $('#group-metadata-controls .chat_lorebook_button').removeClass('disabled').prop('disabled', false); } else { $("#rm_group_submit").show(); if ($("#groupAddMemberListToggle .inline-drawer-content").css('display') !== 'block') { @@ -1117,6 +1118,7 @@ function select_group_chats(groupId, skipAnimation) { } $("#rm_group_delete").hide(); $("#rm_group_scenario").hide(); + $('#group-metadata-controls .chat_lorebook_button').addClass('disabled').prop('disabled', true); } updateFavButtonState(group?.fav ?? false); diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 77fdfef14..4ee3f8958 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -1,4 +1,4 @@ -import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPrompt, MAX_INJECTION_DEPTH, extension_prompt_types, getExtensionPromptByName } from "../script.js"; +import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPrompt, MAX_INJECTION_DEPTH, extension_prompt_types, getExtensionPromptByName, saveMetadata, getCurrentChatId } from "../script.js"; import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition } from "./utils.js"; import { extension_settings, getContext } from "./extensions.js"; import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./authors-note.js"; @@ -53,6 +53,7 @@ let updateEditor = (navigation) => { navigation; }; // Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data. const worldInfoFilter = new FilterHelper(() => updateEditor()); const SORT_ORDER_KEY = 'world_info_sort_order'; +const METADATA_KEY = 'world_info'; const InputWidthReference = $("#WIInputWidthReference"); @@ -167,6 +168,11 @@ function setWorldInfoSettings(settings, data) { $('#world_info_sort_order').val(localStorage.getItem(SORT_ORDER_KEY) || '0'); $("#world_editor_select").trigger("change"); + + eventSource.on(event_types.CHAT_CHANGED, () => { + const hasWorldInfo = !!chat_metadata[METADATA_KEY] && world_names.includes(chat_metadata[METADATA_KEY]); + $('.chat_lorebook_button').toggleClass('world_set', hasWorldInfo); + }); } // World Info Editor @@ -1301,10 +1307,26 @@ async function getGlobalLore() { return entries; } +async function getChatLore() { + const chatWorld = chat_metadata[METADATA_KEY]; + + if (!chatWorld) { + return []; + } + + const data = await loadWorldInfoData(chatWorld); + const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : []; + + console.debug(`Chat lore has ${entries.length} entries`); + + return entries; +} + async function getSortedEntries() { try { const globalLore = await getGlobalLore(); const characterLore = await getCharacterLore(); + const chatLore = await getChatLore(); let entries; @@ -1327,6 +1349,9 @@ async function getSortedEntries() { break; } + // Chat lore always goes first + entries = [...chatLore.sort(sortFn), ...entries]; + console.debug(`Sorted ${entries.length} world lore entries using strategy ${world_info_character_strategy}`); // Need to deep clone the entries to avoid modifying the cached data @@ -1911,6 +1936,39 @@ export async function importWorldInfo(file) { }); } +function assignLorebookToChat() { + const selectedName = chat_metadata[METADATA_KEY]; + const template = $('#chat_world_template .chat_world').clone(); + + const worldSelect = template.find('select'); + const chatName = template.find('.chat_name'); + chatName.text(getCurrentChatId()); + + for (const worldName of world_names) { + const option = document.createElement('option'); + option.value = worldName; + option.innerText = worldName; + option.selected = selectedName === worldName; + worldSelect.append(option); + } + + worldSelect.on('change', function () { + const worldName = $(this).val(); + + if (worldName) { + chat_metadata[METADATA_KEY] = worldName; + $('.chat_lorebook_button').addClass('world_set'); + } else { + delete chat_metadata[METADATA_KEY]; + $('.chat_lorebook_button').removeClass('world_set'); + } + + saveMetadata(); + }); + + callPopup(template, 'text'); +} + jQuery(() => { $(document).ready(function () { @@ -2051,6 +2109,8 @@ jQuery(() => { updateEditor(navigation_option.none); }) + $(document).on('click', '.chat_lorebook_button', assignLorebookToChat); + // Not needed on mobile const deviceInfo = getDeviceInfo(); if (deviceInfo && deviceInfo.device.type === 'desktop') { diff --git a/public/style.css b/public/style.css index 9fb4edf53..b011b344c 100644 --- a/public/style.css +++ b/public/style.css @@ -1316,7 +1316,7 @@ select option:not(:checked) { } .menu_button.disabled { - filter: brightness(50%); + filter: brightness(75%) grayscale(1); cursor: not-allowed; } @@ -1911,10 +1911,10 @@ grammarly-extension { font-weight: bold; padding: 5px; margin: 0; - height: 32px; + height: 26px; filter: grayscale(0.5); text-align: center; - font-size: 20px; + font-size: 17px; aspect-ratio: 1 / 1; } @@ -2307,7 +2307,7 @@ input[type="range"]::-webkit-slider-thumb { #char-management-dropdown, #tagInput { - height: 32px; + height: 26px; margin-bottom: 0; } diff --git a/server.js b/server.js index 6b30399dc..53cbf9a9d 100644 --- a/server.js +++ b/server.js @@ -1811,15 +1811,18 @@ function convertWorldInfoToCharacterBook(name, entries) { } function readWorldInfoFile(worldInfoName) { + const dummyObject = { entries: {} }; + if (!worldInfoName) { - return { entries: {} }; + return dummyObject; } const filename = `${worldInfoName}.json`; const pathToWorldInfo = path.join(DIRECTORIES.worlds, filename); if (!fs.existsSync(pathToWorldInfo)) { - throw new Error(`World info file ${filename} doesn't exist.`); + console.log(`World info file ${filename} doesn't exist.`); + return dummyObject; } const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8');