import { shuffle, onlyUnique, debounce, delay, } from './utils.js'; import { humanizedDateTime } from "./RossAscends-mods.js"; import { chat, sendSystemMessage, printMessages, substituteParams, characters, default_avatar, token, addOneMessage, callPopup, clearChat, Generate, select_rm_info, setCharacterId, setCharacterName, setEditedMessageId, is_send_press, resetChatState, setSendButtonState, getCharacters, system_message_types, online_status, talkativeness_default, } from "../script.js"; export { selected_group, is_group_automode_enabled, is_group_generating, groups, saveGroupChat, generateGroupWrapper, deleteGroup, getGroupAvatar, getGroups, printGroups, resetSelectedGroup, select_group_chats, } let is_group_generating = false; // Group generation flag let is_group_automode_enabled = false; let groups = []; let selected_group = null; const groupAutoModeInterval = setInterval(groupChatAutoModeWorker, 5000); const saveGroupDebounced = debounce(async (group) => await _save(group), 500); async function _save(group) { await fetch("/editgroup", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": token, }, body: JSON.stringify(group), }); } // Group chats async function getGroupChat(id) { const response = await fetch("/getgroupchat", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": token, }, body: JSON.stringify({ id: id }), }); if (response.ok) { const data = await response.json(); if (Array.isArray(data) && data.length) { for (let key of data) { chat.push(key); } printMessages(); } else { sendSystemMessage(system_message_types.GROUP); const group = groups.find((x) => x.id === id); if (group && Array.isArray(group.members)) { for (let name of group.members) { const character = characters.find((x) => x.name === name); if (!character) { continue; } const mes = {}; mes["is_user"] = false; mes["is_system"] = false; mes["name"] = character.name; mes["is_name"] = true; mes["send_date"] = humanizedDateTime(); mes["mes"] = character.first_mes ? substituteParams(character.first_mes.trim()) : default_ch_mes; mes["force_avatar"] = character.avatar != "none" ? `characters/${character.avatar}?${Date.now()}` : default_avatar; chat.push(mes); addOneMessage(mes); } } } await saveGroupChat(id); } } function resetSelectedGroup() { selected_group = null; is_group_generating = false; } async function saveGroupChat(id) { const response = await fetch("/savegroupchat", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": token, }, body: JSON.stringify({ id: id, chat: [...chat] }), }); if (response.ok) { // response ok } } async function getGroups() { const response = await fetch("/getgroups", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": token, }, }); if (response.ok) { const data = await response.json(); groups = data.sort((a, b) => a.id - b.id); } } function printGroups() { for (let group of groups) { const template = $("#group_list_template .group_select").clone(); template.data("id", group.id); template.find(".ch_name").html(group.name); $("#rm_print_characters_block").prepend(template); updateGroupAvatar(group); } } function updateGroupAvatar(group) { $("#rm_print_characters_block .group_select").each(function () { if ($(this).data("id") == group.id) { const avatar = getGroupAvatar(group); if (avatar) { $(this).find(".avatar").replaceWith(avatar); } } }); } function getGroupAvatar(group) { const memberAvatars = []; if (group && Array.isArray(group.members) && group.members.length) { for (const member of group.members) { const charIndex = characters.findIndex((x) => x.name === member); if (charIndex !== -1 && characters[charIndex].avatar !== "none") { const avatar = `characters/${characters[charIndex].avatar}#${Date.now()}`; memberAvatars.push(avatar); } if (memberAvatars.length === 4) { break; } } } // Cohee: there's probably a smarter way to do this.. if (memberAvatars.length === 1) { const groupAvatar = $("#group_avatars_template .collage_1").clone(); groupAvatar.find(".img_1").attr("src", memberAvatars[0]); return groupAvatar; } if (memberAvatars.length === 2) { const groupAvatar = $("#group_avatars_template .collage_2").clone(); groupAvatar.find(".img_1").attr("src", memberAvatars[0]); groupAvatar.find(".img_2").attr("src", memberAvatars[1]); return groupAvatar; } if (memberAvatars.length === 3) { const groupAvatar = $("#group_avatars_template .collage_3").clone(); groupAvatar.find(".img_1").attr("src", memberAvatars[0]); groupAvatar.find(".img_2").attr("src", memberAvatars[1]); groupAvatar.find(".img_3").attr("src", memberAvatars[2]); return groupAvatar; } if (memberAvatars.length === 4) { const groupAvatar = $("#group_avatars_template .collage_4").clone(); groupAvatar.find(".img_1").attr("src", memberAvatars[0]); groupAvatar.find(".img_2").attr("src", memberAvatars[1]); groupAvatar.find(".img_3").attr("src", memberAvatars[2]); groupAvatar.find(".img_4").attr("src", memberAvatars[3]); return groupAvatar; } // default avatar const groupAvatar = $("#group_avatars_template .collage_1").clone(); groupAvatar.find(".img_1").attr("src", group.avatar_url); return groupAvatar; } async function generateGroupWrapper(by_auto_mode) { if (online_status === "no_connection") { is_group_generating = false; setSendButtonState(false); return; } const group = groups.find((x) => x.id === selected_group); if (!group || !Array.isArray(group.members) || !group.members.length) { sendSystemMessage(system_message_types.EMPTY); return; } try { is_group_generating = true; setCharacterName(''); setCharacterId(undefined); const userInput = $("#send_textarea").val(); let typingIndicator = $("#chat .typing_indicator"); if (typingIndicator.length === 0) { typingIndicator = $( "#typing_indicator_template .typing_indicator" ).clone(); typingIndicator.hide(); $("#chat").append(typingIndicator); } let messagesBefore = chat.length; let activationText = ""; if (userInput && userInput.length && !by_auto_mode) { activationText = userInput; messagesBefore++; } else { const lastMessage = chat[chat.length - 1]; if (lastMessage && !lastMessage.is_system) { activationText = lastMessage.mes; } } const activatedMembers = activateMembers(group.members, activationText); // now the real generation begins: cycle through every character for (const chId of activatedMembers) { setCharacterId(chId); setCharacterName(characters[chId].name) await Generate("group_chat", by_auto_mode); // update indicator and scroll down typingIndicator .find(".typing_indicator_name") .text(characters[chId].name); $("#chat").append(typingIndicator); typingIndicator.show(250, function () { typingIndicator.get(0).scrollIntoView({ behavior: "smooth" }); }); while (true) { // check if message generated already if (chat.length == messagesBefore) { await delay(10); } else { messagesBefore++; break; } } // hide and reapply the indicator to the bottom of the list typingIndicator.hide(250); $("#chat").append(typingIndicator); } } finally { is_group_generating = false; setSendButtonState(false); setCharacterId(undefined); } } function activateMembers(members, input) { let activatedNames = []; // find mentions if (input && input.length) { for (let inputWord of extractAllWords(input)) { for (let member of members) { if (extractAllWords(member).includes(inputWord)) { activatedNames.push(member); break; } } } } // activation by talkativeness (in shuffled order) const shuffledMembers = shuffle([...members]); for (let member of shuffledMembers) { const character = characters.find((x) => x.name === member); if (!character) { continue; } const rollValue = Math.random(); let talkativeness = Number(character.talkativeness); talkativeness = Number.isNaN(talkativeness) ? talkativeness_default : talkativeness; if (talkativeness >= rollValue) { activatedNames.push(member); } } // pick 1 at random if no one was activated if (activatedNames.length === 0) { const randomIndex = Math.floor(Math.random() * members.length); activatedNames.push(members[randomIndex]); } // de-duplicate array of names activatedNames = activatedNames.filter(onlyUnique); // map to character ids const memberIds = activatedNames .map((x) => characters.findIndex((y) => y.name === x)) .filter((x) => x !== -1); return memberIds; } function extractAllWords(value) { const words = []; if (!value) { return words; } const matches = value.matchAll(/\b\w+\b/gim); for (let match of matches) { words.push(match[0].toLowerCase()); } return words; } async function deleteGroup(id) { const response = await fetch("/deletegroup", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": token, }, body: JSON.stringify({ id: id }), }); if (response.ok) { selected_group = null; resetChatState(); clearChat(); printMessages(); await getCharacters(); $("#rm_info_avatar").html(""); $("#rm_info_block").transition({ opacity: 0, duration: 0 }); select_rm_info("Group deleted!"); $("#rm_info_block").transition({ opacity: 1.0, duration: 2000 }); } } async function editGroup(id, immediately) { const group = groups.find((x) => x.id == id); if (!group) { return; } if (immediately) { return await _save(); } saveGroupDebounced(group); } async function groupChatAutoModeWorker() { if (!is_group_automode_enabled || online_status === "no_connection") { return; } if (!selected_group || is_send_press || is_group_generating) { return; } const group = groups.find((x) => x.id === selected_group); if (!group || !Array.isArray(group.members) || !group.members.length) { return; } await generateGroupWrapper(true); } function select_group_chats(chat_id) { const group = chat_id && groups.find((x) => x.id == chat_id); const groupName = group?.name ?? ""; $("#rm_group_chat_name").val(groupName); $("#rm_group_chat_name").off(); $("#rm_group_chat_name").on("input", async function () { if (chat_id) { group.name = $(this).val(); await editGroup(chat_id); } }); $("#rm_group_filter").val("").trigger("input"); $("#rm_group_chats_block").css("display", "flex"); $("#rm_group_chats_block").css("opacity", 0.0); $("#rm_group_chats_block").transition({ opacity: 1.0, duration: 200, easing: '', complete: function () { }, }); $("#rm_ch_create_block").css("display", "none"); $("#rm_characters_block").css("display", "none"); async function memberClickHandler(event) { event.stopPropagation(); const id = $(this).data("id"); const isDelete = !!$(this).closest("#rm_group_members").length; const template = $(this).clone(); template.data("id", id); template.click(memberClickHandler); if (isDelete) { template.find(".plus").show(); template.find(".minus").hide(); $("#rm_group_add_members").prepend(template); } else { template.find(".plus").hide(); template.find(".minus").show(); $("#rm_group_members").prepend(template); } if (group) { if (isDelete) { const index = group.members.findIndex((x) => x === id); if (index !== -1) { group.members.splice(index, 1); } } else { group.members.push(id); } await editGroup(chat_id); updateGroupAvatar(group); } $(this).remove(); const groupHasMembers = !!$("#rm_group_members").children().length; $("#rm_group_submit").prop("disabled", !groupHasMembers); } // render characters list $("#rm_group_add_members").empty(); $("#rm_group_members").empty(); for (let character of characters) { const avatar = character.avatar != "none" ? `characters/${character.avatar}#${Date.now()}` : default_avatar; const template = $("#group_member_template .group_member").clone(); template.data("id", character.name); template.find(".avatar img").attr("src", avatar); template.find(".ch_name").html(character.name); template.click(memberClickHandler); if ( group && Array.isArray(group.members) && group.members.includes(character.name) ) { template.find(".plus").hide(); template.find(".minus").show(); $("#rm_group_members").append(template); } else { template.find(".plus").show(); template.find(".minus").hide(); $("#rm_group_add_members").append(template); } } const groupHasMembers = !!$("#rm_group_members").children().length; $("#rm_group_submit").prop("disabled", !groupHasMembers); // bottom buttons if (chat_id) { $("#rm_group_submit").hide(); $("#rm_group_delete").show(); } else { $("#rm_group_submit").show(); $("#rm_group_delete").hide(); } $("#rm_group_delete").off(); $("#rm_group_delete").on("click", function () { $("#dialogue_popup").data("group_id", chat_id); callPopup("