diff --git a/public/index.html b/public/index.html index c378112a8..71d3ea172 100644 --- a/public/index.html +++ b/public/index.html @@ -1786,6 +1786,19 @@ +
+
+
+
+ +
+
+
+
+
+
+
+
diff --git a/public/script.js b/public/script.js index dbc83d23a..2b4b5d65e 100644 --- a/public/script.js +++ b/public/script.js @@ -42,6 +42,11 @@ import { getGroupChat, renameGroupMember, createNewGroupChat, + getGroupPastChats, + getGroupAvatar, + openGroupChat, + editGroup, + deleteGroupChat, } from "./scripts/group-chats.js"; import { @@ -852,20 +857,10 @@ async function delChat(chatfile) { }), }); if (response.ok === true) { - //close past chat popup - $("#select_chat_cross").click(); - // choose another chat if current was deleted if (chatfile.replace('.jsonl', '') === characters[this_chid].chat) { await replaceCurrentChat(); } - - //open the history view again after 100ms - //hide option popup menu - setTimeout(function () { - $("#option_select_chat").click(); - $("#options").hide(); - }, 100); } } @@ -2707,8 +2702,6 @@ async function openCharacterChat(file_name) { await getChat(); $("#selected_chat_pole").val(file_name); $("#create_button").click(); - $("#shadow_select_chat_popup").css("display", "none"); - $("#load_select_chat_div").css("display", "block"); } ////////// OPTIMZED MAIN API CHANGE FUNCTION //////////// @@ -3164,67 +3157,63 @@ function messageEditDone(div) { saveChatConditional(); } -async function getAllCharaChats() { - //console.log('getAllCharaChats() pinging server for character chat history.'); - $("#select_chat_div").html(""); - //console.log(characters[this_chid].chat); - jQuery.ajax({ - type: "POST", - url: "/getallchatsofcharacter", - data: JSON.stringify({ avatar_url: characters[this_chid].avatar }), - beforeSend: function () { - - }, - cache: false, - dataType: "json", - contentType: "application/json", - success: function (data) { - $("#load_select_chat_div").css("display", "none"); - let dataArr = Object.values(data); - data = dataArr.sort((a, b) => - a["file_name"].localeCompare(b["file_name"]) - ); - data = data.reverse(); - $("#ChatHistoryCharName").html(characters[this_chid].name); - for (const key in data) { - let strlen = 300; - let mes = data[key]["mes"]; - if (mes !== undefined) { - if (mes.length > strlen) { - mes = "..." + mes.substring(mes.length - strlen); - } - $("#select_chat_div").append( - '
' + - '
' + - '
' + - '
' + data[key]["file_name"] + '
' + - '
' + - mes + - "
" + - "
" + - '
' + - '
' - - - ); - if ( - characters[this_chid]["chat"] == - data[key]["file_name"].replace(".jsonl", "") - ) { - //children().last() - $("#select_chat_div") - .find(".select_chat_block:last") - .attr("highlight", true); - } - } - } - }, - error: function (jqXHR, exception) { - - console.log(exception); - console.log(jqXHR); - }, +async function getPastCharacterChats() { + const response = await fetch("/getallchatsofcharacter", { + method: 'POST', + body: JSON.stringify({ avatar_url: characters[this_chid].avatar }), + headers: getRequestHeaders(), }); + + if (!response.ok) { + return; + } + + let data = await response.json(); + data = Object.values(data); + data = data.sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse(); + return data; +} + +async function displayPastChats() { + $("#select_chat_div").empty(); + + const group = selected_group ? groups.find(x => x.id === selected_group) : null; + const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats()); + const currentChat = selected_group ? group?.chat_id : characters[this_chid]["chat"]; + const displayName = selected_group ? group?.name : characters[this_chid].name; + const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']); + + $("#load_select_chat_div").css("display", "none"); + $("#ChatHistoryCharName").text(displayName); + + for (const key in data) { + let strlen = 300; + let mes = data[key]["mes"]; + + if (mes !== undefined) { + if (mes.length > strlen) { + mes = "..." + mes.substring(mes.length - strlen); + } + + const fileName = data[key]['file_name']; + const template = $('#past_chat_template .select_chat_block_wrapper').clone(); + template.find('.select_chat_block').attr('file_name', fileName); + template.find('.avatar img').attr('src', avatarImg); + template.find('.select_chat_block_filename').text(fileName); + template.find('.select_chat_block_mes').text(mes); + template.find('.PastChat_cross').attr('file_name', fileName); + + if (selected_group) { + template.find('.avatar img').replaceWith(getGroupAvatar(group)); + } + + $("#select_chat_div").append(template); + + if (currentChat === fileName.replace(".jsonl", "")) { + $("#select_chat_div").find(".select_chat_block:last").attr("highlight", true); + } + } + } } //************************************************************ @@ -3542,6 +3531,10 @@ function read_bg_load(input) { } function showSwipeButtons() { + if (chat.length === 0) { + return; + } + if ( chat[chat.length - 1].is_system || !swipes || @@ -3592,12 +3585,21 @@ function hideSwipeButtons() { $("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`).children('.swipe_left').css('display', 'none'); } -function saveChatConditional() { +async function saveMetadata() { if (selected_group) { - saveGroupChat(selected_group); + await editGroup(selected_group, true, false); } else { - saveChat(); + await saveChat(); + } +} + +async function saveChatConditional() { + if (selected_group) { + await saveGroupChat(selected_group, true); + } + else { + await saveChat(); } } @@ -3681,6 +3683,7 @@ window["SillyTavern"].getContext = function () { setExtensionPrompt: setExtensionPrompt, updateChatMetadata: updateChatMetadata, saveChat: saveChatConditional, + saveMetadata: saveMetadata, sendSystemMessage: sendSystemMessage, activateSendButtons, deactivateSendButtons, @@ -4226,7 +4229,7 @@ $(document).ready(function () { is_advanced_char_open = false; $("#character_popup").css("display", "none"); }); - $("#dialogue_popup_ok").click(function (e) { + $("#dialogue_popup_ok").click(async function (e) { $("#shadow_popup").transition({ opacity: 0, duration: 200, @@ -4239,9 +4242,21 @@ $(document).ready(function () { bg_file_for_del.parent().remove(); } if (popup_type == "del_chat") { + //close past chat popup + $("#select_chat_cross").click(); - delChat(chat_file_for_del); + if (selected_group) { + await deleteGroupChat(selected_group, chat_file_for_del); + } else { + await delChat(chat_file_for_del); + } + //open the history view again after 100ms + //hide option popup menu + setTimeout(function () { + $("#option_select_chat").click(); + $("#options").hide(); + }, 100); } if (popup_type == "del_ch") { console.log( @@ -4312,7 +4327,7 @@ $(document).ready(function () { chat.length = 0; if (selected_group) { - createNewGroupChat(); + createNewGroupChat(selected_group); } else { //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime; @@ -4652,14 +4667,8 @@ $(document).ready(function () { $("#options [id]").on("click", function () { var id = $(this).attr("id"); if (id == "option_select_chat") { - if (selected_group) { - // will open a chat selection screen - /* openNavToggle(); */ - $("#rm_button_characters").trigger("click"); - return; - } - if (this_chid != undefined && !is_send_press) { - getAllCharaChats(); + if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press)) { + displayPastChats(); $("#shadow_select_chat_popup").css("display", "block"); $("#shadow_select_chat_popup").css("opacity", 0.0); $("#shadow_select_chat_popup").transition({ @@ -5266,7 +5275,7 @@ $(document).ready(function () { success: function (data) { //console.log(data); if (data.res) { - getAllCharaChats(); + displayPastChats(); } }, error: function (jqXHR, exception) { @@ -5287,7 +5296,15 @@ $(document).ready(function () { $(document).on("click", ".select_chat_block, .bookmark_link", async function () { let file_name = $(this).attr("file_name").replace(".jsonl", ""); - openCharacterChat(file_name); + + if (selected_group) { + await openGroupChat(selected_group, file_name); + } else { + await openCharacterChat(file_name); + } + + $("#shadow_select_chat_popup").css("display", "none"); + $("#load_select_chat_div").css("display", "block"); }); $(document).on("click", ".mes_stop", function () { diff --git a/public/scripts/extensions/backgrounds/index.js b/public/scripts/extensions/backgrounds/index.js index 5363b1113..e9a1c541d 100644 --- a/public/scripts/extensions/backgrounds/index.js +++ b/public/scripts/extensions/backgrounds/index.js @@ -51,13 +51,13 @@ function hasCustomBackground() { function saveBackgroundMetadata(file) { const context = getContext(); context.chatMetadata[METADATA_KEY] = file; - context.saveChat(); + context.saveMetadata(); } function removeBackgroundMetadata() { const context = getContext(); delete context.chatMetadata[METADATA_KEY]; - context.saveChat(); + context.saveMetadata(); } function setCustomBackground() { diff --git a/public/scripts/extensions/floating-prompt/index.js b/public/scripts/extensions/floating-prompt/index.js index da8563777..c1fc3d87b 100644 --- a/public/scripts/extensions/floating-prompt/index.js +++ b/public/scripts/extensions/floating-prompt/index.js @@ -3,7 +3,7 @@ import { extension_settings, getContext } from "../../extensions.js"; import { debounce } from "../../utils.js"; export { MODULE_NAME }; -const saveChatDebounced = debounce(async () => await getContext().saveChat(), 1000); +const saveMetadataDebounced = debounce(async () => await getContext().saveMetadata(), 1000); const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory const UPDATE_INTERVAL = 1000; @@ -21,12 +21,12 @@ const metadata_keys = { async function onExtensionFloatingPromptInput() { chat_metadata[metadata_keys.prompt] = $(this).val(); - saveChatDebounced(); + saveMetadataDebounced(); } async function onExtensionFloatingIntervalInput() { chat_metadata[metadata_keys.interval] = Number($(this).val()); - saveChatDebounced(); + saveMetadataDebounced(); } async function onExtensionFloatingDepthInput() { @@ -38,12 +38,12 @@ async function onExtensionFloatingDepthInput() { } chat_metadata[metadata_keys.depth] = value; - saveChatDebounced(); + saveMetadataDebounced(); } async function onExtensionFloatingPositionInput(e) { chat_metadata[metadata_keys.position] = e.target.value; - saveChatDebounced(); + saveMetadataDebounced(); } function onExtensionFloatingDefaultInput() { diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 639fe6b1d..4cbefd100 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -81,16 +81,17 @@ export const group_activation_strategy = { const groupAutoModeInterval = setInterval(groupChatAutoModeWorker, 5000); const saveGroupDebounced = debounce(async (group) => await _save(group), 500); -async function _save(group) { +async function _save(group, reload = true) { await fetch("/editgroup", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify(group), }); - await getCharacters(); + if (reload) { + await getCharacters(); + } } - // Group chats async function regenerateGroup() { let generationId = getLastMessageGenerationId(); @@ -114,47 +115,55 @@ async function regenerateGroup() { generateGroupWrapper(); } -export async function getGroupChat(groupId) { - const group = groups.find((x) => x.id === groupId); - const chat_id = group.chat_id; +async function loadGroupChat(chatId) { const response = await fetch("/getgroupchat", { method: "POST", headers: getRequestHeaders(), - body: JSON.stringify({ id: chat_id }), + body: JSON.stringify({ id: chatId }), }); if (response.ok) { const data = await response.json(); - if (Array.isArray(data) && data.length) { - data[0].is_group = true; - for (let key of data) { - chat.push(key); - } - printMessages(); - } else { - sendSystemMessage(system_message_types.GROUP); - if (group && Array.isArray(group.members)) { - for (let member of group.members) { - const character = characters.find(x => x.avatar === member || x.name === member); - - if (!character) { - continue; - } - - const mes = getFirstCharacterMessage(character); - chat.push(mes); - addOneMessage(mes); - } - } - } - - if (group) { - let metadata = group.chat_metadata ?? {}; - updateChatMetadata(metadata, true); - } - - await saveGroupChat(groupId, true); + return data; } + + return []; +} + +export async function getGroupChat(groupId) { + const group = groups.find((x) => x.id === groupId); + const chat_id = group.chat_id; + const data = await loadGroupChat(chat_id); + + if (Array.isArray(data) && data.length) { + data[0].is_group = true; + for (let key of data) { + chat.push(key); + } + printMessages(); + } else { + sendSystemMessage(system_message_types.GROUP); + if (group && Array.isArray(group.members)) { + for (let member of group.members) { + const character = characters.find(x => x.avatar === member || x.name === member); + + if (!character) { + continue; + } + + const mes = getFirstCharacterMessage(character); + chat.push(mes); + addOneMessage(mes); + } + } + } + + if (group) { + let metadata = group.chat_metadata ?? {}; + updateChatMetadata(metadata, true); + } + + await saveGroupChat(groupId, true); } function getFirstCharacterMessage(character) { @@ -197,7 +206,6 @@ export async function renameGroupMember(oldAvatar, newAvatar, newName) { // Scan every group for our renamed character for (const group of groups) { try { - // Try finding the member by old avatar link const memberIndex = group.members.findIndex(x => x == oldAvatar); @@ -213,33 +221,26 @@ export async function renameGroupMember(oldAvatar, newAvatar, newName) { // Load all chats from this group for (const chatId of group.chats) { - const getChatResponse = await fetch("/getgroupchat", { - method: "POST", - headers: getRequestHeaders(), - body: JSON.stringify({ id: chatId }), - }); + const messages = await loadGroupChat(chatId); - if (getChatResponse.ok) { - // Only save the chat if there were any changes to the chat content - let hadChanges = false; - const messages = await getChatResponse.json(); - // Chat shouldn't be empty - if (Array.isArray(messages) && messages.length) { - // Iterate over every chat message - for (const message of messages) { - // Only look at character messages - if (message.is_user || message.is_system) { - continue; - } + // Only save the chat if there were any changes to the chat content + let hadChanges = false; + // Chat shouldn't be empty + if (Array.isArray(messages) && messages.length) { + // Iterate over every chat message + for (const message of messages) { + // Only look at character messages + if (message.is_user || message.is_system) { + continue; + } - // Message belonged to the old-named character: - // Update name, avatar thumbnail URL and original avatar link - if (message.force_avatar && message.force_avatar.indexOf(encodeURIComponent(oldAvatar)) !== -1) { - message.name = newName; - message.force_avatar = message.force_avatar.replace(encodeURIComponent(oldAvatar), encodeURIComponent(newAvatar)); - message.original_avatar = newAvatar; - hadChanges = true; - } + // Message belonged to the old-named character: + // Update name, avatar thumbnail URL and original avatar link + if (message.force_avatar && message.force_avatar.indexOf(encodeURIComponent(oldAvatar)) !== -1) { + message.name = newName; + message.force_avatar = message.force_avatar.replace(encodeURIComponent(oldAvatar), encodeURIComponent(newAvatar)); + message.original_avatar = newAvatar; + hadChanges = true; } } @@ -284,6 +285,9 @@ async function getGroups() { .filter(x => x) .filter(onlyUnique) } + if (group.past_metadata == undefined) { + group.past_metadata = {}; + } } } } @@ -696,16 +700,17 @@ async function deleteGroup(id) { } } -async function editGroup(id, immediately) { - let group = groups.find((x) => x.id == id); - group = { ...group, chat_metadata }; +export async function editGroup(id, immediately, reload=true) { + let group = groups.find((x) => x.id === id); if (!group) { return; } + group['chat_metadata'] = chat_metadata; + if (immediately) { - return await _save(group); + return await _save(group, reload); } saveGroupDebounced(group); @@ -1077,8 +1082,8 @@ function filterMembersByFavorites(value) { } } -export async function createNewGroupChat() { - const group = groups.find(x => x.id === selected_group); +export async function createNewGroupChat(groupId) { + const group = groups.find(x => x.id === groupId); if (!group) { return; @@ -1091,7 +1096,11 @@ export async function createNewGroupChat() { group.past_metadata = {}; } - group.past_metadata[oldChatName] = Object.assign({}, chat_metadata); + clearChat(); + chat.length = 0; + if (oldChatName) { + group.past_metadata[oldChatName] = Object.assign({}, chat_metadata); + } group.chats.push(newChatName); group.chat_id = newChatName; group.chat_metadata = {}; @@ -1101,6 +1110,76 @@ export async function createNewGroupChat() { await getGroupChat(group.id); } +export async function getGroupPastChats(groupId) { + const group = groups.find(x => x.id === groupId); + + if (!group) { + return []; + } + + const chats = []; + + try { + for (const chatId of group.chats) { + const messages = await loadGroupChat(chatId); + const lastMessage = messages.length ? messages[messages.length - 1].mes : '[The chat is empty]'; + chats.push({ 'file_name': chatId, 'mes': lastMessage }); + } + } catch (err) { + console.error(err); + } + finally { + return chats; + } +} + +export async function openGroupChat(groupId, chatId) { + const group = groups.find(x => x.id === groupId); + + if (!group || !group.chats.includes(chatId)) { + return; + } + + clearChat(); + chat.length = 0; + const previousChat = group.chat_id; + group.past_metadata[previousChat] = Object.assign({}, chat_metadata); + group.chat_id = chatId; + group.chat_metadata = group.past_metadata[chatId] || {}; + updateChatMetadata(group.chat_metadata, true); + + await editGroup(groupId, true); + await getGroupChat(groupId); +} + +export async function deleteGroupChat(groupId, chatId) { + const group = groups.find(x => x.id === groupId); + + if (!group || !group.chats.includes(chatId)) { + return; + } + + group.chats.splice(group.chats.indexOf(chatId), 1); + group.chat_metadata = {}; + group.chat_id = ''; + delete group.past_metadata[chatId]; + updateChatMetadata(group.chat_metadata, true); + + const response = await fetch('/deletegroupchat', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ id: chatId }), + }); + + if (response.ok) { + if (group.chats.length) { + await openGroupChat(groupId, group.chats[0]); + } else { + await createNewGroupChat(groupId); + } + } +} + $(document).ready(() => { $(document).on("click", ".group_select", selectGroup); $("#rm_group_filter").on("input", filterGroupMembers); diff --git a/server.js b/server.js index c0df4c220..66baa5087 100644 --- a/server.js +++ b/server.js @@ -1381,22 +1381,19 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) { lastLine = line; }); rl.on('close', () => { + ii--; if (lastLine) { let jsonData = json5.parse(lastLine); - if (jsonData.name !== undefined) { + if (jsonData.name !== undefined || jsonData.character_name !== undefined) { chatData[i] = {}; chatData[i]['file_name'] = file; - chatData[i]['mes'] = jsonData['mes']; - ii--; - if (ii === 0) { - console.log('ii count went to zero, responding with chatData'); - response.send(chatData); - } - } else { - console.log('just returning from getallchatsofcharacter'); - return; + chatData[i]['mes'] = jsonData['mes'] || '[The chat is empty]'; } } + if (ii === 0) { + console.log('ii count went to zero, responding with chatData'); + response.send(chatData); + } console.log('successfully closing getallchatsofcharacter'); rl.close(); }); @@ -1794,6 +1791,22 @@ app.post('/getgroupchat', jsonParser, (request, response) => { } }); +app.post('/deletegroupchat', jsonParser, (request, response) => { + if (!request.body || !request.body.id) { + return response.sendStatus(400); + } + + const id = request.body.id; + const pathToFile = path.join(directories.groupChats, `${id}.jsonl`); + + if (fs.existsSync(pathToFile)) { + fs.rmSync(pathToFile); + return response.send({ ok: true }); + } + + return response.send({ error: true }); +}); + app.post('/savegroupchat', jsonParser, (request, response) => { if (!request.body || !request.body.id) { return response.sendStatus(400);