-
-
Character Names Behavior
-
-
-
- None
-
-
- Don't add character names.
-
-
-
-
- Completion Object
-
-
- Add character names to completion objects.
-
-
-
-
- Message Content
-
- Prepend character names to message contents.
-
-
-
-
Helps the model to associate messages with characters.
+
+
@@ -5231,7 +5299,7 @@
Will be automatically added as the author's note for this character. Will be used in groups, but
can't be modified when a group chat is open.
-
+
Tokens: 0
@@ -5263,22 +5331,38 @@
diff --git a/public/script.js b/public/script.js
index 5f2e92c4d..a87e3f104 100644
--- a/public/script.js
+++ b/public/script.js
@@ -495,6 +495,9 @@ const durationSaveEdit = 1000;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
export const saveCharacterDebounced = debounce(() => $('#create_button').trigger('click'), durationSaveEdit);
+/**
+ * @enum {string} System message types
+ */
const system_message_types = {
HELP: 'help',
WELCOME: 'welcome',
@@ -511,12 +514,24 @@ const system_message_types = {
MACROS: 'macros',
};
+/**
+ * @enum {number} Extension prompt types
+ */
const extension_prompt_types = {
IN_PROMPT: 0,
IN_CHAT: 1,
BEFORE_PROMPT: 2,
};
+/**
+ * @enum {number} Extension prompt roles
+ */
+export const extension_prompt_roles = {
+ SYSTEM: 0,
+ USER: 1,
+ ASSISTANT: 2,
+};
+
export const MAX_INJECTION_DEPTH = 1000;
let system_messages = {};
@@ -2439,7 +2454,7 @@ function addPersonaDescriptionExtensionPrompt() {
? `${power_user.persona_description}\n${originalAN}`
: `${originalAN}\n${power_user.persona_description}`;
- setExtensionPrompt(NOTE_MODULE_NAME, ANWithDesc, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan);
+ setExtensionPrompt(NOTE_MODULE_NAME, ANWithDesc, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]);
}
}
@@ -2462,11 +2477,22 @@ function getExtensionPromptByName(moduleName) {
}
}
-function getExtensionPrompt(position = 0, depth = undefined, separator = '\n') {
+/**
+ * Returns the extension prompt for the given position, depth, and role.
+ * If multiple prompts are found, they are joined with a separator.
+ * @param {number} [position] Position of the prompt
+ * @param {number} [depth] Depth of the prompt
+ * @param {string} [separator] Separator for joining multiple prompts
+ * @param {number} [role] Role of the prompt
+ * @returns {string} Extension prompt
+ */
+function getExtensionPrompt(position = extension_prompt_types.IN_PROMPT, depth = undefined, separator = '\n', role = undefined) {
let extension_prompt = Object.keys(extension_prompts)
.sort()
.map((x) => extension_prompts[x])
- .filter(x => x.position == position && x.value && (depth === undefined || x.depth == depth))
+ .filter(x => x.position == position && x.value)
+ .filter(x => depth === undefined || x.depth === undefined || x.depth === depth)
+ .filter(x => role === undefined || x.role === undefined || x.role === role)
.map(x => x.value.trim())
.join(separator);
if (extension_prompt.length && !extension_prompt.startsWith(separator)) {
@@ -3160,6 +3186,21 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
console.debug('Skipping extension interceptors for dry run');
}
+ // Adjust token limit for Horde
+ let adjustedParams;
+ if (main_api == 'koboldhorde' && (horde_settings.auto_adjust_context_length || horde_settings.auto_adjust_response_length)) {
+ try {
+ adjustedParams = await adjustHordeGenerationParams(max_context, amount_gen);
+ }
+ catch {
+ unblockGeneration();
+ return Promise.resolve();
+ }
+ if (horde_settings.auto_adjust_context_length) {
+ this_max_context = (adjustedParams.maxContextLength - adjustedParams.maxLength);
+ }
+ }
+
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
// kingbri MARK: - Make sure the prompt bias isn't the same as the user bias
@@ -3172,6 +3213,32 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
}
//////////////////////////////////
+ // Extension added strings
+ // Set non-WI AN
+ setFloatingPrompt();
+ // Add WI to prompt (and also inject WI to AN value via hijack)
+
+ const chatForWI = coreChat.map(x => `${x.name}: ${x.mes}`).reverse();
+ let { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun);
+
+ if (skipWIAN !== true) {
+ console.log('skipWIAN not active, adding WIAN');
+ // Add all depth WI entries to prompt
+ flushWIDepthInjections();
+ if (Array.isArray(worldInfoDepth)) {
+ worldInfoDepth.forEach((e) => {
+ const joinedEntries = e.entries.join('\n');
+ setExtensionPrompt(`customDepthWI-${e.depth}-${e.role}`, joinedEntries, extension_prompt_types.IN_CHAT, e.depth, false, e.role);
+ });
+ }
+ } else {
+ console.log('skipping WIAN');
+ }
+
+ // Inject all Depth prompts. Chat Completion does it separately
+ if (main_api !== 'openai') {
+ doChatInject(coreChat, isContinue);
+ }
// Insert character jailbreak as the last user message (if exists, allowed, preferred, and not using Chat Completion)
if (power_user.context.allow_jailbreak && power_user.prefer_character_jailbreak && main_api !== 'openai' && jailbreak) {
@@ -3190,10 +3257,13 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
let chat2 = [];
let continue_mag = '';
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
- // For OpenAI it's only used in WI
- if (main_api == 'openai' && (!world_info || world_info.length === 0)) {
- console.debug('No WI, skipping chat2 for OAI');
- break;
+ if (main_api == 'openai') {
+ chat2[i] = coreChat[j].mes;
+ if (i === 0 && isContinue) {
+ chat2[i] = chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length);
+ continue_mag = coreChat[j].mes;
+ }
+ continue;
}
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, false);
@@ -3215,49 +3285,12 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
}
}
- // Adjust token limit for Horde
- let adjustedParams;
- if (main_api == 'koboldhorde' && (horde_settings.auto_adjust_context_length || horde_settings.auto_adjust_response_length)) {
- try {
- adjustedParams = await adjustHordeGenerationParams(max_context, amount_gen);
- }
- catch {
- unblockGeneration();
- return Promise.resolve();
- }
- if (horde_settings.auto_adjust_context_length) {
- this_max_context = (adjustedParams.maxContextLength - adjustedParams.maxLength);
- }
- }
-
- // Extension added strings
- // Set non-WI AN
- setFloatingPrompt();
- // Add WI to prompt (and also inject WI to AN value via hijack)
-
- let { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoDepth } = await getWorldInfoPrompt(chat2, this_max_context, dryRun);
-
- if (skipWIAN !== true) {
- console.log('skipWIAN not active, adding WIAN');
- // Add all depth WI entries to prompt
- flushWIDepthInjections();
- if (Array.isArray(worldInfoDepth)) {
- worldInfoDepth.forEach((e) => {
- const joinedEntries = e.entries.join('\n');
- setExtensionPrompt(`customDepthWI-${e.depth}`, joinedEntries, extension_prompt_types.IN_CHAT, e.depth);
- });
- }
- } else {
- console.log('skipping WIAN');
- }
-
// Add persona description to prompt
addPersonaDescriptionExtensionPrompt();
// Call combined AN into Generate
let allAnchors = getAllExtensionPrompts();
const beforeScenarioAnchor = getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT).trimStart();
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.IN_PROMPT);
- let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
const storyStringParams = {
description: description,
@@ -3371,8 +3404,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
// Coping mechanism for OAI spacing
const isForceInstruct = isOpenRouterWithInstruct();
if (main_api === 'openai' && !isForceInstruct && !cyclePrompt.endsWith(' ')) {
- cyclePrompt += ' ';
- continue_mag += ' ';
+ cyclePrompt += oai_settings.continue_postfix;
+ continue_mag += oai_settings.continue_postfix;
}
message_already_generated = continue_mag;
}
@@ -3496,7 +3529,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
}
// Add a space if prompt cache doesn't start with one
- if (!/^\s/.test(promptCache) && !isInstruct && !isContinue) {
+ if (!/^\s/.test(promptCache) && !isInstruct) {
promptCache = ' ' + promptCache;
}
@@ -3560,40 +3593,6 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
// Deep clone
let finalMesSend = structuredClone(mesSend);
- // TODO: Rewrite getExtensionPrompt to not require multiple for loops
- // Set all extension prompts where insertion depth > mesSend length
- if (finalMesSend.length) {
- for (let upperDepth = MAX_INJECTION_DEPTH; upperDepth >= finalMesSend.length; upperDepth--) {
- const upperAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, upperDepth);
- if (upperAnchor && upperAnchor.length) {
- finalMesSend[0].extensionPrompts.push(upperAnchor);
- }
- }
- }
-
- finalMesSend.forEach((mesItem, index) => {
- if (index === 0) {
- return;
- }
-
- const anchorDepth = Math.abs(index - finalMesSend.length);
- // NOTE: Depth injected here!
- const extensionAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, anchorDepth);
-
- if (anchorDepth >= 0 && extensionAnchor && extensionAnchor.length) {
- mesItem.extensionPrompts.push(extensionAnchor);
- }
- });
-
- // TODO: Move zero-depth anchor append to work like CFG and bias appends
- if (zeroDepthAnchor?.length && !isContinue) {
- console.debug(/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1)));
- finalMesSend[finalMesSend.length - 1].message +=
- /\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
- ? zeroDepthAnchor
- : `${zeroDepthAnchor}`;
- }
-
let cfgPrompt = {};
if (cfgGuidanceScale && cfgGuidanceScale?.value !== 1) {
cfgPrompt = getCfgPrompt(cfgGuidanceScale, isNegative);
@@ -3969,6 +3968,57 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
}
}
+/**
+ * Injects extension prompts into chat messages.
+ * @param {object[]} messages Array of chat messages
+ * @param {boolean} isContinue Whether the generation is a continuation. If true, the extension prompts of depth 0 are injected at position 1.
+ * @returns {void}
+ */
+function doChatInject(messages, isContinue) {
+ let totalInsertedMessages = 0;
+ messages.reverse();
+
+ for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {
+ // Order of priority (most important go lower)
+ const roles = [extension_prompt_roles.SYSTEM, extension_prompt_roles.USER, extension_prompt_roles.ASSISTANT];
+ const names = {
+ [extension_prompt_roles.SYSTEM]: '',
+ [extension_prompt_roles.USER]: name1,
+ [extension_prompt_roles.ASSISTANT]: name2,
+ };
+ const roleMessages = [];
+ const separator = '\n';
+
+ for (const role of roles) {
+ // Get extension prompt
+ const extensionPrompt = String(getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, role)).trimStart();
+ const isNarrator = role === extension_prompt_roles.SYSTEM;
+ const isUser = role === extension_prompt_roles.USER;
+ const name = names[role];
+
+ if (extensionPrompt) {
+ roleMessages.push({
+ name: name,
+ is_user: isUser,
+ mes: extensionPrompt,
+ extra: {
+ type: isNarrator ? system_message_types.NARRATOR : null,
+ }
+ });
+ }
+ }
+
+ if (roleMessages.length) {
+ const depth = isContinue && i === 0 ? 1 : i;
+ const injectIdx = depth + totalInsertedMessages;
+ messages.splice(injectIdx, 0, ...roleMessages);
+ totalInsertedMessages += roleMessages.length;
+ }
+ }
+
+ messages.reverse();
+}
+
function flushWIDepthInjections() {
//prevent custom depth WI entries (which have unique random key names) from duplicating
for (const key of Object.keys(extension_prompts)) {
@@ -4229,25 +4279,6 @@ function addChatsSeparator(mesSendString) {
}
}
-// There's a TODO related to zero-depth anchors; not removing this function until that's resolved
-// eslint-disable-next-line no-unused-vars
-function appendZeroDepthAnchor(force_name2, zeroDepthAnchor, finalPrompt) {
- const trimBothEnds = !force_name2;
- let trimmedPrompt = (trimBothEnds ? zeroDepthAnchor.trim() : zeroDepthAnchor.trimEnd());
-
- if (trimBothEnds && !finalPrompt.endsWith('\n')) {
- finalPrompt += '\n';
- }
-
- finalPrompt += trimmedPrompt;
-
- if (force_name2) {
- finalPrompt += ' ';
- }
-
- return finalPrompt;
-}
-
async function DupeChar() {
if (!this_chid) {
toastr.warning('You must first select a character to duplicate!');
@@ -5715,7 +5746,7 @@ async function uploadUserAvatar(e) {
}
const rawFile = formData.get('avatar');
- if (rawFile instanceof File){
+ if (rawFile instanceof File) {
const convertedFile = await ensureImageFormatSupported(rawFile);
formData.set('avatar', convertedFile);
}
@@ -6656,10 +6687,17 @@ function select_rm_characters() {
* @param {string} value Prompt injection value.
* @param {number} position Insertion position. 0 is after story string, 1 is in-chat with custom depth.
* @param {number} depth Insertion depth. 0 represets the last message in context. Expected values up to MAX_INJECTION_DEPTH.
+ * @param {number} role Extension prompt role. Defaults to SYSTEM.
* @param {boolean} scan Should the prompt be included in the world info scan.
*/
-export function setExtensionPrompt(key, value, position, depth, scan = false) {
- extension_prompts[key] = { value: String(value), position: Number(position), depth: Number(depth), scan: !!scan };
+export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM) {
+ extension_prompts[key] = {
+ value: String(value),
+ position: Number(position),
+ depth: Number(depth),
+ scan: !!scan,
+ role: Number(role ?? extension_prompt_roles.SYSTEM),
+ };
}
/**
@@ -7223,7 +7261,7 @@ async function createOrEditCharacter(e) {
formData.set('fav', fav_ch_checked);
const rawFile = formData.get('avatar');
- if (rawFile instanceof File){
+ if (rawFile instanceof File) {
const convertedFile = await ensureImageFormatSupported(rawFile);
formData.set('avatar', convertedFile);
}
@@ -8450,7 +8488,7 @@ jQuery(async function () {
$('#advanced_div').click(function () {
if (!is_advanced_char_open) {
is_advanced_char_open = true;
- $('#character_popup').css({'display': 'flex', 'opacity': 0.0}).addClass('open');
+ $('#character_popup').css({ 'display': 'flex', 'opacity': 0.0 }).addClass('open');
$('#character_popup').transition({
opacity: 1.0,
duration: animation_duration,
diff --git a/public/scripts/PromptManager.js b/public/scripts/PromptManager.js
index cc0beac7b..bf73b7265 100644
--- a/public/scripts/PromptManager.js
+++ b/public/scripts/PromptManager.js
@@ -70,7 +70,7 @@ const registerPromptManagerMigration = () => {
* Represents a prompt.
*/
class Prompt {
- identifier; role; content; name; system_prompt; position; injection_position; injection_depth;
+ identifier; role; content; name; system_prompt; position; injection_position; injection_depth; forbid_overrides;
/**
* Create a new Prompt instance.
@@ -84,8 +84,9 @@ class Prompt {
* @param {string} param0.position - The position of the prompt in the prompt list.
* @param {number} param0.injection_position - The insert position of the prompt.
* @param {number} param0.injection_depth - The depth of the prompt in the chat.
+ * @param {boolean} param0.forbid_overrides - Indicates if the prompt should not be overridden.
*/
- constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position } = {}) {
+ constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides } = {}) {
this.identifier = identifier;
this.role = role;
this.content = content;
@@ -94,6 +95,7 @@ class Prompt {
this.position = position;
this.injection_depth = injection_depth;
this.injection_position = injection_position;
+ this.forbid_overrides = forbid_overrides;
}
}
@@ -102,6 +104,7 @@ class Prompt {
*/
class PromptCollection {
collection = [];
+ overriddenPrompts = [];
/**
* Create a new PromptCollection instance.
@@ -176,6 +179,11 @@ class PromptCollection {
has(identifier) {
return this.index(identifier) !== -1;
}
+
+ override(prompt, position) {
+ this.set(prompt, position);
+ this.overriddenPrompts.push(prompt.identifier);
+ }
}
class PromptManager {
@@ -187,6 +195,13 @@ class PromptManager {
'enhanceDefinitions',
];
+ this.overridablePrompts = [
+ 'main',
+ 'jailbreak',
+ ];
+
+ this.overriddenPrompts = [];
+
this.configuration = {
version: 1,
prefix: '',
@@ -389,6 +404,7 @@ class PromptManager {
case 'main':
prompt.name = 'Main Prompt';
prompt.content = this.configuration.defaultPrompts.main;
+ prompt.forbid_overrides = false;
break;
case 'nsfw':
prompt.name = 'Nsfw Prompt';
@@ -397,6 +413,7 @@ class PromptManager {
case 'jailbreak':
prompt.name = 'Jailbreak Prompt';
prompt.content = this.configuration.defaultPrompts.jailbreak;
+ prompt.forbid_overrides = false;
break;
case 'enhanceDefinitions':
prompt.name = 'Enhance Definitions';
@@ -410,6 +427,8 @@ class PromptManager {
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0;
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH;
document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
+ document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;
+ document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';
if (!this.systemPrompts.includes(promptId)) {
document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled');
@@ -711,6 +730,7 @@ class PromptManager {
prompt.content = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value;
prompt.injection_position = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value);
prompt.injection_depth = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value);
+ prompt.forbid_overrides = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked;
}
/**
@@ -884,7 +904,7 @@ class PromptManager {
* @returns {boolean} True if the prompt can be deleted, false otherwise.
*/
isPromptToggleAllowed(prompt) {
- const forceTogglePrompts = ['charDescription', 'charPersonality', 'scenario', 'personaDescription', 'worldInfoBefore', 'worldInfoAfter'];
+ const forceTogglePrompts = ['charDescription', 'charPersonality', 'scenario', 'personaDescription', 'worldInfoBefore', 'worldInfoAfter', 'main'];
return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier);
}
@@ -1133,6 +1153,8 @@ class PromptManager {
const injectionPositionField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position');
const injectionDepthField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth');
const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block');
+ const forbidOverridesField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides');
+ const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');
nameField.value = prompt.name ?? '';
roleField.value = prompt.role ?? '';
@@ -1141,6 +1163,8 @@ class PromptManager {
injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH;
injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
injectionPositionField.removeAttribute('disabled');
+ forbidOverridesField.checked = prompt.forbid_overrides ?? false;
+ forbidOverridesBlock.style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';
if (this.systemPrompts.includes(prompt.identifier)) {
injectionPositionField.setAttribute('disabled', 'disabled');
@@ -1224,6 +1248,8 @@ class PromptManager {
const injectionPositionField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position');
const injectionDepthField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth');
const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block');
+ const forbidOverridesField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides');
+ const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');
nameField.value = '';
roleField.selectedIndex = 0;
@@ -1232,6 +1258,8 @@ class PromptManager {
injectionPositionField.removeAttribute('disabled');
injectionDepthField.value = DEFAULT_DEPTH;
injectionDepthBlock.style.visibility = 'unset';
+ forbidOverridesBlock.style.visibility = 'unset';
+ forbidOverridesField.checked = false;
roleField.disabled = false;
}
@@ -1255,6 +1283,12 @@ class PromptManager {
if (true === entry.enabled) {
const prompt = this.getPromptById(entry.identifier);
if (prompt) promptCollection.add(this.preparePrompt(prompt));
+ } else if (!entry.enabled && entry.identifier === 'main') {
+ // Some extensions require main prompt to be present for relative inserts.
+ // So we make a GMO-free vegan replacement.
+ const prompt = this.getPromptById(entry.identifier);
+ prompt.content = '';
+ if (prompt) promptCollection.add(this.preparePrompt(prompt));
}
});
@@ -1264,7 +1298,7 @@ class PromptManager {
/**
* Setter for messages property
*
- * @param {MessageCollection} messages
+ * @param {import('./openai.js').MessageCollection} messages
*/
setMessages(messages) {
this.messages = messages;
@@ -1273,19 +1307,20 @@ class PromptManager {
/**
* Set and process a finished chat completion object
*
- * @param {ChatCompletion} chatCompletion
+ * @param {import('./openai.js').ChatCompletion} chatCompletion
*/
setChatCompletion(chatCompletion) {
const messages = chatCompletion.getMessages();
this.setMessages(messages);
this.populateTokenCounts(messages);
+ this.overriddenPrompts = chatCompletion.getOverriddenPrompts();
}
/**
* Populates the token handler
*
- * @param {MessageCollection} messages
+ * @param {import('./openai.js').MessageCollection} messages
*/
populateTokenCounts(messages) {
this.tokenHandler.resetCounts();
@@ -1495,18 +1530,23 @@ class PromptManager {
}
const encodedName = escapeHtml(prompt.name);
- const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;
+ const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && !prompt.forbid_overrides;
+ const isImportantPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && prompt.forbid_overrides;
const isUserPrompt = !prompt.marker && !prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE;
const isInjectionPrompt = !prompt.marker && prompt.injection_position === INJECTION_POSITION.ABSOLUTE;
+ const isOverriddenPrompt = Array.isArray(this.overriddenPrompts) && this.overriddenPrompts.includes(prompt.identifier);
+ const importantClass = isImportantPrompt ? `${prefix}prompt_manager_important` : '';
listItemHtml += `
-
+
- ${prompt.marker ? ' ' : ''}
- ${isSystemPrompt ? ' ' : ''}
- ${isUserPrompt ? ' ' : ''}
- ${isInjectionPrompt ? ' ' : ''}
+ ${prompt.marker ? ' ' : ''}
+ ${isSystemPrompt ? ' ' : ''}
+ ${isImportantPrompt ? ' ' : ''}
+ ${isUserPrompt ? ' ' : ''}
+ ${isInjectionPrompt ? ' ' : ''}
${this.isPromptInspectionAllowed(prompt) ? `${encodedName} ` : encodedName}
${isInjectionPrompt ? `@ ${prompt.injection_depth} ` : ''}
+ ${isOverriddenPrompt ? ' ' : ''}
diff --git a/public/scripts/authors-note.js b/public/scripts/authors-note.js
index 6642f998b..773f2ebc8 100644
--- a/public/scripts/authors-note.js
+++ b/public/scripts/authors-note.js
@@ -3,6 +3,7 @@ import {
chat_metadata,
eventSource,
event_types,
+ extension_prompt_roles,
saveSettingsDebounced,
this_chid,
} from '../script.js';
@@ -22,6 +23,7 @@ export const metadata_keys = {
interval: 'note_interval',
depth: 'note_depth',
position: 'note_position',
+ role: 'note_role',
};
const chara_note_position = {
@@ -113,13 +115,13 @@ async function onExtensionFloatingDepthInput() {
}
async function onExtensionFloatingPositionInput(e) {
- chat_metadata[metadata_keys.position] = e.target.value;
+ chat_metadata[metadata_keys.position] = Number(e.target.value);
updateSettings();
saveMetadataDebounced();
}
async function onDefaultPositionInput(e) {
- extension_settings.note.defaultPosition = e.target.value;
+ extension_settings.note.defaultPosition = Number(e.target.value);
saveSettingsDebounced();
}
@@ -140,6 +142,16 @@ async function onDefaultIntervalInput() {
saveSettingsDebounced();
}
+function onExtensionFloatingRoleInput(e) {
+ chat_metadata[metadata_keys.role] = Number(e.target.value);
+ updateSettings();
+}
+
+function onExtensionDefaultRoleInput(e) {
+ extension_settings.note.defaultRole = Number(e.target.value);
+ saveSettingsDebounced();
+}
+
async function onExtensionFloatingCharPositionInput(e) {
const value = e.target.value;
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
@@ -217,6 +229,7 @@ function loadSettings() {
const DEFAULT_DEPTH = 4;
const DEFAULT_POSITION = 1;
const DEFAULT_INTERVAL = 1;
+ const DEFAULT_ROLE = extension_prompt_roles.SYSTEM;
if (extension_settings.note.defaultPosition === undefined) {
extension_settings.note.defaultPosition = DEFAULT_POSITION;
@@ -230,14 +243,20 @@ function loadSettings() {
extension_settings.note.defaultInterval = DEFAULT_INTERVAL;
}
+ if (extension_settings.note.defaultRole === undefined) {
+ extension_settings.note.defaultRole = DEFAULT_ROLE;
+ }
+
chat_metadata[metadata_keys.prompt] = chat_metadata[metadata_keys.prompt] ?? extension_settings.note.default ?? '';
chat_metadata[metadata_keys.interval] = chat_metadata[metadata_keys.interval] ?? extension_settings.note.defaultInterval ?? DEFAULT_INTERVAL;
chat_metadata[metadata_keys.position] = chat_metadata[metadata_keys.position] ?? extension_settings.note.defaultPosition ?? DEFAULT_POSITION;
chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? extension_settings.note.defaultDepth ?? DEFAULT_DEPTH;
+ chat_metadata[metadata_keys.role] = chat_metadata[metadata_keys.role] ?? extension_settings.note.defaultRole ?? DEFAULT_ROLE;
$('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]);
$('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]);
$('#extension_floating_allow_wi_scan').prop('checked', extension_settings.note.allowWIScan ?? false);
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
+ $('#extension_floating_role').val(chat_metadata[metadata_keys.role]);
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
if (extension_settings.note.chara && getContext().characterId) {
@@ -255,6 +274,7 @@ function loadSettings() {
$('#extension_floating_default').val(extension_settings.note.default);
$('#extension_default_depth').val(extension_settings.note.defaultDepth);
$('#extension_default_interval').val(extension_settings.note.defaultInterval);
+ $('#extension_default_role').val(extension_settings.note.defaultRole);
$(`input[name="extension_default_position"][value="${extension_settings.note.defaultPosition}"]`).prop('checked', true);
}
@@ -274,6 +294,10 @@ export function setFloatingPrompt() {
------
lastMessageNumber = ${lastMessageNumber}
metadata_keys.interval = ${chat_metadata[metadata_keys.interval]}
+ metadata_keys.position = ${chat_metadata[metadata_keys.position]}
+ metadata_keys.depth = ${chat_metadata[metadata_keys.depth]}
+ metadata_keys.role = ${chat_metadata[metadata_keys.role]}
+ ------
`);
// interval 1 should be inserted no matter what
@@ -313,7 +337,14 @@ export function setFloatingPrompt() {
}
}
}
- context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan);
+ context.setExtensionPrompt(
+ MODULE_NAME,
+ prompt,
+ chat_metadata[metadata_keys.position],
+ chat_metadata[metadata_keys.depth],
+ extension_settings.note.allowWIScan,
+ chat_metadata[metadata_keys.role],
+ );
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
}
@@ -410,6 +441,8 @@ export function initAuthorsNote() {
$('#extension_default_depth').on('input', onDefaultDepthInput);
$('#extension_default_interval').on('input', onDefaultIntervalInput);
$('#extension_floating_allow_wi_scan').on('input', onAllowWIScanCheckboxChanged);
+ $('#extension_floating_role').on('input', onExtensionFloatingRoleInput);
+ $('#extension_default_role').on('input', onExtensionDefaultRoleInput);
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
$('input[name="extension_default_position"]').on('change', onDefaultPositionInput);
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
diff --git a/public/scripts/extensions/memory/index.js b/public/scripts/extensions/memory/index.js
index 778ef5caa..19003052e 100644
--- a/public/scripts/extensions/memory/index.js
+++ b/public/scripts/extensions/memory/index.js
@@ -1,6 +1,6 @@
import { getStringHash, debounce, waitUntilCondition, extractAllWords } from '../../utils.js';
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from '../../extensions.js';
-import { animation_duration, eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from '../../../script.js';
+import { animation_duration, eventSource, event_types, extension_prompt_roles, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { is_group_generating, selected_group } from '../../group-chats.js';
import { registerSlashCommand } from '../../slash-commands.js';
import { loadMovingUIState } from '../../power-user.js';
@@ -49,6 +49,7 @@ const defaultSettings = {
prompt: defaultPrompt,
template: defaultTemplate,
position: extension_prompt_types.IN_PROMPT,
+ role: extension_prompt_roles.SYSTEM,
depth: 2,
promptWords: 200,
promptMinWords: 25,
@@ -83,6 +84,7 @@ function loadSettings() {
$('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input');
$('#memory_template').val(extension_settings.memory.template).trigger('input');
$('#memory_depth').val(extension_settings.memory.depth).trigger('input');
+ $('#memory_role').val(extension_settings.memory.role).trigger('input');
$(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input');
$('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input');
switchSourceControls(extension_settings.memory.source);
@@ -148,6 +150,13 @@ function onMemoryDepthInput() {
saveSettingsDebounced();
}
+function onMemoryRoleInput() {
+ const value = $(this).val();
+ extension_settings.memory.role = Number(value);
+ reinsertMemory();
+ saveSettingsDebounced();
+}
+
function onMemoryPositionChange(e) {
const value = e.target.value;
extension_settings.memory.position = value;
@@ -480,11 +489,12 @@ function reinsertMemory() {
function setMemoryContext(value, saveToMessage) {
const context = getContext();
- context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth);
+ context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, false, extension_settings.memory.role);
$('#memory_contents').val(value);
console.log('Summary set to: ' + value);
console.debug('Position: ' + extension_settings.memory.position);
console.debug('Depth: ' + extension_settings.memory.depth);
+ console.debug('Role: ' + extension_settings.memory.role);
if (saveToMessage && context.chat.length) {
const idx = context.chat.length - 2;
@@ -560,6 +570,7 @@ function setupListeners() {
$('#memory_force_summarize').off('click').on('click', forceSummarizeChat);
$('#memory_template').off('click').on('input', onMemoryTemplateInput);
$('#memory_depth').off('click').on('input', onMemoryDepthInput);
+ $('#memory_role').off('click').on('input', onMemoryRoleInput);
$('input[name="memory_position"]').off('click').on('change', onMemoryPositionChange);
$('#memory_prompt_words_force').off('click').on('input', onMemoryPromptWordsForceInput);
$('#summarySettingsBlockToggle').off('click').on('click', function () {
@@ -620,9 +631,15 @@ jQuery(function () {
After Main Prompt / Story String
-
+
In-chat @ Depth
+ as
+
+ System
+ User
+ Assistant
+
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index 44a7e977b..4737dec48 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -354,7 +354,7 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
if (!isImpersonate && promptBias) {
- text += (includeNames ? promptBias : (separator + promptBias));
+ text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
}
return (power_user.instruct.wrap ? text.trimEnd() : text) + (includeNames ? '' : separator);
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 118a4f45a..f053e91fc 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -10,6 +10,7 @@ import {
characters,
event_types,
eventSource,
+ extension_prompt_roles,
extension_prompt_types,
Generate,
getExtensionPrompt,
@@ -178,6 +179,12 @@ const character_names_behavior = {
CONTENT: 2,
};
+const continue_postfix_types = {
+ SPACE: ' ',
+ NEWLINE: '\n',
+ DOUBLE_NEWLINE: '\n\n',
+};
+
const prefixMap = selected_group ? {
assistant: '',
user: '',
@@ -252,6 +259,7 @@ const default_settings = {
bypass_status_check: false,
continue_prefill: false,
names_behavior: character_names_behavior.NONE,
+ continue_postfix: continue_postfix_types.SPACE,
seed: -1,
n: 1,
};
@@ -319,6 +327,7 @@ const oai_settings = {
bypass_status_check: false,
continue_prefill: false,
names_behavior: character_names_behavior.NONE,
+ continue_postfix: continue_postfix_types.SPACE,
seed: -1,
n: 1,
};
@@ -540,7 +549,7 @@ function setupChatCompletionPromptManager(openAiSettings) {
prefix: 'completion_',
containerIdentifier: 'completion_prompt_manager',
listIdentifier: 'completion_prompt_manager_list',
- toggleDisabled: ['main'],
+ toggleDisabled: [],
sortableDelay: getSortableDelay(),
defaultPrompts: {
main: default_main_prompt,
@@ -648,6 +657,12 @@ function formatWorldInfo(value) {
function populationInjectionPrompts(prompts, messages) {
let totalInsertedMessages = 0;
+ const roleTypes = {
+ 'system': extension_prompt_roles.SYSTEM,
+ 'user': extension_prompt_roles.USER,
+ 'assistant': extension_prompt_roles.ASSISTANT,
+ };
+
for (let i = 0; i <= MAX_INJECTION_DEPTH; i++) {
// Get prompts for current depth
const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content);
@@ -655,14 +670,15 @@ function populationInjectionPrompts(prompts, messages) {
// Order of priority (most important go lower)
const roles = ['system', 'user', 'assistant'];
const roleMessages = [];
+ const separator = '\n';
for (const role of roles) {
// Get prompts for current role
- const rolePrompts = depthPrompts.filter(prompt => prompt.role === role).map(x => x.content).join('\n');
- // Get extension prompt (only for system role)
- const extensionPrompt = role === 'system' ? getExtensionPrompt(extension_prompt_types.IN_CHAT, i) : '';
+ const rolePrompts = depthPrompts.filter(prompt => prompt.role === role).map(x => x.content).join(separator);
+ // Get extension prompt
+ const extensionPrompt = getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role]);
- const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join('\n');
+ const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator);
if (jointPrompt && jointPrompt.length) {
roleMessages.push({ 'role': role, 'content': jointPrompt });
@@ -710,20 +726,13 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
// Reserve budget for continue nudge
let continueMessage = null;
const instruct = isOpenRouterWithInstruct();
- if (type === 'continue' && cyclePrompt && !instruct) {
- const promptObject = oai_settings.continue_prefill ?
- {
- identifier: 'continueNudge',
- role: 'assistant',
- content: cyclePrompt,
- system_prompt: true,
- } :
- {
- identifier: 'continueNudge',
- role: 'system',
- content: oai_settings.continue_nudge_prompt.replace('{{lastChatMessage}}', cyclePrompt),
- system_prompt: true,
- };
+ if (type === 'continue' && cyclePrompt && !instruct && !oai_settings.continue_prefill) {
+ const promptObject = {
+ identifier: 'continueNudge',
+ role: 'system',
+ content: oai_settings.continue_nudge_prompt.replace('{{lastChatMessage}}', String(cyclePrompt).trim()),
+ system_prompt: true,
+ };
const continuePrompt = new Prompt(promptObject);
const preparedPrompt = promptManager.preparePrompt(continuePrompt);
continueMessage = Message.fromPrompt(preparedPrompt);
@@ -833,6 +842,24 @@ function getPromptPosition(position) {
return false;
}
+/**
+ * Gets a Chat Completion role based on the prompt role.
+ * @param {number} role Role of the prompt.
+ * @returns {string} Mapped role.
+ */
+function getPromptRole(role) {
+ switch (role) {
+ case extension_prompt_roles.SYSTEM:
+ return 'system';
+ case extension_prompt_roles.USER:
+ return 'user';
+ case extension_prompt_roles.ASSISTANT:
+ return 'assistant';
+ default:
+ return 'system';
+ }
+}
+
/**
* Populate a chat conversation by adding prompts to the conversation and managing system and user prompts.
*
@@ -854,7 +881,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
// We need the prompts array to determine a position for the source.
if (false === prompts.has(source)) return;
- if (promptManager.isPromptDisabledForActiveCharacter(source)) {
+ if (promptManager.isPromptDisabledForActiveCharacter(source) && source !== 'main') {
promptManager.log(`Skipping prompt ${source} because it is disabled`);
return;
}
@@ -877,6 +904,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
addToChatCompletion('personaDescription');
// Collection of control prompts that will always be positioned last
+ chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts);
const controlPrompts = new MessageCollection('controlPrompts');
const impersonateMessage = Message.fromPrompt(prompts.get('impersonate')) ?? null;
@@ -1012,7 +1040,7 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
// Tavern Extras - Summary
const summary = extensionPrompts['1_memory'];
if (summary && summary.value) systemPrompts.push({
- role: 'system',
+ role: getPromptRole(summary.role),
content: summary.value,
identifier: 'summary',
position: getPromptPosition(summary.position),
@@ -1021,7 +1049,7 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
// Authors Note
const authorsNote = extensionPrompts['2_floating_prompt'];
if (authorsNote && authorsNote.value) systemPrompts.push({
- role: 'system',
+ role: getPromptRole(authorsNote.role),
content: authorsNote.value,
identifier: 'authorsNote',
position: getPromptPosition(authorsNote.position),
@@ -1064,20 +1092,20 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
// Apply character-specific main prompt
const systemPrompt = prompts.get('main') ?? null;
- if (systemPromptOverride && systemPrompt) {
+ if (systemPromptOverride && systemPrompt && systemPrompt.forbid_overrides !== true) {
const mainOriginalContent = systemPrompt.content;
systemPrompt.content = systemPromptOverride;
const mainReplacement = promptManager.preparePrompt(systemPrompt, mainOriginalContent);
- prompts.set(mainReplacement, prompts.index('main'));
+ prompts.override(mainReplacement, prompts.index('main'));
}
// Apply character-specific jailbreak
const jailbreakPrompt = prompts.get('jailbreak') ?? null;
- if (jailbreakPromptOverride && jailbreakPrompt) {
+ if (jailbreakPromptOverride && jailbreakPrompt && jailbreakPrompt.forbid_overrides !== true) {
const jbOriginalContent = jailbreakPrompt.content;
jailbreakPrompt.content = jailbreakPromptOverride;
const jbReplacement = promptManager.preparePrompt(jailbreakPrompt, jbOriginalContent);
- prompts.set(jbReplacement, prompts.index('jailbreak'));
+ prompts.override(jbReplacement, prompts.index('jailbreak'));
}
return prompts;
@@ -2178,7 +2206,7 @@ class MessageCollection {
* @see https://platform.openai.com/docs/guides/gpt/chat-completions-api
*
*/
-class ChatCompletion {
+export class ChatCompletion {
/**
* Combines consecutive system messages into one if they have no name attached.
@@ -2223,6 +2251,7 @@ class ChatCompletion {
this.tokenBudget = 0;
this.messages = new MessageCollection('root');
this.loggingEnabled = false;
+ this.overriddenPrompts = [];
}
/**
@@ -2497,6 +2526,18 @@ class ChatCompletion {
}
return index;
}
+
+ /**
+ * Sets the list of overridden prompts.
+ * @param {string[]} list A list of prompts that were overridden.
+ */
+ setOverriddenPrompts(list) {
+ this.overriddenPrompts = list;
+ }
+
+ getOverriddenPrompts() {
+ return this.overriddenPrompts ?? [];
+ }
}
function loadOpenAISettings(data, settings) {
@@ -2574,6 +2615,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.squash_system_messages = settings.squash_system_messages ?? default_settings.squash_system_messages;
oai_settings.continue_prefill = settings.continue_prefill ?? default_settings.continue_prefill;
oai_settings.names_behavior = settings.names_behavior ?? default_settings.names_behavior;
+ oai_settings.continue_postfix = settings.continue_postfix ?? default_settings.continue_postfix;
// Migrate from old settings
if (settings.names_in_completion === true) {
@@ -2690,6 +2732,7 @@ function loadOpenAISettings(data, settings) {
}
setNamesBehaviorControls();
+ setContinuePostfixControls();
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
@@ -2707,6 +2750,32 @@ function setNamesBehaviorControls() {
$('#character_names_content').prop('checked', true);
break;
}
+
+ const checkedItemText = $('input[name="character_names"]:checked ~ span').text().trim();
+ $('#character_names_display').text(checkedItemText);
+}
+
+function setContinuePostfixControls() {
+ switch (oai_settings.continue_postfix) {
+ case continue_postfix_types.SPACE:
+ $('#continue_postfix_space').prop('checked', true);
+ break;
+ case continue_postfix_types.NEWLINE:
+ $('#continue_postfix_newline').prop('checked', true);
+ break;
+ case continue_postfix_types.DOUBLE_NEWLINE:
+ $('#continue_postfix_double_newline').prop('checked', true);
+ break;
+ default:
+ // Prevent preset value abuse
+ oai_settings.continue_postfix = continue_postfix_types.SPACE;
+ $('#continue_postfix_space').prop('checked', true);
+ break;
+ }
+
+ $('#continue_postfix').val(oai_settings.continue_postfix);
+ const checkedItemText = $('input[name="continue_postfix"]:checked ~ span').text().trim();
+ $('#continue_postfix_display').text(checkedItemText);
}
async function getStatusOpen() {
@@ -2865,6 +2934,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
image_inlining: settings.image_inlining,
bypass_status_check: settings.bypass_status_check,
continue_prefill: settings.continue_prefill,
+ continue_postfix: settings.continue_postfix,
seed: settings.seed,
n: settings.n,
};
@@ -3239,6 +3309,7 @@ function onSettingsPresetChange() {
squash_system_messages: ['#squash_system_messages', 'squash_system_messages', true],
image_inlining: ['#openai_image_inlining', 'image_inlining', true],
continue_prefill: ['#continue_prefill', 'continue_prefill', true],
+ continue_postfix: ['#continue_postfix', 'continue_postfix', false],
seed: ['#seed_openai', 'seed', false],
n: ['#n_openai', 'n', false],
};
@@ -3435,7 +3506,7 @@ async function onModelChange() {
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
- } else if (value === 'gemini-1.5-pro') {
+ } else if (value === 'gemini-1.5-pro') {
$('#openai_max_context').attr('max', max_1mil);
} else if (value === 'gemini-pro') {
$('#openai_max_context').attr('max', max_32k);
@@ -4348,16 +4419,43 @@ $(document).ready(async function () {
$('#character_names_none').on('input', function () {
oai_settings.names_behavior = character_names_behavior.NONE;
+ setNamesBehaviorControls();
saveSettingsDebounced();
});
$('#character_names_completion').on('input', function () {
oai_settings.names_behavior = character_names_behavior.COMPLETION;
+ setNamesBehaviorControls();
saveSettingsDebounced();
});
$('#character_names_content').on('input', function () {
oai_settings.names_behavior = character_names_behavior.CONTENT;
+ setNamesBehaviorControls();
+ saveSettingsDebounced();
+ });
+
+ $('#continue_postifx').on('input', function () {
+ oai_settings.continue_postfix = String($(this).val());
+ setContinuePostfixControls();
+ saveSettingsDebounced();
+ });
+
+ $('#continue_postfix_space').on('input', function () {
+ oai_settings.continue_postfix = continue_postfix_types.SPACE;
+ setContinuePostfixControls();
+ saveSettingsDebounced();
+ });
+
+ $('#continue_postfix_newline').on('input', function () {
+ oai_settings.continue_postfix = continue_postfix_types.NEWLINE;
+ setContinuePostfixControls();
+ saveSettingsDebounced();
+ });
+
+ $('#continue_postfix_double_newline').on('input', function () {
+ oai_settings.continue_postfix = continue_postfix_types.DOUBLE_NEWLINE;
+ setContinuePostfixControls();
saveSettingsDebounced();
});
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index 74968fcb0..605f85e00 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -11,6 +11,7 @@ import {
default_avatar,
eventSource,
event_types,
+ extension_prompt_roles,
extension_prompt_types,
extractMessageBias,
generateQuietPrompt,
@@ -231,7 +232,7 @@ parser.addCommand('buttons', buttonsCallback, [], 'label
parser.addCommand('trimtokens', trimTokensCallback, [], 'limit=number (direction=start/end [text]) – trims the start or end of text to the specified number of tokens.', true, true);
parser.addCommand('trimstart', trimStartCallback, [], '(text) – trims the text to the start of the first full sentence.', true, true);
parser.addCommand('trimend', trimEndCallback, [], '(text) – trims the text to the end of the last full sentence.', true, true);
-parser.addCommand('inject', injectCallback, [], 'id=injectId (position=before/after/chat depth=number [text]) – injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4).', true, true);
+parser.addCommand('inject', injectCallback, [], 'id=injectId (position=before/after/chat depth=number scan=true/false role=system/user/assistant [text]) – injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false).', true, true);
parser.addCommand('listinjects', listInjectsCallback, [], ' – lists all script injections for the current chat.', true, true);
parser.addCommand('flushinjects', flushInjectsCallback, [], ' – removes all script injections for the current chat.', true, true);
parser.addCommand('tokens', (_, text) => getTokenCount(text), [], '(text) – counts the number of tokens in the text.', true, true);
@@ -249,6 +250,11 @@ function injectCallback(args, value) {
'after': extension_prompt_types.IN_PROMPT,
'chat': extension_prompt_types.IN_CHAT,
};
+ const roles = {
+ 'system': extension_prompt_roles.SYSTEM,
+ 'user': extension_prompt_roles.USER,
+ 'assistant': extension_prompt_roles.ASSISTANT,
+ };
const id = resolveVariable(args?.id);
@@ -264,6 +270,9 @@ function injectCallback(args, value) {
const position = positions[positionValue] ?? positions[defaultPosition];
const depthValue = Number(args?.depth) ?? defaultDepth;
const depth = isNaN(depthValue) ? defaultDepth : depthValue;
+ const roleValue = typeof args?.role === 'string' ? args.role.toLowerCase().trim() : Number(args?.role ?? extension_prompt_roles.SYSTEM);
+ const role = roles[roleValue] ?? roles[extension_prompt_roles.SYSTEM];
+ const scan = isTrueBoolean(args?.scan);
value = value || '';
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
@@ -276,9 +285,11 @@ function injectCallback(args, value) {
value,
position,
depth,
+ scan,
+ role,
};
- setExtensionPrompt(prefixedId, value, position, depth);
+ setExtensionPrompt(prefixedId, value, position, depth, scan, role);
saveMetadataDebounced();
return '';
}
@@ -293,7 +304,7 @@ function listInjectsCallback() {
.map(([id, inject]) => {
const position = Object.entries(extension_prompt_types);
const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
- return `* **${id}**: ${inject.value}
(${positionName}, depth: ${inject.depth})`;
+ return `* **${id}**: ${inject.value}
(${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
})
.join('\n');
@@ -311,7 +322,7 @@ function flushInjectsCallback() {
for (const [id, inject] of Object.entries(chat_metadata.script_injects)) {
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
- setExtensionPrompt(prefixedId, '', inject.position, inject.depth);
+ setExtensionPrompt(prefixedId, '', inject.position, inject.depth, inject.scan, inject.role);
}
chat_metadata.script_injects = {};
@@ -338,7 +349,7 @@ export function processChatSlashCommands() {
for (const [id, inject] of Object.entries(context.chatMetadata.script_injects)) {
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
console.log('Adding script injection', id);
- setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth);
+ setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role);
}
}
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index 3f6dbc862..29582a1f0 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -1,4 +1,4 @@
-import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId } from '../script.js';
+import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath } from './utils.js';
import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
@@ -931,6 +931,7 @@ const originalDataKeyMap = {
'depth': 'extensions.depth',
'probability': 'extensions.probability',
'position': 'extensions.position',
+ 'role': 'extensions.role',
'content': 'content',
'enabled': 'enabled',
'key': 'keys',
@@ -1375,9 +1376,12 @@ function getWorldEntry(name, data, entry) {
depthInput.prop('disabled', false);
depthInput.css('visibility', 'visible');
//depthInput.parent().show();
+ const role = Number($(this).find(':selected').data('role'));
+ data.entries[uid].role = role;
} else {
depthInput.prop('disabled', true);
depthInput.css('visibility', 'hidden');
+ data.entries[uid].role = null;
//depthInput.parent().hide();
}
updatePosOrdDisplay(uid);
@@ -1385,11 +1389,13 @@ function getWorldEntry(name, data, entry) {
setOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
// Write the original value as extensions field
setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
+ setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
saveWorldInfo(name, data);
});
+ const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
template
- .find(`select[name="position"] option[value=${entry.position}]`)
+ .find(`select[name="position"] option[value=${entry.position}][data-role="${roleValue}"]`)
.prop('selected', true)
.trigger('input');
@@ -1610,7 +1616,7 @@ function getWorldEntry(name, data, entry) {
* @returns {(input: any, output: any) => any} Callback function for the autocomplete
*/
function getInclusionGroupCallback(data) {
- return function(input, output) {
+ return function (input, output) {
const groups = new Set();
for (const entry of Object.values(data.entries)) {
if (entry.group) {
@@ -1633,7 +1639,7 @@ function getInclusionGroupCallback(data) {
}
function getAutomationIdCallback(data) {
- return function(input, output) {
+ return function (input, output) {
const ids = new Set();
for (const entry of Object.values(data.entries)) {
if (entry.automationId) {
@@ -1714,6 +1720,7 @@ const newEntryTemplate = {
caseSensitive: null,
matchWholeWords: null,
automationId: '',
+ role: 0,
};
function createWorldInfoEntry(name, data, fromSlashCommand = false) {
@@ -2255,13 +2262,14 @@ async function checkWorldInfo(chat, maxContext) {
ANBottomEntries.unshift(entry.content);
break;
case world_info_position.atDepth: {
- const existingDepthIndex = WIDepthEntries.findIndex((e) => e.depth === entry.depth ?? DEFAULT_DEPTH);
+ const existingDepthIndex = WIDepthEntries.findIndex((e) => e.depth === (entry.depth ?? DEFAULT_DEPTH) && e.role === (entry.role ?? extension_prompt_roles.SYSTEM));
if (existingDepthIndex !== -1) {
WIDepthEntries[existingDepthIndex].entries.unshift(entry.content);
} else {
WIDepthEntries.push({
depth: entry.depth,
entries: [entry.content],
+ role: entry.role ?? extension_prompt_roles.SYSTEM,
});
}
break;
@@ -2277,7 +2285,7 @@ async function checkWorldInfo(chat, maxContext) {
if (shouldWIAddPrompt) {
const originalAN = context.extensionPrompts[NOTE_MODULE_NAME].value;
const ANWithWI = `${ANTopEntries.join('\n')}\n${originalAN}\n${ANBottomEntries.join('\n')}`;
- context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan);
+ context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]);
}
return { worldInfoBefore, worldInfoAfter, WIDepthEntries, allActivatedEntries };
@@ -2358,6 +2366,7 @@ function convertAgnaiMemoryBook(inputObj) {
inputObj.entries.forEach((entry, index) => {
outputObj.entries[index] = {
+ ...newEntryTemplate,
uid: index,
key: entry.keywords,
keysecondary: [],
@@ -2375,6 +2384,11 @@ function convertAgnaiMemoryBook(inputObj) {
probability: null,
useProbability: false,
group: '',
+ scanDepth: entry.extensions?.scan_depth ?? null,
+ caseSensitive: entry.extensions?.case_sensitive ?? null,
+ matchWholeWords: entry.extensions?.match_whole_words ?? null,
+ automationId: entry.extensions?.automation_id ?? '',
+ role: entry.extensions?.role ?? extension_prompt_roles.SYSTEM,
};
});
@@ -2386,6 +2400,7 @@ function convertRisuLorebook(inputObj) {
inputObj.data.forEach((entry, index) => {
outputObj.entries[index] = {
+ ...newEntryTemplate,
uid: index,
key: entry.key.split(',').map(x => x.trim()),
keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],
@@ -2403,6 +2418,11 @@ function convertRisuLorebook(inputObj) {
probability: entry.activationPercent ?? null,
useProbability: entry.activationPercent ?? false,
group: '',
+ scanDepth: entry.extensions?.scan_depth ?? null,
+ caseSensitive: entry.extensions?.case_sensitive ?? null,
+ matchWholeWords: entry.extensions?.match_whole_words ?? null,
+ automationId: entry.extensions?.automation_id ?? '',
+ role: entry.extensions?.role ?? extension_prompt_roles.SYSTEM,
};
});
@@ -2419,6 +2439,7 @@ function convertNovelLorebook(inputObj) {
const addMemo = displayName !== undefined && displayName.trim() !== '';
outputObj.entries[index] = {
+ ...newEntryTemplate,
uid: index,
key: entry.keys,
keysecondary: [],
@@ -2436,6 +2457,11 @@ function convertNovelLorebook(inputObj) {
probability: null,
useProbability: false,
group: '',
+ scanDepth: entry.extensions?.scan_depth ?? null,
+ caseSensitive: entry.extensions?.case_sensitive ?? null,
+ matchWholeWords: entry.extensions?.match_whole_words ?? null,
+ automationId: entry.extensions?.automation_id ?? '',
+ role: entry.extensions?.role ?? extension_prompt_roles.SYSTEM,
};
});
@@ -2452,6 +2478,7 @@ function convertCharacterBook(characterBook) {
}
result.entries[entry.id] = {
+ ...newEntryTemplate,
uid: entry.id,
key: entry.keys,
keysecondary: entry.secondary_keys || [],
@@ -2475,6 +2502,7 @@ function convertCharacterBook(characterBook) {
caseSensitive: entry.extensions?.case_sensitive ?? null,
matchWholeWords: entry.extensions?.match_whole_words ?? null,
automationId: entry.extensions?.automation_id ?? '',
+ role: entry.extensions?.role ?? extension_prompt_roles.SYSTEM,
};
});
diff --git a/src/endpoints/characters.js b/src/endpoints/characters.js
index 03260f1a7..e2063def3 100644
--- a/src/endpoints/characters.js
+++ b/src/endpoints/characters.js
@@ -406,6 +406,7 @@ function convertWorldInfoToCharacterBook(name, entries) {
match_whole_words: entry.matchWholeWords ?? null,
case_sensitive: entry.caseSensitive ?? null,
automation_id: entry.automationId ?? '',
+ role: entry.role ?? 0,
},
};