mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
4 Commits
OptimizedW
...
CollapseSa
Author | SHA1 | Date | |
---|---|---|---|
|
5b270c3333 | ||
|
ffe0e76812 | ||
|
7ccf05d160 | ||
|
b5da8742e8 |
@@ -140,7 +140,7 @@
|
||||
<div data-preset-manager-import="novel" class="margin0 menu_button_icon menu_button" title="Import preset" data-i18n="[title]Import preset">
|
||||
<i class="fa-fw fa-solid fa-file-import"></i>
|
||||
</div>
|
||||
<div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
|
||||
<div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
|
||||
<i class="fa-fw fa-solid fa-file-export"></i>
|
||||
</div>
|
||||
<div data-preset-manager-delete="novel" class="margin0 menu_button_icon menu_button" title="Delete the preset" data-i18n="[title]Delete the preset">
|
||||
@@ -663,7 +663,7 @@
|
||||
<div data-source="openrouter" class="range-block">
|
||||
<div class="range-block-title" title="Allow compressing requests by removing messages from the middle of the prompt.">
|
||||
<span data-i18n="Middle-out Transform">Middle-out Transform</span>
|
||||
<a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
|
||||
<a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<select id="openrouter_middleout" class="text_pole">
|
||||
@@ -3825,7 +3825,7 @@
|
||||
<input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
|
||||
<small><i class="fa-solid fa-link menu_button margin0"></i></small>
|
||||
</label>
|
||||
<label id="instruct_enabled_label"for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
|
||||
<label id="instruct_enabled_label" for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
|
||||
<input id="instruct_enabled" type="checkbox" style="display:none;" />
|
||||
<small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>
|
||||
</label>
|
||||
@@ -3869,6 +3869,10 @@
|
||||
<input id="instruct_skip_examples" type="checkbox" />
|
||||
<small data-i18n="Skip Example Dialogues Formatting">Skip Example Dialogues Formatting</small>
|
||||
</label>
|
||||
<label for="instruct_collapse_same_entity" class="checkbox_label">
|
||||
<input id="instruct_collapse_same_entity" type="checkbox" />
|
||||
<small data-i18n="Collapse Consecutive Same-Entity Sequences" title="If enabled, all consecutive messages with the same entity will be merged in prompt." data-i18n="[title]If enabled, all consecutive messages with the same entity will be merged in prompt.">Collapse Consecutive Same-Entity Sequences</small>
|
||||
</label>
|
||||
<div>
|
||||
<small data-i18n="Include Names">
|
||||
Include Names
|
||||
@@ -5284,7 +5288,7 @@
|
||||
<div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
|
||||
<div class="flex-container alignItemsBaseline wide100p">
|
||||
<div class="flex1 flex-container alignItemsBaseline">
|
||||
<h3 class="margin0" >
|
||||
<h3 class="margin0">
|
||||
<span data-i18n="Persona Management">Persona Management</span>
|
||||
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">
|
||||
<span class="fa-solid fa-circle-question note-link-span"></span>
|
||||
|
158
public/script.js
158
public/script.js
@@ -3746,6 +3746,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
|
||||
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 && power_user.instruct.collapse_same_entity ? (collapseConsecutiveMessagesInPrompt(prompt, power_user.instruct)) : prompt;
|
||||
|
||||
try {
|
||||
if (responseLengthCustomized) {
|
||||
@@ -3945,6 +3946,159 @@ function removeLastMessage() {
|
||||
});
|
||||
}
|
||||
|
||||
function collapseConsecutiveMessagesInPrompt(prompt, instruct) {
|
||||
if (!instruct.enabled) {
|
||||
console.debug('Instruct mode is disabled; returning original prompt');
|
||||
return prompt;
|
||||
}
|
||||
|
||||
if (!instruct.collapse_same_entity) {
|
||||
console.debug('collapse_same_entity is false; returning original prompt');
|
||||
return prompt;
|
||||
}
|
||||
|
||||
console.debug('Collapsing consecutive messages in prompt');
|
||||
|
||||
// Define prefixes and suffixes
|
||||
const prefixes = {
|
||||
system: instruct.system_sequence,
|
||||
system_first: instruct.system_sequence_prefix,
|
||||
user: instruct.input_sequence,
|
||||
assistant: instruct.output_sequence,
|
||||
};
|
||||
const suffixes = {
|
||||
system_first: instruct.system_sequence_suffix,
|
||||
system: instruct.system_suffix,
|
||||
user: instruct.input_suffix,
|
||||
assistant: instruct.output_suffix,
|
||||
};
|
||||
|
||||
// Validate required settings
|
||||
// Fail only if all prefixes and suffixes are empty strings
|
||||
const allPrefixesEmpty = Object.values(prefixes).every(val => !val);
|
||||
const allSuffixesEmpty = Object.values(suffixes).every(val => !val);
|
||||
if (allPrefixesEmpty && allSuffixesEmpty) {
|
||||
console.debug('All prefixes and suffixes are empty; returning original prompt', {
|
||||
system_sequence: prefixes.system,
|
||||
system_sequence_prefix: prefixes.system_first,
|
||||
input_sequence: prefixes.user,
|
||||
output_sequence: prefixes.assistant,
|
||||
system_first_suffix: suffixes.system_first,
|
||||
system_suffix: suffixes.system,
|
||||
input_suffix: suffixes.user,
|
||||
output_suffix: suffixes.assistant,
|
||||
});
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Escape prefixes and suffixes for regex, handling empty strings
|
||||
const escapeRegex = (str) => str ? str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') : '';
|
||||
const prefixRegexStr = Object.values(prefixes)
|
||||
.filter(val => val)
|
||||
.map(escapeRegex)
|
||||
.join('|') || '\\b';
|
||||
const suffixRegexStr = Object.values(suffixes)
|
||||
.filter(val => val)
|
||||
.map(escapeRegex)
|
||||
.join('|') || '\\b';
|
||||
// Use lookahead to match content up to suffix or next prefix
|
||||
const messageRegex = new RegExp(
|
||||
`(${prefixRegexStr})([\\s\\S]*?)(?=(?:${suffixRegexStr}|${prefixRegexStr}|$))((?:${suffixRegexStr})?)`,
|
||||
'g',
|
||||
);
|
||||
|
||||
// Parse the prompt into messages
|
||||
const messages = [];
|
||||
let match;
|
||||
let lastIndex = 0;
|
||||
|
||||
console.debug('Parsing prompt with regex:', messageRegex);
|
||||
while ((match = messageRegex.exec(prompt)) !== null) {
|
||||
const [, prefix, content, suffix = ''] = match;
|
||||
const prefixStart = match.index;
|
||||
const gap = prompt.slice(lastIndex, prefixStart);
|
||||
if (gap) {
|
||||
console.debug(`Found gap between messages: ${JSON.stringify(gap)}`);
|
||||
}
|
||||
messages.push({ prefix, content: content.trim(), suffix, fullMatch: match[0], gap });
|
||||
lastIndex = match.index + match[0].length;
|
||||
console.debug(`Matched message: prefix=${prefix}, content=${content}, suffix=${suffix}`);
|
||||
}
|
||||
|
||||
// Capture any trailing text
|
||||
if (lastIndex < prompt.length) {
|
||||
const trailing = prompt.slice(lastIndex);
|
||||
console.debug(`Found trailing text: ${JSON.stringify(trailing)}`);
|
||||
// Check if trailing text starts with a prefix
|
||||
const trailingMatch = trailing.match(new RegExp(`^(${prefixRegexStr})([\\s\\S]*?)(?=(?:${suffixRegexStr}|${prefixRegexStr}|$))((?:${suffixRegexStr})?)`));
|
||||
if (trailingMatch) {
|
||||
const [, prefix, content, suffix = ''] = trailingMatch;
|
||||
messages.push({ prefix, content: content.trim(), suffix, fullMatch: trailingMatch[0], gap: '' });
|
||||
console.debug(`Matched trailing message: prefix=${prefix}, content=${content}, suffix=${suffix}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
console.debug('No messages parsed; returning original prompt');
|
||||
return prompt;
|
||||
}
|
||||
|
||||
// Collapse consecutive messages with the same effective prefix
|
||||
const collapsedMessages = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < messages.length) {
|
||||
const current = messages[i];
|
||||
const effectivePrefix = (i === 0 && current.prefix === prefixes.system_first) ? prefixes.system : current.prefix;
|
||||
let combinedContent = [current.content];
|
||||
let currentSuffix = current.suffix;
|
||||
let j = i + 1;
|
||||
|
||||
// Avoid collapsing the last complete assistant message if followed by an incomplete assistant message
|
||||
const isLastCompleteAssistant = i === messages.length - 2 &&
|
||||
messages[i].prefix === prefixes.assistant &&
|
||||
messages[i + 1].prefix === prefixes.assistant;
|
||||
|
||||
if (!isLastCompleteAssistant) {
|
||||
// Collect consecutive messages with the same effective prefix
|
||||
while (j < messages.length) {
|
||||
const next = messages[j];
|
||||
const nextEffectivePrefix = (j === 0 && next.prefix === prefixes.system_first) ? prefixes.system : next.prefix;
|
||||
|
||||
if (nextEffectivePrefix !== effectivePrefix) {
|
||||
console.debug(`Stopping collapse at index ${j}: effectivePrefix=${effectivePrefix}, nextEffectivePrefix=${nextEffectivePrefix}`);
|
||||
break;
|
||||
}
|
||||
|
||||
combinedContent.push(next.content);
|
||||
currentSuffix = next.suffix; // Use the last suffix
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
// Join contents with newlines
|
||||
const joinedContent = combinedContent.reduce((acc, curr, idx) => {
|
||||
if (idx === 0) return curr;
|
||||
return acc + '\n' + curr;
|
||||
}, '');
|
||||
|
||||
// Add newline after prefix if wrap is true and prefix doesn't end with newline
|
||||
const prefixNewline = instruct.wrap && !current.prefix.match(/\n$/) ? '\n' : '';
|
||||
|
||||
// Create the collapsed message
|
||||
const combinedMessage = `${current.prefix}${prefixNewline}${joinedContent}${currentSuffix}`;
|
||||
collapsedMessages.push(combinedMessage);
|
||||
console.debug(`Collapsed message at index ${i}: ${combinedMessage}`);
|
||||
i = j; // Skip the combined messages
|
||||
}
|
||||
|
||||
// Reconstruct the prompt without extra newlines
|
||||
const collapsedPrompt = collapsedMessages.join('');
|
||||
console.debug('Before collapsing:', prompt);
|
||||
console.debug('After collapsing:', collapsedPrompt);
|
||||
return collapsedPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* MARK:Generate()
|
||||
* Runs a generation using the current chat context.
|
||||
@@ -4988,6 +5142,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
|
||||
showStopButton();
|
||||
|
||||
if (isInstruct && power_user.instruct.collapse_same_entity) {
|
||||
generate_data.prompt = collapseConsecutiveMessagesInPrompt(generate_data.prompt, power_user.instruct);
|
||||
}
|
||||
|
||||
//set array object for prompt token itemization of this message
|
||||
let currentArrayEntry = Number(thisPromptBits.length - 1);
|
||||
let additionalPromptStuff = {
|
||||
|
@@ -43,6 +43,7 @@ const controls = [
|
||||
{ id: 'instruct_derived', property: 'derived', isCheckbox: true },
|
||||
{ id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
|
||||
{ id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
|
||||
{ id: 'instruct_collapse_same_entity', property: 'collapse_same_entity', isCheckbox: true },
|
||||
{ id: 'instruct_names_behavior', property: 'names_behavior', isCheckbox: false },
|
||||
{ id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },
|
||||
];
|
||||
|
Reference in New Issue
Block a user