diff --git a/public/index.html b/public/index.html index 7143a2e35..705cf5662 100644 --- a/public/index.html +++ b/public/index.html @@ -2453,7 +2453,7 @@
-
+
@@ -2468,6 +2468,7 @@ ${characterName}
+
diff --git a/public/script.js b/public/script.js index 5f6588688..6a5c8c61c 100644 --- a/public/script.js +++ b/public/script.js @@ -833,6 +833,13 @@ async function getCharacters() { characters[i] = []; characters[i] = getData[i]; characters[i]['name'] = DOMPurify.sanitize(characters[i]['name']); + + // For dropped-in cards + if (!characters[i]['chat']) { + characters[i]['chat'] = `${characters[i]['name']} - ${humanizedDateTime()}`; + } + + characters[i]['chat'] = String(characters[i]['chat']); } if (this_chid != undefined && this_chid != "invalid-safety-id") { $("#avatar_url_pole").val(characters[this_chid].avatar); @@ -1062,9 +1069,15 @@ function messageFormatting(mes, ch_name, isSystem, isUser) { return mes; } -function getMessageFromTemplate({ mesId, characterName, isUser, avatarImg, bias, isSystem, title, timerValue, timerTitle } = {}) { +function getMessageFromTemplate({ mesId, characterName, isUser, avatarImg, bias, isSystem, title, timerValue, timerTitle, bookmarkLink } = {}) { const mes = $('#message_template .mes').clone(); - mes.attr({ 'mesid': mesId, 'ch_name': characterName, 'is_user': isUser, 'is_system': !!isSystem }); + mes.attr({ + 'mesid': mesId, + 'ch_name': characterName, + 'is_user': isUser, + 'is_system': !!isSystem, + 'bookmark_link': bookmarkLink, + }); mes.find('.avatar img').attr('src', avatarImg); mes.find('.ch_name .name_text').text(characterName); mes.find('.mes_bias').html(bias); @@ -1098,15 +1111,7 @@ function addCopyToCodeBlocks(messageElement) { codeBlocks.get(i).appendChild(copyButton); copyButton.addEventListener('pointerup', function (event) { navigator.clipboard.writeText(codeBlocks.get(i).innerText); - const copiedMsg = document.createElement("div"); - copiedMsg.classList.add('code-copied'); - copiedMsg.innerText = "Copied!"; - copiedMsg.style.top = `${event.clientY - 55}px`; - copiedMsg.style.left = `${event.clientX - 55}px`; - document.body.append(copiedMsg); - setTimeout(() => { - document.body.removeChild(copiedMsg); - }, 1000); + toastr.info('Copied!', '', { timeOut: 2000 }); }); } } @@ -1158,6 +1163,7 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true mes.is_user, ); const bias = messageFormatting(mes.extra?.bias ?? ""); + const bookmarkLink = mes?.extra?.bookmark_link ?? ''; let params = { mesId: count_view_mes, @@ -1167,6 +1173,7 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true bias: bias, isSystem: isSystem, title: title, + bookmarkLink: bookmarkLink, ...formatGenerationTimer(mes.gen_started, mes.gen_finished), }; @@ -1496,7 +1503,7 @@ class StreamingProcessor { return; } - $(`#chat .mes[mesid="${messageId}"] .mes_stop`).css({ 'display': 'block' }); + $(`#chat .mes[mesid="${messageId}"] .mes_stop`).css({ 'display': '' }); $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'none' }); } @@ -1506,7 +1513,7 @@ class StreamingProcessor { } $(`#chat .mes[mesid="${messageId}"] .mes_stop`).css({ 'display': 'none' }); - $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'block' }); + $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': '' }); } onStartStreaming(text) { @@ -3990,7 +3997,7 @@ function messageEditDone(div) { mesBlock.find(".mes_text").empty(); mesBlock.find(".mes_edit_buttons").css("display", "none"); - mesBlock.find(".mes_buttons").css("display", "inline-block"); + mesBlock.find(".mes_buttons").css("display", ""); mesBlock.find(".mes_text").append( messageFormatting( text, @@ -6019,15 +6026,7 @@ $(document).ready(function () { var edit_mes_id = $(this).closest(".mes").attr("mesid"); var text = chat[edit_mes_id]["mes"]; navigator.clipboard.writeText(text); - const copiedMsg = document.createElement("div"); - copiedMsg.classList.add('code-copied'); - copiedMsg.innerText = "Copied!"; - copiedMsg.style.top = `${event.clientY - 55}px`; - copiedMsg.style.left = `${event.clientX - 55}px`; - document.body.append(copiedMsg); - setTimeout(() => { - document.body.removeChild(copiedMsg); - }, 1000); + toastr.info('Copied!', '', { timeOut: 2000 }); } catch (err) { console.error('Failed to copy: ', err); } @@ -6140,7 +6139,7 @@ $(document).ready(function () { $(this).closest(".mes_block").find(".mes_text").empty(); $(this).closest(".mes_edit_buttons").css("display", "none"); - $(this).closest(".mes_block").find(".mes_buttons").css("display", "inline-block"); + $(this).closest(".mes_block").find(".mes_buttons").css("display", ""); $(this) .closest(".mes_block") .find(".mes_text") @@ -6213,8 +6212,9 @@ $(document).ready(function () { showSwipeButtons(); }); - $(document).on("click", ".mes_edit_copy", function () { - if (!confirm('Create a copy of this message?')) { + $(document).on("click", ".mes_edit_copy", async function () { + const confirmation = await callPopup('Create a copy of this message?', 'confirm'); + if (!confirmation) { return; } @@ -6234,8 +6234,9 @@ $(document).ready(function () { }); - $(document).on("click", ".mes_edit_delete", function () { - if (!confirm("Are you sure you want to delete this message?")) { + $(document).on("click", ".mes_edit_delete", async function () { + const confirmation = await callPopup("Are you sure you want to delete this message?", 'confirm'); + if (!confirmation) { return; } @@ -6432,8 +6433,14 @@ $(document).ready(function () { select_rm_characters(); }); - $(document).on("click", ".select_chat_block, .bookmark_link", async function () { - let file_name = $(this).attr("file_name").replace(".jsonl", ""); + $(document).on("click", ".select_chat_block, .bookmark_link, .mes_bookmark", async function () { + let file_name = $(this).hasClass('mes_bookmark') + ? $(this).closest('.mes').attr('bookmark_link') + : $(this).attr("file_name").replace(".jsonl", ""); + + if (!file_name) { + return; + } if (selected_group) { await openGroupChat(selected_group, file_name); @@ -6598,10 +6605,6 @@ $(document).ready(function () { }); }); - $('#chat').on('scroll', () => { - $('.code-copied').css({ 'display': 'none' }); - }); - $(document).on('click', '.mes_img_enlarge', enlargeMessageImage); $(document).on('click', '.mes_img_delete', deleteMessageImage); diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js index cf39397c6..9496a38bc 100644 --- a/public/scripts/bookmarks.js +++ b/public/scripts/bookmarks.js @@ -124,6 +124,26 @@ function showBookmarksButtons() { } async function createNewBookmark() { + if (!chat.length) { + toastr.warning('The chat is empty.', 'Bookmark creation failed'); + return; + } + + const mesId = chat.length - 1; + const lastMes = chat[mesId]; + + if (typeof lastMes.extra !== 'object') { + lastMes.extra = {}; + } + + if (lastMes.extra.bookmark_link) { + const confirm = await callPopup('Bookmark checkpoint for the last message already exists. Would you like to replace it?', 'confirm'); + + if (!confirm) { + return; + } + } + let name = await getBookmarkName(); if (!name) { @@ -139,9 +159,11 @@ async function createNewBookmark() { await saveChat(name, newMetadata); } - let mainMessage = stringFormat(system_messages[system_message_types.BOOKMARK_CREATED].mes, name, name); - sendSystemMessage(system_message_types.BOOKMARK_CREATED, mainMessage); + lastMes.extra['bookmark_link'] = name; + $(`.mes[mesid="${mesId}"]`).attr('bookmark_link', name); + await saveChatConditional(); + toastr.success('Click the bookmark icon in the last message to open the checkpoint chat.', 'Bookmark created', { timeOut: 10000 }); } async function backToMainChat() { diff --git a/public/scripts/context-template.js b/public/scripts/context-template.js index d7480b1dc..a59c908ab 100644 --- a/public/scripts/context-template.js +++ b/public/scripts/context-template.js @@ -11,15 +11,7 @@ function openContextTemplateEditor() { function copyTemplateParameter(event) { const text = $(event.target).text(); navigator.clipboard.writeText(text); - const copiedMsg = document.createElement("div"); - copiedMsg.classList.add('code-copied'); - copiedMsg.innerText = "Copied!"; - copiedMsg.style.top = `${event.clientY - 55}px`; - copiedMsg.style.left = `${event.clientX - 55}px`; - document.body.append(copiedMsg); - setTimeout(() => { - document.body.removeChild(copiedMsg); - }, 1000); + toastr.info('Copied!', '', { timeOut: 2000 }); } jQuery(() => { diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index d7664e4d2..52722712a 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -174,6 +174,7 @@ function debugTtsPlayback() { "audioQueueProcessorReady": audioQueueProcessorReady, "ttsJobQueue": ttsJobQueue, "currentTtsJob": currentTtsJob, + "ttsConfig": extension_settings.tts } )) } @@ -372,6 +373,7 @@ async function processTtsQueue() { try { if (!text) { console.warn('Got empty text in TTS queue job.'); + completeTtsJob() return; } diff --git a/public/style.css b/public/style.css index 3bacb52bc..cb3c04013 100644 --- a/public/style.css +++ b/public/style.css @@ -2297,25 +2297,25 @@ input[type="range"]::-webkit-slider-thumb { margin-right: 30px; } -.mes_prompt, -.mes_narrate, -.sd_message_gen, -.mes_copy, -.mes_edit { +.mes_buttons>div { cursor: pointer; transition: 0.3s ease-in-out; filter: drop-shadow(0px 0px 2px black); opacity: 0.2; } -.mes_edit:hover, -.mes_copy:hover, -.sd_message_gen:hover, -.mes_narrate:hover, -.mes_stop:hover { +.mes_buttons>div:hover { opacity: 1; } +.mes_bookmark { + display: none; +} + +.mes:not([bookmark_link='']) .mes_bookmark { + display: inline-block; +} + .mes_edit_buttons { display: none; flex-direction: row; @@ -3611,17 +3611,6 @@ label[for="extensions_autoconnect"] { opacity: 0.8; } -.code-copied { - position: absolute; - z-index: 10000; - font-size: var(--mainFontSize); - color: var(--SmartThemeBodyColor); - background-color: var(--SmartThemeFastUIBGColor); - border-radius: 5px; - padding: 6px; - border: 1px solid var(--grey30a); -} - .inline-drawer-icon { display: block; cursor: pointer; diff --git a/server.js b/server.js index 96522bbc4..2a40f7e60 100644 --- a/server.js +++ b/server.js @@ -515,65 +515,51 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r app.post("/savechat", jsonParser, function (request, response) { - var dir_name = String(request.body.avatar_url).replace('.png', ''); - let chat_data = request.body.chat; - let jsonlData = chat_data.map(JSON.stringify).join('\n'); - fs.writeFile(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, jsonlData, 'utf8', function (err) { - if (err) { - response.send(err); - return console.log(err); - } else { - response.send({ result: "ok" }); - } - }); - + try { + var dir_name = String(request.body.avatar_url).replace('.png', ''); + let chat_data = request.body.chat; + let jsonlData = chat_data.map(JSON.stringify).join('\n'); + fs.writeFileSync(`${chatsPath + dir_name}/${sanitize(String(request.body.file_name))}.jsonl`, jsonlData, 'utf8'); + return response.send({ result: "ok" }); + } catch (error) { + response.send(error); + return console.log(error); + } }); + app.post("/getchat", jsonParser, function (request, response) { - var dir_name = String(request.body.avatar_url).replace('.png', ''); + try { + const dirName = String(request.body.avatar_url).replace('.png', ''); + const chatDirExists = fs.existsSync(chatsPath + dirName); - fs.stat(chatsPath + dir_name, function (err, stat) { - - if (stat === undefined) { //if no chat dir for the character is found, make one with the character name - - fs.mkdirSync(chatsPath + dir_name); - response.send({}); - return; - } else { - - if (err === null) { //if there is a dir, then read the requested file from the JSON call - - fs.stat(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, function (err, stat) { - if (err === null) { //if no error (the file exists), read the file - if (stat !== undefined) { - fs.readFile(`${chatsPath + dir_name}/${sanitize(request.body.file_name)}.jsonl`, 'utf8', (err, data) => { - if (err) { - console.error(err); - response.send(err); - return; - } - //console.log(data); - const lines = data.split('\n'); - - // Iterate through the array of strings and parse each line as JSON - const jsonData = lines.map(tryParse).filter(x => x); - response.send(jsonData); - //console.log('read the requested file') - - }); - } - } else { - response.send({}); - //return console.log(err); - return; - } - }); - } else { - console.error(err); - response.send({}); - return; - } + //if no chat dir for the character is found, make one with the character name + if (!chatDirExists) { + fs.mkdirSync(chatsPath + dirName); + return response.send({}); } - }); + + + if (!request.body.file_name) { + return response.send({}); + } + + const fileName = `${chatsPath + dirName}/${sanitize(String(request.body.file_name))}.jsonl`; + const chatFileExists = fs.existsSync(fileName); + + if (!chatFileExists) { + return response.send({}); + } + + const data = fs.readFileSync(fileName, 'utf8'); + const lines = data.split('\n'); + + // Iterate through the array of strings and parse each line as JSON + const jsonData = lines.map(tryParse).filter(x => x); + return response.send(jsonData); + } catch (error) { + console.error(error); + return response.send({}); + } }); app.post("/getstatus", jsonParser, async function (request, response_getstatus = response) {