mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-23 07:27:41 +01:00
Merge branch 'staging' into tc-split-generic
This commit is contained in:
commit
756f88b5aa
108
public/script.js
108
public/script.js
@ -2876,23 +2876,54 @@ function addPersonaDescriptionExtensionPrompt() {
|
||||
}
|
||||
}
|
||||
|
||||
function getAllExtensionPrompts() {
|
||||
const value = Object
|
||||
.values(extension_prompts)
|
||||
.filter(x => x.value)
|
||||
.map(x => x.value.trim())
|
||||
.join('\n');
|
||||
/**
|
||||
* Returns all extension prompts combined.
|
||||
* @returns {Promise<string>} Combined extension prompts
|
||||
*/
|
||||
async function getAllExtensionPrompts() {
|
||||
const values = [];
|
||||
|
||||
return value.length ? substituteParams(value) : '';
|
||||
for (const prompt of Object.values(extension_prompts)) {
|
||||
const value = prompt?.value?.trim();
|
||||
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wrapper to fetch extension prompts by module name
|
||||
export function getExtensionPromptByName(moduleName) {
|
||||
if (moduleName) {
|
||||
return substituteParams(extension_prompts[moduleName]?.value);
|
||||
} else {
|
||||
return;
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
if (hasFilter && !await prompt.filter()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
return substituteParams(values.join('\n'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to fetch extension prompts by module name
|
||||
* @param {string} moduleName Module name
|
||||
* @returns {Promise<string>} Extension prompt
|
||||
*/
|
||||
export async function getExtensionPromptByName(moduleName) {
|
||||
if (!moduleName) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const prompt = extension_prompts[moduleName];
|
||||
|
||||
if (!prompt) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
|
||||
if (hasFilter && !await prompt.filter()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return substituteParams(prompt.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2903,27 +2934,36 @@ export function getExtensionPromptByName(moduleName) {
|
||||
* @param {string} [separator] Separator for joining multiple prompts
|
||||
* @param {number} [role] Role of the prompt
|
||||
* @param {boolean} [wrap] Wrap start and end with a separator
|
||||
* @returns {string} Extension prompt
|
||||
* @returns {Promise<string>} Extension prompt
|
||||
*/
|
||||
export function getExtensionPrompt(position = extension_prompt_types.IN_PROMPT, depth = undefined, separator = '\n', role = undefined, wrap = true) {
|
||||
let extension_prompt = Object.keys(extension_prompts)
|
||||
export async function getExtensionPrompt(position = extension_prompt_types.IN_PROMPT, depth = undefined, separator = '\n', role = undefined, wrap = true) {
|
||||
const filterByFunction = async (prompt) => {
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
if (hasFilter && !await prompt.filter()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const promptPromises = Object.keys(extension_prompts)
|
||||
.sort()
|
||||
.map((x) => extension_prompts[x])
|
||||
.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 (wrap && extension_prompt.length && !extension_prompt.startsWith(separator)) {
|
||||
extension_prompt = separator + extension_prompt;
|
||||
.filter(filterByFunction);
|
||||
const prompts = await Promise.all(promptPromises);
|
||||
|
||||
let values = prompts.map(x => x.value.trim()).join(separator);
|
||||
if (wrap && values.length && !values.startsWith(separator)) {
|
||||
values = separator + values;
|
||||
}
|
||||
if (wrap && extension_prompt.length && !extension_prompt.endsWith(separator)) {
|
||||
extension_prompt = extension_prompt + separator;
|
||||
if (wrap && values.length && !values.endsWith(separator)) {
|
||||
values = values + separator;
|
||||
}
|
||||
if (extension_prompt.length) {
|
||||
extension_prompt = substituteParams(extension_prompt);
|
||||
if (values.length) {
|
||||
values = substituteParams(values);
|
||||
}
|
||||
return extension_prompt;
|
||||
return values;
|
||||
}
|
||||
|
||||
export function baseChatReplace(value, name1, name2) {
|
||||
@ -3837,7 +3877,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
// Inject all Depth prompts. Chat Completion does it separately
|
||||
let injectedIndices = [];
|
||||
if (main_api !== 'openai') {
|
||||
injectedIndices = doChatInject(coreChat, isContinue);
|
||||
injectedIndices = await doChatInject(coreChat, isContinue);
|
||||
}
|
||||
|
||||
// Insert character jailbreak as the last user message (if exists, allowed, preferred, and not using Chat Completion)
|
||||
@ -3910,8 +3950,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
}
|
||||
|
||||
// Call combined AN into Generate
|
||||
const beforeScenarioAnchor = getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT).trimStart();
|
||||
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.IN_PROMPT);
|
||||
const beforeScenarioAnchor = (await getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT)).trimStart();
|
||||
const afterScenarioAnchor = await getExtensionPrompt(extension_prompt_types.IN_PROMPT);
|
||||
|
||||
const storyStringParams = {
|
||||
description: description,
|
||||
@ -4474,7 +4514,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
...thisPromptBits[currentArrayEntry],
|
||||
rawPrompt: generate_data.prompt || generate_data.input,
|
||||
mesId: getNextMessageId(type),
|
||||
allAnchors: getAllExtensionPrompts(),
|
||||
allAnchors: await getAllExtensionPrompts(),
|
||||
chatInjects: injectedIndices?.map(index => arrMes[arrMes.length - index - 1])?.join('') || '',
|
||||
summarizeString: (extension_prompts['1_memory']?.value || ''),
|
||||
authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''),
|
||||
@ -4743,9 +4783,9 @@ export function stopGeneration() {
|
||||
* 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 {number[]} Array of indices where the extension prompts were injected
|
||||
* @returns {Promise<number[]>} Array of indices where the extension prompts were injected
|
||||
*/
|
||||
function doChatInject(messages, isContinue) {
|
||||
async function doChatInject(messages, isContinue) {
|
||||
const injectedIndices = [];
|
||||
let totalInsertedMessages = 0;
|
||||
messages.reverse();
|
||||
@ -4763,7 +4803,7 @@ function doChatInject(messages, isContinue) {
|
||||
const wrap = false;
|
||||
|
||||
for (const role of roles) {
|
||||
const extensionPrompt = String(getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, role, wrap)).trimStart();
|
||||
const extensionPrompt = String(await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, role, wrap)).trimStart();
|
||||
const isNarrator = role === extension_prompt_roles.SYSTEM;
|
||||
const isUser = role === extension_prompt_roles.USER;
|
||||
const name = names[role];
|
||||
@ -7456,14 +7496,16 @@ function select_rm_characters() {
|
||||
* @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.
|
||||
* @param {(function(): Promise<boolean>|boolean)} filter Filter function to determine if the prompt should be injected.
|
||||
*/
|
||||
export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM) {
|
||||
export function setExtensionPrompt(key, value, position, depth, scan = false, role = extension_prompt_roles.SYSTEM, filter = null) {
|
||||
extension_prompts[key] = {
|
||||
value: String(value),
|
||||
position: Number(position),
|
||||
depth: Number(depth),
|
||||
scan: !!scan,
|
||||
role: Number(role ?? extension_prompt_roles.SYSTEM),
|
||||
filter: filter,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -431,7 +431,8 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
|
||||
}
|
||||
|
||||
const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS || (!!selected_group && power_user.instruct.names_behavior === names_behavior_types.FORCE);
|
||||
const includeNames = power_user.instruct.names_behavior === names_behavior_types.ALWAYS;
|
||||
const includeGroupNames = selected_group && [names_behavior_types.ALWAYS, names_behavior_types.FORCE].includes(power_user.instruct.names_behavior);
|
||||
|
||||
let inputPrefix = power_user.instruct.input_sequence || '';
|
||||
let outputPrefix = power_user.instruct.output_sequence || '';
|
||||
@ -463,7 +464,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
|
||||
for (const item of mesExamplesArray) {
|
||||
const cleanedItem = item.replace(/<START>/i, '{Example Dialogue:}').replace(/\r/gm, '');
|
||||
const blockExamples = parseExampleIntoIndividual(cleanedItem);
|
||||
const blockExamples = parseExampleIntoIndividual(cleanedItem, includeGroupNames);
|
||||
|
||||
if (blockExamples.length === 0) {
|
||||
continue;
|
||||
@ -474,8 +475,9 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
}
|
||||
|
||||
for (const example of blockExamples) {
|
||||
// If group names were already included, we don't want to add an additional prefix
|
||||
// If force group/persona names is set, we should override the include names for the user placeholder
|
||||
const includeThisName = includeNames || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user');
|
||||
const includeThisName = (includeNames && !includeGroupNames) || (power_user.instruct.names_behavior === names_behavior_types.FORCE && example.name == 'example_user');
|
||||
|
||||
const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
|
||||
const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
|
||||
@ -489,7 +491,6 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
if (formattedExamples.length === 0) {
|
||||
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
|
||||
}
|
||||
|
||||
return formattedExamples;
|
||||
}
|
||||
|
||||
|
@ -611,8 +611,9 @@ function formatWorldInfo(value) {
|
||||
*
|
||||
* @param {Prompt[]} prompts - Array containing injection prompts.
|
||||
* @param {Object[]} messages - Array containing all messages.
|
||||
* @returns {Promise<Object[]>} - Array containing all messages with injections.
|
||||
*/
|
||||
function populationInjectionPrompts(prompts, messages) {
|
||||
async function populationInjectionPrompts(prompts, messages) {
|
||||
let totalInsertedMessages = 0;
|
||||
|
||||
const roleTypes = {
|
||||
@ -635,7 +636,7 @@ function populationInjectionPrompts(prompts, messages) {
|
||||
// Get prompts for current role
|
||||
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], wrap);
|
||||
const extensionPrompt = await getExtensionPrompt(extension_prompt_types.IN_CHAT, i, separator, roleTypes[role], wrap);
|
||||
|
||||
const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join(separator);
|
||||
|
||||
@ -1020,7 +1021,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
|
||||
}
|
||||
|
||||
// Add in-chat injections
|
||||
messages = populationInjectionPrompts(absolutePrompts, messages);
|
||||
messages = await populationInjectionPrompts(absolutePrompts, messages);
|
||||
|
||||
// Decide whether dialogue examples should always be added
|
||||
if (power_user.pin_examples) {
|
||||
@ -1051,9 +1052,9 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
|
||||
* @param {string} options.systemPromptOverride
|
||||
* @param {string} options.jailbreakPromptOverride
|
||||
* @param {string} options.personaDescription
|
||||
* @returns {Object} prompts - The prepared and merged system and user-defined prompts.
|
||||
* @returns {Promise<Object>} prompts - The prepared and merged system and user-defined prompts.
|
||||
*/
|
||||
function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
|
||||
async function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
|
||||
const scenarioText = Scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : '';
|
||||
const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : '';
|
||||
const groupNudge = substituteParams(oai_settings.group_nudge_prompt);
|
||||
@ -1142,6 +1143,9 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
|
||||
if (!extensionPrompts[key].value) continue;
|
||||
if (![extension_prompt_types.BEFORE_PROMPT, extension_prompt_types.IN_PROMPT].includes(prompt.position)) continue;
|
||||
|
||||
const hasFilter = typeof prompt.filter === 'function';
|
||||
if (hasFilter && !await prompt.filter()) continue;
|
||||
|
||||
systemPrompts.push({
|
||||
identifier: key.replace(/\W/g, '_'),
|
||||
position: getPromptPosition(prompt.position),
|
||||
@ -1254,7 +1258,7 @@ export async function prepareOpenAIMessages({
|
||||
|
||||
try {
|
||||
// Merge markers and ordered user prompts with system prompts
|
||||
const prompts = preparePromptsForChatCompletion({
|
||||
const prompts = await preparePromptsForChatCompletion({
|
||||
Scenario,
|
||||
charPersonality,
|
||||
name2,
|
||||
|
@ -84,6 +84,25 @@ export const parser = new SlashCommandParser();
|
||||
const registerSlashCommand = SlashCommandParser.addCommand.bind(SlashCommandParser);
|
||||
const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
||||
|
||||
/**
|
||||
* Converts a SlashCommandClosure to a filter function that returns a boolean.
|
||||
* @param {SlashCommandClosure} closure
|
||||
* @returns {() => Promise<boolean>}
|
||||
*/
|
||||
function closureToFilter(closure) {
|
||||
return async () => {
|
||||
try {
|
||||
const localClosure = closure.getCopy();
|
||||
localClosure.onProgress = () => { };
|
||||
const result = await localClosure.execute();
|
||||
return isTrueBoolean(result.pipe);
|
||||
} catch (e) {
|
||||
console.error('Error executing filter closure', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function initDefaultSlashCommands() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: '?',
|
||||
@ -1611,6 +1630,13 @@ export function initDefaultSlashCommands() {
|
||||
new SlashCommandNamedArgument(
|
||||
'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
|
||||
),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'filter',
|
||||
description: 'if a filter is defined, an injection will only be performed if the closure returns true',
|
||||
typeList: [ARGUMENT_TYPE.CLOSURE],
|
||||
isRequired: false,
|
||||
acceptsMultiple: false,
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -1901,6 +1927,11 @@ const NARRATOR_NAME_DEFAULT = 'System';
|
||||
export const COMMENT_NAME_DEFAULT = 'Note';
|
||||
const SCRIPT_PROMPT_KEY = 'script_inject_';
|
||||
|
||||
/**
|
||||
* Adds a new script injection to the chat.
|
||||
* @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments
|
||||
* @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value Unnamed argument
|
||||
*/
|
||||
function injectCallback(args, value) {
|
||||
const positions = {
|
||||
'before': extension_prompt_types.BEFORE_PROMPT,
|
||||
@ -1914,8 +1945,8 @@ function injectCallback(args, value) {
|
||||
'assistant': extension_prompt_roles.ASSISTANT,
|
||||
};
|
||||
|
||||
const id = args?.id;
|
||||
const ephemeral = isTrueBoolean(args?.ephemeral);
|
||||
const id = String(args?.id);
|
||||
const ephemeral = isTrueBoolean(String(args?.ephemeral));
|
||||
|
||||
if (!id) {
|
||||
console.warn('WARN: No ID provided for /inject command');
|
||||
@ -1931,9 +1962,15 @@ function injectCallback(args, value) {
|
||||
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);
|
||||
const scan = isTrueBoolean(String(args?.scan));
|
||||
const filter = args?.filter instanceof SlashCommandClosure ? args.filter.rawText : null;
|
||||
const filterFunction = args?.filter instanceof SlashCommandClosure ? closureToFilter(args.filter) : null;
|
||||
value = value || '';
|
||||
|
||||
if (args?.filter && !String(filter ?? '').trim()) {
|
||||
throw new Error('Failed to parse the filter argument. Make sure it is a valid non-empty closure.');
|
||||
}
|
||||
|
||||
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
|
||||
|
||||
if (!chat_metadata.script_injects) {
|
||||
@ -1941,13 +1978,13 @@ function injectCallback(args, value) {
|
||||
}
|
||||
|
||||
if (value) {
|
||||
const inject = { value, position, depth, scan, role };
|
||||
const inject = { value, position, depth, scan, role, filter };
|
||||
chat_metadata.script_injects[id] = inject;
|
||||
} else {
|
||||
delete chat_metadata.script_injects[id];
|
||||
}
|
||||
|
||||
setExtensionPrompt(prefixedId, value, position, depth, scan, role);
|
||||
setExtensionPrompt(prefixedId, String(value), position, depth, scan, role, filterFunction);
|
||||
saveMetadataDebounced();
|
||||
|
||||
if (ephemeral) {
|
||||
@ -1958,7 +1995,7 @@ function injectCallback(args, value) {
|
||||
}
|
||||
console.log('Removing ephemeral script injection', id);
|
||||
delete chat_metadata.script_injects[id];
|
||||
setExtensionPrompt(prefixedId, '', position, depth, scan, role);
|
||||
setExtensionPrompt(prefixedId, '', position, depth, scan, role, filterFunction);
|
||||
saveMetadataDebounced();
|
||||
deleted = true;
|
||||
};
|
||||
@ -2053,9 +2090,28 @@ export function processChatSlashCommands() {
|
||||
}
|
||||
|
||||
for (const [id, inject] of Object.entries(context.chatMetadata.script_injects)) {
|
||||
/**
|
||||
* Rehydrates a filter closure from a string.
|
||||
* @returns {SlashCommandClosure | null}
|
||||
*/
|
||||
function reviveFilterClosure() {
|
||||
if (!inject.filter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new SlashCommandParser().parse(inject.filter, true);
|
||||
} catch (error) {
|
||||
console.warn('Failed to revive filter closure for script injection', id, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const prefixedId = `${SCRIPT_PROMPT_KEY}${id}`;
|
||||
const filterClosure = reviveFilterClosure();
|
||||
const filter = filterClosure ? closureToFilter(filterClosure) : null;
|
||||
console.log('Adding script injection', id);
|
||||
setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role);
|
||||
setExtensionPrompt(prefixedId, inject.value, inject.position, inject.depth, inject.scan, inject.role, filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { tag_map, tags } from './tags.js';
|
||||
import { textgenerationwebui_settings } from './textgen-settings.js';
|
||||
import { getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
|
||||
import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
|
||||
import { ToolManager } from './tool-calling.js';
|
||||
import { timestampToMoment } from './utils.js';
|
||||
|
||||
@ -95,6 +95,8 @@ export function getContext() {
|
||||
sendStreamingRequest,
|
||||
sendGenerationRequest,
|
||||
stopGeneration,
|
||||
tokenizers,
|
||||
getTextTokens,
|
||||
/** @deprecated Use getTokenCountAsync instead */
|
||||
getTokenCount,
|
||||
getTokenCountAsync,
|
||||
|
@ -3760,7 +3760,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
// Put this code here since otherwise, the chat reference is modified
|
||||
for (const key of Object.keys(context.extensionPrompts)) {
|
||||
if (context.extensionPrompts[key]?.scan) {
|
||||
const prompt = getExtensionPromptByName(key);
|
||||
const prompt = await getExtensionPromptByName(key);
|
||||
if (prompt) {
|
||||
buffer.addInject(prompt);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user