diff --git a/public/script.js b/public/script.js index 6358cb5a6..1b79a80d6 100644 --- a/public/script.js +++ b/public/script.js @@ -93,7 +93,7 @@ import { import { debounce, delay } from "./scripts/utils.js"; import { extension_settings, loadExtensionSettings } from "./scripts/extensions.js"; import { executeSlashCommands, getSlashCommandsHelp } from "./scripts/slash-commands.js"; -import { tag_map, tags, loadTagsSettings, printTags, isElementTagged, getTagsList, appendTagToList } from "./scripts/tags.js"; +import { tag_map, tags, loadTagsSettings, printTags, isElementTagged, getTagsList, appendTagToList, createTagMapFromList, renameTagKey } from "./scripts/tags.js"; //exporting functions and vars for mods export { @@ -450,6 +450,13 @@ var colab_ini_step = 1; let token; +function getRequestHeaders() { + return { + "Content-Type": "application/json", + "X-CSRF-Token": token, + }; +} + //////////// Is this needed? setInterval(function () { switch (colab_ini_step) { @@ -610,10 +617,7 @@ async function getSoftPromptsList() { const response = await fetch("/getsoftprompts", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ api_server: api_server }), }); @@ -697,10 +701,7 @@ async function getCharacters() { await getGroups(); var response = await fetch("/getcharacters", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ "": "", }), @@ -724,10 +725,7 @@ async function getCharacters() { async function getBackgrounds() { const response = await fetch("/getbackgrounds", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ "": "", }), @@ -752,10 +750,7 @@ async function isColab() { is_checked_colab = true; const response = await fetch("/iscolab", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ "": "", }), @@ -801,10 +796,7 @@ async function setBackground(bg) { async function delBackground(bg) { const response = await fetch("/delbackground", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ bg: bg, }), @@ -817,10 +809,7 @@ async function delBackground(bg) { async function delChat(chatfile) { const response = await fetch("/delchat", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ chatfile: chatfile, id: characters[this_chid].name @@ -850,10 +839,7 @@ async function replaceCurrentChat() { const chatsResponse = await fetch("/getallchatsofcharacter", { method: 'POST', - headers: { - 'Content-Type': 'application/json', - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ avatar_url: characters[this_chid].avatar }) }); @@ -2384,6 +2370,62 @@ function resultCheckStatusNovel() { $("#api_button_novel").css("display", "inline-block"); } +async function renameCharacter() { + const oldAvatar = characters[this_chid].avatar; + const newValue = await callPopup('New name:', 'input', characters[this_chid].name); + + if (newValue && newValue !== characters[this_chid].name) { + const body = JSON.stringify({ avatar_url: oldAvatar, new_name: newValue }); + const response = await fetch('/renamecharacter', { + method: 'POST', + headers: getRequestHeaders(), + body, + }); + + try { + if (response.ok) { + const data = await response.json(); + const newAvatar = data.avatar; + + // Replace tags list + renameTagKey(oldAvatar, newAvatar); + + // Reload characters list + await getCharacters(); + + // Find newly renamed character + const newChId = characters.findIndex(c => c.avatar == data.avatar); + + if (newChId !== -1) { + // Select the character after the renaming + this_chid = -1; + $(`.character_select[chid="${newChId}"]`).click(); + + // Async delay to update UI + await delay(1); + + if (this_chid == -1) { + throw new Error('New character not selected'); + } + + callPopup('Character renamed! Sprites folder (if any) should be renamed manually.', 'text'); + } + else { + throw new Error('Newly renamed character was lost?'); + } + } + else { + throw new Error('Could not rename the character'); + } + } + catch { + // Reloading to prevent data corruption + await callPopup('Something went wrong. The page will be reloaded.', 'text'); + location.reload(); + } + } +} + async function saveChat(chat_name, withMetadata) { const metadata = { ...chat_metadata, ...(withMetadata || {}) }; let file_name = chat_name ?? characters[this_chid].chat; @@ -2654,10 +2696,7 @@ async function getUserAvatars() { $("#user_avatar_block").append('
+
'); const response = await fetch("/getuseravatars", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ "": "", }), @@ -3174,7 +3213,7 @@ function select_selected_character(chid) { $("#rm_button_back").css("display", "none"); //$("#character_import_button").css("display", "none"); $("#create_button").attr("value", "Save"); // what is the use case for this? - //$("#create_button_label").css("display", "none"); + $("#create_button_label").css("display", "none"); $("#rm_button_selected_ch").children("h2").text(display_name); $("#add_avatar_button").val(""); @@ -3291,7 +3330,7 @@ function updateFavButtonState(state) { $("#favorite_button").toggleClass('fav_off', !fav_ch_checked); } -function callPopup(text, type) { +function callPopup(text, type, inputValue = '') { if (type) { popup_type = type; } @@ -3315,7 +3354,7 @@ function callPopup(text, type) { $("#dialogue_popup_ok").text("Delete"); } - $("#dialogue_popup_input").val(''); + $("#dialogue_popup_input").val(inputValue); if (popup_type == 'input') { $("#dialogue_popup_input").css("display", "block"); $("#dialogue_popup_ok").text("Save"); @@ -4253,26 +4292,23 @@ $(document).ready(function () { ); $("#create_button").attr("value", "✅"); - if (true) { - let oldSelectedChar = null; - if (this_chid != undefined && this_chid != "invalid-safety-id") { - oldSelectedChar = characters[this_chid].name; - } - - await getCharacters(); - - $("#rm_info_block").transition({ opacity: 0, duration: 0 }); - var $prev_img = $("#avatar_div_div").clone(); - $("#rm_info_avatar").append($prev_img); - select_rm_info(`Character created

${DOMPurify.sanitize(save_name)}

`, oldSelectedChar); - - $("#rm_info_block").transition({ opacity: 1.0, duration: 2000 }); - } else { - $("#result_info").html(html); + let oldSelectedChar = null; + if (this_chid != undefined && this_chid != "invalid-safety-id") { + oldSelectedChar = characters[this_chid].name; } + + console.log(`new avatar id: ${html}`); + createTagMapFromList("#tagList", html); + await getCharacters(); + + $("#rm_info_block").transition({ opacity: 0, duration: 0 }); + var $prev_img = $("#avatar_div_div").clone(); + $("#rm_info_avatar").append($prev_img); + select_rm_info(`Character created

${DOMPurify.sanitize(save_name)}

`, oldSelectedChar); + + $("#rm_info_block").transition({ opacity: 1.0, duration: 2000 }); }, error: function (jqXHR, exception) { - //alert('ERROR: '+xhr.status+ ' Status Text: '+xhr.statusText+' '+xhr.responseText); $("#create_button").removeAttr("disabled"); }, }); @@ -4397,9 +4433,7 @@ $(document).ready(function () { } }); - $("#renameCharButton").on('click', function () { - $("#name_div").toggleClass('displayNone displayBlock'); - }) + $("#renameCharButton").on('click', renameCharacter); $("#talkativeness_slider").on("input", function () { if (menu_type == "create") { @@ -4682,10 +4716,7 @@ $(document).ready(function () { const selected = $("#softprompt").find(":selected").val(); const response = await fetch("/setsoftprompt", { method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, + headers: getRequestHeaders(), body: JSON.stringify({ name: selected, api_server: api_server }), }); @@ -5060,10 +5091,7 @@ $(document).ready(function () { const response = await fetch('/exportcharacter', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-Token': token, - }, + headers: getRequestHeaders(), body: JSON.stringify(body), }); diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index fde8a56f2..40ebb6b60 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -11,7 +11,7 @@ import { api_server_textgenerationwebui, is_send_press, getTokenCount, - max_context, + selected_button, } from "../script.js"; @@ -148,7 +148,7 @@ $("#character_popup").on("input", function () { RA_CountCharTokens(); }); function RA_CountCharTokens() { $("#result_info").html(""); //console.log('RA_TC -- starting with this_chid = ' + this_chid); - if (document.getElementById('name_div').style.display == "block") { //if new char + if (selected_button === "create") { //if new char function saveFormVariables() { create_save_name = $("#character_name_pole").val(); create_save_description = $("#description_textarea").val(); diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 9ebfade12..b44a488e1 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -9,6 +9,8 @@ export { isElementTagged, getTagsList, appendTagToList, + createTagMapFromList, + renameTagKey, }; const random_id = () => Math.round(Date.now() * Math.random()).toString(); @@ -30,6 +32,19 @@ function loadTagsSettings(settings) { tag_map = settings.tag_map !== undefined ? settings.tag_map : Object.create(null); } +function renameTagKey(oldKey, newKey) { + const value = tag_map[oldKey]; + tag_map[newKey] = value || []; + delete tag_map[oldKey]; + saveSettingsDebounced(); +} + +function createTagMapFromList(listElement, key) { + const tagIds = [...($(listElement).find(".tag").map((_, el) => $(el).attr("id")))]; + tag_map[key] = tagIds; + saveSettingsDebounced(); +} + function getTagsList(key) { if (!Array.isArray(tag_map[key])) { tag_map[key] = []; diff --git a/server.js b/server.js index efaf296fd..240d4b7a9 100644 --- a/server.js +++ b/server.js @@ -636,144 +636,106 @@ function charaFormatData(data) { var char = { "name": data.ch_name, "description": data.description, "personality": data.personality, "first_mes": data.first_mes, "avatar": 'none', "chat": data.ch_name + ' - ' + humanizedISO8601DateTime(), "mes_example": data.mes_example, "scenario": data.scenario, "create_date": humanizedISO8601DateTime(), "talkativeness": data.talkativeness, "fav": data.fav }; return char; } + app.post("/createcharacter", urlencodedParser, function (request, response) { - //var sameNameChar = fs.existsSync(charactersPath+request.body.ch_name+'.png'); - //if (sameNameChar == true) return response.sendStatus(500); if (!request.body) return response.sendStatus(400); request.body.ch_name = sanitize(request.body.ch_name); - console.log('/createcharacter -- looking for -- ' + (charactersPath + request.body.ch_name + '.png')); - console.log('Does this file already exists? ' + fs.existsSync(charactersPath + request.body.ch_name + '.png')); - if (!fs.existsSync(charactersPath + request.body.ch_name + '.png')) { - if (!fs.existsSync(chatsPath + request.body.ch_name)) fs.mkdirSync(chatsPath + request.body.ch_name); - let filedata = request.file; - //console.log(filedata.mimetype); - var fileType = ".png"; - var img_file = "ai"; - var img_path = "public/img/"; - var char = charaFormatData(request.body);//{"name": request.body.ch_name, "description": request.body.description, "personality": request.body.personality, "first_mes": request.body.first_mes, "avatar": 'none', "chat": Date.now(), "last_mes": '', "mes_example": ''}; - char = JSON.stringify(char); - if (!filedata) { - charaWrite('./public/img/ai4.png', char, request.body.ch_name, response); - } else { + const char = JSON.stringify(charaFormatData(request.body)); + const internalName = getPngName(request.body.ch_name); + const avatarName = `${internalName}.png`; + const defaultAvatar = './public/img/ai4.png'; + const chatsPath = path.join(chatsPath, internalName); - img_path = "./uploads/"; - img_file = filedata.filename - if (filedata.mimetype == "image/jpeg") fileType = ".jpeg"; - if (filedata.mimetype == "image/png") fileType = ".png"; - if (filedata.mimetype == "image/gif") fileType = ".gif"; - if (filedata.mimetype == "image/bmp") fileType = ".bmp"; - charaWrite(img_path + img_file, char, request.body.ch_name, response); - } - //console.log("The file was saved."); + if (!fs.existsSync(chatsPath)) fs.mkdirSync(chatsPath); + if (!request.file) { + charaWrite(defaultAvatar, char, internalName, response, avatarName); } else { - console.error("Error: Cannot save file. A character with that name already exists."); - response.send("Error: A character with that name already exists."); - //response.send({error: true}); + const uploadPath = path.join("./uploads/", request.file.filename); + charaWrite(uploadPath, char, internalName, response, avatarName); } }); +app.post("/renamecharacter", jsonParser, async function (request, response) { + if (!request.body.avatar_url || !request.body.new_name) { + return response.sendStatus(400); + } + + const oldAvatarName = request.body.avatar_url; + const newName = sanitize(request.body.new_name); + const oldInternalName = path.parse(request.body.avatar_url).name; + const newInternalName = getPngName(newName); + const newAvatarName = `${newInternalName}.png`; + + const oldAvatarPath = path.join(charactersPath, oldAvatarName); + const newAvatarPath = path.join(charactersPath, newAvatarName); + + const oldChatsPath = path.join(chatsPath, oldInternalName); + const newChatsPath = path.join(chatsPath, newInternalName); + + try { + // Read old file, replace name int it + const rawOldData = await charaRead(oldAvatarPath); + const oldData = json5.parse(rawOldData); + oldData['name'] = newName; + const newData = JSON.stringify(oldData); + + // Write data to new location + await charaWrite(oldAvatarPath, newData, newInternalName); + + // Rename chats folder + if (fs.existsSync(oldChatsPath) && !fs.existsSync(newChatsPath)) { + fs.renameSync(oldChatsPath, newChatsPath); + } + + // Remove the old character file + fs.rmSync(oldAvatarPath); + + // Return new avatar name to ST + return response.send({ 'avatar': newAvatarName }); + } + catch (err) { + console.error(err); + return response.sendStatus(500); + } +}); app.post("/editcharacter", urlencodedParser, async function (request, response) { - if (!request.body) { console.error('Error: no response body detected'); - response.send('Error: no response body detected'); + response.status(400).send('Error: no response body detected'); return; - // return response.sendStatus(400); } if (request.body.ch_name === '' || request.body.ch_name === undefined || request.body.ch_name === '.') { console.error('Error: invalid name.'); - response.send('Error: invalid name.'); + response.status(400).send('Error: invalid name.'); return; } - let filedata = request.file; - //console.log(filedata.mimetype); - var fileType = ".png"; - var img_file = "ai"; - var img_path = charactersPath; - var to_del_file = undefined; - var char = charaFormatData(request.body);//{"name": request.body.ch_name, "description": request.body.description, "personality": request.body.personality, "first_mes": request.body.first_mes, "avatar": request.body.avatar_url, "chat": request.body.chat, "last_mes": request.body.last_mes, "mes_example": ''}; + + let char = charaFormatData(request.body); char.chat = request.body.chat; char.create_date = request.body.create_date; char = JSON.stringify(char); let target_img = (request.body.avatar_url).replace('.png', ''); - let potentialNewCharCardFile = directories.characters + sanitize(request.body.ch_name) + '.png'; - let potentialSpritesFolder = directories.characters + sanitize(target_img) + '/'; - let potentialChatsFolder = directories.chats + sanitize(target_img) + '/'; - let potentialNewName = request.body.ch_name; - //console.log('CurFile: ' + target_img); - //console.log('CurChatDir: ' + potentialChatsFolder); - //console.log('CurSpritesDir: ' + potentialSpritesFolder); - //console.log('NewFile: ' + potentialNewCharCardFile); - - //if name_div has been edited, that means the char is being renamed - if (potentialNewName !== undefined && potentialNewName !== '' && potentialNewName !== target_img) { - //console.log(potentialNewName + 'is valid and not same as ' + target_img + ', possible rename detected.'); - if (!fs.existsSync(potentialNewCharCardFile)) { - to_del_file = img_path + sanitize(target_img) + '.png'; - target_img = sanitize(request.body.ch_name); - //console.log(potentialNewCharCardFile + ' does not already exist, renaming greenlit.'); - var renameChatsFolderTo = directories.chats + sanitize(target_img) + "/"; - var renameSpritesFolderTo = directories.characters + sanitize(target_img) + "/"; - //console.log('NewFile: ' + target_img); - //console.log('NewChatsDir: ' + renameChatsFolderTo); - //console.log('NewSpritesDir: ' + renameSpritesFolderTo); - //console.log('DelFile: ' + to_del_file); - - } else { - console.error(potentialNewCharCardFile + " already existed, so you can't rename to that!"); - //this needs to send a response back to the HTML to give a visual warning - return; - } - } try { - if (!filedata) { - if (to_del_file !== undefined) { - if (!fs.existsSync(renameChatsFolderTo)) { - if (fs.existsSync(potentialChatsFolder)) { - fs.renameSync(potentialChatsFolder, renameChatsFolderTo); - console.log('RENAMED: Chats folder: ' + potentialChatsFolder + " >> " + renameChatsFolderTo); - } else { console.log("Info: No chat folder to rename."); } - } else { console.error('ERROR: same chat folder name already existed. Chats Folder rename aborted.'); } - - if (!fs.existsSync(renameSpritesFolderTo)) { - if (fs.existsSync(potentialSpritesFolder)) { - fs.renameSync(potentialSpritesFolder, renameSpritesFolderTo); - console.log('RENAMED: Sprites folder:' + potentialSpritesFolder + ' >> ' + renameSpritesFolderTo); - } else { console.log("Info: No sprites folder to rename."); } - } else { console.error('ERROR: same sprites folder name already existed. Sprites Folder rename aborted.'); } - - console.log('REPLACING: ' + to_del_file + ' with ' + directories.characters + target_img + '.png'); - await charaWrite(img_path + request.body.avatar_url, char, target_img, response, 'Character saved'); - - fs.rmSync(to_del_file); - to_del_file = undefined; - return; - } //else { console.log("No rename detected, updating current card only."); } - - await charaWrite(img_path + request.body.avatar_url, char, target_img, response, 'Character saved'); - + if (!request.file) { + const avatarPath = path.join(charactersPath, request.body.avatar_url); + await charaWrite(avatarPath, char, target_img, response, 'Character saved'); } else { - //console.log(filedata.filename); - img_path = "uploads/"; - img_file = filedata.filename; - + const newAvatarPath = path.join("./uploads/", request.file.filename); invalidateThumbnail('avatar', request.body.avatar_url); - await charaWrite(img_path + img_file, char, target_img, response, 'Character saved'); - //response.send('Character saved'); + await charaWrite(newAvatarPath, char, target_img, response, 'Character saved'); } } catch { - //return response.send(400); console.error('An error occured, character edit invalidated.'); } - }); + app.post("/deletecharacter", urlencodedParser, function (request, response) { if (!request.body || !request.body.avatar_url) { return response.sendStatus(400);