+
+
+
+
@@ -404,6 +450,7 @@ setTimeout(function () {
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
+ $('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
$('#ANClose').on('click', function () {
$("#floatingPrompt").transition({
opacity: 0,
diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js
index fd8d18554..ce673275d 100644
--- a/public/scripts/group-chats.js
+++ b/public/scripts/group-chats.js
@@ -3,6 +3,8 @@ import {
onlyUnique,
debounce,
delay,
+ isDataURL,
+ createThumbnail,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime } from "./RossAscends-mods.js";
import { sortCharactersList, sortGroupMembers } from './power-user.js';
@@ -57,6 +59,7 @@ import {
event_types,
getCurrentChatId,
setScenarioOverride,
+ getCropPopup,
} from "../script.js";
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
@@ -357,6 +360,14 @@ function updateGroupAvatar(group) {
}
function getGroupAvatar(group) {
+ if (!group) {
+ return $(`

`);
+ }
+
+ if (isDataURL(group.avatar_url)) {
+ return $(`

`);
+ }
+
const memberAvatars = [];
if (group && Array.isArray(group.members) && group.members.length) {
for (const member of group.members) {
@@ -925,6 +936,8 @@ function select_group_chats(groupId, skipAnimation) {
const group = groupId && groups.find((x) => x.id == groupId);
const groupName = group?.name ?? "";
setMenuType(!!group ? 'group_edit' : 'group_create');
+ $("#group_avatar_preview").empty().append(getGroupAvatar(group));
+ $("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
$("#rm_group_chat_name").val(groupName);
$("#rm_group_chat_name").off();
$("#rm_group_chat_name").on("input", async function () {
@@ -1049,6 +1062,67 @@ function select_group_chats(groupId, skipAnimation) {
$("#rm_group_automode_label").hide();
}
+ $("#group_avatar_button").off('input').on("input", uploadGroupAvatar);
+ $("#rm_group_restore_avatar").off('click').on("click", restoreGroupAvatar);
+
+
+ async function uploadGroupAvatar(event) {
+ const file = event.target.files[0];
+
+ if (!file) {
+ return;
+ }
+
+ const e = await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = resolve;
+ reader.onerror = reject;
+ reader.readAsDataURL(file);
+ });
+
+ $('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
+
+ const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
+
+ if (!croppedImage) {
+ return;
+ }
+
+ const thumbnail = await createThumbnail(croppedImage, 96, 144);
+
+ if (!groupId) {
+ $('#group_avatar_preview img').attr('src', thumbnail);
+ $('#rm_group_restore_avatar').show();
+ return;
+ }
+
+ let _thisGroup = groups.find((x) => x.id == groupId);
+ _thisGroup.avatar_url = thumbnail;
+ $("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
+ $("#rm_group_restore_avatar").show();
+ await editGroup(groupId, true, true);
+ }
+
+ async function restoreGroupAvatar() {
+ const confirm = await callPopup('
Are you sure you want to restore the group avatar?
Your custom image will be deleted, and a collage will be used instead.', 'confirm');
+
+ if (!confirm) {
+ return;
+ }
+
+ if (!groupId) {
+ $("#group_avatar_preview img").attr("src", default_avatar);
+ $("#rm_group_restore_avatar").hide();
+ return;
+ }
+
+ let _thisGroup = groups.find((x) => x.id == groupId);
+ _thisGroup.avatar_url = '';
+ $("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
+ $("#rm_group_restore_avatar").hide();
+ await editGroup(groupId, true, true);
+ }
+
$(document).off("click", ".group_member .right_menu_button");
$(document).on("click", ".group_member .right_menu_button", async function (event) {
event.stopPropagation();
@@ -1174,8 +1248,7 @@ async function createGroup() {
name = `Group: ${memberNames}`;
}
- // placeholder
- const avatar_url = 'img/five.png';
+ const avatar_url = $('#group_avatar_preview img').attr('src');
const chatName = humanizedDateTime();
const chats = [chatName];
@@ -1186,7 +1259,7 @@ async function createGroup() {
body: JSON.stringify({
name: name,
members: members,
- avatar_url: avatar_url,
+ avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
allow_self_responses: allow_self_responses,
activation_strategy: activation_strategy,
disabled_members: [],
diff --git a/public/scripts/utils.js b/public/scripts/utils.js
index ecbedf038..2087ffa90 100644
--- a/public/scripts/utils.js
+++ b/public/scripts/utils.js
@@ -616,3 +616,38 @@ export function extractDataFromPng(data, identifier = 'chara') {
}
}
}
+
+export function createThumbnail(dataUrl, maxWidth, maxHeight) {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+ img.src = dataUrl;
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ // Calculate the thumbnail dimensions while maintaining the aspect ratio
+ const aspectRatio = img.width / img.height;
+ let thumbnailWidth = maxWidth;
+ let thumbnailHeight = maxHeight;
+
+ if (img.width > img.height) {
+ thumbnailHeight = maxWidth / aspectRatio;
+ } else {
+ thumbnailWidth = maxHeight * aspectRatio;
+ }
+
+ // Set the canvas dimensions and draw the resized image
+ canvas.width = thumbnailWidth;
+ canvas.height = thumbnailHeight;
+ ctx.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight);
+
+ // Convert the canvas to a data URL and resolve the promise
+ const thumbnailDataUrl = canvas.toDataURL('image/jpeg');
+ resolve(thumbnailDataUrl);
+ };
+
+ img.onerror = () => {
+ reject(new Error('Failed to load the image.'));
+ };
+ });
+}
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index 6c4ec8f3c..43dcfb8ce 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -45,6 +45,8 @@ const world_info_position = {
};
+const worldInfoCache = {};
+
async function getWorldInfoPrompt(chat2, maxContext) {
let worldInfoString = "", worldInfoBefore = "", worldInfoAfter = "";
@@ -124,6 +126,10 @@ async function loadWorldInfoData(name) {
return;
}
+ if (worldInfoCache[name]) {
+ return worldInfoCache[name];
+ }
+
const response = await fetch("/getworldinfo", {
method: "POST",
headers: getRequestHeaders(),
@@ -133,6 +139,7 @@ async function loadWorldInfoData(name) {
if (response.ok) {
const data = await response.json();
+ worldInfoCache[name] = data;
return data;
}
@@ -557,6 +564,8 @@ async function saveWorldInfo(name, data, immediately) {
return;
}
+ delete worldInfoCache[name];
+
if (immediately) {
return await _save(name, data);
}
@@ -902,6 +911,30 @@ function convertAgnaiMemoryBook(inputObj) {
return outputObj;
}
+function convertRisuLorebook(inputObj) {
+ const outputObj = { entries: {} };
+
+ inputObj.data.forEach((entry, index) => {
+ outputObj.entries[index] = {
+ uid: index,
+ key: entry.key.split(',').map(x => x.trim()),
+ keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],
+ comment: entry.comment,
+ content: entry.content,
+ constant: entry.alwaysActive,
+ selective: entry.selective,
+ order: entry.insertorder,
+ position: world_info_position.before,
+ disable: false,
+ addMemo: true,
+ excludeRecursion: false,
+ displayIndex: index,
+ };
+ });
+
+ return outputObj;
+}
+
function convertNovelLorebook(inputObj) {
const outputObj = {
entries: {}
@@ -1055,13 +1088,21 @@ jQuery(() => {
// Convert Novel Lorebook
if (jsonData.lorebookVersion !== undefined) {
+ console.log('Converting Novel Lorebook');
formData.append('convertedData', JSON.stringify(convertNovelLorebook(jsonData)));
}
// Convert Agnai Memory Book
if (jsonData.kind === 'memory') {
+ console.log('Converting Agnai Memory Book');
formData.append('convertedData', JSON.stringify(convertAgnaiMemoryBook(jsonData)));
}
+
+ // Convert Risu Lorebook
+ if (jsonData.type === 'risu') {
+ console.log('Converting Risu Lorebook');
+ formData.append('convertedData', JSON.stringify(convertRisuLorebook(jsonData)));
+ }
} catch (error) {
toastr.error(`Error parsing file: ${error}`);
return;
diff --git a/public/style.css b/public/style.css
index 4ebe9bba3..6fe3f98f7 100644
--- a/public/style.css
+++ b/public/style.css
@@ -687,14 +687,14 @@ hr {
gap: 5px;
}
-#avatar_div_div {
+.add_avatar {
border: 2px solid var(--SmartThemeBodyColor);
margin: 2px;
cursor: pointer;
transition: filter 0.2s ease-in-out;
}
-#avatar_div_div:hover {
+.add_avatar:hover {
filter: drop-shadow(0px 0px 5px var(--SmartThemeQuoteColor));
}