From 44182970bcaa21a5a20630e23c59b9b798e45cd3 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 18 Feb 2025 22:12:32 +0200 Subject: [PATCH] Text Completion: Add context-specific {{timestamp}} macro --- public/script.js | 39 +++++++++++++++--- public/scripts/instruct-mode.js | 70 ++++++++++++++++++--------------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/public/script.js b/public/script.js index d86945523..ed569dcc3 100644 --- a/public/script.js +++ b/public/script.js @@ -3429,6 +3429,15 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride; const isQuiet = true; + const getInstructFormatParams = (/** @type {string} */ mes) => ({ + name: name1, + mes: mes, + isUser: false, + isNarrator: true, + forceAvatar: '', + forceOutputSequence: false, + timestamp: moment(), + }); let eventHook = () => { }; if (systemPrompt) { @@ -3439,8 +3448,8 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy prompt = substituteParams(prompt); prompt = api == 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt; - prompt = isInstruct ? formatInstructModeChat(name1, prompt, false, true, '', name1, name2, false) : prompt; - prompt = isInstruct ? (prompt + formatInstructModePrompt(name2, false, '', name1, name2, isQuiet, quietToLoud)) : (prompt + '\n'); + prompt = isInstruct ? formatInstructModeChat(getInstructFormatParams(prompt)) : prompt; + prompt = isInstruct ? (prompt + formatInstructModePrompt(name2, false, '', isQuiet, quietToLoud)) : (prompt + '\n'); try { if (responseLengthCustomized) { @@ -4016,7 +4025,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro const mesExamplesRawArray = [...mesExamplesArray]; if (mesExamplesArray && isInstruct) { - mesExamplesArray = formatInstructModeExamples(mesExamplesArray, name1, name2); + mesExamplesArray = formatInstructModeExamples(mesExamplesArray); } if (skipWIAN !== true) { @@ -4336,7 +4345,16 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro // here name1 is forced for all quiet prompts..why? const name = name1; //checks if we are in instruct, if so, formats the chat as such, otherwise just adds the quiet prompt - const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, false, true, '', name1, name2, false) : `\n${quiet_prompt}`; + const quietInstructParams = { + name: name, + mes: quiet_prompt, + isUser: false, + isNarrator: true, + forceAvatar: '', + forceOutputSequence: false, + timestamp: moment(), + }; + const quietAppend = isInstruct ? formatInstructModeChat(quietInstructParams) : `\n${quiet_prompt}`; //This begins to fix quietPrompts (particularly /sysgen) for instruct //previously instruct input sequence was being appended to the last chat message w/o '\n' @@ -4366,7 +4384,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro if (isInstruct && !isContinue) { const name = (quiet_prompt && !quietToLoud && !isImpersonate) ? (quietName ?? 'System') : (isImpersonate ? name1 : name2); const isQuiet = quiet_prompt && type == 'quiet'; - lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, quietToLoud); + lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, isQuiet, quietToLoud); } // Get non-instruct impersonation line @@ -5147,7 +5165,16 @@ function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) { let textResult = chatItem?.name && shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`; if (isInstruct) { - textResult = formatInstructModeChat(itemName, chatItem.mes, chatItem.is_user, isNarratorType, chatItem.force_avatar, name1, name2, forceOutputSequence); + const instructFormatParams = { + name: itemName, + mes: chatItem.mes, + isUser: chatItem.is_user, + isNarrator: isNarratorType, + forceAvatar: chatItem.force_avatar, + forceOutputSequence: forceOutputSequence, + timestamp: timestampToMoment(chatItem.send_date), + }; + textResult = formatInstructModeChat(instructFormatParams); } return textResult; diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index 746682859..0f9a0b08f 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -8,6 +8,7 @@ import { context_presets, } from './power-user.js'; import { regexFromString, resetScrollHeight } from './utils.js'; +import { moment } from '../lib.js'; /** * @type {any[]} Instruct mode presets. @@ -254,8 +255,10 @@ export function getInstructStoppingSequences() { // Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string // But it's a problem for Metharme which doesn't use newlines to separate them. const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s; + // Should not contain timestamp macros + const hasTimestamp = /{{timestamp}}/gi.test(sequence) || /{{timestamp::(.*?)}}/gi.test(sequence); // Sequence must be a non-empty string - if (typeof sequence === 'string' && sequence.length > 0) { + if (typeof sequence === 'string' && sequence.length > 0 && !hasTimestamp) { // If sequence is just a whitespace or newline - we don't want to make it a stopping string // User can always add it as a custom stop string if really needed if (sequence.trim().length > 0) { @@ -309,19 +312,35 @@ export const force_output_sequence = { LAST: 2, }; +/** + * Replaces instruct mode macros in the given sequence string. + * @param {string} sequence Sequence string. + * @param {string} name Item name. + * @param {import('moment').Moment} timestamp Message timestamp. + * @returns {string} Sequence string with macros replaced. + */ +function replaceSequenceMacros(sequence, name, timestamp) { + sequence = substituteParams(sequence); + sequence = sequence.replace(/{{name}}/gi, name || 'System'); + sequence = sequence.replace(/{{timestamp}}/gi, () => timestamp.format('YYYY-MM-DD HH:mm:ss')); + sequence = sequence.replace(/{{timestamp::(.*?)}}/gi, (_, format) => timestamp.format(format)); + return sequence; +} + /** * Formats instruct mode chat message. - * @param {string} name Character name. - * @param {string} mes Message text. - * @param {boolean} isUser Is the message from the user. - * @param {boolean} isNarrator Is the message from the narrator. - * @param {string} forceAvatar Force avatar string. - * @param {string} name1 User name. - * @param {string} name2 Character name. - * @param {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured). + * @param {InstructFormatParams} params Message parameters. + * @typedef {object} InstructFormatParams Instruct mode chat message parameters. + * @property {string} name Character name. + * @property {string} mes Message text. + * @property {boolean} isUser Is the message from the user. + * @property {boolean} isNarrator Is the message from the narrator. + * @property {string} forceAvatar Force avatar string. + * @property {boolean|number} forceOutputSequence Force to use first/last output sequence (if configured). + * @property {import('moment').Moment} timestamp Message timestamp. * @returns {string} Formatted instruct mode chat message. */ -export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2, forceOutputSequence) { +export function formatInstructModeChat({ name, mes, isUser, isNarrator, forceAvatar, forceOutputSequence, timestamp }) { let includeNames = isNarrator ? false : power_user.instruct.names_behavior === names_behavior_types.ALWAYS; if (!isNarrator && power_user.instruct.names_behavior === names_behavior_types.FORCE && ((selected_group && name !== name1) || (forceAvatar && name !== name1))) { @@ -372,11 +391,8 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata let suffix = getSuffix() || ''; if (power_user.instruct.macro) { - prefix = substituteParams(prefix, name1, name2); - prefix = prefix.replace(/{{name}}/gi, name || 'System'); - - suffix = substituteParams(suffix, name1, name2); - suffix = suffix.replace(/{{name}}/gi, name || 'System'); + prefix = replaceSequenceMacros(prefix, (name || 'System'), timestamp); + suffix = replaceSequenceMacros(suffix, (name || 'System'), timestamp); } if (!suffix && power_user.instruct.wrap) { @@ -420,11 +436,9 @@ export function formatInstructModeSystemPrompt(systemPrompt) { /** * Formats example messages according to instruct mode settings. * @param {string[]} mesExamplesArray Example messages array. - * @param {string} name1 User name. - * @param {string} name2 Character name. * @returns {string[]} Formatted example messages string. */ -export function formatInstructModeExamples(mesExamplesArray, name1, name2) { +export function formatInstructModeExamples(mesExamplesArray) { const blockHeading = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : ''; if (power_user.instruct.skip_examples) { @@ -440,15 +454,10 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) { let outputSuffix = power_user.instruct.output_suffix || ''; if (power_user.instruct.macro) { - inputPrefix = substituteParams(inputPrefix, name1, name2); - outputPrefix = substituteParams(outputPrefix, name1, name2); - inputSuffix = substituteParams(inputSuffix, name1, name2); - outputSuffix = substituteParams(outputSuffix, name1, name2); - - inputPrefix = inputPrefix.replace(/{{name}}/gi, name1); - outputPrefix = outputPrefix.replace(/{{name}}/gi, name2); - inputSuffix = inputSuffix.replace(/{{name}}/gi, name1); - outputSuffix = outputSuffix.replace(/{{name}}/gi, name2); + inputPrefix = replaceSequenceMacros(inputPrefix, name1, moment()); + outputPrefix = replaceSequenceMacros(outputPrefix, name2, moment()); + inputSuffix = replaceSequenceMacros(inputSuffix, name1, moment()); + outputSuffix = replaceSequenceMacros(outputSuffix, name2, moment()); if (!inputSuffix && power_user.instruct.wrap) { inputSuffix = '\n'; @@ -499,13 +508,11 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) { * @param {string} name Character name. * @param {boolean} isImpersonate Is generation in impersonation mode. * @param {string} promptBias Prompt bias string. - * @param {string} name1 User name. - * @param {string} name2 Character name. * @param {boolean} isQuiet Is quiet mode generation. * @param {boolean} isQuietToLoud Is quiet to loud generation. * @returns {string} Formatted instruct mode last prompt line. */ -export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) { +export function formatInstructModePrompt(name, isImpersonate, promptBias, isQuiet, isQuietToLoud) { const includeNames = name && (power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE)) && !(isQuiet && !isQuietToLoud); function getSequence() { @@ -545,8 +552,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, } if (power_user.instruct.macro) { - sequence = substituteParams(sequence, name1, name2); - sequence = sequence.replace(/{{name}}/gi, name || 'System'); + sequence = replaceSequenceMacros(sequence, (name || 'System'), moment()); } const separator = power_user.instruct.wrap ? '\n' : '';