diff --git a/public/script.js b/public/script.js index 4acbc3457..c35cf5221 100644 --- a/public/script.js +++ b/public/script.js @@ -82,6 +82,7 @@ import { registerDebugFunction, ui_mode, switchSimpleMode, + flushEphemeralStoppingStrings, } from "./scripts/power-user.js"; import { @@ -3837,6 +3838,7 @@ function unblockGeneration() { activateSendButtons(); showSwipeButtons(); setGenerationProgress(0); + flushEphemeralStoppingStrings(); $("#send_textarea").removeAttr('disabled'); } diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 7daeb8431..1c29cd1fb 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -2374,45 +2374,69 @@ async function setmovingUIPreset(_, text) { saveSettingsDebounced(); } +const EPHEMERAL_STOPPING_STRINGS = []; + +/** + * Adds a stopping string to the list of stopping strings that are only used for the next generation. + * @param {string} value The stopping string to add + */ +export function addEphemeralStoppingString(value) { + if (!EPHEMERAL_STOPPING_STRINGS.includes(value)) { + console.debug('Adding ephemeral stopping string:', value); + EPHEMERAL_STOPPING_STRINGS.push(value); + } +} + +export function flushEphemeralStoppingStrings() { + console.debug('Flushing ephemeral stopping strings:', EPHEMERAL_STOPPING_STRINGS); + EPHEMERAL_STOPPING_STRINGS.length = 0; +} + /** * Gets the custom stopping strings from the power user settings. * @param {number | undefined} limit Number of strings to return. If 0 or undefined, returns all strings. * @returns {string[]} An array of custom stopping strings */ export function getCustomStoppingStrings(limit = undefined) { - try { - // If there's no custom stopping strings, return an empty array - if (!power_user.custom_stopping_strings) { + function getPermanent() { + try { + // If there's no custom stopping strings, return an empty array + if (!power_user.custom_stopping_strings) { + return []; + } + + // Parse the JSON string + let strings = JSON.parse(power_user.custom_stopping_strings); + + // Make sure it's an array + if (!Array.isArray(strings)) { + return []; + } + + // Make sure all the elements are strings and non-empty. + strings = strings.filter(s => typeof s === 'string' && s.length > 0); + + // Substitute params if necessary + if (power_user.custom_stopping_strings_macro) { + strings = strings.map(x => substituteParams(x)); + } + + // Apply the limit. If limit is 0, return all strings. + if (limit > 0) { + strings = strings.slice(0, limit); + } + + return strings; + } catch (error) { + // If there's an error, return an empty array + console.warn('Error parsing custom stopping strings:', error); return []; } - - // Parse the JSON string - let strings = JSON.parse(power_user.custom_stopping_strings); - - // Make sure it's an array - if (!Array.isArray(strings)) { - return []; - } - - // Make sure all the elements are strings and non-empty. - strings = strings.filter(s => typeof s === 'string' && s.length > 0); - - // Substitute params if necessary - if (power_user.custom_stopping_strings_macro) { - strings = strings.map(x => substituteParams(x)); - } - - // Apply the limit. If limit is 0, return all strings. - if (limit > 0) { - strings = strings.slice(0, limit); - } - - return strings; - } catch (error) { - // If there's an error, return an empty array - console.warn('Error parsing custom stopping strings:', error); - return []; } + + const permanent = getPermanent(); + const ephemeral = EPHEMERAL_STOPPING_STRINGS; + return [...permanent, ...ephemeral]; } $(document).ready(() => { diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 9243c6eee..4709179a6 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -29,7 +29,7 @@ import { import { getMessageTimeStamp } from "./RossAscends-mods.js"; import { findGroupMemberId, groups, is_group_generating, resetSelectedGroup, saveGroupChat, selected_group } from "./group-chats.js"; import { getRegexedString, regex_placement } from "./extensions/regex/engine.js"; -import { chat_styles, power_user } from "./power-user.js"; +import { addEphemeralStoppingString, chat_styles, power_user } from "./power-user.js"; import { autoSelectPersona } from "./personas.js"; import { getContext } from "./extensions.js"; import { hideChatMessage, unhideChatMessage } from "./chats.js"; @@ -161,7 +161,7 @@ parser.addCommand('peek', peekCallback, [], '(message in parser.addCommand('delswipe', deleteSwipeCallback, ['swipedel'], '(optional 1-based id) – deletes a swipe from the last chat message. If swipe id not provided - deletes the current swipe.', true, true); parser.addCommand('echo', echoCallback, [], '(text) – echoes the text to toast message. Useful for pipes debugging.', true, true); parser.addCommand('gen', generateCallback, [], '(prompt) – generates text using the provided prompt and passes it to the next command through the pipe.', true, true); -parser.addCommand('genraw', generateRawCallback, [], '(prompt) – generates text using the provided prompt and passes it to the next command through the pipe. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?', true, true); +parser.addCommand('genraw', generateRawCallback, [], '(prompt) – generates text using the provided prompt and passes it to the next command through the pipe. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serializer array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi', true, true); parser.addCommand('addswipe', addSwipeCallback, ['swipeadd'], '(text) – adds a swipe to the last chat message.', true, true); parser.addCommand('abort', abortCallback, [], ' – aborts the slash command batch execution', true, true); registerVariableCommands(); @@ -184,6 +184,19 @@ async function generateRawCallback(args, value) { // Prevent generate recursion $('#send_textarea').val(''); + if (typeof args.stop === 'string' && args.stop.length) { + try { + const stopStrings = JSON.parse(args.stop); + if (Array.isArray(stopStrings)) { + for (const stopString of stopStrings) { + addEphemeralStoppingString(stopString); + } + } + } catch { + // Do nothing + } + } + const result = await generateRaw(value, '', args.instruct); return result; }