mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'SillyTavern:dev' into dev
This commit is contained in:
@ -2430,7 +2430,7 @@
|
||||
</div>
|
||||
|
||||
<div id="avatar_div" class="avatar_div alignitemsflexstart justifySpaceBetween flexnowrap flexGap5">
|
||||
<label id="avatar_div_div" for="add_avatar_button" class="avatar" title="Click to select a new avatar for this character">
|
||||
<label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character">
|
||||
<img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
|
||||
<input hidden type="file" id="add_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
|
||||
</label>
|
||||
@ -2547,8 +2547,18 @@
|
||||
</div>
|
||||
<div id="groupTagList" class="tags paddingTopBot5"></div>
|
||||
</div>
|
||||
<div id="rm_group_top_bar" class="flex-container spaceBetween width100p">
|
||||
<div name="GroupStragegyAndOrder" id="rm_group_buttons" class="fontsize80p flex-container paddingLeftRight5">
|
||||
<div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p">
|
||||
<div class="flex1 flex-container alignitemscenter justifyCenter">
|
||||
<label class="add_avatar avatar" for="group_avatar_button" title="Click to select a new avatar for this group">
|
||||
<div id="group_avatar_preview">
|
||||
<div class="avatar">
|
||||
<img src="img/ai4.png" alt="avatar">
|
||||
</div>
|
||||
</div>
|
||||
<input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
|
||||
</label>
|
||||
</div>
|
||||
<div name="GroupStragegyAndOrder" id="rm_group_buttons" class="fontsize80p flex-container paddingLeftRight5 flex2">
|
||||
<div class="">
|
||||
<div class="flex-container flexnowrap width100p whitespacenowrap">
|
||||
<span data-i18n="Group reply strategy">Group reply strategy</span>
|
||||
@ -2576,12 +2586,13 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly">
|
||||
<div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly flex1">
|
||||
<div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-left-long"></div>
|
||||
<div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-scroll" title="Set a group chat scenario"></div>
|
||||
<div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-star" title="Add to Favorites"></div>
|
||||
<input id="rm_group_fav" type="hidden" />
|
||||
<div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-check" title="Create"></div>
|
||||
<div id="rm_group_restore_avatar" class="heightFitContent margin0 menu_button fa-solid fa-images" title="Restore collage avatar"></div>
|
||||
<div id="rm_group_delete" class="heightFitContent margin0 menu_button fa-solid fa-trash-can" title="Delete"></div>
|
||||
</div>
|
||||
|
||||
@ -3207,7 +3218,7 @@
|
||||
<div title="Move up" data-action="up" class="right_menu_button fa-solid fa-chevron-up"></div>
|
||||
<div title="Move down" data-action="down" class="right_menu_button fa-solid fa-chevron-down"></div>
|
||||
</div>
|
||||
<div title="View character card" data-action="view" class="right_menu_button fa-solid fa-xl fa-id-badge"></div>
|
||||
<div title="View character card" data-action="view" class="right_menu_button fa-solid fa-xl fa-image-portrait"></div>
|
||||
<div title="Remove from group" data-action="remove" class="right_menu_button fa-solid fa-2xl fa-xmark">
|
||||
</div>
|
||||
<div title="Add to group" data-action="add" class="right_menu_button fa-solid fa-2xl fa-plus"></div>
|
||||
|
@ -3742,7 +3742,7 @@ async function read_avatar_load(input) {
|
||||
}
|
||||
}
|
||||
|
||||
function getCropPopup(src) {
|
||||
export function getCropPopup(src) {
|
||||
return `<h3>Set the crop position of the avatar image and click Ok to confirm.</h3>
|
||||
<div id='avatarCropWrap'>
|
||||
<img id='avatarToCrop' src='${src}'>
|
||||
@ -7351,6 +7351,8 @@ $(document).ready(function () {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save before exporting
|
||||
await createOrEditCharacter();
|
||||
const body = { format, avatar_url: characters[this_chid].avatar };
|
||||
|
||||
const response = await fetch('/exportcharacter', {
|
||||
|
@ -27,6 +27,12 @@ export const metadata_keys = {
|
||||
position: 'note_position',
|
||||
}
|
||||
|
||||
const chara_note_position = {
|
||||
replace: 0,
|
||||
before: 1,
|
||||
after: 2,
|
||||
}
|
||||
|
||||
function setNoteTextCommand(_, text) {
|
||||
$('#extension_floating_prompt').val(text).trigger('input');
|
||||
toastr.success("Author's Note text updated");
|
||||
@ -87,11 +93,13 @@ async function onExtensionFloatingPromptInput() {
|
||||
chat_metadata[metadata_keys.prompt] = $(this).val();
|
||||
setMainPromptTokenCounterDebounced(chat_metadata[metadata_keys.prompt]);
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingIntervalInput() {
|
||||
chat_metadata[metadata_keys.interval] = Number($(this).val());
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingDepthInput() {
|
||||
@ -104,11 +112,23 @@ async function onExtensionFloatingDepthInput() {
|
||||
|
||||
chat_metadata[metadata_keys.depth] = value;
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingPositionInput(e) {
|
||||
chat_metadata[metadata_keys.position] = e.target.value;
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingCharPositionInput(e) {
|
||||
const value = e.target.value;
|
||||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||||
|
||||
if (charaNote) {
|
||||
charaNote.position = Number(value);
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
function onExtensionFloatingCharaPromptInput() {
|
||||
@ -143,7 +163,7 @@ function onExtensionFloatingCharaPromptInput() {
|
||||
if (!extension_settings.note.chara) {
|
||||
extension_settings.note.chara = []
|
||||
}
|
||||
Object.assign(tempCharaNote, { useChara: false })
|
||||
Object.assign(tempCharaNote, { useChara: false, position: chara_note_position.replace })
|
||||
|
||||
extension_settings.note.chara.push(tempCharaNote);
|
||||
} else {
|
||||
@ -189,9 +209,11 @@ function loadSettings() {
|
||||
|
||||
$('#extension_floating_chara').val(charaNote ? charaNote.prompt : '');
|
||||
$('#extension_use_floating_chara').prop('checked', charaNote ? charaNote.useChara : false);
|
||||
$(`input[name="extension_floating_char_position"][value="${charaNote?.position ?? chara_note_position.replace}"]`).prop('checked', true);
|
||||
} else {
|
||||
$('#extension_floating_chara').val('');
|
||||
$('#extension_use_floating_chara').prop('checked', false);
|
||||
$(`input[name="extension_floating_char_position"][value="${chara_note_position.replace}"]`).prop('checked', true);
|
||||
}
|
||||
|
||||
$('#extension_floating_default').val(extension_settings.note.default);
|
||||
@ -236,7 +258,17 @@ export function setFloatingPrompt() {
|
||||
|
||||
// Only replace with the chara note if the user checked the box
|
||||
if (charaNote && charaNote.useChara) {
|
||||
switch (charaNote.position) {
|
||||
case chara_note_position.before:
|
||||
prompt = charaNote.prompt + '\n' + prompt;
|
||||
break;
|
||||
case chara_note_position.after:
|
||||
prompt = prompt + '\n' + charaNote.prompt;
|
||||
break;
|
||||
default:
|
||||
prompt = charaNote.prompt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||||
@ -369,6 +401,20 @@ setTimeout(function () {
|
||||
<input id="extension_use_floating_chara" type="checkbox" />
|
||||
<span data-i18n="Use character author's note">Use character author's note</span>
|
||||
</label>
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="0" />
|
||||
Replace Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="1" />
|
||||
Top of Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="2" />
|
||||
Bottom of Author's Note
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="sysHR">
|
||||
@ -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,
|
||||
|
@ -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 $(`<div class="avatar"><img src="${default_avatar}"></div>`);
|
||||
}
|
||||
|
||||
if (isDataURL(group.avatar_url)) {
|
||||
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
|
||||
}
|
||||
|
||||
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('<h3>Are you sure you want to restore the group avatar?</h3> 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: [],
|
||||
|
@ -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.'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user