From 71f47588cd8377c9e3fe894d11c35622833e92d8 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Fri, 12 Jan 2024 05:19:37 -0500 Subject: [PATCH 1/5] Pass macro variables in to evaluateMacros This doesn't cover *all* the variables yet, just the ones that were previously passed in as arguments. I'll expand this later to separate the macro parsing from the execution of the functions themselves. --- public/script.js | 27 ++++++++++++++++++++++- public/scripts/macros.js | 46 ++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/public/script.js b/public/script.js index b21443f41..97e63c911 100644 --- a/public/script.js +++ b/public/script.js @@ -2157,10 +2157,35 @@ function scrollChatToBottom() { * @param {*} _name2 - The name of the character. Uses global name2 if not provided. * @param {*} _original - The original message for {{original}} substitution. * @param {*} _group - The group members list for {{group}} substitution. + * @param {boolean} _replaceCharacterCard - Whether to replace character card macros. * @returns {string} The string with substituted parameters. */ function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true) { - return evaluateMacros(content, _name1 ?? name1, _name2 ?? name2, _original, _group ?? name2, _replaceCharacterCard); + const environment = {}; + + environment.user = _name1 ?? name1; + environment.char = _name2 ?? name2; + environment.group = environment.charIfNotGroup = _group ?? name2; + + let substitutedOriginal = false; + environment.original = () => { + // Only substitute {{original}} on its first occurrence + if (substitutedOriginal || typeof _original !== 'string') return ''; + return _original; + }; + + if (_replaceCharacterCard) { + const fields = getCharacterCardFields(); + environment.charPrompt = fields.system || ''; + environment.charJailbreak = fields.jailbreak || ''; + environment.description = fields.description || ''; + environment.personality = fields.personality || ''; + environment.scenario = fields.scenario || ''; + environment.persona = fields.persona || ''; + environment.mesExamples = fields.mesExamples || ''; + } + + return evaluateMacros(content, environment); } diff --git a/public/scripts/macros.js b/public/scripts/macros.js index 2734a3a04..d83bef7e4 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -1,4 +1,4 @@ -import { chat, main_api, getMaxContextSize, getCharacterCardFields } from '../script.js'; +import { chat, main_api, getMaxContextSize } from '../script.js'; import { timestampToMoment, isDigitsOnly } from './utils.js'; import { textgenerationwebui_banned_in_macros } from './textgen-settings.js'; import { replaceInstructMacros } from './instruct-mode.js'; @@ -201,56 +201,42 @@ function diceRollReplace(input, invalidRollPlaceholder = '') { /** * Substitutes {{macro}} parameters in a string. * @param {string} content - The string to substitute parameters in. - * @param {*} _name1 - The name of the user. - * @param {*} _name2 - The name of the character. - * @param {*} _original - The original message for {{original}} substitution. - * @param {*} _group - The group members list for {{group}} substitution. - * @param {boolean} _replaceCharacterCard - Whether to replace character card macros. + * @param {Object} env - Map of macro names to the values they'll be substituted with. If the param + * values are functions, those functions will be called and their return values are used. * @returns {string} The string with substituted parameters. */ -export function evaluateMacros(content, _name1, _name2, _original, _group, _replaceCharacterCard = true) { +export function evaluateMacros(content, env) { if (!content) { return ''; } - // Replace {{original}} with the original message - // Note: only replace the first instance of {{original}} - // This will hopefully prevent the abuse - if (typeof _original === 'string') { - content = content.replace(/{{original}}/i, _original); - } content = diceRollReplace(content); content = replaceInstructMacros(content); content = replaceVariableMacros(content); content = content.replace(/{{newline}}/gi, '\n'); content = content.replace(/{{input}}/gi, String($('#send_textarea').val())); - if (_replaceCharacterCard) { - const fields = getCharacterCardFields(); - content = content.replace(/{{charPrompt}}/gi, fields.system || ''); - content = content.replace(/{{charJailbreak}}/gi, fields.jailbreak || ''); - content = content.replace(/{{description}}/gi, fields.description || ''); - content = content.replace(/{{personality}}/gi, fields.personality || ''); - content = content.replace(/{{scenario}}/gi, fields.scenario || ''); - content = content.replace(/{{persona}}/gi, fields.persona || ''); - content = content.replace(/{{mesExamples}}/gi, fields.mesExamples || ''); + // Substitute passed-in variables + for (const varName in env) { + if (!Object.hasOwn(env, varName)) continue; + + const param = env[varName]; + const paramValue = typeof param === 'function' ? param() : param; + content = content.replace(new RegExp(`{{${varName}}}`, 'gi'), paramValue); } content = content.replace(/{{maxPrompt}}/gi, () => String(getMaxContextSize())); - content = content.replace(/{{user}}/gi, _name1); - content = content.replace(/{{char}}/gi, _name2); - content = content.replace(/{{charIfNotGroup}}/gi, _group); - content = content.replace(/{{group}}/gi, _group); content = content.replace(/{{lastMessage}}/gi, getLastMessage()); content = content.replace(/{{lastMessageId}}/gi, getLastMessageId()); content = content.replace(/{{firstIncludedMessageId}}/gi, getFirstIncludedMessageId()); content = content.replace(/{{lastSwipeId}}/gi, getLastSwipeId()); content = content.replace(/{{currentSwipeId}}/gi, getCurrentSwipeId()); - content = content.replace(//gi, _name1); - content = content.replace(//gi, _name2); - content = content.replace(//gi, _group); - content = content.replace(//gi, _group); + // Legacy non-macro substitutions + content = content.replace(//gi, typeof env.user === 'function' ? env.user() : env.user); + content = content.replace(//gi, typeof env.char === 'function' ? env.char() : env.char); + content = content.replace(//gi, typeof env.group === 'function' ? env.group() : env.group); + content = content.replace(//gi, typeof env.group === 'function' ? env.group() : env.group); content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); From 4bd7364a8eccfff3cae94d07c1c28ec0dff319b2 Mon Sep 17 00:00:00 2001 From: valadaptive Date: Sat, 27 Jan 2024 13:22:22 -0500 Subject: [PATCH 2/5] Change macro substitution order --- public/script.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/script.js b/public/script.js index 97e63c911..984d963fb 100644 --- a/public/script.js +++ b/public/script.js @@ -2163,10 +2163,6 @@ function scrollChatToBottom() { function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true) { const environment = {}; - environment.user = _name1 ?? name1; - environment.char = _name2 ?? name2; - environment.group = environment.charIfNotGroup = _group ?? name2; - let substitutedOriginal = false; environment.original = () => { // Only substitute {{original}} on its first occurrence @@ -2185,6 +2181,12 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh environment.mesExamples = fields.mesExamples || ''; } + // Must be substituted last so that they're replaced inside {{description}} + // TODO: evaluate macros recursively so we don't need to rely on substitution order + environment.user = _name1 ?? name1; + environment.char = _name2 ?? name2; + environment.group = environment.charIfNotGroup = _group ?? name2; + return evaluateMacros(content, environment); } From 8037e31c5312f20d8f2ff448f3c28d1a2f88037c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:31:19 +0200 Subject: [PATCH 3/5] Fix {{original}} --- public/script.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/public/script.js b/public/script.js index 984d963fb..2e6aa5074 100644 --- a/public/script.js +++ b/public/script.js @@ -2163,12 +2163,9 @@ function scrollChatToBottom() { function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true) { const environment = {}; - let substitutedOriginal = false; - environment.original = () => { - // Only substitute {{original}} on its first occurrence - if (substitutedOriginal || typeof _original !== 'string') return ''; - return _original; - }; + if (typeof _original === 'string') { + environment.original = _original; + } if (_replaceCharacterCard) { const fields = getCharacterCardFields(); @@ -2487,7 +2484,7 @@ function showStopButton() { function hideStopButton() { // prevent NOOP, because hideStopButton() gets called multiple times - if($('#mes_stop').css('display') !== 'none') { + if ($('#mes_stop').css('display') !== 'none') { $('#mes_stop').css({ 'display': 'none' }); eventSource.emit(event_types.GENERATION_ENDED, chat.length); } From ef9cdf64cfc60b86cfa21fbc1a705dcf67001dda Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 29 Jan 2024 00:37:51 +0200 Subject: [PATCH 4/5] Fix swipe buttons display when using /comment after last AI message --- public/script.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/public/script.js b/public/script.js index b410625b5..6193b5b54 100644 --- a/public/script.js +++ b/public/script.js @@ -2035,18 +2035,19 @@ function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true const messageId = forceId ?? chat.length - 1; $('#chat').find(`[mesid="${messageId}"]`).find('.mes_text').append(messageText); appendMediaToMessage(mes, newMessage); - hideSwipeButtons(newMessageId); + hideSwipeButtons(); } addCopyToCodeBlocks(newMessage); + $('#chat .mes').last().addClass('last_mes'); + $('#chat .mes').eq(-2).removeClass('last_mes'); + + hideSwipeButtons(); + showSwipeButtons(); + // Don't scroll if not inserting last if (!insertAfter && !insertBefore && scroll) { - $('#chat .mes').last().addClass('last_mes'); - $('#chat .mes').eq(-2).removeClass('last_mes'); - - hideSwipeButtons(newMessageId); - showSwipeButtons(newMessageId); scrollChatToBottom(); } } @@ -6475,7 +6476,7 @@ function callPopup(text, type, inputValue = '', { okButton, rows, wide, large } }); } -function showSwipeButtons(id = chat.length - 1) { +function showSwipeButtons() { if (chat.length === 0) { return; } @@ -6483,10 +6484,9 @@ function showSwipeButtons(id = chat.length - 1) { if ( chat[chat.length - 1].is_system || !swipes || - $('.mes:last').attr('mesid') < 0 || + Number($('.mes:last').attr('mesid')) < 0 || chat[chat.length - 1].is_user || chat[chat.length - 1].extra?.image || - id < 0 || (selected_group && is_group_generating) ) { return; } @@ -6505,7 +6505,7 @@ function showSwipeButtons(id = chat.length - 1) { chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat } - const currentMessage = $('#chat').children().filter(`[mesid="${id}"]`); + const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`); const swipeId = chat[chat.length - 1].swipe_id; var swipesCounterHTML = (`${(swipeId + 1)}/${(chat[chat.length - 1].swipes.length)}`); @@ -6530,10 +6530,10 @@ function showSwipeButtons(id = chat.length - 1) { //console.log(chat[chat.length - 1].swipes.length); } -function hideSwipeButtons(id = chat.length - 1) { +function hideSwipeButtons() { //console.log('hideswipebuttons entered'); - $('#chat').children().filter(`[mesid="${id}"]`).children('.swipe_right').css('display', 'none'); - $('#chat').children().filter(`[mesid="${id}"]`).children('.swipe_left').css('display', 'none'); + $('#chat').find('.swipe_right').css('display', 'none'); + $('#chat').find('.swipe_left').css('display', 'none'); } export async function saveMetadata() { From 5f1e290bdaf556f64ca3432cd8c09713232fed51 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 29 Jan 2024 00:58:29 +0200 Subject: [PATCH 5/5] Disallow multiple {{original}} macro substitutions --- public/script.js | 10 +++++++++- public/scripts/macros.js | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/public/script.js b/public/script.js index 8d74159e1..e9bb2add6 100644 --- a/public/script.js +++ b/public/script.js @@ -2147,7 +2147,15 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh const environment = {}; if (typeof _original === 'string') { - environment.original = _original; + let originalSubstituted = false; + environment.original = () => { + if (originalSubstituted) { + return ''; + } + + originalSubstituted = true; + return _original; + }; } if (_replaceCharacterCard) { diff --git a/public/scripts/macros.js b/public/scripts/macros.js index d83bef7e4..e88971de1 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -221,8 +221,7 @@ export function evaluateMacros(content, env) { if (!Object.hasOwn(env, varName)) continue; const param = env[varName]; - const paramValue = typeof param === 'function' ? param() : param; - content = content.replace(new RegExp(`{{${varName}}}`, 'gi'), paramValue); + content = content.replace(new RegExp(`{{${varName}}}`, 'gi'), param); } content = content.replace(/{{maxPrompt}}/gi, () => String(getMaxContextSize()));