mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into qr-editor-wordwrap
This commit is contained in:
@@ -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: '',
|
||||
@@ -310,7 +325,8 @@ class PromptManager {
|
||||
|
||||
counts[promptID] = null;
|
||||
promptOrderEntry.enabled = !promptOrderEntry.enabled;
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
this.render();
|
||||
this.saveServiceSettings();
|
||||
};
|
||||
|
||||
// Open edit form and load selected prompt
|
||||
@@ -350,7 +366,8 @@ class PromptManager {
|
||||
this.detachPrompt(prompt, this.activeCharacter);
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
this.render();
|
||||
this.saveServiceSettings();
|
||||
};
|
||||
|
||||
// Save prompt edit form to settings and close form.
|
||||
@@ -374,7 +391,8 @@ class PromptManager {
|
||||
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
this.render();
|
||||
this.saveServiceSettings();
|
||||
};
|
||||
|
||||
// Reset prompt should it be a system prompt
|
||||
@@ -386,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';
|
||||
@@ -394,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';
|
||||
@@ -407,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');
|
||||
@@ -420,7 +442,8 @@ class PromptManager {
|
||||
|
||||
if (prompt) {
|
||||
this.appendPrompt(prompt, this.activeCharacter);
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
this.render();
|
||||
this.saveServiceSettings();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -437,7 +460,8 @@ class PromptManager {
|
||||
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
this.render();
|
||||
this.saveServiceSettings();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -541,7 +565,8 @@ class PromptManager {
|
||||
this.removePromptOrderForCharacter(this.activeCharacter);
|
||||
this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder);
|
||||
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
this.render();
|
||||
this.saveServiceSettings();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -705,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -878,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);
|
||||
}
|
||||
|
||||
@@ -1127,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 ?? '';
|
||||
@@ -1135,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');
|
||||
@@ -1218,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;
|
||||
@@ -1226,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;
|
||||
}
|
||||
@@ -1249,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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1258,7 +1298,7 @@ class PromptManager {
|
||||
/**
|
||||
* Setter for messages property
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
* @param {import('./openai.js').MessageCollection} messages
|
||||
*/
|
||||
setMessages(messages) {
|
||||
this.messages = messages;
|
||||
@@ -1267,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();
|
||||
@@ -1297,6 +1338,11 @@ class PromptManager {
|
||||
* Empties, then re-assembles the container containing the prompt list.
|
||||
*/
|
||||
renderPromptManager() {
|
||||
let selectedPromptIndex = 0;
|
||||
const existingAppendSelect = document.getElementById(`${this.configuration.prefix}prompt_manager_footer_append_prompt`);
|
||||
if (existingAppendSelect instanceof HTMLSelectElement) {
|
||||
selectedPromptIndex = existingAppendSelect.selectedIndex;
|
||||
}
|
||||
const promptManagerDiv = this.containerElement;
|
||||
promptManagerDiv.innerHTML = '';
|
||||
|
||||
@@ -1326,13 +1372,21 @@ class PromptManager {
|
||||
if (null !== this.activeCharacter) {
|
||||
const prompts = [...this.serviceSettings.prompts]
|
||||
.filter(prompt => prompt && !prompt?.system_prompt)
|
||||
.sort((promptA, promptB) => promptA.name.localeCompare(promptB.name))
|
||||
.reduce((acc, prompt) => acc + `<option value="${prompt.identifier}">${escapeHtml(prompt.name)}</option>`, '');
|
||||
.sort((promptA, promptB) => promptA.name.localeCompare(promptB.name));
|
||||
const promptsHtml = prompts.reduce((acc, prompt) => acc + `<option value="${prompt.identifier}">${escapeHtml(prompt.name)}</option>`, '');
|
||||
|
||||
if (selectedPromptIndex > 0) {
|
||||
selectedPromptIndex = Math.min(selectedPromptIndex, prompts.length - 1);
|
||||
}
|
||||
|
||||
if (selectedPromptIndex === -1 && prompts.length) {
|
||||
selectedPromptIndex = 0;
|
||||
}
|
||||
|
||||
const footerHtml = `
|
||||
<div class="${this.configuration.prefix}prompt_manager_footer">
|
||||
<select id="${this.configuration.prefix}prompt_manager_footer_append_prompt" class="text_pole" name="append-prompt">
|
||||
${prompts}
|
||||
${promptsHtml}
|
||||
</select>
|
||||
<a class="menu_button fa-chain fa-solid" title="Insert prompt" data-i18n="[title]Insert prompt"></a>
|
||||
<a class="caution menu_button fa-x fa-solid" title="Delete prompt" data-i18n="[title]Delete prompt"></a>
|
||||
@@ -1351,6 +1405,7 @@ class PromptManager {
|
||||
footerDiv.querySelector('.menu_button:nth-child(2)').addEventListener('click', this.handleAppendPrompt);
|
||||
footerDiv.querySelector('.caution').addEventListener('click', this.handleDeletePrompt);
|
||||
footerDiv.querySelector('.menu_button:last-child').addEventListener('click', this.handleNewPrompt);
|
||||
footerDiv.querySelector('select').selectedIndex = selectedPromptIndex;
|
||||
|
||||
// Add prompt export dialogue and options
|
||||
const exportForCharacter = `
|
||||
@@ -1365,7 +1420,7 @@ class PromptManager {
|
||||
<a class="export-promptmanager-prompts-full list-group-item" data-i18n="Export all">Export all</a>
|
||||
<span class="tooltip fa-solid fa-info-circle" title="Export all your prompts to a file"></span>
|
||||
</div>
|
||||
${'global' === this.configuration.promptOrder.strategy ? '' : exportForCharacter }
|
||||
${'global' === this.configuration.promptOrder.strategy ? '' : exportForCharacter}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1475,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 += `
|
||||
<li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass}" data-pm-identifier="${prompt.identifier}">
|
||||
<li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${prompt.identifier}">
|
||||
<span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}">
|
||||
${prompt.marker ? '<span class="fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
|
||||
${isSystemPrompt ? '<span class="fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
|
||||
${isUserPrompt ? '<span class="fa-solid fa-user" title="User Prompt"></span>' : ''}
|
||||
${isInjectionPrompt ? '<span class="fa-solid fa-syringe" title="In-Chat Injection"></span>' : ''}
|
||||
${prompt.marker ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''}
|
||||
${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''}
|
||||
${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''}
|
||||
${isUserPrompt ? '<span class="fa-fw fa-solid fa-user" title="User Prompt"></span>' : ''}
|
||||
${isInjectionPrompt ? '<span class="fa-fw fa-solid fa-syringe" title="In-Chat Injection"></span>' : ''}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${encodedName}</a>` : encodedName}
|
||||
${isInjectionPrompt ? `<small class="prompt-manager-injection-depth">@ ${prompt.injection_depth}</small>` : ''}
|
||||
${isOverriddenPrompt ? '<small class="fa-solid fa-address-card prompt-manager-overridden" title="Pulled from a character card"></small>' : ''}
|
||||
</span>
|
||||
<span>
|
||||
<span class="prompt_manager_prompt_controls">
|
||||
|
@@ -126,7 +126,7 @@ export function isMobile() {
|
||||
return mobileTypes.includes(parsedUA?.platform?.type);
|
||||
}
|
||||
|
||||
function shouldSendOnEnter() {
|
||||
export function shouldSendOnEnter() {
|
||||
if (!power_user) {
|
||||
return false;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -29,7 +29,7 @@ let galleryMaxRows = 3;
|
||||
* @returns {Promise<Array>} - Resolves with an array of gallery item objects, rejects on error.
|
||||
*/
|
||||
async function getGalleryItems(url) {
|
||||
const response = await fetch(`/listimgfiles/${url}`, {
|
||||
const response = await fetch(`/api/images/list/${url}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
@@ -201,7 +201,7 @@ async function uploadFile(file, url) {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
const response = await fetch('/uploadimage', {
|
||||
const response = await fetch('/api/images/upload', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(payload),
|
||||
|
@@ -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 () {
|
||||
<input type="radio" name="memory_position" value="0" />
|
||||
After Main Prompt / Story String
|
||||
</label>
|
||||
<label for="memory_depth" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
|
||||
<label class="flex-container alignItemsCenter" title="How many messages before the current end of the chat." data-i18n="[title]How many messages before the current end of the chat.">
|
||||
<input type="radio" name="memory_position" value="1" />
|
||||
In-chat @ Depth <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
|
||||
as
|
||||
<select id="memory_role" class="text_pole widthNatural">
|
||||
<option value="0">System</option>
|
||||
<option value="1">User</option>
|
||||
<option value="2">Assistant</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div data-source="main" class="memory_contents_controls">
|
||||
|
@@ -177,7 +177,7 @@ export class QuickReplySet {
|
||||
|
||||
|
||||
async performSave() {
|
||||
const response = await fetch('/savequickreply', {
|
||||
const response = await fetch('/api/quick-replies/save', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(this),
|
||||
@@ -191,7 +191,7 @@ export class QuickReplySet {
|
||||
}
|
||||
|
||||
async delete() {
|
||||
const response = await fetch('/deletequickreply', {
|
||||
const response = await fetch('/api/quick-replies/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(this),
|
||||
|
@@ -118,7 +118,7 @@ function runRegexScript(regexScript, rawString, { characterOverride } = {}) {
|
||||
newString = rawString.replace(findRegex, function(match) {
|
||||
const args = [...arguments];
|
||||
const replaceString = regexScript.replaceString.replace(/{{match}}/gi, '$0');
|
||||
const replaceWithGroups = replaceString.replaceAll(/\$(\d)+/g, (_, num) => {
|
||||
const replaceWithGroups = replaceString.replaceAll(/\$(\d+)/g, (_, num) => {
|
||||
// Get a full match or a capture group
|
||||
const match = args[Number(num)];
|
||||
|
||||
|
@@ -237,6 +237,8 @@ const defaultSettings = {
|
||||
novel_upscale_ratio_step: 0.1,
|
||||
novel_upscale_ratio: 1.0,
|
||||
novel_anlas_guard: false,
|
||||
novel_sm: false,
|
||||
novel_sm_dyn: false,
|
||||
|
||||
// OpenAI settings
|
||||
openai_style: 'vivid',
|
||||
@@ -372,6 +374,9 @@ async function loadSettings() {
|
||||
$('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');
|
||||
$('#sd_novel_upscale_ratio').val(extension_settings.sd.novel_upscale_ratio).trigger('input');
|
||||
$('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);
|
||||
$('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);
|
||||
$('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);
|
||||
$('#sd_novel_sm_dyn').prop('disabled', !extension_settings.sd.novel_sm);
|
||||
$('#sd_horde').prop('checked', extension_settings.sd.horde);
|
||||
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
|
||||
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
|
||||
@@ -799,6 +804,22 @@ function onNovelAnlasGuardInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onNovelSmInput() {
|
||||
extension_settings.sd.novel_sm = !!$('#sd_novel_sm').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (!extension_settings.sd.novel_sm) {
|
||||
$('#sd_novel_sm_dyn').prop('checked', false).prop('disabled', true).trigger('input');
|
||||
} else {
|
||||
$('#sd_novel_sm_dyn').prop('disabled', false);
|
||||
}
|
||||
}
|
||||
|
||||
function onNovelSmDynInput() {
|
||||
extension_settings.sd.novel_sm_dyn = !!$('#sd_novel_sm_dyn').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHordeNsfwInput() {
|
||||
extension_settings.sd.horde_nsfw = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@@ -2165,7 +2186,7 @@ async function generateAutoImage(prompt, negativePrompt) {
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateNovelImage(prompt, negativePrompt) {
|
||||
const { steps, width, height } = getNovelParams();
|
||||
const { steps, width, height, sm, sm_dyn } = getNovelParams();
|
||||
|
||||
const result = await fetch('/api/novelai/generate-image', {
|
||||
method: 'POST',
|
||||
@@ -2180,6 +2201,8 @@ async function generateNovelImage(prompt, negativePrompt) {
|
||||
height: height,
|
||||
negative_prompt: negativePrompt,
|
||||
upscale_ratio: extension_settings.sd.novel_upscale_ratio,
|
||||
sm: sm,
|
||||
sm_dyn: sm_dyn,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2194,16 +2217,23 @@ async function generateNovelImage(prompt, negativePrompt) {
|
||||
|
||||
/**
|
||||
* Adjusts extension parameters for NovelAI. Applies Anlas guard if needed.
|
||||
* @returns {{steps: number, width: number, height: number}} - A tuple of parameters for NovelAI API.
|
||||
* @returns {{steps: number, width: number, height: number, sm: boolean, sm_dyn: boolean}} - A tuple of parameters for NovelAI API.
|
||||
*/
|
||||
function getNovelParams() {
|
||||
let steps = extension_settings.sd.steps;
|
||||
let width = extension_settings.sd.width;
|
||||
let height = extension_settings.sd.height;
|
||||
let sm = extension_settings.sd.novel_sm;
|
||||
let sm_dyn = extension_settings.sd.novel_sm_dyn;
|
||||
|
||||
if (extension_settings.sd.sampler === 'ddim') {
|
||||
sm = false;
|
||||
sm_dyn = false;
|
||||
}
|
||||
|
||||
// Don't apply Anlas guard if it's disabled.
|
||||
if (!extension_settings.sd.novel_anlas_guard) {
|
||||
return { steps, width, height };
|
||||
return { steps, width, height, sm, sm_dyn };
|
||||
}
|
||||
|
||||
const MAX_STEPS = 28;
|
||||
@@ -2244,7 +2274,7 @@ function getNovelParams() {
|
||||
steps = MAX_STEPS;
|
||||
}
|
||||
|
||||
return { steps, width, height };
|
||||
return { steps, width, height, sm, sm_dyn };
|
||||
}
|
||||
|
||||
async function generateOpenAiImage(prompt) {
|
||||
@@ -2725,6 +2755,8 @@ jQuery(async () => {
|
||||
$('#sd_novel_upscale_ratio').on('input', onNovelUpscaleRatioInput);
|
||||
$('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);
|
||||
$('#sd_novel_view_anlas').on('click', onViewAnlasClick);
|
||||
$('#sd_novel_sm').on('input', onNovelSmInput);
|
||||
$('#sd_novel_sm_dyn').on('input', onNovelSmDynInput);;
|
||||
$('#sd_comfy_validate').on('click', validateComfyUrl);
|
||||
$('#sd_comfy_url').on('input', onComfyUrlInput);
|
||||
$('#sd_comfy_workflow').on('change', onComfyWorkflowChange);
|
||||
|
@@ -85,15 +85,9 @@
|
||||
Sanitize prompts (recommended)
|
||||
</span>
|
||||
</label>
|
||||
<label for="sd_horde_karras" class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
<span data-i18n="Karras (not all samplers supported)">
|
||||
Karras (not all samplers supported)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div data-sd-source="novel">
|
||||
<div class="flex-container">
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="sd_novel_anlas_guard" class="checkbox_label flex1" title="Automatically adjust generation parameters to ensure free image generations.">
|
||||
<input id="sd_novel_anlas_guard" type="checkbox" />
|
||||
<span data-i18n="Avoid spending Anlas">
|
||||
@@ -160,6 +154,26 @@
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<label data-sd-source="horde" for="sd_horde_karras" class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
<span data-i18n="Karras (not all samplers supported)">
|
||||
Karras (not all samplers supported)
|
||||
</span>
|
||||
</label>
|
||||
<div data-sd-source="novel" class="flex-container">
|
||||
<label class="flex1 checkbox_label" title="SMEA versions of samplers are modified to perform better at high resolution.">
|
||||
<input id="sd_novel_sm" type="checkbox" />
|
||||
<span data-i18n="SMEA">
|
||||
SMEA
|
||||
</span>
|
||||
</label>
|
||||
<label class="flex1 checkbox_label" title="DYN variants of SMEA samplers often lead to more varied output, but may fail at very high resolutions.">
|
||||
<input id="sd_novel_sm_dyn" type="checkbox" />
|
||||
<span data-i18n="DYN">
|
||||
DYN
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_resolution">Resolution</label>
|
||||
<select id="sd_resolution"><!-- Populated in JS --></select>
|
||||
<div data-sd-source="comfy">
|
||||
|
@@ -101,7 +101,9 @@ function drawChunks(chunks, ids) {
|
||||
}
|
||||
|
||||
const color = pastelRainbow[i % pastelRainbow.length];
|
||||
const chunkHtml = $(`<code style="background-color: ${color};">${chunk}</code>`);
|
||||
const chunkHtml = $('<code></code>');
|
||||
chunkHtml.css('background-color', color);
|
||||
chunkHtml.text(chunk);
|
||||
chunkHtml.attr('title', ids[i]);
|
||||
$('#tokenized_chunks_display').append(chunkHtml);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -185,31 +185,27 @@ function randomReplace(input, emptyListPlaceholder = '') {
|
||||
const randomPatternNew = /{{random\s?::\s?([^}]+)}}/gi;
|
||||
const randomPatternOld = /{{random\s?:\s?([^}]+)}}/gi;
|
||||
|
||||
if (randomPatternNew.test(input)) {
|
||||
return input.replace(randomPatternNew, (match, listString) => {
|
||||
//split on double colons instead of commas to allow for commas inside random items
|
||||
const list = listString.split('::').filter(item => item.length > 0);
|
||||
if (list.length === 0) {
|
||||
return emptyListPlaceholder;
|
||||
}
|
||||
var rng = new Math.seedrandom('added entropy.', { entropy: true });
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
//trim() at the end to allow for empty random values
|
||||
return list[randomIndex].trim();
|
||||
});
|
||||
} else if (randomPatternOld.test(input)) {
|
||||
return input.replace(randomPatternOld, (match, listString) => {
|
||||
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
||||
if (list.length === 0) {
|
||||
return emptyListPlaceholder;
|
||||
}
|
||||
var rng = new Math.seedrandom('added entropy.', { entropy: true });
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
return list[randomIndex];
|
||||
});
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
input = input.replace(randomPatternNew, (match, listString) => {
|
||||
//split on double colons instead of commas to allow for commas inside random items
|
||||
const list = listString.split('::').filter(item => item.length > 0);
|
||||
if (list.length === 0) {
|
||||
return emptyListPlaceholder;
|
||||
}
|
||||
const rng = new Math.seedrandom('added entropy.', { entropy: true });
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
//trim() at the end to allow for empty random values
|
||||
return list[randomIndex].trim();
|
||||
});
|
||||
input = input.replace(randomPatternOld, (match, listString) => {
|
||||
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
||||
if (list.length === 0) {
|
||||
return emptyListPlaceholder;
|
||||
}
|
||||
const rng = new Math.seedrandom('added entropy.', { entropy: true });
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
return list[randomIndex];
|
||||
});
|
||||
return input;
|
||||
}
|
||||
|
||||
function diceRollReplace(input, invalidRollPlaceholder = '') {
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
characters,
|
||||
event_types,
|
||||
eventSource,
|
||||
extension_prompt_roles,
|
||||
extension_prompt_types,
|
||||
Generate,
|
||||
getExtensionPrompt,
|
||||
@@ -115,6 +116,7 @@ const max_16k = 16383;
|
||||
const max_32k = 32767;
|
||||
const max_128k = 128 * 1000;
|
||||
const max_200k = 200 * 1000;
|
||||
const max_1mil = 1000 * 1000;
|
||||
const scale_max = 8191;
|
||||
const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||
const claude_100k_max = 99000;
|
||||
@@ -171,6 +173,18 @@ export const chat_completion_sources = {
|
||||
CUSTOM: 'custom',
|
||||
};
|
||||
|
||||
const character_names_behavior = {
|
||||
NONE: 0,
|
||||
COMPLETION: 1,
|
||||
CONTENT: 2,
|
||||
};
|
||||
|
||||
const continue_postfix_types = {
|
||||
SPACE: ' ',
|
||||
NEWLINE: '\n',
|
||||
DOUBLE_NEWLINE: '\n\n',
|
||||
};
|
||||
|
||||
const prefixMap = selected_group ? {
|
||||
assistant: '',
|
||||
user: '',
|
||||
@@ -197,7 +211,6 @@ const default_settings = {
|
||||
openai_max_context: max_4k,
|
||||
openai_max_tokens: 300,
|
||||
wrap_in_quotes: false,
|
||||
names_in_completion: false,
|
||||
...chatCompletionDefaultPrompts,
|
||||
...promptManagerDefaultPromptOrders,
|
||||
send_if_empty: '',
|
||||
@@ -245,6 +258,8 @@ const default_settings = {
|
||||
image_inlining: false,
|
||||
bypass_status_check: false,
|
||||
continue_prefill: false,
|
||||
names_behavior: character_names_behavior.NONE,
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
seed: -1,
|
||||
n: 1,
|
||||
};
|
||||
@@ -264,7 +279,6 @@ const oai_settings = {
|
||||
openai_max_context: max_4k,
|
||||
openai_max_tokens: 300,
|
||||
wrap_in_quotes: false,
|
||||
names_in_completion: false,
|
||||
...chatCompletionDefaultPrompts,
|
||||
...promptManagerDefaultPromptOrders,
|
||||
send_if_empty: '',
|
||||
@@ -312,6 +326,8 @@ const oai_settings = {
|
||||
image_inlining: false,
|
||||
bypass_status_check: false,
|
||||
continue_prefill: false,
|
||||
names_behavior: character_names_behavior.NONE,
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
seed: -1,
|
||||
n: 1,
|
||||
};
|
||||
@@ -466,11 +482,22 @@ function setOpenAIMessages(chat) {
|
||||
}
|
||||
|
||||
// for groups or sendas command - prepend a character's name
|
||||
if (!oai_settings.names_in_completion) {
|
||||
if (selected_group || (chat[j].force_avatar && chat[j].name !== name1 && chat[j].extra?.type !== system_message_types.NARRATOR)) {
|
||||
content = `${chat[j].name}: ${content}`;
|
||||
}
|
||||
switch (oai_settings.names_behavior) {
|
||||
case character_names_behavior.NONE:
|
||||
if (selected_group || (chat[j].force_avatar && chat[j].name !== name1 && chat[j].extra?.type !== system_message_types.NARRATOR)) {
|
||||
content = `${chat[j].name}: ${content}`;
|
||||
}
|
||||
break;
|
||||
case character_names_behavior.CONTENT:
|
||||
if (chat[j].extra?.type !== system_message_types.NARRATOR) {
|
||||
content = `${chat[j].name}: ${content}`;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// No action for character_names_behavior.COMPLETION
|
||||
break;
|
||||
}
|
||||
|
||||
// remove caret return (waste of tokens)
|
||||
content = content.replace(/\r/gm, '');
|
||||
|
||||
@@ -522,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,
|
||||
@@ -630,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);
|
||||
@@ -637,14 +670,16 @@ function populationInjectionPrompts(prompts, messages) {
|
||||
// Order of priority (most important go lower)
|
||||
const roles = ['system', 'user', 'assistant'];
|
||||
const roleMessages = [];
|
||||
const separator = '\n';
|
||||
const wrap = false;
|
||||
|
||||
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], wrap);
|
||||
|
||||
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 });
|
||||
@@ -692,20 +727,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);
|
||||
@@ -730,7 +758,7 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
|
||||
prompt.identifier = `chatHistory-${messages.length - index}`;
|
||||
const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt));
|
||||
|
||||
if (true === promptManager.serviceSettings.names_in_completion && prompt.name) {
|
||||
if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) {
|
||||
const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
chatMessage.setName(messageName);
|
||||
}
|
||||
@@ -815,6 +843,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.
|
||||
*
|
||||
@@ -836,7 +882,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;
|
||||
}
|
||||
@@ -859,6 +905,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;
|
||||
@@ -994,7 +1041,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),
|
||||
@@ -1003,7 +1050,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),
|
||||
@@ -1046,20 +1093,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;
|
||||
@@ -1612,12 +1659,6 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
delete generate_data.stop;
|
||||
}
|
||||
|
||||
// Remove logit bias and stop strings if it's not supported by the model
|
||||
if (isOAI && oai_settings.openai_model.includes('vision') || isOpenRouter && oai_settings.openrouter_model.includes('vision')) {
|
||||
delete generate_data.logit_bias;
|
||||
delete generate_data.stop;
|
||||
}
|
||||
|
||||
// Proxy is only supported for Claude, OpenAI and Mistral
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) {
|
||||
validateReverseProxy();
|
||||
@@ -1630,6 +1671,13 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
generate_data['logprobs'] = 5;
|
||||
}
|
||||
|
||||
// Remove logit bias, logprobs and stop strings if it's not supported by the model
|
||||
if (isOAI && oai_settings.openai_model.includes('vision') || isOpenRouter && oai_settings.openrouter_model.includes('vision')) {
|
||||
delete generate_data.logit_bias;
|
||||
delete generate_data.stop;
|
||||
delete generate_data.logprobs;
|
||||
}
|
||||
|
||||
if (isClaude) {
|
||||
generate_data['top_k'] = Number(oai_settings.top_k_openai);
|
||||
generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;
|
||||
@@ -2159,7 +2207,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.
|
||||
@@ -2204,6 +2252,7 @@ class ChatCompletion {
|
||||
this.tokenBudget = 0;
|
||||
this.messages = new MessageCollection('root');
|
||||
this.loggingEnabled = false;
|
||||
this.overriddenPrompts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2478,6 +2527,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) {
|
||||
@@ -2554,9 +2615,15 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.continue_nudge_prompt = settings.continue_nudge_prompt ?? default_settings.continue_nudge_prompt;
|
||||
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) {
|
||||
oai_settings.names_behavior = character_names_behavior.COMPLETION;
|
||||
}
|
||||
|
||||
if (settings.wrap_in_quotes !== undefined) oai_settings.wrap_in_quotes = !!settings.wrap_in_quotes;
|
||||
if (settings.names_in_completion !== undefined) oai_settings.names_in_completion = !!settings.names_in_completion;
|
||||
if (settings.openai_model !== undefined) oai_settings.openai_model = settings.openai_model;
|
||||
if (settings.use_ai21_tokenizer !== undefined) { oai_settings.use_ai21_tokenizer = !!settings.use_ai21_tokenizer; oai_settings.use_ai21_tokenizer ? ai21_max = 8191 : ai21_max = 9200; }
|
||||
if (settings.use_google_tokenizer !== undefined) oai_settings.use_google_tokenizer = !!settings.use_google_tokenizer;
|
||||
@@ -2592,7 +2659,6 @@ function loadOpenAISettings(data, settings) {
|
||||
$('#openai_max_tokens').val(oai_settings.openai_max_tokens);
|
||||
|
||||
$('#wrap_in_quotes').prop('checked', oai_settings.wrap_in_quotes);
|
||||
$('#names_in_completion').prop('checked', oai_settings.names_in_completion);
|
||||
$('#jailbreak_system').prop('checked', oai_settings.jailbreak_system);
|
||||
$('#openai_show_external_models').prop('checked', oai_settings.show_external_models);
|
||||
$('#openai_external_category').toggle(oai_settings.show_external_models);
|
||||
@@ -2666,10 +2732,53 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.chat_completion_source = chat_completion_sources.MAKERSUITE;
|
||||
}
|
||||
|
||||
setNamesBehaviorControls();
|
||||
setContinuePostfixControls();
|
||||
|
||||
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
|
||||
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
|
||||
}
|
||||
|
||||
function setNamesBehaviorControls() {
|
||||
switch (oai_settings.names_behavior) {
|
||||
case character_names_behavior.NONE:
|
||||
$('#character_names_none').prop('checked', true);
|
||||
break;
|
||||
case character_names_behavior.COMPLETION:
|
||||
$('#character_names_completion').prop('checked', true);
|
||||
break;
|
||||
case character_names_behavior.CONTENT:
|
||||
$('#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() {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
let status;
|
||||
@@ -2794,7 +2903,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
openai_max_context: settings.openai_max_context,
|
||||
openai_max_tokens: settings.openai_max_tokens,
|
||||
wrap_in_quotes: settings.wrap_in_quotes,
|
||||
names_in_completion: settings.names_in_completion,
|
||||
names_behavior: settings.names_behavior,
|
||||
send_if_empty: settings.send_if_empty,
|
||||
jailbreak_prompt: settings.jailbreak_prompt,
|
||||
jailbreak_system: settings.jailbreak_system,
|
||||
@@ -2826,6 +2935,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,
|
||||
};
|
||||
@@ -3172,7 +3282,7 @@ function onSettingsPresetChange() {
|
||||
openai_max_context: ['#openai_max_context', 'openai_max_context', false],
|
||||
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false],
|
||||
wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true],
|
||||
names_in_completion: ['#names_in_completion', 'names_in_completion', true],
|
||||
names_behavior: ['#names_behavior', 'names_behavior', false],
|
||||
send_if_empty: ['#send_if_empty_textarea', 'send_if_empty', false],
|
||||
impersonation_prompt: ['#impersonation_prompt_textarea', 'impersonation_prompt', false],
|
||||
new_chat_prompt: ['#newchat_prompt_textarea', 'new_chat_prompt', false],
|
||||
@@ -3200,6 +3310,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],
|
||||
};
|
||||
@@ -3209,6 +3320,11 @@ function onSettingsPresetChange() {
|
||||
|
||||
const preset = structuredClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]);
|
||||
|
||||
// Migrate old settings
|
||||
if (preset.names_in_completion === true && preset.names_behavior === undefined) {
|
||||
preset.names_behavior = character_names_behavior.COMPLETION;
|
||||
}
|
||||
|
||||
const updateInput = (selector, value) => $(selector).val(value).trigger('input');
|
||||
const updateCheckbox = (selector, value) => $(selector).prop('checked', value).trigger('input');
|
||||
|
||||
@@ -3391,6 +3507,8 @@ 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') {
|
||||
$('#openai_max_context').attr('max', max_1mil);
|
||||
} else if (value === 'gemini-pro') {
|
||||
$('#openai_max_context').attr('max', max_32k);
|
||||
} else if (value === 'gemini-pro-vision') {
|
||||
@@ -4077,11 +4195,6 @@ $(document).ready(async function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#names_in_completion').on('change', function () {
|
||||
oai_settings.names_in_completion = !!$('#names_in_completion').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#send_if_empty_textarea').on('input', function () {
|
||||
oai_settings.send_if_empty = String($('#send_if_empty_textarea').val());
|
||||
saveSettingsDebounced();
|
||||
@@ -4299,6 +4412,54 @@ $(document).ready(async function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#names_behavior').on('input', function () {
|
||||
oai_settings.names_behavior = Number($(this).val());
|
||||
setNamesBehaviorControls();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#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();
|
||||
});
|
||||
|
||||
$(document).on('input', '#openai_settings .autoSetHeight', function () {
|
||||
resetScrollHeight($(this));
|
||||
});
|
||||
|
@@ -46,7 +46,7 @@ async function uploadUserAvatar(url, name) {
|
||||
|
||||
return jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: '/uploaduseravatar',
|
||||
url: '/api/avatars/upload',
|
||||
data: formData,
|
||||
beforeSend: () => { },
|
||||
cache: false,
|
||||
@@ -355,7 +355,7 @@ async function deleteUserAvatar(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = await fetch('/deleteuseravatar', {
|
||||
const request = await fetch('/api/avatars/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
|
@@ -1995,6 +1995,45 @@ async function updateTheme() {
|
||||
toastr.success('Theme saved.');
|
||||
}
|
||||
|
||||
async function deleteTheme() {
|
||||
const themeName = power_user.theme;
|
||||
|
||||
if (!themeName) {
|
||||
toastr.info('No theme selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup(`Are you sure you want to delete the theme "${themeName}"?`, 'confirm', '', { okButton: 'Yes' });
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/themes/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name: themeName }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.error('Failed to delete theme. Check the console for more information.');
|
||||
return;
|
||||
}
|
||||
|
||||
const themeIndex = themes.findIndex(x => x.name == themeName);
|
||||
|
||||
if (themeIndex !== -1) {
|
||||
themes.splice(themeIndex, 1);
|
||||
$(`#themes option[value="${themeName}"]`).remove();
|
||||
power_user.theme = themes[0]?.name;
|
||||
saveSettingsDebounced();
|
||||
if (power_user.theme) {
|
||||
await applyTheme(power_user.theme);
|
||||
}
|
||||
toastr.success('Theme deleted.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the current theme to a file.
|
||||
*/
|
||||
@@ -2094,7 +2133,7 @@ async function saveTheme(name = undefined) {
|
||||
compact_input_area: power_user.compact_input_area,
|
||||
};
|
||||
|
||||
const response = await fetch('/savetheme', {
|
||||
const response = await fetch('/api/themes/save', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(theme),
|
||||
@@ -2136,7 +2175,7 @@ async function saveMovingUI() {
|
||||
};
|
||||
console.log(movingUIPreset);
|
||||
|
||||
const response = await fetch('/savemovingui', {
|
||||
const response = await fetch('/api/moving-ui/save', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(movingUIPreset),
|
||||
@@ -2992,6 +3031,7 @@ $(document).ready(() => {
|
||||
|
||||
$('#ui-preset-save-button').on('click', () => saveTheme());
|
||||
$('#ui-preset-update-button').on('click', () => updateTheme());
|
||||
$('#ui-preset-delete-button').on('click', () => deleteTheme());
|
||||
$('#movingui-preset-save-button').on('click', saveMovingUI);
|
||||
|
||||
$('#never_resize_avatars').on('input', function () {
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
default_avatar,
|
||||
eventSource,
|
||||
event_types,
|
||||
extension_prompt_roles,
|
||||
extension_prompt_types,
|
||||
extractMessageBias,
|
||||
generateQuietPrompt,
|
||||
@@ -50,6 +51,11 @@ export {
|
||||
};
|
||||
|
||||
class SlashCommandParser {
|
||||
static COMMENT_KEYWORDS = ['#', '/'];
|
||||
static RESERVED_KEYWORDS = [
|
||||
...this.COMMENT_KEYWORDS,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.commands = {};
|
||||
this.helpStrings = {};
|
||||
@@ -58,6 +64,11 @@ class SlashCommandParser {
|
||||
addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
||||
const fnObj = { callback, helpString, interruptsGeneration, purgeFromMessage };
|
||||
|
||||
if ([command, ...aliases].some(x => SlashCommandParser.RESERVED_KEYWORDS.includes(x))) {
|
||||
console.error('ERROR: Reserved slash command keyword used!');
|
||||
return;
|
||||
}
|
||||
|
||||
if ([command, ...aliases].some(x => Object.hasOwn(this.commands, x))) {
|
||||
console.trace('WARN: Duplicate slash command registered!');
|
||||
}
|
||||
@@ -231,7 +242,7 @@ parser.addCommand('buttons', buttonsCallback, [], '<span class="monospace">label
|
||||
parser.addCommand('trimtokens', trimTokensCallback, [], '<span class="monospace">limit=number (direction=start/end [text])</span> – trims the start or end of text to the specified number of tokens.', true, true);
|
||||
parser.addCommand('trimstart', trimStartCallback, [], '<span class="monospace">(text)</span> – trims the text to the start of the first full sentence.', true, true);
|
||||
parser.addCommand('trimend', trimEndCallback, [], '<span class="monospace">(text)</span> – trims the text to the end of the last full sentence.', true, true);
|
||||
parser.addCommand('inject', injectCallback, [], '<span class="monospace">id=injectId (position=before/after/chat depth=number [text])</span> – 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, [], '<span class="monospace">id=injectId (position=before/after/chat depth=number scan=true/false role=system/user/assistant [text])</span> – 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), [], '<span class="monospace">(text)</span> – counts the number of tokens in the text.', true, true);
|
||||
@@ -249,6 +260,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 +280,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 +295,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 +314,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}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth})`;
|
||||
return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
@@ -311,7 +332,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 +359,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1724,6 +1745,11 @@ async function executeSlashCommands(text, unescape = false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip comment commands. They don't run macros or interrupt pipes.
|
||||
if (SlashCommandParser.COMMENT_KEYWORDS.includes(result.command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.value && typeof result.value === 'string') {
|
||||
result.value = substituteParams(result.value.trim());
|
||||
}
|
||||
|
@@ -996,7 +996,7 @@ export async function saveBase64AsFile(base64Data, characterName, filename = '',
|
||||
};
|
||||
|
||||
// Send the data URL to your backend using fetch
|
||||
const response = await fetch('/uploadimage', {
|
||||
const response = await fetch('/api/images/upload', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(requestBody),
|
||||
headers: {
|
||||
@@ -1047,15 +1047,51 @@ export function loadFileToDocument(url, type) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that we can import war crime image formats like WEBP and AVIF.
|
||||
* @param {File} file Input file
|
||||
* @returns {Promise<File>} A promise that resolves to the supported file.
|
||||
*/
|
||||
export async function ensureImageFormatSupported(file) {
|
||||
const supportedTypes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/bmp',
|
||||
'image/tiff',
|
||||
'image/gif',
|
||||
'image/apng',
|
||||
];
|
||||
|
||||
if (supportedTypes.includes(file.type) || !file.type.startsWith('image/')) {
|
||||
return file;
|
||||
}
|
||||
|
||||
return await convertImageFile(file, 'image/png');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an image file to a given format.
|
||||
* @param {File} inputFile File to convert
|
||||
* @param {string} type Target file type
|
||||
* @returns {Promise<File>} A promise that resolves to the converted file.
|
||||
*/
|
||||
export async function convertImageFile(inputFile, type = 'image/png') {
|
||||
const base64 = await getBase64Async(inputFile);
|
||||
const thumbnail = await createThumbnail(base64, null, null, type);
|
||||
const blob = await fetch(thumbnail).then(res => res.blob());
|
||||
const outputFile = new File([blob], inputFile.name, { type });
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thumbnail from a data URL.
|
||||
* @param {string} dataUrl The data URL encoded data of the image.
|
||||
* @param {number} maxWidth The maximum width of the thumbnail.
|
||||
* @param {number} maxHeight The maximum height of the thumbnail.
|
||||
* @param {number|null} maxWidth The maximum width of the thumbnail.
|
||||
* @param {number|null} maxHeight The maximum height of the thumbnail.
|
||||
* @param {string} [type='image/jpeg'] The type of the thumbnail.
|
||||
* @returns {Promise<string>} A promise that resolves to the thumbnail data URL.
|
||||
*/
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight, type = 'image/jpeg') {
|
||||
export function createThumbnail(dataUrl, maxWidth = null, maxHeight = null, type = 'image/jpeg') {
|
||||
// Someone might pass in a base64 encoded string without the data URL prefix
|
||||
if (!dataUrl.includes('data:')) {
|
||||
dataUrl = `data:image/jpeg;base64,${dataUrl}`;
|
||||
@@ -1073,6 +1109,16 @@ export function createThumbnail(dataUrl, maxWidth, maxHeight, type = 'image/jpeg
|
||||
let thumbnailWidth = maxWidth;
|
||||
let thumbnailHeight = maxHeight;
|
||||
|
||||
if (maxWidth === null) {
|
||||
thumbnailWidth = img.width;
|
||||
maxWidth = img.width;
|
||||
}
|
||||
|
||||
if (maxHeight === null) {
|
||||
thumbnailHeight = img.height;
|
||||
maxHeight = img.height;
|
||||
}
|
||||
|
||||
if (img.width > img.height) {
|
||||
thumbnailHeight = maxWidth / aspectRatio;
|
||||
} else {
|
||||
|
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user