mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
622 lines
18 KiB
JavaScript
622 lines
18 KiB
JavaScript
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("<h3>Delete the group?</h3>", "del_group");
|
|
});
|
|
|
|
// top bar
|
|
if (group) {
|
|
$("#rm_button_selected_ch").children("h2").css({});
|
|
$("#rm_button_selected_ch").children("h2").text("");
|
|
}
|
|
}
|
|
|
|
$(document).ready(() => {
|
|
$(document).on("click", ".group_select", async function () {
|
|
const id = $(this).data("id");
|
|
|
|
if (!is_send_press && !is_group_generating) {
|
|
if (selected_group !== id) {
|
|
selected_group = id;
|
|
setCharacterId(undefined);
|
|
setCharacterName('');
|
|
setEditedMessageId(undefined);
|
|
clearChat();
|
|
chat.length = 0;
|
|
await getGroupChat(id);
|
|
}
|
|
|
|
select_group_chats(id);
|
|
}
|
|
});
|
|
|
|
$("#rm_group_filter").on("input", function () {
|
|
const searchValue = $(this).val().trim().toLowerCase();
|
|
|
|
if (!searchValue) {
|
|
$("#rm_group_add_members .group_member").show();
|
|
} else {
|
|
$("#rm_group_add_members .group_member").each(function () {
|
|
$(this).children(".ch_name").text().toLowerCase().includes(searchValue)
|
|
? $(this).show()
|
|
: $(this).hide();
|
|
});
|
|
}
|
|
});
|
|
|
|
$("#rm_group_submit").click(async function () {
|
|
let name = $("#rm_group_chat_name").val();
|
|
const members = $("#rm_group_members .group_member")
|
|
.map((_, x) => $(x).data("id"))
|
|
.toArray();
|
|
|
|
if (!name) {
|
|
name = `Chat with ${members.join(", ")}`;
|
|
}
|
|
|
|
// placeholder
|
|
const avatar_url = 'img/five.png';
|
|
|
|
const createGroupResponse = await fetch("/creategroup", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-CSRF-Token": token,
|
|
},
|
|
body: JSON.stringify({
|
|
name: name,
|
|
members: members,
|
|
avatar_url: avatar_url,
|
|
}),
|
|
});
|
|
|
|
if (createGroupResponse.ok) {
|
|
await getCharacters();
|
|
$("#rm_info_avatar").html("");
|
|
const avatar = $("#avatar_div_div").clone();
|
|
avatar.find("img").attr("src", avatar_url);
|
|
$("#rm_info_avatar").append(avatar);
|
|
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
|
|
select_rm_info("Group chat created");
|
|
$("#rm_info_block").transition({ opacity: 1.0, duration: 2000 });
|
|
}
|
|
});
|
|
|
|
$("#rm_group_automode").on("input", function () {
|
|
const value = $(this).prop("checked");
|
|
is_group_automode_enabled = value;
|
|
});
|
|
}); |