From 15c81749b8c1ef68ec6fd96ca4b2eb98041cb9ab Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:09:33 +0300 Subject: [PATCH 01/19] Filter out invalid/broken characters --- server.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server.js b/server.js index b769ee605..03cd45014 100644 --- a/server.js +++ b/server.js @@ -1350,6 +1350,12 @@ app.post("/getcharacters", jsonParser, function (request, response) { let processingPromises = pngFiles.map((file, index) => processCharacter(file, index)); await Promise.all(processingPromises); performance.mark('B'); + // Filter out invalid/broken characters + characters = Object.values(characters).filter(x => x?.name).reduce((acc, val, index) => { + acc[index] = val; + return acc; + }, {}); + response.send(JSON.stringify(characters)); }); }); From b8939b8ccb388e654635f281f546f93cdb24182c Mon Sep 17 00:00:00 2001 From: city-unit <140349364+city-unit@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:14:36 -0400 Subject: [PATCH 02/19] Hide extra buttons when you click away --- public/script.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/public/script.js b/public/script.js index 78ca282f8..e073665ca 100644 --- a/public/script.js +++ b/public/script.js @@ -8101,6 +8101,14 @@ jQuery(async function () { }, 150); }) + $(document).on("click", function (e) { + // Check if the click was outside the relevant elements + if (!$(e.target).closest('.extraMesButtons, .extraMesButtonsHint').length) { + $('.extraMesButtonsHint').show().css('opacity', .2); // Reset the hint button to its original state + $('.extraMesButtons').hide().css('opacity', 0); // Hide the extra buttons and reset their opacity + } + }); + $(document).on("click", ".mes_edit_cancel", function () { let text = chat[this_edit_mes_id]["mes"]; From 95a3021e53eb9709ce6508b4a2af17290a4f5cd2 Mon Sep 17 00:00:00 2001 From: city-unit <140349364+city-unit@users.noreply.github.com> Date: Fri, 20 Oct 2023 22:31:57 -0400 Subject: [PATCH 03/19] Smooth transition --- public/script.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index e073665ca..88a3a7538 100644 --- a/public/script.js +++ b/public/script.js @@ -8104,8 +8104,22 @@ jQuery(async function () { $(document).on("click", function (e) { // Check if the click was outside the relevant elements if (!$(e.target).closest('.extraMesButtons, .extraMesButtonsHint').length) { - $('.extraMesButtonsHint').show().css('opacity', .2); // Reset the hint button to its original state - $('.extraMesButtons').hide().css('opacity', 0); // Hide the extra buttons and reset their opacity + // Transition out the .extraMesButtons first + $('.extraMesButtons').transition({ + opacity: 0, + duration: 150, + easing: 'ease-in-out', + complete: function () { + $(this).hide(); // Hide the .extraMesButtons after the transition + + // Transition the .extraMesButtonsHint back in + $('.extraMesButtonsHint').show().transition({ + opacity: .2, + duration: 150, + easing: 'ease-in-out' + }); + } + }); } }); From 63ecca1fe2b54824d97c61b6a9e969f63da154bc Mon Sep 17 00:00:00 2001 From: city-unit <140349364+city-unit@users.noreply.github.com> Date: Sat, 21 Oct 2023 01:04:16 -0400 Subject: [PATCH 04/19] Add token counting slash command. --- public/scripts/power-user.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 2d8ffbd47..91b4579fa 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -28,7 +28,7 @@ import { } from "./instruct-mode.js"; import { registerSlashCommand } from "./slash-commands.js"; -import { tokenizers } from "./tokenizers.js"; +import { tokenizers, getTokenCount } from "./tokenizers.js"; import { countOccurrences, debounce, delay, isOdd, resetScrollHeight, sortMoments, timestampToMoment } from "./utils.js"; @@ -1554,6 +1554,21 @@ function doResetPanels() { $("#movingUIreset").trigger('click'); } +function doCount() { + // get all of the messages in the chat + const messages = $('#chat .mes'); + + //concat all the messages into a single string + const allMessages = messages.toArray().map(x => $(x).find('.mes_text').text()).join(' '); + + console.log(allMessages); + + //toastr success with the token count of the chat + toastr.success(`Token count: ${getTokenCount(allMessages)}`); +} + + + function setAvgBG() { const bgimg = new Image(); bgimg.src = $('#bg1') @@ -2396,4 +2411,5 @@ $(document).ready(() => { registerSlashCommand('cut', doMesCut, [], '(number) – cuts the specified message from the chat', true, true); registerSlashCommand('resetpanels', doResetPanels, ['resetui'], '– resets UI panels to original state.', true, true); registerSlashCommand('bgcol', setAvgBG, [], '– WIP test of auto-bg avg coloring', true, true); + registerSlashCommand('count', doCount, [], '– counts the number of tokens in the current chat', true, false); }); From 6f0f420063e9dddcd1bfea6828c04f741d0e004c Mon Sep 17 00:00:00 2001 From: Xrystal Date: Sat, 21 Oct 2023 18:55:52 +0800 Subject: [PATCH 05/19] Promisify getallchatsofcharacter --- server.js | 69 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/server.js b/server.js index 03cd45014..542359c37 100644 --- a/server.js +++ b/server.js @@ -1829,7 +1829,7 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) { if (!request.body) return response.sendStatus(400); var char_dir = (request.body.avatar_url).replace('.png', '') - fs.readdir(chatsPath + char_dir, (err, files) => { + fs.readdir(chatsPath + char_dir, async (err, files) => { if (err) { console.log('found error in history loading'); console.error(err); @@ -1843,20 +1843,20 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) { // sort the files by name //jsonFiles.sort().reverse(); // print the sorted file names - var chatData = {}; - let ii = jsonFiles.length; //this is the number of files belonging to the character - if (ii !== 0) { - //console.log('found '+ii+' chat logs to load'); - for (let i = jsonFiles.length - 1; i >= 0; i--) { - const file = jsonFiles[i]; + let ii = jsonFiles.length; //this is the number of files belonging to the character + if (ii === 0) { + response.send({ error: true }); + return; + } + + const jsonFilesPromise = jsonFiles.map((file) => { + return new Promise(async (res) => { const fileStream = fs.createReadStream(chatsPath + char_dir + '/' + file); const fullPathAndFile = chatsPath + char_dir + '/' + file const stats = fs.statSync(fullPathAndFile); const fileSizeInKB = (stats.size / 1024).toFixed(2) + "kb"; - //console.log(fileSizeInKB); - const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity @@ -1869,33 +1869,42 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) { lastLine = line; }); rl.on('close', () => { - ii--; - if (lastLine) { + rl.close(); + if (lastLine) { let jsonData = tryParse(lastLine); - if (jsonData && (jsonData.name !== undefined || jsonData.character_name !== undefined)) { - chatData[i] = {}; - chatData[i]['file_name'] = file; - chatData[i]['file_size'] = fileSizeInKB; - chatData[i]['chat_items'] = itemCounter - 1; - chatData[i]['mes'] = jsonData['mes'] || '[The chat is empty]'; - chatData[i]['last_mes'] = jsonData['send_date'] || Date.now(); + if ( + jsonData && + (jsonData.name !== undefined || + jsonData.character_name !== undefined) + ) { + const chatData = {}; + + chatData['file_name'] = file; + chatData['file_size'] = fileSizeInKB; + chatData['chat_items'] = itemCounter - 1; + chatData['mes'] = + jsonData['mes'] || '[The chat is empty]'; + chatData['last_mes'] = + jsonData['send_date'] || Date.now(); + + res(chatData); } else { - console.log('Found an invalid or corrupted chat file: ' + fullPathAndFile); + console.log( + 'Found an invalid or corrupted chat file: ' + + fullPathAndFile + ); + + res({}); } } - if (ii === 0) { - //console.log('ii count went to zero, responding with chatData'); - response.send(chatData); - } - //console.log('successfully closing getallchatsofcharacter'); - rl.close(); }); - }; - } else { - //console.log('Found No Chats. Exiting Load Routine.'); - response.send({ error: true }); - }; + }); + }); + + const chatData = await Promise.all(jsonFilesPromise); + + response.send(chatData); }) }); From 1d38109dcf7788fdc70c681754e464281ca766cf Mon Sep 17 00:00:00 2001 From: Xrystal Date: Sat, 21 Oct 2023 18:56:51 +0800 Subject: [PATCH 06/19] Use JSON instead of json5 --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 542359c37..06e5e59c6 100644 --- a/server.js +++ b/server.js @@ -713,7 +713,7 @@ app.post("/getchat", jsonParser, function (request, response) { 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); + const jsonData = lines.map((l) => { try { return JSON.parse(l); } catch (_) { }}).filter(x => x); return response.send(jsonData); } catch (error) { console.error(error); From 85de5055533cbf02586ecc627da8d75651c72680 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:23:56 +0300 Subject: [PATCH 07/19] Move counter to plugin. Use chat context to get messages --- .../scripts/extensions/token-counter/index.js | 18 +++++++++++++++++- public/scripts/power-user.js | 18 +----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/public/scripts/extensions/token-counter/index.js b/public/scripts/extensions/token-counter/index.js index bdc8e19f0..bda1707bc 100644 --- a/public/scripts/extensions/token-counter/index.js +++ b/public/scripts/extensions/token-counter/index.js @@ -1,6 +1,7 @@ import { callPopup, main_api } from "../../../script.js"; import { getContext } from "../../extensions.js"; -import { getTokenizerModel } from "../../tokenizers.js"; +import { registerSlashCommand } from "../../slash-commands.js"; +import { getTokenCount, getTokenizerModel } from "../../tokenizers.js"; async function doTokenCounter() { const selectedTokenizer = main_api == 'openai' @@ -29,6 +30,20 @@ async function doTokenCounter() { callPopup(dialog, 'text'); } +function doCount() { + // get all of the messages in the chat + const context = getContext(); + const messages = context.chat.filter(x => x.mes && !x.is_system).map(x => x.mes); + + //concat all the messages into a single string + const allMessages = messages.join(' '); + + console.debug('All messages:', allMessages); + + //toastr success with the token count of the chat + toastr.success(`Token count: ${getTokenCount(allMessages)}`); +} + jQuery(() => { const buttonHtml = `
@@ -37,4 +52,5 @@ jQuery(() => {
`; $('#extensionsMenu').prepend(buttonHtml); $('#token_counter').on('click', doTokenCounter); + registerSlashCommand('count', doCount, [], '– counts the number of tokens in the current chat', true, false); }); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 91b4579fa..2d8ffbd47 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -28,7 +28,7 @@ import { } from "./instruct-mode.js"; import { registerSlashCommand } from "./slash-commands.js"; -import { tokenizers, getTokenCount } from "./tokenizers.js"; +import { tokenizers } from "./tokenizers.js"; import { countOccurrences, debounce, delay, isOdd, resetScrollHeight, sortMoments, timestampToMoment } from "./utils.js"; @@ -1554,21 +1554,6 @@ function doResetPanels() { $("#movingUIreset").trigger('click'); } -function doCount() { - // get all of the messages in the chat - const messages = $('#chat .mes'); - - //concat all the messages into a single string - const allMessages = messages.toArray().map(x => $(x).find('.mes_text').text()).join(' '); - - console.log(allMessages); - - //toastr success with the token count of the chat - toastr.success(`Token count: ${getTokenCount(allMessages)}`); -} - - - function setAvgBG() { const bgimg = new Image(); bgimg.src = $('#bg1') @@ -2411,5 +2396,4 @@ $(document).ready(() => { registerSlashCommand('cut', doMesCut, [], '(number) – cuts the specified message from the chat', true, true); registerSlashCommand('resetpanels', doResetPanels, ['resetui'], '– resets UI panels to original state.', true, true); registerSlashCommand('bgcol', setAvgBG, [], '– WIP test of auto-bg avg coloring', true, true); - registerSlashCommand('count', doCount, [], '– counts the number of tokens in the current chat', true, false); }); From 6fe4232f753f9db01ec0c7be6c82af0a3dbbf8ec Mon Sep 17 00:00:00 2001 From: IkariDevGIT <90197782+IkariDevGIT@users.noreply.github.com> Date: Sat, 21 Oct 2023 13:28:02 +0200 Subject: [PATCH 08/19] Add {{// (note)}} macro (#1265) * add {{# (note)}} macro * change from # to // for future updates * fix docs --- public/script.js | 3 +++ public/scripts/templates/macros.html | 1 + 2 files changed, 4 insertions(+) diff --git a/public/script.js b/public/script.js index 943bb7cc4..30f90bb74 100644 --- a/public/script.js +++ b/public/script.js @@ -1685,11 +1685,14 @@ function substituteParams(content, _name1, _name2, _original, _group) { content = content.replace(//gi, _group); content = content.replace(//gi, _group); + content = content.replace(/\{\{\/\/(.*?)\}\}/g, ""); + content = content.replace(/{{time}}/gi, moment().format('LT')); content = content.replace(/{{date}}/gi, moment().format('LL')); content = content.replace(/{{weekday}}/gi, moment().format('dddd')); content = content.replace(/{{isotime}}/gi, moment().format('HH:mm')); content = content.replace(/{{isodate}}/gi, moment().format('YYYY-MM-DD')); + content = content.replace(/{{datetimeformat +([^}]*)}}/gi, (_, format) => { const formattedTime = moment().format(format); return formattedTime; diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 1a3751ba1..007861308 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -3,6 +3,7 @@ System-wide Replacement Macros:
  • {{user}} - your current Persona username
  • {{char}} - the Character's name
  • {{input}} - the user input
  • +
  • {{// (note)}} - you can leave a note here, and the macro will be replaced with blank content
  • {{time}} - the current time
  • {{date}} - the current date
  • {{weekday}} - the current weekday
  • From 703965aec8f1b677b8c791f44656513659019857 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:39:01 +0300 Subject: [PATCH 09/19] Substitute macro in character editor token counter --- public/script.js | 6 +++--- public/scripts/RossAscends-mods.js | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/public/script.js b/public/script.js index 30f90bb74..8656a913a 100644 --- a/public/script.js +++ b/public/script.js @@ -1674,7 +1674,7 @@ function substituteParams(content, _name1, _name2, _original, _group) { if (typeof _original === 'string') { content = content.replace(/{{original}}/i, _original); } - content = content.replace(/{{input}}/gi, $('#send_textarea').val()); + content = content.replace(/{{input}}/gi, String($('#send_textarea').val())); content = content.replace(/{{user}}/gi, _name1); content = content.replace(/{{char}}/gi, _name2); content = content.replace(/{{charIfNotGroup}}/gi, _group); @@ -1692,7 +1692,7 @@ function substituteParams(content, _name1, _name2, _original, _group) { content = content.replace(/{{weekday}}/gi, moment().format('dddd')); content = content.replace(/{{isotime}}/gi, moment().format('HH:mm')); content = content.replace(/{{isodate}}/gi, moment().format('YYYY-MM-DD')); - + content = content.replace(/{{datetimeformat +([^}]*)}}/gi, (_, format) => { const formattedTime = moment().format(format); return formattedTime; @@ -8090,7 +8090,7 @@ jQuery(async function () { easing: 'ease-in-out' }); } - }); + }); } }); diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index 17197a808..9e1eb2c22 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -18,6 +18,8 @@ import { getThumbnailUrl, selectCharacterById, eventSource, + menu_type, + substituteParams, } from "../script.js"; import { @@ -234,7 +236,9 @@ export function RA_CountCharTokens() { total_tokens += Number(counter.text()); permanent_tokens += isPermanent ? Number(counter.text()) : 0; } else { - const tokens = getTokenCount(value); + // We substitute macro for existing characters, but not for the character being created + const valueToCount = menu_type === 'create' ? value : substituteParams(value); + const tokens = getTokenCount(valueToCount); counter.text(tokens); total_tokens += tokens; permanent_tokens += isPermanent ? tokens : 0; From 70fa93f0c9566c8ae53a85d4c6a5cbe93203e6fd Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:42:53 +0300 Subject: [PATCH 10/19] Clarify note macro docs --- public/scripts/templates/macros.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 007861308..8960a3a78 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -3,7 +3,7 @@ System-wide Replacement Macros:
  • {{user}} - your current Persona username
  • {{char}} - the Character's name
  • {{input}} - the user input
  • -
  • {{// (note)}} - you can leave a note here, and the macro will be replaced with blank content
  • +
  • {{// (note)}} - you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.
  • {{time}} - the current time
  • {{date}} - the current date
  • {{weekday}} - the current weekday
  • From 008fcece04f1705b27af14f0aee301e6af8b33b4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:04:36 +0300 Subject: [PATCH 11/19] Rewrite to sync readdir, add try-catch, filter out invalid files --- server.js | 60 ++++++++++++++++++++----------------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/server.js b/server.js index 06e5e59c6..da56ec549 100644 --- a/server.js +++ b/server.js @@ -713,7 +713,7 @@ app.post("/getchat", jsonParser, function (request, response) { const lines = data.split('\n'); // Iterate through the array of strings and parse each line as JSON - const jsonData = lines.map((l) => { try { return JSON.parse(l); } catch (_) { }}).filter(x => x); + const jsonData = lines.map((l) => { try { return JSON.parse(l); } catch (_) { } }).filter(x => x); return response.send(jsonData); } catch (error) { console.error(error); @@ -1825,37 +1825,27 @@ function getImages(path) { .sort(Intl.Collator().compare); } -app.post("/getallchatsofcharacter", jsonParser, function (request, response) { +app.post("/getallchatsofcharacter", jsonParser, async function (request, response) { if (!request.body) return response.sendStatus(400); - var char_dir = (request.body.avatar_url).replace('.png', '') - fs.readdir(chatsPath + char_dir, async (err, files) => { - if (err) { - console.log('found error in history loading'); - console.error(err); - response.send({ error: true }); - return; - } + const characterDirectory = (request.body.avatar_url).replace('.png', ''); - // filter for JSON files + try { + const chatsDirectory = path.join(chatsPath, characterDirectory); + const files = fs.readdirSync(chatsDirectory); const jsonFiles = files.filter(file => path.extname(file) === '.jsonl'); - // sort the files by name - //jsonFiles.sort().reverse(); - // print the sorted file names - let ii = jsonFiles.length; //this is the number of files belonging to the character - if (ii === 0) { + if (jsonFiles.length === 0) { response.send({ error: true }); return; } const jsonFilesPromise = jsonFiles.map((file) => { return new Promise(async (res) => { - const fileStream = fs.createReadStream(chatsPath + char_dir + '/' + file); - - const fullPathAndFile = chatsPath + char_dir + '/' + file - const stats = fs.statSync(fullPathAndFile); - const fileSizeInKB = (stats.size / 1024).toFixed(2) + "kb"; + const pathToFile = path.join(chatsPath, characterDirectory, file); + const fileStream = fs.createReadStream(pathToFile); + const stats = fs.statSync(pathToFile); + const fileSizeInKB = `${(stats.size / 1024).toFixed(2)}kb`; const rl = readline.createInterface({ input: fileStream, @@ -1872,29 +1862,19 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) { rl.close(); if (lastLine) { - let jsonData = tryParse(lastLine); - if ( - jsonData && - (jsonData.name !== undefined || - jsonData.character_name !== undefined) - ) { + const jsonData = tryParse(lastLine); + if (jsonData && (jsonData.name || jsonData.character_name)) { const chatData = {}; chatData['file_name'] = file; chatData['file_size'] = fileSizeInKB; chatData['chat_items'] = itemCounter - 1; - chatData['mes'] = - jsonData['mes'] || '[The chat is empty]'; - chatData['last_mes'] = - jsonData['send_date'] || Date.now(); + chatData['mes'] = jsonData['mes'] || '[The chat is empty]'; + chatData['last_mes'] = jsonData['send_date'] || Date.now(); res(chatData); } else { - console.log( - 'Found an invalid or corrupted chat file: ' + - fullPathAndFile - ); - + console.log('Found an invalid or corrupted chat file:', pathToFile); res({}); } } @@ -1903,9 +1883,13 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) { }); const chatData = await Promise.all(jsonFilesPromise); + const validFiles = chatData.filter(i => i.file_name); - response.send(chatData); - }) + return response.send(validFiles); + } catch (error) { + console.log(error); + return response.send({ error: true }); + } }); function getPngName(file) { From 25c461bd3facfa9b5d56507a9dfd71edc0c21c95 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:10:48 +0300 Subject: [PATCH 12/19] Add text to open alternate greetings --- public/css/tags.css | 5 ++--- public/index.html | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/css/tags.css b/public/css/tags.css index b6985c8a9..9fe53cdf6 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -138,8 +138,7 @@ filter: brightness(1); } -.tags_view, -.open_alternate_greetings { +.tags_view { margin: 0; aspect-ratio: 1 / 1; } @@ -171,4 +170,4 @@ -1px 1px 0px black, 1px -1px 0px black; opacity: 1; -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index f4e527548..8e79824b3 100644 --- a/public/index.html +++ b/public/index.html @@ -3519,8 +3519,11 @@ ? - From 97d75aef7382ffb417e65a77e42540c37383cf3b Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:17:18 +0300 Subject: [PATCH 13/19] #1272 Fix control buttons when expand actions is used --- public/script.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/script.js b/public/script.js index 8656a913a..e8c204e24 100644 --- a/public/script.js +++ b/public/script.js @@ -8073,6 +8073,11 @@ jQuery(async function () { }) $(document).on("click", function (e) { + // Expanded options don't need to be closed + if (power_user.expand_message_actions) { + return; + } + // Check if the click was outside the relevant elements if (!$(e.target).closest('.extraMesButtons, .extraMesButtonsHint').length) { // Transition out the .extraMesButtons first From b4e29bf157d11923dcdd2d69915935202f669061 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:41:27 +0300 Subject: [PATCH 14/19] #1272 Optimize performance of context buttons visibility switch --- public/script.js | 4 ++-- public/scripts/power-user.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index e8c204e24..1fa76da9d 100644 --- a/public/script.js +++ b/public/script.js @@ -8081,7 +8081,7 @@ jQuery(async function () { // Check if the click was outside the relevant elements if (!$(e.target).closest('.extraMesButtons, .extraMesButtonsHint').length) { // Transition out the .extraMesButtons first - $('.extraMesButtons').transition({ + $('.extraMesButtons:visible').transition({ opacity: 0, duration: 150, easing: 'ease-in-out', @@ -8089,7 +8089,7 @@ jQuery(async function () { $(this).hide(); // Hide the .extraMesButtons after the transition // Transition the .extraMesButtonsHint back in - $('.extraMesButtonsHint').show().transition({ + $('.extraMesButtonsHint:not(:visible)').show().transition({ opacity: .2, duration: 150, easing: 'ease-in-out' diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 6345904ff..417b3f916 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -399,6 +399,7 @@ function switchMessageActions() { power_user.expand_message_actions = value === null ? false : value == "true"; $("body").toggleClass("expandMessageActions", power_user.expand_message_actions); $("#expandMessageActions").prop("checked", power_user.expand_message_actions); + $('.extraMesButtons, .extraMesButtonsHint').removeAttr('style'); } function switchUiMode() { From 08a1eaad62e65e98624579b05af02f139b7127a0 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:54:05 +0300 Subject: [PATCH 15/19] Less obnoxious alt.greetings button --- public/index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index 8e79824b3..2871efcea 100644 --- a/public/index.html +++ b/public/index.html @@ -3520,9 +3520,8 @@ From 1e251c09e3b1785643c760e35aebde2df01f9708 Mon Sep 17 00:00:00 2001 From: IkariDevGIT <90197782+IkariDevGIT@users.noreply.github.com> Date: Sat, 21 Oct 2023 15:02:29 +0200 Subject: [PATCH 16/19] Better input inject Quick Reply (#1255) * Force open Char WI with Shift QoL - Force open character WI selector menu if icon clicked with Shift. * Update world-info.js (revert personal new WI pos preference) * Fix element widths * Fix event typing * Update index.js * Update index.js * change Prompt to Input this makes it more clear what it does(i think) --------- Co-authored-by: valden80 <111227649+valden80@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> --- .../scripts/extensions/quick-reply/index.js | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index 654e5fe4b..5e874bd28 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -15,8 +15,9 @@ const defaultSettings = { quickReplyEnabled: false, numberOfSlots: 5, quickReplySlots: [], - placeBeforePromptEnabled: false, + placeBeforeInputEnabled: false, quickActionEnabled: false, + AutoInputInject: true } //method from worldinfo @@ -77,8 +78,9 @@ async function loadSettings(type) { $('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled); $('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots); - $('#placeBeforePromptEnabled').prop('checked', extension_settings.quickReply.placeBeforePromptEnabled); + $('#placeBeforeInputEnabled').prop('checked', extension_settings.quickReply.placeBeforeInputEnabled); $('#quickActionEnabled').prop('checked', extension_settings.quickReply.quickActionEnabled); + $('#AutoInputInject').prop('checked', extension_settings.quickReply.AutoInputInject); } function onQuickReplyInput(id) { @@ -109,8 +111,13 @@ async function onQuickActionEnabledInput() { saveSettingsDebounced(); } -async function onPlaceBeforePromptEnabledInput() { - extension_settings.quickReply.placeBeforePromptEnabled = !!$(this).prop('checked'); +async function onPlaceBeforeInputEnabledInput() { + extension_settings.quickReply.placeBeforeInputEnabled = !!$(this).prop('checked'); + saveSettingsDebounced(); +} + +async function onAutoInputInject() { + extension_settings.quickReply.AutoInputInject = !!$(this).prop('checked'); saveSettingsDebounced(); } @@ -125,16 +132,15 @@ async function sendQuickReply(index) { let newText; - if (existingText) { - // If existing text, add space after prompt - if (extension_settings.quickReply.placeBeforePromptEnabled) { + if (existingText && extension_settings.quickReply.AutoInputInject){ + if (extension_settings.quickReply.placeBeforeInputEnabled) { newText = `${prompt} ${existingText} `; } else { newText = `${existingText} ${prompt} `; } } else { - // If no existing text, add prompt only (with a trailing space) - newText = prompt + ' '; + // If no existing text and placeBeforeInputEnabled false, add prompt only (with a trailing space) + newText = `${prompt} `; } newText = substituteParams(newText); @@ -355,8 +361,12 @@ jQuery(async () => { Disable Send / Insert In User Input +
    - Enable Quick Replies + Enable Quick Replies
    - + +
    `; } @@ -374,10 +374,14 @@ jQuery(async () => { Inject user input automatically
    (If disabled, use {{input}} macro for manual injection) +
    - - +
    From 5012237eb39f4b4a8f495d503ac15ccc96ad7d30 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 21 Oct 2023 17:43:25 +0300 Subject: [PATCH 19/19] Display generated bg in the bg block. Add ability to copy chat bg to system list. --- public/css/mobile-styles.css | 4 +- public/index.html | 13 +- public/script.js | 1 + public/scripts/backgrounds.js | 244 ++++++++++++------ .../extensions/stable-diffusion/index.js | 7 +- public/scripts/utils.js | 2 +- public/style.css | 14 +- 7 files changed, 200 insertions(+), 85 deletions(-) diff --git a/public/css/mobile-styles.css b/public/css/mobile-styles.css index dd448c5e9..80df97ff6 100644 --- a/public/css/mobile-styles.css +++ b/public/css/mobile-styles.css @@ -293,7 +293,7 @@ display: none; } - #bg_menu_content { + .bg_list { width: unset; } } @@ -445,4 +445,4 @@ #horde_model { height: unset; } -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index 2871efcea..4b08cce31 100644 --- a/public/index.html +++ b/public/index.html @@ -3243,10 +3243,10 @@ Auto-select -

    +

    System Backgrounds

    -
    +
    +

    + Chat Backgrounds +

    +
    + Chat backgrounds generated with the  Stable Diffusion extension will appear here. +
    +
    +
    @@ -3894,6 +3902,7 @@
    +
    diff --git a/public/script.js b/public/script.js index 1fa76da9d..097f4a187 100644 --- a/public/script.js +++ b/public/script.js @@ -7721,6 +7721,7 @@ jQuery(async function () { $("#rm_button_selected_ch").children("h2").text(''); select_rm_characters(); sendSystemMessage(system_message_types.WELCOME); + eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId()); } else { toastr.info("Please stop the message generation first."); } diff --git a/public/scripts/backgrounds.js b/public/scripts/backgrounds.js index 309fadece..1087f43b1 100644 --- a/public/scripts/backgrounds.js +++ b/public/scripts/backgrounds.js @@ -3,14 +3,24 @@ import { saveMetadataDebounced } from "./extensions.js"; import { registerSlashCommand } from "./slash-commands.js"; import { stringFormat } from "./utils.js"; -const METADATA_KEY = 'custom_background'; +const BG_METADATA_KEY = 'custom_background'; +const LIST_METADATA_KEY = 'chat_backgrounds'; /** - * @param {string} background + * Sets the background for the current chat and adds it to the list of custom backgrounds. + * @param {{url: string, path:string}} backgroundInfo */ -function forceSetBackground(background) { - saveBackgroundMetadata(background); +function forceSetBackground(backgroundInfo) { + saveBackgroundMetadata(backgroundInfo.url); setCustomBackground(); + + const list = chat_metadata[LIST_METADATA_KEY] || []; + const bg = backgroundInfo.path; + list.push(bg); + chat_metadata[LIST_METADATA_KEY] = list; + saveMetadataDebounced(); + getChatBackgroundsList(); + highlightNewBackground(bg); highlightLockedBackground(); } @@ -22,9 +32,27 @@ async function onChatChanged() { unsetCustomBackground(); } + getChatBackgroundsList(); highlightLockedBackground(); } +function getChatBackgroundsList() { + const list = chat_metadata[LIST_METADATA_KEY]; + const listEmpty = !Array.isArray(list) || list.length === 0; + + $('#bg_custom_content').empty(); + $('#bg_chat_hint').toggle(listEmpty); + + if (listEmpty) { + return; + } + + for (const bg of list) { + const template = getBackgroundFromTemplate(bg, true); + $('#bg_custom_content').append(template); + } +} + function getBackgroundPath(fileUrl) { return `backgrounds/${fileUrl}`; } @@ -32,7 +60,7 @@ function getBackgroundPath(fileUrl) { function highlightLockedBackground() { $('.bg_example').removeClass('locked'); - const lockedBackground = chat_metadata[METADATA_KEY]; + const lockedBackground = chat_metadata[BG_METADATA_KEY]; if (!lockedBackground) { return; @@ -71,21 +99,21 @@ function onUnlockBackgroundClick(e) { } function hasCustomBackground() { - return chat_metadata[METADATA_KEY]; + return chat_metadata[BG_METADATA_KEY]; } function saveBackgroundMetadata(file) { - chat_metadata[METADATA_KEY] = file; + chat_metadata[BG_METADATA_KEY] = file; saveMetadataDebounced(); } function removeBackgroundMetadata() { - delete chat_metadata[METADATA_KEY]; + delete chat_metadata[BG_METADATA_KEY]; saveMetadataDebounced(); } function setCustomBackground() { - const file = chat_metadata[METADATA_KEY]; + const file = chat_metadata[BG_METADATA_KEY]; // bg already set if (document.getElementById("bg_custom").style.backgroundImage == file) { @@ -100,6 +128,7 @@ function unsetCustomBackground() { } function onSelectBackgroundClick() { + const isCustom = $(this).attr('custom') === 'true'; const relativeBgImage = getUrlParameter(this); // if clicked on upload button @@ -107,15 +136,18 @@ function onSelectBackgroundClick() { return; } - if (hasCustomBackground()) { + // Automatically lock the background if it's custom or other background is locked + if (hasCustomBackground() || isCustom) { saveBackgroundMetadata(relativeBgImage); setCustomBackground(); + highlightLockedBackground(); + } else { + highlightLockedBackground(); } - highlightLockedBackground(); const customBg = window.getComputedStyle(document.getElementById('bg_custom')).backgroundImage; - // custom background is set. Do not override the layer below + // Custom background is set. Do not override the layer below if (customBg !== 'none') { return; } @@ -123,7 +155,7 @@ function onSelectBackgroundClick() { const bgFile = $(this).attr("bgfile"); const backgroundUrl = getBackgroundPath(bgFile); - // fetching to browser memory to reduce flicker + // Fetching to browser memory to reduce flicker fetch(backgroundUrl).then(() => { $("#bg1").css("background-image", relativeBgImage); setBackground(bgFile); @@ -132,32 +164,80 @@ function onSelectBackgroundClick() { }); } -async function onRenameBackgroundClick(e) { +async function onCopyToSystemBackgroundClick(e) { e.stopPropagation(); - const old_bg = $(this).closest('.bg_example').attr('bgfile'); + const bgNames = await getNewBackgroundName(this); - if (!old_bg) { + if (!bgNames) { + return; + } + + const bgFile = await fetch(bgNames.oldBg); + + if (!bgFile.ok) { + toastr.warning('Failed to copy background'); + return; + } + + const blob = await bgFile.blob(); + const file = new File([blob], bgNames.newBg); + const formData = new FormData(); + formData.set('avatar', file); + + uploadBackground(formData); + + const list = chat_metadata[LIST_METADATA_KEY] || []; + const index = list.indexOf(bgNames.oldBg); + list.splice(index, 1); + saveMetadataDebounced(); + getChatBackgroundsList(); +} + +/** + * Gets the new background name from the user. + * @param {Element} referenceElement + * @returns {Promise<{oldBg: string, newBg: string}>} + * */ +async function getNewBackgroundName(referenceElement) { + const exampleBlock = $(referenceElement).closest('.bg_example'); + const isCustom = exampleBlock.attr('custom') === 'true'; + const oldBg = exampleBlock.attr('bgfile'); + + if (!oldBg) { console.debug('no bgfile'); return; } - const fileExtension = old_bg.split('.').pop(); - const old_bg_extensionless = old_bg.replace(`.${fileExtension}`, ''); - const new_bg_extensionless = await callPopup('

    Enter new background name:

    ', 'input', old_bg_extensionless); + const fileExtension = oldBg.split('.').pop(); + const fileNameBase = isCustom ? oldBg.split('/').pop() : oldBg; + const oldBgExtensionless = fileNameBase.replace(`.${fileExtension}`, ''); + const newBgExtensionless = await callPopup('

    Enter new background name:

    ', 'input', oldBgExtensionless); - if (!new_bg_extensionless) { + if (!newBgExtensionless) { console.debug('no new_bg_extensionless'); return; } - const new_bg = `${new_bg_extensionless}.${fileExtension}`; + const newBg = `${newBgExtensionless}.${fileExtension}`; - if (old_bg_extensionless === new_bg_extensionless) { + if (oldBgExtensionless === newBgExtensionless) { console.debug('new_bg === old_bg'); return; } - const data = { old_bg, new_bg }; + return { oldBg, newBg }; +} + +async function onRenameBackgroundClick(e) { + e.stopPropagation(); + + const bgNames = await getNewBackgroundName(this); + + if (!bgNames) { + return; + } + + const data = { old_bg: bgNames.oldBg, new_bg: bgNames.newBg }; const response = await fetch('/renamebackground', { method: 'POST', headers: getRequestHeaders(), @@ -167,7 +247,7 @@ async function onRenameBackgroundClick(e) { if (response.ok) { await getBackgrounds(); - highlightNewBackground(new_bg); + highlightNewBackground(bgNames.newBg); } else { toastr.warning('Failed to rename background'); } @@ -177,28 +257,45 @@ async function onDeleteBackgroundClick(e) { e.stopPropagation(); const bgToDelete = $(this).closest('.bg_example'); const url = bgToDelete.data('url'); + const isCustom = bgToDelete.attr('custom') === 'true'; const confirm = await callPopup("

    Delete the background?

    ", 'confirm'); + const bg = bgToDelete.attr('bgfile'); if (confirm) { - delBackground(bgToDelete.attr("bgfile")); + // If it's not custom, it's a built-in background. Delete it from the server + if (!isCustom) { + delBackground(bg); + } else { + const list = chat_metadata[LIST_METADATA_KEY] || []; + const index = list.indexOf(bg); + list.splice(index, 1); + } const siblingSelector = '.bg_example:not(#form_bg_download)'; const nextBg = bgToDelete.next(siblingSelector); const prevBg = bgToDelete.prev(siblingSelector); + const anyBg = $(siblingSelector); if (nextBg.length > 0) { nextBg.trigger('click'); - } else { + } else if (prevBg.length > 0) { prevBg.trigger('click'); + } else { + $(anyBg[Math.floor(Math.random() * anyBg.length)]).trigger('click'); } bgToDelete.remove(); - if (url === chat_metadata[METADATA_KEY]) { + if (url === chat_metadata[BG_METADATA_KEY]) { removeBackgroundMetadata(); unsetCustomBackground(); highlightLockedBackground(); } + + if (isCustom) { + getChatBackgroundsList(); + saveMetadataDebounced(); + } } } @@ -259,18 +356,20 @@ function getUrlParameter(block) { * Instantiates a background template * @param {string} bg Path to background * @param {boolean} isCustom Whether the background is custom - * @returns + * @returns {JQuery} Background template */ function getBackgroundFromTemplate(bg, isCustom) { - const thumbPath = getThumbnailUrl('bg', bg); const template = $('#background_template .bg_example').clone(); - const url = isCustom ? `url("${bg}")` : `url("${getBackgroundPath(bg)}")`; - template.attr('title', bg); + const thumbPath = isCustom ? bg : getThumbnailUrl('bg', bg); + const url = isCustom ? `url("${encodeURI(bg)}")` : `url("${getBackgroundPath(bg)}")`; + const title = isCustom ? bg.split('/').pop() : bg; + const friendlyTitle = title.slice(0, title.lastIndexOf('.')); + template.attr('title', title); template.attr('bgfile', bg); template.attr('custom', String(isCustom)); template.data('url', url); template.css('background-image', `url('${thumbPath}')`); - template.find('.BGSampleTitle').text(bg.slice(0, bg.lastIndexOf('.'))); + template.find('.BGSampleTitle').text(friendlyTitle); return template; } @@ -307,49 +406,43 @@ async function delBackground(bg) { } function onBackgroundUploadSelected() { - const input = this; + const form = $("#form_bg_download").get(0); - if (input.files && input.files[0]) { - const reader = new FileReader(); - - reader.onload = function (e) { - const form = $("#form_bg_download").get(0); - - if (!(form instanceof HTMLFormElement)) { - console.error('form_bg_download is not a form'); - return; - } - - const formData = new FormData(form); - - //console.log(formData); - jQuery.ajax({ - type: "POST", - url: "/downloadbackground", - data: formData, - beforeSend: function () { - - }, - cache: false, - contentType: false, - processData: false, - success: async function (bg) { - form.reset(); - setBackground(bg); - $("#bg1").css("background-image", `url("${getBackgroundPath(bg)}"`); - await getBackgrounds(); - highlightNewBackground(bg); - }, - error: function (jqXHR, exception) { - form.reset(); - console.log(exception); - console.log(jqXHR); - }, - }); - }; - - reader.readAsDataURL(input.files[0]); + if (!(form instanceof HTMLFormElement)) { + console.error('form_bg_download is not a form'); + return; } + + const formData = new FormData(form); + uploadBackground(formData); + form.reset(); +} + +/** + * Uploads a background to the server + * @param {FormData} formData + */ +function uploadBackground(formData) { + jQuery.ajax({ + type: "POST", + url: "/downloadbackground", + data: formData, + beforeSend: function () { + }, + cache: false, + contentType: false, + processData: false, + success: async function (bg) { + setBackground(bg); + $("#bg1").css("background-image", `url("${getBackgroundPath(bg)}"`); + await getBackgrounds(); + highlightNewBackground(bg); + }, + error: function (jqXHR, exception) { + console.log(exception); + console.log(jqXHR); + }, + }); } /** @@ -378,11 +471,12 @@ function onBackgroundFilterInput() { export function initBackgrounds() { eventSource.on(event_types.CHAT_CHANGED, onChatChanged); eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground); - $(document).on("click", ".bg_example", onSelectBackgroundClick); + $(document).on("click", '.bg_example', onSelectBackgroundClick); $(document).on('click', '.bg_example_lock', onLockBackgroundClick); $(document).on('click', '.bg_example_unlock', onUnlockBackgroundClick); $(document).on('click', '.bg_example_edit', onRenameBackgroundClick); - $(document).on("click", ".bg_example_cross", onDeleteBackgroundClick); + $(document).on("click", '.bg_example_cross', onDeleteBackgroundClick); + $(document).on("click", '.bg_example_copy', onCopyToSystemBackgroundClick); $('#auto_background').on("click", autoBackgroundCommand); $("#add_bg_button").on('change', onBackgroundUploadSelected); $("#bg-filter").on("input", onBackgroundFilterInput); diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 93a293d90..ac5f66148 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -1116,10 +1116,9 @@ async function generatePicture(_, trigger, message, callback) { extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64; } const callbackOriginal = callback; - callback = async function (prompt, base64Image) { - const imagePath = base64Image; - const imgUrl = `url("${encodeURI(base64Image)}")`; - eventSource.emit(event_types.FORCE_SET_BACKGROUND, imgUrl); + callback = async function (prompt, imagePath) { + const imgUrl = `url("${encodeURI(imagePath)}")`; + eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath }); if (typeof callbackOriginal === 'function') { callbackOriginal(prompt, imagePath); diff --git a/public/scripts/utils.js b/public/scripts/utils.js index b413b3986..b9efb6506 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -870,7 +870,7 @@ export async function saveBase64AsFile(base64Data, characterName, filename = "", // If the response is successful, get the saved image path from the server's response if (response.ok) { const responseData = await response.json(); - return responseData.path; + return responseData.path.replace(/\\/g, '/'); // Replace backslashes with forward slashes } else { const errorData = await response.json(); throw new Error(errorData.error || 'Failed to upload the image to the server'); diff --git a/public/style.css b/public/style.css index cb3e2fc47..5f4cbcbe7 100644 --- a/public/style.css +++ b/public/style.css @@ -1506,7 +1506,7 @@ input[type=search]:focus::-webkit-search-cancel-button { z-index: 3001; } -#bg_menu_content { +.bg_list { display: flex; flex-wrap: wrap; width: calc(var(--sheldWidth) - 10px); @@ -1543,6 +1543,14 @@ input[type=search]:focus::-webkit-search-cancel-button { display: none; } +.bg_example:hover[custom="true"] .bg_example_edit { + display: none; +} + +.bg_example:hover[custom="false"] .bg_example_copy { + display: none; +} + .BGSampleTitle { display: flex; width: 100%; @@ -1591,6 +1599,10 @@ input[type=search]:focus::-webkit-search-cancel-button { left: 10px; } +.bg_example_copy { + left: 10px; +} + .bg_example_lock, .bg_example_unlock { left: 50%;