diff --git a/public/script.js b/public/script.js index 068870fee..5184500f4 100644 --- a/public/script.js +++ b/public/script.js @@ -944,7 +944,7 @@ function messageFormating(mes, ch_name, isSystem, forceAvatar) { if (this_chid != undefined && !isSystem) mes = mes.replaceAll("<", "<").replaceAll(">", ">"); //for welcome message - if ((this_chid === undefined || this_chid == "invalid-safety-id") && !selected_group) { + if ((this_chid === undefined || this_chid === "invalid-safety-id") && !selected_group) { mes = mes .replace(/\*\*(.+?)\*\*/g, "$1") .replace(/\n/g, "
"); @@ -1033,7 +1033,7 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true if (!mes["is_user"]) { if (mes.force_avatar) { avatarImg = mes.force_avatar; - } else if (this_chid == undefined || this_chid == "invalid-safety-id") { + } else if (this_chid === undefined || this_chid === "invalid-safety-id") { avatarImg = system_avatar; } else { if (characters[this_chid].avatar != "none") { @@ -1172,11 +1172,11 @@ function sendSystemMessage(type, text) { newMessage.mes += getSlashCommandsHelp(); } - if (!newMessage.extras) { - newMessage.extras = {}; + if (!newMessage.extra) { + newMessage.extra = {}; } - newMessage.extras.type = type; + newMessage.extra.type = type; chat.push(newMessage); addOneMessage(newMessage); @@ -2156,8 +2156,7 @@ async function Generate(type, automatic_trigger, force_name2) { } //rungenerate ends } else { //generate's primary loop ends, after this is error handling for no-connection or safety-id - - if (this_chid == undefined || this_chid == 'invalid-safety-id') { + if (this_chid === undefined || this_chid === 'invalid-safety-id') { //send ch sel popup_type = 'char_not_selected'; callPopup('

Сharacter is not selected

'); @@ -2535,7 +2534,7 @@ async function renameCharacter() { // Async delay to update UI await delay(1); - if (this_chid == -1) { + if (this_chid === -1) { throw new Error('New character not selected'); } diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js index 0ab4f80aa..212124975 100644 --- a/public/scripts/bookmarks.js +++ b/public/scripts/bookmarks.js @@ -9,10 +9,17 @@ import { chat_metadata, callPopup, getRequestHeaders, + getThumbnailUrl, + getCharacters, + chat, } from "../script.js"; -import { selected_group } from "./group-chats.js"; +import { humanizedDateTime } from "./RossAscends-mods.js"; +import { group_activation_strategy, groups, selected_group } from "./group-chats.js"; +import { createTagMapFromList } from "./tags.js"; import { + delay, + getUniqueName, stringFormat, } from "./utils.js"; @@ -100,32 +107,147 @@ function showBookmarksButtons() { } } +async function createNewBookmark() { + if (selected_group) { + alert('Chat bookmarks unsupported for groups'); + throw new Error(); + } + + let name = await getBookmarkName(characters[this_chid].chat); + + if (!name) { + return; + } + + const newMetadata = { main_chat: characters[this_chid].chat }; + saveChat(name, newMetadata); + let mainMessage = stringFormat(system_messages[system_message_types.BOOKMARK_CREATED].mes, name, name); + sendSystemMessage(system_message_types.BOOKMARK_CREATED, mainMessage); + saveChat(); +} + +async function backToMainChat() { + const mainChatName = getMainChatName(characters[this_chid].chat); + const allChats = await getExistingChatNames(); + + if (allChats.includes(mainChatName)) { + openCharacterChat(mainChatName); + } +} + +async function convertSoloToGroupChat() { + if (selected_group) { + console.log('Already in group. No need for conversion'); + return; + } + + if (this_chid === undefined) { + console.log('Need to have a character selected'); + return; + } + + const character = characters[this_chid]; + + // Populate group required fields + const name = getUniqueName(`Chat with ${character.name}`, y => groups.findIndex(x => x.name === y) !== -1); + const avatar = getThumbnailUrl('avatar', character.avatar); + const chatName = humanizedDateTime(); + const chats = [chatName]; + const members = [character.avatar]; + const activationStrategy = group_activation_strategy.NATURAL; + const allowSelfResponses = false; + const favChecked = character.fav == 'true'; + const metadata = Object.assign({}, chat_metadata); + + const createGroupResponse = await fetch("/creategroup", { + method: "POST", + headers: getRequestHeaders(), + body: JSON.stringify({ + name: name, + members: members, + avatar_url: avatar, + allow_self_responses: activationStrategy, + activation_strategy: allowSelfResponses, + chat_metadata: metadata, + fav: favChecked, + chat_id: chatName, + chats: chats, + }), + }); + + if (!createGroupResponse.ok) { + console.error('Group creation unsuccessful'); + return; + } + + const group = await createGroupResponse.json(); + + // Convert tags list and assign to group + createTagMapFromList("#tagList", group.id); + + // Update chars list + await getCharacters(); + + // Convert chat to group format + const groupChat = chat.slice(); + const genIdFirst = Date.now(); + + // Add something if the chat is empty + if (groupChat.length === 0) { + const newMessage = { + ...system_messages[system_message_types.GROUP], + send_date: humanizedDateTime(), + extra: { type: system_message_types.GROUP } + }; + groupChat.push(newMessage); + } + + for (let index = 0; index < groupChat.length; index++) { + const message = groupChat[index]; + + // Save group-chat marker + if (index == 0) { + message.is_group = true; + } + + // Skip messages we don't care about + if (message.is_user || message.is_system) { + continue; + } + + // Set force fields for solo character + message.name = character.name; + message.original_avatar = character.avatar; + message.force_avatar = getThumbnailUrl('avatar', character.avatar); + message.is_name = true; + + // Allow regens of a single message in group + if (typeof message.extra !== 'object') { + message.extra = { gen_id: genIdFirst + index }; + } + } + + // Save group chat + const createChatResponse = await fetch("/savegroupchat", { + method: "POST", + headers: getRequestHeaders(), + body: JSON.stringify({ id: chatName, chat: groupChat }), + }); + + if (!createChatResponse.ok) { + console.error('Group chat creation unsuccessful'); + return; + } + + // Click on the freshly selected group to open it + $(`.group_select[grid="${group.id}"]`).click(); + + await delay(1); + callPopup('The chat has been successfully converted!', 'text'); +} + $(document).ready(function () { - $('#option_new_bookmark').on('click', async function () { - if (selected_group) { - alert('Chat bookmarks unsupported for groups'); - throw new Error(); - } - - let name = await getBookmarkName(characters[this_chid].chat); - - if (!name) { - return; - } - - const newMetadata = { main_chat: characters[this_chid].chat }; - saveChat(name, newMetadata); - let mainMessage = stringFormat(system_messages[system_message_types.BOOKMARK_CREATED].mes, name, name); - sendSystemMessage(system_message_types.BOOKMARK_CREATED, mainMessage); - saveChat(); - }); - - $('#option_back_to_main').on('click', async function () { - const mainChatName = getMainChatName(characters[this_chid].chat); - const allChats = await getExistingChatNames(); - - if (allChats.includes(mainChatName)) { - openCharacterChat(mainChatName); - } - }); + $('#option_new_bookmark').on('click', createNewBookmark); + $('#option_back_to_main').on('click', backToMainChat); + $('#option_convert_to_group').on('click', convertSoloToGroupChat); }); diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index f3e6fea7d..041b474cb 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -71,7 +71,7 @@ let group_generation_id = null; let fav_grp_checked = false; let group_rm_panel_mode; -const group_activation_strategy = { +export const group_activation_strategy = { NATURAL: 0, LIST: 1, }; diff --git a/public/scripts/utils.js b/public/scripts/utils.js index ec316899f..64bf900f2 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -1,24 +1,8 @@ -export { - onlyUnique, - shuffle, - download, - urlContentToDataUri, - getBase64Async, - getStringHash, - debounce, - delay, - isSubsetOf, - incrementString, - stringFormat, - parseJsonFile, -}; - -/// UTILS -function onlyUnique(value, index, array) { +export function onlyUnique(value, index, array) { return array.indexOf(value) === index; } -function shuffle(array) { +export function shuffle(array) { let currentIndex = array.length, randomIndex; @@ -33,7 +17,7 @@ function shuffle(array) { return array; } -function download(content, fileName, contentType) { +export function download(content, fileName, contentType) { const a = document.createElement("a"); const file = new Blob([content], { type: contentType }); a.href = URL.createObjectURL(file); @@ -41,7 +25,7 @@ function download(content, fileName, contentType) { a.click(); } -async function urlContentToDataUri(url, params) { +export async function urlContentToDataUri(url, params) { const response = await fetch(url, params); const blob = await response.blob(); return await new Promise(callback => { @@ -51,7 +35,7 @@ async function urlContentToDataUri(url, params) { }); } -function getBase64Async(file) { +export function getBase64Async(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); @@ -64,7 +48,7 @@ function getBase64Async(file) { }); } -async function parseJsonFile(file) { +export async function parseJsonFile(file) { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = event => resolve(JSON.parse(event.target.result)); @@ -73,7 +57,7 @@ async function parseJsonFile(file) { }); } -function getStringHash(str, seed = 0) { +export function getStringHash(str, seed = 0) { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < str.length; i++) { @@ -88,7 +72,7 @@ function getStringHash(str, seed = 0) { return 4294967296 * (2097151 & h2) + (h1 >>> 0); }; -function debounce(func, timeout = 300) { +export function debounce(func, timeout = 300) { let timer; return (...args) => { clearTimeout(timer); @@ -96,10 +80,20 @@ function debounce(func, timeout = 300) { }; } -const delay = (ms) => new Promise((res) => setTimeout(res, ms)); -const isSubsetOf = (a, b) => (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false; +export function getUniqueName(name, exists) { + let i = 1; + let baseName = name; + while (exists(name)) { + name = `${baseName} (${i})`; + i++; + } + return name; +} -function incrementString(str) { +export const delay = (ms) => new Promise((res) => setTimeout(res, ms)); +export const isSubsetOf = (a, b) => (Array.isArray(a) && Array.isArray(b)) ? b.every(val => a.includes(val)) : false; + +export function incrementString(str) { // Find the trailing number or it will match the empty string const count = str.match(/\d*$/); @@ -108,7 +102,7 @@ function incrementString(str) { return str.substr(0, count.index) + (++count[0]); }; -function stringFormat(format) { +export function stringFormat(format) { const args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined'