mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Split modules
This commit is contained in:
@ -42,6 +42,9 @@
|
||||
<script type=module src="script.js">
|
||||
</script>
|
||||
|
||||
<script type="module" src="scripts/worldinfo.js"></script>
|
||||
<script type="module" src="scripts/groupchats.js"></script>
|
||||
|
||||
<title>Tavern.AI</title>
|
||||
</head>
|
||||
|
||||
|
1700
public/script.js
1700
public/script.js
File diff suppressed because it is too large
Load Diff
622
public/scripts/groupchats.js
Normal file
622
public/scripts/groupchats.js
Normal file
@ -0,0 +1,622 @@
|
||||
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;
|
||||
});
|
||||
});
|
86
public/scripts/utils.js
Normal file
86
public/scripts/utils.js
Normal file
@ -0,0 +1,86 @@
|
||||
export {
|
||||
onlyUnique,
|
||||
shuffle,
|
||||
download,
|
||||
urlContentToDataUri,
|
||||
getBase64Async,
|
||||
getStringHash,
|
||||
debounce,
|
||||
delay,
|
||||
};
|
||||
|
||||
/// UTILS
|
||||
function onlyUnique(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
}
|
||||
|
||||
function shuffle(array) {
|
||||
let currentIndex = array.length,
|
||||
randomIndex;
|
||||
|
||||
while (currentIndex != 0) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex--;
|
||||
[array[currentIndex], array[randomIndex]] = [
|
||||
array[randomIndex],
|
||||
array[currentIndex],
|
||||
];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function download(content, fileName, contentType) {
|
||||
const a = document.createElement("a");
|
||||
const file = new Blob([content], { type: contentType });
|
||||
a.href = URL.createObjectURL(file);
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
}
|
||||
|
||||
async function urlContentToDataUri(url, params) {
|
||||
const response = await fetch(url, params);
|
||||
const blob = await response.blob();
|
||||
return await new Promise(callback => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function () { callback(this.result); };
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function getBase64Async(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getStringHash(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
};
|
||||
|
||||
function debounce(func, timeout = 300) {
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
const delay = (ms) => new Promise((res) => setTimeout(res, ms));
|
649
public/scripts/worldinfo.js
Normal file
649
public/scripts/worldinfo.js
Normal file
@ -0,0 +1,649 @@
|
||||
import { saveSettings, callPopup, token, substituteParams } from "../script.js";
|
||||
import { download, debounce } from "./utils.js";
|
||||
import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js";
|
||||
|
||||
export {
|
||||
world_info,
|
||||
world_info_data,
|
||||
world_info_budget,
|
||||
world_info_depth,
|
||||
world_names,
|
||||
imported_world_name,
|
||||
checkWorldInfo,
|
||||
deleteWorldInfo,
|
||||
selectImportedWorldInfo,
|
||||
setWorldInfoSettings,
|
||||
}
|
||||
|
||||
let world_info = null;
|
||||
let world_names;
|
||||
let world_info_data = null;
|
||||
let world_info_depth = 2;
|
||||
let world_info_budget = 128;
|
||||
let is_world_edit_open = false;
|
||||
let imported_world_name = "";
|
||||
const saveWorldDebounced = debounce(async () => await _save(), 500);
|
||||
const saveSettingsDebounced = debounce(() => saveSettings(), 500);
|
||||
|
||||
const world_info_position = {
|
||||
before: 0,
|
||||
after: 1,
|
||||
};
|
||||
|
||||
function setWorldInfoSettings(settings, data) {
|
||||
if (settings.world_info_depth !== undefined)
|
||||
world_info_depth = Number(settings.world_info_depth);
|
||||
if (settings.world_info_budget !== undefined)
|
||||
world_info_budget = Number(settings.world_info_budget);
|
||||
|
||||
$("#world_info_depth_counter").html(`${world_info_depth} Messages`);
|
||||
$("#world_info_depth").val(world_info_depth);
|
||||
|
||||
$("#world_info_budget_counter").html(`${world_info_budget} Tokens`);
|
||||
$("#world_info_budget").val(world_info_budget);
|
||||
|
||||
world_names = data.world_names?.length ? data.world_names : [];
|
||||
|
||||
if (settings.world_info != undefined) {
|
||||
if (world_names.includes(settings.world_info)) {
|
||||
world_info = settings.world_info;
|
||||
}
|
||||
}
|
||||
|
||||
world_names.forEach((item, i) => {
|
||||
$("#world_info").append(`<option value='${i}'>${item}</option>`);
|
||||
// preselect world if saved
|
||||
if (item == world_info) {
|
||||
$("#world_info").val(i).change();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// World Info Editor
|
||||
async function showWorldEditor() {
|
||||
if (!world_info) {
|
||||
callPopup("<h3>Select a world info first!</h3>", "default");
|
||||
return;
|
||||
}
|
||||
|
||||
is_world_edit_open = true;
|
||||
$("#world_popup_name").val(world_info);
|
||||
$("#world_popup").css("display", "flex");
|
||||
await loadWorldInfoData();
|
||||
displayWorldEntries(world_info_data);
|
||||
}
|
||||
|
||||
async function loadWorldInfoData() {
|
||||
if (!world_info) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch("/getworldinfo", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": token,
|
||||
},
|
||||
body: JSON.stringify({ name: world_info }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
world_info_data = await response.json();
|
||||
}
|
||||
}
|
||||
|
||||
async function updateWorldInfoList(importedWorldName) {
|
||||
var result = await fetch("/getsettings", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": token,
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
var data = await result.json();
|
||||
world_names = data.world_names?.length ? data.world_names : [];
|
||||
$("#world_info").find('option[value!="None"]').remove();
|
||||
|
||||
world_names.forEach((item, i) => {
|
||||
$("#world_info").append(`<option value='${i}'>${item}</option>`);
|
||||
});
|
||||
|
||||
if (importedWorldName) {
|
||||
const indexOf = world_names.indexOf(world_info);
|
||||
$("#world_info").val(indexOf);
|
||||
|
||||
callPopup("<h3>World imported successfully! Select it now?</h3>", "world_imported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideWorldEditor() {
|
||||
is_world_edit_open = false;
|
||||
$("#world_popup").css("display", "none");
|
||||
}
|
||||
|
||||
function displayWorldEntries(data) {
|
||||
$("#world_popup_entries_list").empty();
|
||||
|
||||
if (!data || !("entries" in data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entryUid in data.entries) {
|
||||
const entry = data.entries[entryUid];
|
||||
appendWorldEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
function appendWorldEntry(entry) {
|
||||
const template = $("#entry_edit_template .world_entry").clone();
|
||||
template.data("uid", entry.uid);
|
||||
|
||||
// key
|
||||
const keyInput = template.find('textarea[name="key"]');
|
||||
keyInput.data("uid", entry.uid);
|
||||
keyInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
$(this).css("height", ""); //reset the height
|
||||
$(this).css("height", $(this).prop("scrollHeight") + "px");
|
||||
world_info_data.entries[uid].key = value
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.filter((x) => x);
|
||||
saveWorldInfo();
|
||||
});
|
||||
keyInput.val(entry.key.join(",")).trigger("input");
|
||||
keyInput.css("height", ""); //reset the height
|
||||
keyInput.css("height", $(this).prop("scrollHeight") + "px");
|
||||
|
||||
// keysecondary
|
||||
const keySecondaryInput = template.find('textarea[name="keysecondary"]');
|
||||
keySecondaryInput.data("uid", entry.uid);
|
||||
keySecondaryInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
$(this).css("height", ""); //reset the height
|
||||
$(this).css("height", $(this).prop("scrollHeight") + "px");
|
||||
world_info_data.entries[uid].keysecondary = value
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.filter((x) => x);
|
||||
saveWorldInfo();
|
||||
});
|
||||
keySecondaryInput.val(entry.keysecondary.join(",")).trigger("input");
|
||||
keySecondaryInput.css("height", ""); //reset the height
|
||||
keySecondaryInput.css("height", $(this).prop("scrollHeight") + "px");
|
||||
|
||||
// comment
|
||||
const commentInput = template.find('textarea[name="comment"]');
|
||||
commentInput.data("uid", entry.uid);
|
||||
commentInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
$(this).css("height", ""); //reset the height
|
||||
$(this).css("height", $(this).prop("scrollHeight") + "px");
|
||||
world_info_data.entries[uid].comment = value;
|
||||
saveWorldInfo();
|
||||
});
|
||||
commentInput.val(entry.comment).trigger("input");
|
||||
commentInput.css("height", ""); //reset the height
|
||||
commentInput.css("height", $(this).prop("scrollHeight") + "px");
|
||||
|
||||
// content
|
||||
const contentInput = template.find('textarea[name="content"]');
|
||||
contentInput.data("uid", entry.uid);
|
||||
contentInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
world_info_data.entries[uid].content = value;
|
||||
$(this).css("height", ""); //reset the height
|
||||
$(this).css("height", $(this).prop("scrollHeight") + "px");
|
||||
saveWorldInfo();
|
||||
|
||||
// count tokens
|
||||
const numberOfTokens = encode(value).length;
|
||||
$(this)
|
||||
.closest(".world_entry")
|
||||
.find(".world_entry_form_token_counter")
|
||||
.html(numberOfTokens);
|
||||
});
|
||||
contentInput.val(entry.content).trigger("input");
|
||||
contentInput.css("height", ""); //reset the height
|
||||
contentInput.css("height", $(this).prop("scrollHeight") + "px");
|
||||
|
||||
// selective
|
||||
const selectiveInput = template.find('input[name="selective"]');
|
||||
selectiveInput.data("uid", entry.uid);
|
||||
selectiveInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).prop("checked");
|
||||
world_info_data.entries[uid].selective = value;
|
||||
saveWorldInfo();
|
||||
|
||||
const keysecondary = $(this)
|
||||
.closest(".world_entry")
|
||||
.find(".keysecondary");
|
||||
value ? keysecondary.show() : keysecondary.hide();
|
||||
});
|
||||
selectiveInput.prop("checked", entry.selective).trigger("input");
|
||||
selectiveInput.siblings(".checkbox_fancy").click(function () {
|
||||
$(this).siblings("input").click();
|
||||
});
|
||||
|
||||
// constant
|
||||
const constantInput = template.find('input[name="constant"]');
|
||||
constantInput.data("uid", entry.uid);
|
||||
constantInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).prop("checked");
|
||||
world_info_data.entries[uid].constant = value;
|
||||
saveWorldInfo();
|
||||
});
|
||||
constantInput.prop("checked", entry.constant).trigger("input");
|
||||
constantInput.siblings(".checkbox_fancy").click(function () {
|
||||
$(this).siblings("input").click();
|
||||
});
|
||||
|
||||
// order
|
||||
const orderInput = template.find('input[name="order"]');
|
||||
orderInput.data("uid", entry.uid);
|
||||
orderInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = Number($(this).val());
|
||||
|
||||
world_info_data.entries[uid].order = !isNaN(value) ? value : 0;
|
||||
saveWorldInfo();
|
||||
});
|
||||
orderInput.val(entry.order).trigger("input");
|
||||
|
||||
// position
|
||||
if (entry.position === undefined) {
|
||||
entry.position = 0;
|
||||
}
|
||||
|
||||
const positionInput = template.find('input[name="position"]');
|
||||
positionInput.data("uid", entry.uid);
|
||||
positionInput.on("input", function () {
|
||||
const uid = $(this).data("uid");
|
||||
const value = Number($(this).val());
|
||||
world_info_data.entries[uid].position = !isNaN(value) ? value : 0;
|
||||
saveWorldInfo();
|
||||
});
|
||||
template
|
||||
.find(`input[name="position"][value=${entry.position}]`)
|
||||
.prop("checked", true)
|
||||
.trigger("input");
|
||||
|
||||
// display uid
|
||||
template.find(".world_entry_form_uid_value").html(entry.uid);
|
||||
|
||||
// delete button
|
||||
const deleteButton = template.find("input.delete_entry_button");
|
||||
deleteButton.data("uid", entry.uid);
|
||||
deleteButton.on("click", function () {
|
||||
const uid = $(this).data("uid");
|
||||
deleteWorldInfoEntry(uid);
|
||||
$(this).closest(".world_entry").remove();
|
||||
saveWorldInfo();
|
||||
});
|
||||
|
||||
template.appendTo("#world_popup_entries_list");
|
||||
return template;
|
||||
}
|
||||
|
||||
async function deleteWorldInfoEntry(uid) {
|
||||
if (!world_info_data || !("entries" in world_info_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete world_info_data.entries[uid];
|
||||
}
|
||||
|
||||
function createWorldInfoEntry() {
|
||||
const newEntryTemplate = {
|
||||
key: [],
|
||||
keysecondary: [],
|
||||
comment: "",
|
||||
content: "",
|
||||
constant: false,
|
||||
selective: false,
|
||||
order: 100,
|
||||
position: 0,
|
||||
};
|
||||
const newUid = getFreeWorldEntryUid();
|
||||
|
||||
if (!Number.isInteger(newUid)) {
|
||||
console.error("Couldn't assign UID to a new entry");
|
||||
return;
|
||||
}
|
||||
|
||||
const newEntry = { uid: newUid, ...newEntryTemplate };
|
||||
world_info_data.entries[newUid] = newEntry;
|
||||
|
||||
const entryTemplate = appendWorldEntry(newEntry);
|
||||
entryTemplate.get(0).scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
|
||||
async function _save() {
|
||||
const response = await fetch("/editworldinfo", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": token,
|
||||
},
|
||||
body: JSON.stringify({ name: world_info, data: world_info_data }),
|
||||
});
|
||||
}
|
||||
|
||||
async function saveWorldInfo(immediately) {
|
||||
if (!world_info || !world_info_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (immediately) {
|
||||
return await _save();
|
||||
}
|
||||
|
||||
saveWorldDebounced();
|
||||
}
|
||||
|
||||
async function renameWorldInfo() {
|
||||
const oldName = world_info;
|
||||
const newName = $("#world_popup_name").val();
|
||||
|
||||
if (oldName === newName) {
|
||||
return;
|
||||
}
|
||||
|
||||
world_info = newName;
|
||||
await saveWorldInfo(true);
|
||||
await deleteWorldInfo(oldName, newName);
|
||||
}
|
||||
|
||||
async function deleteWorldInfo(worldInfoName, selectWorldName) {
|
||||
if (!world_names.includes(worldInfoName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch("/deleteworldinfo", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": token,
|
||||
},
|
||||
body: JSON.stringify({ name: worldInfoName }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await updateWorldInfoList();
|
||||
|
||||
const selectedIndex = world_names.indexOf(selectWorldName);
|
||||
if (selectedIndex !== -1) {
|
||||
$("#world_info").val(selectedIndex).change();
|
||||
} else {
|
||||
$("#world_info").val("None").change();
|
||||
}
|
||||
|
||||
hideWorldEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function getFreeWorldEntryUid() {
|
||||
if (!world_info_data || !("entries" in world_info_data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const MAX_UID = 1_000_000; // <- should be safe enough :)
|
||||
for (let uid = 0; uid < MAX_UID; uid++) {
|
||||
if (uid in world_info_data.entries) {
|
||||
continue;
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getFreeWorldName() {
|
||||
const MAX_FREE_NAME = 100_000;
|
||||
for (let index = 1; index < MAX_FREE_NAME; index++) {
|
||||
const newName = `New World (${index})`;
|
||||
if (world_names.includes(newName)) {
|
||||
continue;
|
||||
}
|
||||
return newName;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function createNewWorldInfo() {
|
||||
const worldInfoTemplate = { entries: {} };
|
||||
const worldInfoName = getFreeWorldName();
|
||||
|
||||
if (!worldInfoName) {
|
||||
return;
|
||||
}
|
||||
|
||||
world_info = worldInfoName;
|
||||
world_info_data = { ...worldInfoTemplate };
|
||||
await saveWorldInfo(true);
|
||||
await updateWorldInfoList();
|
||||
|
||||
const selectedIndex = world_names.indexOf(worldInfoName);
|
||||
if (selectedIndex !== -1) {
|
||||
$("#world_info").val(selectedIndex).change();
|
||||
} else {
|
||||
$("#world_info").val("None").change();
|
||||
}
|
||||
}
|
||||
|
||||
function checkWorldInfo(chat) {
|
||||
if (world_info_data.entries.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const messagesToLookBack = world_info_depth * 2;
|
||||
let textToScan = chat.slice(0, messagesToLookBack).join("").toLowerCase();
|
||||
let worldInfoBefore = "";
|
||||
let worldInfoAfter = "";
|
||||
let needsToScan = true;
|
||||
let allActivatedEntries = new Set();
|
||||
|
||||
const sortedEntries = Object.keys(world_info_data.entries)
|
||||
.map((x) => world_info_data.entries[x])
|
||||
.sort((a, b) => b.order - a.order);
|
||||
while (needsToScan) {
|
||||
let activatedNow = new Set();
|
||||
|
||||
for (let entry of sortedEntries) {
|
||||
if (allActivatedEntries.has(entry.uid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.constant) {
|
||||
activatedNow.add(entry.uid);
|
||||
}
|
||||
|
||||
if (Array.isArray(entry.key) && entry.key.length) {
|
||||
primary: for (let key of entry.key) {
|
||||
if (key && textToScan.includes(key.trim().toLowerCase())) {
|
||||
if (
|
||||
entry.selective &&
|
||||
Array.isArray(entry.keysecondary) &&
|
||||
entry.keysecondary.length
|
||||
) {
|
||||
secondary: for (let keysecondary of entry.keysecondary) {
|
||||
if (
|
||||
keysecondary &&
|
||||
textToScan.includes(keysecondary.trim().toLowerCase())
|
||||
) {
|
||||
activatedNow.add(entry.uid);
|
||||
break secondary;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activatedNow.add(entry.uid);
|
||||
break primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
needsToScan = activatedNow.size > 0;
|
||||
const newEntries = [...activatedNow]
|
||||
.map((x) => world_info_data.entries[x])
|
||||
.sort((a, b) => sortedEntries.indexOf(a) - sortedEntries.indexOf(b));
|
||||
|
||||
for (const entry of newEntries) {
|
||||
if (entry.position === world_info_position.after) {
|
||||
worldInfoAfter = `${substituteParams(
|
||||
entry.content
|
||||
)}\n${worldInfoAfter}`;
|
||||
} else {
|
||||
worldInfoBefore = `${substituteParams(
|
||||
entry.content
|
||||
)}\n${worldInfoBefore}`;
|
||||
}
|
||||
|
||||
if (
|
||||
encode(worldInfoBefore + worldInfoAfter).length >= world_info_budget
|
||||
) {
|
||||
needsToScan = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsToScan) {
|
||||
textToScan =
|
||||
newEntries
|
||||
.map((x) => x.content)
|
||||
.join("\n")
|
||||
.toLowerCase() + textToScan;
|
||||
}
|
||||
|
||||
allActivatedEntries = new Set([...allActivatedEntries, ...activatedNow]);
|
||||
}
|
||||
|
||||
return { worldInfoBefore, worldInfoAfter };
|
||||
}
|
||||
|
||||
function selectImportedWorldInfo() {
|
||||
if (!imported_world_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
world_names.forEach((item, i) => {
|
||||
if (item === imported_world_name) {
|
||||
$("#world_info").val(i).change();
|
||||
}
|
||||
});
|
||||
imported_world_name = "";
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
$("#world_info").change(async function () {
|
||||
const selectedWorld = $("#world_info").find(":selected").val();
|
||||
world_info = null;
|
||||
world_info_data = null;
|
||||
|
||||
if (selectedWorld !== "None") {
|
||||
const worldIndex = Number(selectedWorld);
|
||||
world_info = !isNaN(worldIndex) ? world_names[worldIndex] : null;
|
||||
await loadWorldInfoData();
|
||||
}
|
||||
|
||||
hideWorldEditor();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
//**************************WORLD INFO IMPORT EXPORT*************************//
|
||||
$("#world_import_button").click(function () {
|
||||
$("#world_import_file").click();
|
||||
});
|
||||
|
||||
$("#world_import_file").on("change", function (e) {
|
||||
var file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ext = file.name.match(/\.(\w+)$/);
|
||||
if (!ext || ext[1].toLowerCase() !== "json") {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData($("#form_world_import").get(0));
|
||||
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
url: "/importworldinfo",
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (data) {
|
||||
if (data.name) {
|
||||
imported_world_name = data.name;
|
||||
updateWorldInfoList(imported_world_name);
|
||||
}
|
||||
},
|
||||
error: (jqXHR, exception) => { },
|
||||
});
|
||||
|
||||
// Will allow to select the same file twice in a row
|
||||
$("#form_world_import").trigger("reset");
|
||||
});
|
||||
|
||||
$("#world_info_edit_button").click(() => {
|
||||
is_world_edit_open ? hideWorldEditor() : showWorldEditor();
|
||||
});
|
||||
|
||||
$("#world_popup_export").click(() => {
|
||||
if (world_info && world_info_data) {
|
||||
const jsonValue = JSON.stringify(world_info_data);
|
||||
const fileName = `${world_info}.json`;
|
||||
download(jsonValue, fileName, "application/json");
|
||||
}
|
||||
});
|
||||
|
||||
$("#world_popup_delete").click(() => {
|
||||
callPopup("<h3>Delete the World Info?</h3>", "del_world");
|
||||
});
|
||||
|
||||
$("#world_popup_new").click(() => {
|
||||
createWorldInfoEntry();
|
||||
});
|
||||
|
||||
$("#world_cross").click(() => {
|
||||
hideWorldEditor();
|
||||
});
|
||||
|
||||
$("#world_popup_name_button").click(() => {
|
||||
renameWorldInfo();
|
||||
});
|
||||
|
||||
$("#world_create_button").click(() => {
|
||||
createNewWorldInfo();
|
||||
});
|
||||
|
||||
$(document).on("input", "#world_info_depth", function () {
|
||||
world_info_depth = Number($(this).val());
|
||||
$("#world_info_depth_counter").html(`${$(this).val()} Messages`);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on("input", "#world_info_budget", function () {
|
||||
world_info_budget = Number($(this).val());
|
||||
$("#world_info_budget_counter").html(`${$(this).val()} Tokens`);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
});
|
@ -2490,9 +2490,10 @@ a {
|
||||
max-width: 600px;
|
||||
max-height: 300px;
|
||||
border-radius: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mes img.img_extra~* {
|
||||
.mes img.img_extra ~ * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user