Merge branch 'staging' into wi-regex-keys

This commit is contained in:
Wolfsblvt 2024-05-09 04:23:14 +02:00
commit f4bb4fe51e
16 changed files with 352 additions and 157 deletions

View File

@ -3955,7 +3955,7 @@
</div> </div>
</label> </label>
<div id="movingui-preset-save-button" title="Save changes to a new MovingUI preset file." data-i18n="[title]Save movingUI changes to a new file" class="menu_button margin0 fa-solid fa-save"></div> <div id="movingui-preset-save-button" title="Save changes to a new MovingUI preset file." data-i18n="[title]Save movingUI changes to a new file" class="menu_button margin0 fa-solid fa-save"></div>
<div data-newbie-hidden id="movingUIreset" title="Reset MovingUI panel sizes/locations." class="menu_button fa-solid fa-recycle margin0" data-i18n="Reset Panels"></div> <div data-newbie-hidden id="movingUIreset" title="Reset MovingUI panel sizes/locations." class="menu_button fa-solid fa-recycle margin0" data-i18n="[title]Reset MovingUI panel sizes/locations."></div>
</div> </div>
</div> </div>
<div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn"> <div data-newbie-hidden id="CustomCSS-block" class="flex-container flexFlowColumn">
@ -5057,13 +5057,19 @@
<div class="WIEnteryHeaderControls flex-container"> <div class="WIEnteryHeaderControls flex-container">
<div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text"> <div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text">
<label for="position" class="WIEntryHeaderTitleMobile" data-i18n="Position:">Position:</label> <label for="position" class="WIEntryHeaderTitleMobile" data-i18n="Position:">Position:</label>
<select name="position" class="text_pole widthNatural margin0" data-i18n="[title]T_Position" title="↑Char: Before Character Definitions&#13;↓Char: After Character Definitions&#13;↑AN: Before Author's Note&#13;↓AN: After Author's Note&#13;@D ⚙️: at Depth (System)&#13;@D 👤: at Depth (User)&#13;@D 🤖: at Depth (Assistant)"> <select name="position" class="text_pole widthNatural margin0" data-i18n="[title]T_Position" title="↑Char: Before Character Definitions&#13;↓Char: After Character Definitions&#13;↑EM: Before Example Messages&#13;↓EM: After Example Messages&#13;↑AN: Before Author's Note&#13;↓AN: After Author's Note&#13;@D ⚙️: at Depth (System)&#13;@D 👤: at Depth (User)&#13;@D 🤖: at Depth (Assistant)">
<option value="0" data-role="" data-i18n="Before Char Defs"> <option value="0" data-role="" data-i18n="Before Char Defs">
↑Char ↑Char
</option> </option>
<option value="1" data-role="" data-i18n="After Char Defs"> <option value="1" data-role="" data-i18n="After Char Defs">
↓Char ↓Char
</option> </option>
<option value="5" data-role="" data-i18n="Before EM">
↑EM
</option>
<option value="6" data-role="" data-i18n="After EM">
↓EM
</option>
<option value="2" data-role="" data-i18n="Before AN"> <option value="2" data-role="" data-i18n="Before AN">
↑AN ↑AN
</option> </option>
@ -5256,7 +5262,7 @@
</small> </small>
</div> </div>
<div class="range-block-range"> <div class="range-block-range">
<input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="0" max="999999"> <input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
</div> </div>
</div> </div>
</div> </div>

View File

@ -891,7 +891,7 @@
"Chat API": "API чата", "Chat API": "API чата",
"and pick a character": "и выберите персонажа", "and pick a character": "и выберите персонажа",
"in the chat bar": " в поле чата", "in the chat bar": " в поле чата",
"Confused or lost?": "Запутались или потерялись?", "Confused or lost?": "Не можете в чём-то разобраться?",
"click these icons!": "нажмите на эти значки!", "click these icons!": "нажмите на эти значки!",
"SillyTavern Documentation Site": "Сайт документации SillyTavern", "SillyTavern Documentation Site": "Сайт документации SillyTavern",
"Extras Installation Guide": "Руководство по установке Extras", "Extras Installation Guide": "Руководство по установке Extras",
@ -1095,5 +1095,86 @@
"Space": "Пробел", "Space": "Пробел",
"Newline": "Новая строка", "Newline": "Новая строка",
"Double Newline": "Две новые строки", "Double Newline": "Две новые строки",
"The next chunk of the continued message will be appended using this as a separator.": "Используется в качестве разделителя между уже имеющимся сообщением и его новым отрывком, при генерации продолжения" "The next chunk of the continued message will be appended using this as a separator.": "Используется в качестве разделителя между уже имеющимся сообщением и его новым отрывком, при генерации продолжения",
"Regex Editor": "Редактор рег. выражений",
"ext_regex_open_editor": "Открыть редактор",
"ext_regex_import_script": "Импорт скрипта",
"ext_regex_saved_scripts": "Сохранённые скрипты",
"ext_regex_desc": "Regex - это инструмент, позволяющий находить и изменять строки, используя регулярные выражения. Для более подробной информации нажмите ? рядом с заголовком.",
"Input": "Поле ввода",
"ext_regex_test_input_placeholder": "Введите текст...",
"Output": "Результат",
"ext_regex_output_placeholder": "Пусто",
"Script Name": "Название скрипта",
"Find Regex": "Рег. выражение для поиска",
"Replace With": "Замена",
"ext_regex_replace_string_placeholder": "Чтобы вставить всё вхождение рег. выражения, используйте {{match}}. Чтобы вставить группу символов, используйте $1, $2 и т.д.",
"Trim Out": "Усечение",
"ext_regex_trim_placeholder": "Удалить перед обработкой ненужные части текста. Каждый элемент с новой строки.",
"Slash Commands": "Слэш-команды",
"Min Depth": "Мин. глубина",
"ext_regex_min_depth_desc": "При форматировании затрагивать только те сообщения, которые находятся как минимум на глубине N. 0 = последнее сообщение, 1 = предпоследнее и т.д. Учитываются только видимые сообщения, т.е. не скрытые и не системные.",
"ext_regex_max_depth_desc": "При форматировании затрагивать только те сообщения, которые находятся на глубине не более N. 0 = последнее сообщение, 1 = предпоследнее и т.д. Учитываются только видимые сообщения, т.е. не скрытые и не системные.",
"ext_regex_min_depth_placeholder": "Неогранич.",
"ext_regex_other_options": "Другие опции",
"Only Format Display": "Только визуально",
"ext_regex_only_format_prompt_desc": "История чата не изменится, замена будет осуществляться только в промпте (при генерации)",
"Only Format Prompt (?)": "Только промпт",
"Run On Edit": "Выполнять при редактировании",
"Substitute Regex": "Заменить в рег. выражении",
"ext_regex_substitute_regex_desc": "Перед выполнением заменять {{макросы}} в рег. выражении",
"Test Mode": "Протестировать",
"ext_regex_affects": "Затрагивает",
"ext_regex_user_input": "Ваши сообщения",
"ext_regex_ai_output": "Ответы ИИ",
"ext_regex_disable_script": "Отключить скрипт",
"ext_regex_enable_script": "Включить скрипт",
"ext_regex_edit_script": "Редактировать",
"ext_regex_export_script": "Экспортировать",
"ext_regex_delete_script": "Удалить",
"ext_sum_with": "Для пересказа использовать:",
"ext_sum_main_api": "Основное API",
"ext_sum_current_summary": "Текущий пересказ:",
"ext_sum_restore_previous": "Восстановить предыдущий",
"ext_sum_memory_placeholder": "Сгенерированный пересказ будет здесь...",
"ext_sum_force_text": "Пересказать сейчас",
"ext_sum_force_tip": "Сгенерировать пересказ прямо сейчас.",
"Disable automatic summary updates. While paused, the summary remains as-is. You can still force an update by pressing the Summarize now button (which is only available with the Main API).": "Отключить авто-обновление пересказа. Пересказ всё время будет фиксированным. Однако останется возможность принудительно обновить пересказ через кнопку \"Пересказать сейчас\" (доступно только через Основное API)",
"ext_sum_pause": "Приостановить",
"Omit World Info and Author's Note from text to be summarized. Only has an effect when using the Main API. The Extras API always omits WI/AN.": "Исключать из пересказа Информацию о мире и Заметки автора. Работает только для Основного API. Extras API всегда их исключает.",
"ext_sum_no_wi_an": "Без мира и заметок",
"ext_sum_settings_tip": "Изменить промпт пересказа, место для инжекта и т.д.",
"ext_sum_settings": "Настройки пересказа",
"ext_sum_prompt_builder": "Алгоритм формирования промпта",
"ext_sum_prompt_builder_1_desc": "Расширение само составит промпт с учётом непересказанных сообщений. Во время генерации чат недоступен.",
"ext_sum_prompt_builder_1": "Прямой, блокирующий",
"ext_sum_prompt_builder_2_desc": "Расширение само составит промпт с учётом непересказанных сообщений. Во время генерации чат доступен. Может не поддерживаться некоторыми бэкендами.",
"ext_sum_prompt_builder_2": "Прямой, неблокирующий",
"ext_sum_prompt_builder_3_desc": "Расширение будет использовать стандартные основные настройки промпта, и добавит свой промпт в качестве последнего системного сообщения.",
"ext_sum_prompt_builder_3": "Классический, блокирующий",
"Summary Prompt": "Промпт для пересказа",
"ext_sum_restore_default_prompt_tip": "Восстановить стандартный промпт",
"ext_sum_prompt_placeholder": "Этот промпт будет отправлен ИИ при запросе на генерацию пересказа. Макрос {{words}} будет заменён на значение параметра \"Количество слов\".",
"ext_sum_target_length_1": "Целевая длина пересказа (слов):",
"ext_sum_target_length_2": "",
"ext_sum_target_length_3": "",
"ext_sum_api_response_length_1": "Длина ответа от API (токенов):",
"ext_sum_api_response_length_2": "",
"ext_sum_api_response_length_3": " ",
"ext_sum_0_default": "по умолчанию = 0",
"ext_sum_raw_max_msg": "[Прямое форматирование] Макс. сообщений в запросе",
"ext_sum_0_unlimited": "неограничено = 0",
"Update frequency": "Частота обновления",
"ext_sum_update_every_messages_1": "Интервал обновления (кол-во сообщений):",
"ext_sum_update_every_messages_2": "",
"ext_sum_pause": "Приостановить",
"ext_sum_update_every_words_1": "Интервал обновления (кол-во слов):",
"ext_sum_update_every_words_2": "",
"ext_sum_0_disable": "для отключения поставьте 0",
"ext_sum_auto_adjust_desc": "Попытаться автоматически рассчитать значение интервала, исходя из статистики чата",
"ext_sum_both_sliders": "Если оба ползунка отличны от нуля, то оба будут триггерить генерацию пересказа с соответствующей периодичностью.",
"ext_sum_injection_template": "Шаблон для инжекта",
"ext_sum_memory_template_placeholder": "Макрос {{summary}} будет заменён на содержимое пересказа",
"ext_sum_injection_position": "Куда инжектить",
"How many messages before the current end of the chat.": "Сколько сообщений от конца чата."
} }

View File

@ -34,6 +34,7 @@ import {
checkEmbeddedWorld, checkEmbeddedWorld,
setWorldInfoButtonClass, setWorldInfoButtonClass,
importWorldInfo, importWorldInfo,
wi_anchor_position,
} from './scripts/world-info.js'; } from './scripts/world-info.js';
import { import {
@ -423,8 +424,6 @@ const characterContextMenu = new CharacterContextMenu(characterGroupOverlay);
eventSource.on(event_types.CHARACTER_PAGE_LOADED, characterGroupOverlay.onPageLoad); eventSource.on(event_types.CHARACTER_PAGE_LOADED, characterGroupOverlay.onPageLoad);
console.debug('Character context menu initialized', characterContextMenu); console.debug('Character context menu initialized', characterContextMenu);
hljs.addPlugin({ 'before:highlightElement': ({ el }) => { el.textContent = el.innerText; } });
// Markdown converter // Markdown converter
export let mesForShowdownParse; //intended to be used as a context to compare showdown strings against export let mesForShowdownParse; //intended to be used as a context to compare showdown strings against
let converter; let converter;
@ -3223,32 +3222,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole); setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);
} }
// Parse example messages
if (!mesExamples.startsWith('<START>')) {
mesExamples = '<START>\n' + mesExamples.trim();
}
if (mesExamples.replace(/<START>/gi, '').trim().length === 0) {
mesExamples = '';
}
const mesExamplesRaw = mesExamples;
/**
* Adds a block heading to the examples string.
* @param {string} examplesStr
* @returns {string[]} Examples array with block heading
*/
function addBlockHeading(examplesStr) {
const exampleSeparator = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : '';
const blockHeading = main_api === 'openai' ? '<START>\n' : (exampleSeparator || (isInstruct ? '<START>\n' : ''));
return examplesStr.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
}
let mesExamplesArray = addBlockHeading(mesExamples);
let mesExamplesRawArray = addBlockHeading(mesExamplesRaw);
if (mesExamplesArray && isInstruct) {
mesExamplesArray = formatInstructModeExamples(mesExamplesArray, name1, name2);
}
// First message in fresh 1-on-1 chat reacts to user/character settings changes // First message in fresh 1-on-1 chat reacts to user/character settings changes
if (chat.length) { if (chat.length) {
chat[0].mes = substituteParams(chat[0].mes); chat[0].mes = substituteParams(chat[0].mes);
@ -3317,6 +3290,30 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
force_name2 = false; force_name2 = false;
} }
// TODO (kingbri): Migrate to a utility function
/**
* Parses an examples string.
* @param {string} examplesStr
* @returns {string[]} Examples array with block heading
*/
function parseMesExamples(examplesStr) {
if (examplesStr.length === 0) {
return [];
}
if (!examplesStr.startsWith('<START>')) {
examplesStr = '<START>\n' + examplesStr.trim();
}
const exampleSeparator = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : '';
const blockHeading = main_api === 'openai' ? '<START>\n' : (exampleSeparator || (isInstruct ? '<START>\n' : ''));
const splitExamples = examplesStr.split(/<START>/gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
return splitExamples;
}
let mesExamplesArray = parseMesExamples(mesExamples);
////////////////////////////////// //////////////////////////////////
// Extension added strings // Extension added strings
// Set non-WI AN // Set non-WI AN
@ -3326,9 +3323,35 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
// Make quiet prompt available for WIAN // Make quiet prompt available for WIAN
setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true); setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true);
const chatForWI = coreChat.map(x => `${x.name}: ${x.mes}`).reverse(); const chatForWI = coreChat.map(x => `${x.name}: ${x.mes}`).reverse();
let { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun); const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun);
setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true); setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true);
// Add message example WI
for (const example of worldInfoExamples) {
const exampleMessage = example.content;
if (exampleMessage.length === 0) {
continue;
}
const formattedExample = baseChatReplace(exampleMessage, name1, name2);
const cleanedExample = parseMesExamples(formattedExample);
// Insert depending on before or after position
if (example.position === wi_anchor_position.before) {
mesExamplesArray.unshift(...cleanedExample);
} else {
mesExamplesArray.push(...cleanedExample);
}
}
// At this point, the raw message examples can be created
const mesExamplesRawArray = [...mesExamplesArray];
if (mesExamplesArray && isInstruct) {
mesExamplesArray = formatInstructModeExamples(mesExamplesArray, name1, name2);
}
if (skipWIAN !== true) { if (skipWIAN !== true) {
console.log('skipWIAN not active, adding WIAN'); console.log('skipWIAN not active, adding WIAN');
// Add all depth WI entries to prompt // Add all depth WI entries to prompt

View File

@ -972,8 +972,8 @@ function sampleClassifyText(text) {
return text; return text;
} }
// Remove asterisks and quotes // Replace macros, remove asterisks and quotes
let result = text.replace(/[*"]/g, ''); let result = substituteParams(text).replace(/[*"]/g, '');
const SAMPLE_THRESHOLD = 500; const SAMPLE_THRESHOLD = 500;
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2; const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;

View File

@ -9,73 +9,73 @@
</div> </div>
<div class="inline-drawer-content"> <div class="inline-drawer-content">
<div id="summaryExtensionDrawerContents"> <div id="summaryExtensionDrawerContents">
<label for="summary_source">Summarize with:</label> <label for="summary_source" data-i18n="ext_sum_with">Summarize with:</label>
<select id="summary_source"> <select id="summary_source">
<option value="main">Main API</option> <option value="main" data-i18n="ext_sum_main_api">Main API</option>
<option value="extras">Extras API</option> <option value="extras">Extras API</option>
</select><br> </select><br>
<div class="flex-container justifyspacebetween alignitemscenter"> <div class="flex-container justifyspacebetween alignitemscenter">
<span class="flex1">Current summary:</span> <span class="flex1" data-i18n="ext_sum_current_summary">Current summary:</span>
<div id="memory_restore" class="menu_button flex1 margin0"> <div id="memory_restore" class="menu_button flex1 margin0">
<span>Restore Previous</span> <span data-i18n="ext_sum_restore_previous">Restore Previous</span>
</div> </div>
</div> </div>
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" placeholder="Summary will be generated here..."></textarea> <textarea id="memory_contents" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_memory_placeholder" placeholder="Summary will be generated here..."></textarea>
<div class="memory_contents_controls"> <div class="memory_contents_controls">
<div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" title="Trigger a summary update right now." data-i18n="Trigger a summary update right now."> <div id="memory_force_summarize" data-summary-source="main" class="menu_button menu_button_icon" data-i18n="[title]ext_sum_force_tip" title="Trigger a summary update right now." data-i18n="Trigger a summary update right now.">
<i class="fa-solid fa-database"></i> <i class="fa-solid fa-database"></i>
<span>Summarize now</span> <span data-i18n="ext_sum_force_text">Summarize now</span>
</div> </div>
<label for="memory_frozen" title="Disable automatic summary updates. While paused, the summary remains as-is. You can still force an update by pressing the Summarize now button (which is only available with the Main API)." data-i18n="[title]Disable automatic summary updates. While paused, the summary remains as-is. You can still force an update by pressing the Summarize now button (which is only available with the Main API)."><input id="memory_frozen" type="checkbox" />Pause</label> <label for="memory_frozen" title="Disable automatic summary updates. While paused, the summary remains as-is. You can still force an update by pressing the Summarize now button (which is only available with the Main API)." data-i18n="[title]Disable automatic summary updates. While paused, the summary remains as-is. You can still force an update by pressing the Summarize now button (which is only available with the Main API)."><input id="memory_frozen" type="checkbox" /><span data-i18n="ext_sum_pause">Pause</span></label>
<label data-summary-source="main" for="memory_skipWIAN" title="Omit World Info and Author's Note from text to be summarized. Only has an effect when using the Main API. The Extras API always omits WI/AN." data-i18n="[title]Omit World Info and Author's Note from text to be summarized. Only has an effect when using the Main API. The Extras API always omits WI/AN."> <label data-summary-source="main" for="memory_skipWIAN" title="Omit World Info and Author's Note from text to be summarized. Only has an effect when using the Main API. The Extras API always omits WI/AN." data-i18n="[title]Omit World Info and Author's Note from text to be summarized. Only has an effect when using the Main API. The Extras API always omits WI/AN.">
<input id="memory_skipWIAN" type="checkbox" /> <input id="memory_skipWIAN" type="checkbox" />
<span>No WI/AN</span> <span data-i18n="ext_sum_no_wi_an">No WI/AN</span>
</label> </label>
</div> </div>
<div class="memory_contents_controls"> <div class="memory_contents_controls">
<div id="summarySettingsBlockToggle" class="menu_button menu_button_icon" title="Edit summarization prompt, insertion position, etc."> <div id="summarySettingsBlockToggle" class="menu_button menu_button_icon" data-i18n="[title]ext_sum_settings_tip" title="Edit summarization prompt, insertion position, etc.">
<i class="fa-solid fa-cog"></i> <i class="fa-solid fa-cog"></i>
<span>Summary Settings</span> <span data-i18n="ext_sum_settings">Summary Settings</span>
</div> </div>
</div> </div>
<div id="summarySettingsBlock" style="display:none;"> <div id="summarySettingsBlock" style="display:none;">
<div data-summary-source="main"> <div data-summary-source="main">
<label> <label data-i18n="ext_sum_prompt_builder">
Prompt builder Prompt builder
</label> </label>
<label class="checkbox_label" for="memory_prompt_builder_raw_blocking" title="Extension will build its own prompt using messages that were not summarized yet. Blocks the chat until the summary is generated."> <label class="checkbox_label" for="memory_prompt_builder_raw_blocking" data-i18n="[title]ext_sum_prompt_builder_1_desc" title="Extension will build its own prompt using messages that were not summarized yet. Blocks the chat until the summary is generated.">
<input id="memory_prompt_builder_raw_blocking" type="radio" name="memory_prompt_builder" value="1" /> <input id="memory_prompt_builder_raw_blocking" type="radio" name="memory_prompt_builder" value="1" />
<span>Raw, blocking</span> <span data-i18n="ext_sum_prompt_builder_1">Raw, blocking</span>
</label> </label>
<label class="checkbox_label" for="memory_prompt_builder_raw_non_blocking" title="Extension will build its own prompt using messages that were not summarized yet. Does not block the chat while the summary is being generated. Not all backends support this mode."> <label class="checkbox_label" for="memory_prompt_builder_raw_non_blocking" data-i18n="[title]ext_sum_prompt_builder_2_desc" title="Extension will build its own prompt using messages that were not summarized yet. Does not block the chat while the summary is being generated. Not all backends support this mode.">
<input id="memory_prompt_builder_raw_non_blocking" type="radio" name="memory_prompt_builder" value="2" /> <input id="memory_prompt_builder_raw_non_blocking" type="radio" name="memory_prompt_builder" value="2" />
<span>Raw, non-blocking</span> <span data-i18n="ext_sum_prompt_builder_2">Raw, non-blocking</span>
</label> </label>
<label class="checkbox_label" id="memory_prompt_builder_default" title="Extension will use the regular main prompt builder and add the summary request to it as the last system message."> <label class="checkbox_label" id="memory_prompt_builder_default" data-i18n="[title]ext_sum_prompt_builder_3_desc" title="Extension will use the regular main prompt builder and add the summary request to it as the last system message.">
<input id="memory_prompt_builder_default" type="radio" name="memory_prompt_builder" value="0" /> <input id="memory_prompt_builder_default" type="radio" name="memory_prompt_builder" value="0" />
<span>Classic, blocking</span> <span data-i18n="ext_sum_prompt_builder_3">Classic, blocking</span>
</label> </label>
</div> </div>
<div data-summary-source="main"> <div data-summary-source="main">
<label for="memory_prompt" class="title_restorable"> <label for="memory_prompt" class="title_restorable">
<span data-i18n="Summary Prompt">Summary Prompt</span> <span data-i18n="Summary Prompt">Summary Prompt</span>
<div id="memory_prompt_restore" title="Restore default prompt" class="right_menu_button"> <div id="memory_prompt_restore" data-i18n="[title]ext_sum_restore_default_prompt_tip" title="Restore default prompt" class="right_menu_button">
<div class="fa-solid fa-clock-rotate-left"></div> <div class="fa-solid fa-clock-rotate-left"></div>
</div> </div>
</label> </label>
<textarea id="memory_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be sent to AI to request the summary generation. &lcub;&lcub;words&rcub;&rcub; will resolve to the 'Number of words' parameter."></textarea> <textarea id="memory_prompt" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_prompt_placeholder" placeholder="This prompt will be sent to AI to request the summary generation. &lcub;&lcub;words&rcub;&rcub; will resolve to the 'Number of words' parameter."></textarea>
<label for="memory_prompt_words">Target summary length (<span id="memory_prompt_words_value"></span> words)</label> <label for="memory_prompt_words"><span data-i18n="ext_sum_target_length_1">Target summary length</span> <span data-i18n="ext_sum_target_length_2">(</span><span id="memory_prompt_words_value"></span><span data-i18n="ext_sum_target_length_3"> words)</span></label>
<input id="memory_prompt_words" type="range" value="{{defaultSettings.promptWords}}" min="{{defaultSettings.promptMinWords}}" max="{{defaultSettings.promptMaxWords}}" step="{{defaultSettings.promptWordsStep}}" /> <input id="memory_prompt_words" type="range" value="{{defaultSettings.promptWords}}" min="{{defaultSettings.promptMinWords}}" max="{{defaultSettings.promptMaxWords}}" step="{{defaultSettings.promptWordsStep}}" />
<label for="memory_override_response_length"> <label for="memory_override_response_length">
API response length (<span id="memory_override_response_length_value"></span> tokens) <span data-i18n="ext_sum_api_response_length_1">API response length</span> <span data-i18n="ext_sum_api_response_length_2">(</span><span id="memory_override_response_length_value"></span><span data-i18n="ext_sum_api_response_length_3"> tokens)</span>
<small class="memory_disabled_hint">0 = default</small> <small class="memory_disabled_hint" data-i18n="ext_sum_0_default">0 = default</small>
</label> </label>
<input id="memory_override_response_length" type="range" value="{{defaultSettings.overrideResponseLength}}" min="{{defaultSettings.overrideResponseLengthMin}}" max="{{defaultSettings.overrideResponseLengthMax}}" step="{{defaultSettings.overrideResponseLengthStep}}" /> <input id="memory_override_response_length" type="range" value="{{defaultSettings.overrideResponseLength}}" min="{{defaultSettings.overrideResponseLengthMin}}" max="{{defaultSettings.overrideResponseLengthMax}}" step="{{defaultSettings.overrideResponseLengthStep}}" />
<label for="memory_max_messages_per_request"> <label for="memory_max_messages_per_request">
[Raw] Max messages per request (<span id="memory_max_messages_per_request_value"></span>) <span data-i18n="ext_sum_raw_max_msg">[Raw] Max messages per request</span> (<span id="memory_max_messages_per_request_value"></span>)
<small class="memory_disabled_hint">0 = unlimited</small> <small class="memory_disabled_hint" data-i18n="ext_sum_0_unlimited">0 = unlimited</small>
</label> </label>
<input id="memory_max_messages_per_request" type="range" value="{{defaultSettings.maxMessagesPerRequest}}" min="{{defaultSettings.maxMessagesPerRequestMin}}" max="{{defaultSettings.maxMessagesPerRequestMax}}" step="{{defaultSettings.maxMessagesPerRequestStep}}" /> <input id="memory_max_messages_per_request" type="range" value="{{defaultSettings.maxMessagesPerRequest}}" min="{{defaultSettings.maxMessagesPerRequestMin}}" max="{{defaultSettings.maxMessagesPerRequestMax}}" step="{{defaultSettings.maxMessagesPerRequestStep}}" />
<h4 data-i18n="Update frequency" class="textAlignCenter"> <h4 data-i18n="Update frequency" class="textAlignCenter">
@ -83,49 +83,49 @@
</h4> </h4>
<label for="memory_prompt_interval" class="title_restorable"> <label for="memory_prompt_interval" class="title_restorable">
<span> <span>
Update every <span id="memory_prompt_interval_value"></span> messages <span data-i18n="ext_sum_update_every_messages_1">Update every</span> <span id="memory_prompt_interval_value"></span><span data-i18n="ext_sum_update_every_messages_2"> messages</span>
<small class="memory_disabled_hint">0 = disable</small> <small class="memory_disabled_hint" data-i18n="ext_sum_0_disable">0 = disable</small>
</span> </span>
<div id="memory_prompt_interval_auto" title="Try to automatically adjust the interval based on the chat metrics." class="right_menu_button"> <div id="memory_prompt_interval_auto" data-i18n="[title]ext_sum_auto_adjust_desc" title="Try to automatically adjust the interval based on the chat metrics." class="right_menu_button">
<div class="fa-solid fa-wand-magic-sparkles"></div> <div class="fa-solid fa-wand-magic-sparkles"></div>
</div> </div>
</label> </label>
<input id="memory_prompt_interval" type="range" value="{{defaultSettings.promptInterval}}" min="{{defaultSettings.promptMinInterval}}" max="{{defaultSettings.promptMaxInterval}}" step="{{defaultSettings.promptIntervalStep}}" /> <input id="memory_prompt_interval" type="range" value="{{defaultSettings.promptInterval}}" min="{{defaultSettings.promptMinInterval}}" max="{{defaultSettings.promptMaxInterval}}" step="{{defaultSettings.promptIntervalStep}}" />
<label for="memory_prompt_words_force" class="title_restorable"> <label for="memory_prompt_words_force" class="title_restorable">
<span> <span>
Update every <span id="memory_prompt_words_force_value"></span> words <span data-i18n="ext_sum_update_every_words_1">Update every</span> <span id="memory_prompt_words_force_value"></span><span data-i18n="ext_sum_update_every_words_2"> words</span>
<small class="memory_disabled_hint">0 = disable</small> <small class="memory_disabled_hint" data-i18n="ext_sum_0_disable">0 = disable</small>
</span> </span>
<div id="memory_prompt_words_auto" title="Try to automatically adjust the interval based on the chat metrics." class="right_menu_button"> <div id="memory_prompt_words_auto" data-i18n="[title]ext_sum_auto_adjust_desc" title="Try to automatically adjust the interval based on the chat metrics." class="right_menu_button">
<div class="fa-solid fa-wand-magic-sparkles"></div> <div class="fa-solid fa-wand-magic-sparkles"></div>
</div> </div>
</label> </label>
<input id="memory_prompt_words_force" type="range" value="{{defaultSettings.promptForceWords}}" min="{{defaultSettings.promptMinForceWords}}" max="{{defaultSettings.promptMaxForceWords}}" step="{{defaultSettings.promptForceWordsStep}}" /> <input id="memory_prompt_words_force" type="range" value="{{defaultSettings.promptForceWords}}" min="{{defaultSettings.promptMinForceWords}}" max="{{defaultSettings.promptMaxForceWords}}" step="{{defaultSettings.promptForceWordsStep}}" />
<small>If both sliders are non-zero, then both will trigger summary updates at their respective intervals.</small> <small data-i18n="ext_sum_both_sliders">If both sliders are non-zero, then both will trigger summary updates at their respective intervals.</small>
<hr> <hr>
</div> </div>
<div class="memory_template"> <div class="memory_template">
<label for="memory_template">Injection Template</label> <label for="memory_template" data-i18n="ext_sum_injection_template">Injection Template</label>
<textarea id="memory_template" class="text_pole textarea_compact" rows="2" placeholder="&lcub;&lcub;summary&rcub;&rcub; will resolve to the current summary contents."></textarea> <textarea id="memory_template" class="text_pole textarea_compact" rows="2" data-i18n="[placeholder]ext_sum_memory_template_placeholder" placeholder="&lcub;&lcub;summary&rcub;&rcub; will resolve to the current summary contents."></textarea>
</div> </div>
<label for="memory_position">Injection Position</label> <label for="memory_position" data-i18n="ext_sum_injection_position">Injection Position</label>
<div class="radio_group"> <div class="radio_group">
<label> <label>
<input type="radio" name="memory_position" value="2" /> <input type="radio" name="memory_position" value="2" />
Before Main Prompt / Story String <span data-i18n="Before Main Prompt / Story String">Before Main Prompt / Story String</span>
</label> </label>
<label> <label>
<input type="radio" name="memory_position" value="0" /> <input type="radio" name="memory_position" value="0" />
After Main Prompt / Story String <span data-i18n="After Main Prompt / Story String">After Main Prompt / Story String</span>
</label> </label>
<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."> <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" /> <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" /> <span data-i18n="In-chat @ Depth">In-chat @ Depth</span> <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="999" />
as <span data-i18n="as">as</span>
<select id="memory_role" class="text_pole widthNatural"> <select id="memory_role" class="text_pole widthNatural">
<option value="0">System</option> <option value="0" data-i18n="System">System</option>
<option value="1">User</option> <option value="1" data-i18n="User">User</option>
<option value="2">Assistant</option> <option value="2" data-i18n="Assistant">Assistant</option>
</select> </select>
</label> </label>
</div> </div>

View File

@ -8,16 +8,16 @@
<div class="flex-container"> <div class="flex-container">
<div id="open_regex_editor" class="menu_button"> <div id="open_regex_editor" class="menu_button">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square"></i>
<span>Open Editor</span> <span data-i18n="ext_regex_open_editor">Open Editor</span>
</div> </div>
<div id="import_regex" class="menu_button"> <div id="import_regex" class="menu_button">
<i class="fa-solid fa-file-import"></i> <i class="fa-solid fa-file-import"></i>
<span>Import Script</span> <span data-i18n="ext_regex_import_script">Import Script</span>
</div> </div>
<input type="file" id="import_regex_file" hidden accept="*.json" /> <input type="file" id="import_regex_file" hidden accept="*.json" />
</div> </div>
<hr /> <hr />
<label>Saved Scripts</label> <label data-i18n="ext_regex_saved_scripts">Saved Scripts</label>
<div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div> <div id="saved_regex_scripts" class="flex-container regex-script-container flexFlowColumn"></div>
</div> </div>
</div> </div>

View File

@ -11,7 +11,7 @@
</div> </div>
</h3> </h3>
<small class="flex-container extensions_info"> <small class="flex-container extensions_info" data-i18n="ext_regex_desc">
Regex is a tool to find/replace strings using regular expressions. If you want to learn more, click on the ? next to the title. Regex is a tool to find/replace strings using regular expressions. If you want to learn more, click on the ? next to the title.
</small> </small>
<hr /> <hr />
@ -21,13 +21,13 @@
<label class="title_restorable" for="regex_test_input"> <label class="title_restorable" for="regex_test_input">
<small data-i18n="Input">Input</small> <small data-i18n="Input">Input</small>
</label> </label>
<textarea id="regex_test_input" class="text_pole textarea_compact" rows="4" placeholder="Type here..."></textarea> <textarea id="regex_test_input" class="text_pole textarea_compact" rows="4" data-i18n="[placeholder]ext_regex_test_input_placeholder" placeholder="Type here..."></textarea>
</div> </div>
<div class="flex1"> <div class="flex1">
<label class="title_restorable" for="regex_test_output"> <label class="title_restorable" for="regex_test_output">
<small data-i18n="Output">Output</small> <small data-i18n="Output">Output</small>
</label> </label>
<textarea id="regex_test_output" class="text_pole textarea_compact" rows="4" placeholder="Empty" readonly></textarea> <textarea id="regex_test_output" class="text_pole textarea_compact" rows="4" data-i18n="[placeholder]ext_regex_output_placeholder" placeholder="Empty" readonly></textarea>
</div> </div>
<hr> <hr>
</div> </div>
@ -56,6 +56,7 @@
<div> <div>
<textarea <textarea
class="regex_replace_string text_pole wide100p textarea_compact" class="regex_replace_string text_pole wide100p textarea_compact"
data-i18n="[placeholder]ext_regex_replace_string_placeholder"
placeholder="Use {{match}} to include the matched text from the Find Regex or $1, $2, etc. for capture groups." placeholder="Use {{match}} to include the matched text from the Find Regex or $1, $2, etc. for capture groups."
rows="2" rows="2"
></textarea> ></textarea>
@ -67,7 +68,7 @@
</label> </label>
<div> <div>
<textarea <textarea
class="regex_trim_strings text_pole wide100p textarea_compact" class="regex_trim_strings text_pole wide100p textarea_compact" data-i18n="[placeholder]ext_regex_trim_placeholder"
placeholder="Globally trims any unwanted parts from a regex match before replacement. Separate each element by an enter." placeholder="Globally trims any unwanted parts from a regex match before replacement. Separate each element by an enter."
rows="3" rows="3"
></textarea> ></textarea>
@ -77,53 +78,59 @@
<div class="flex-container"> <div class="flex-container">
<div class="flex1 wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart"> <div class="flex1 wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
<small>Affects</small> <small data-i18n="ext_regex_affects">Affects</small>
<div> <div title="Messages sent by the user.">
<label class="checkbox flex-container"> <label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="1"> <input type="checkbox" name="replace_position" value="1">
<span data-i18n="Before Char">User Input</span> <span data-i18n="ext_regex_user_input">User Input</span>
</label> </label>
</div> </div>
<div> <div title="Messages received from the Generation API.">
<label class="checkbox flex-container"> <label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="2"> <input type="checkbox" name="replace_position" value="2">
<span data-i18n="After Char">AI Output</span> <span data-i18n="ext_regex_ai_output">AI Output</span>
</label> </label>
</div> </div>
<div> <div title="Messages sent using STscript commands.">
<label class="checkbox flex-container"> <label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="3"> <input type="checkbox" name="replace_position" value="3">
<span data-i18n="Slash Commands">Slash Commands</span> <span data-i18n="Slash Commands">Slash Commands</span>
</label> </label>
</div> </div>
<div title="Lorebook/World Info entry contents. Requires 'Only Format Prompt' to be checked!">
<label class="checkbox flex-container">
<input type="checkbox" name="replace_position" value="5">
<span data-i18n="World Info">World Info</span>
</label>
</div>
<div class="flex-container wide100p marginTop5"> <div class="flex-container wide100p marginTop5">
<div class="flex1 flex-container flexNoGap"> <div class="flex1 flex-container flexNoGap">
<small title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts usable messages, i.e. not hidden or system."> <small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">
<span data-i18n="Min Depth">Min Depth</span> <span data-i18n="Min Depth">Min Depth</span>
<span class="fa-solid fa-circle-question note-link-span"></span> <span class="fa-solid fa-circle-question note-link-span"></span>
</small> </small>
<input name="min_depth" class="text_pole textarea_compact" type="number" min="0" max="999" placeholder="Unlimited" /> <input name="min_depth" class="text_pole textarea_compact" type="number" min="0" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
</div> </div>
<div class="flex1 flex-container flexNoGap"> <div class="flex1 flex-container flexNoGap">
<small title="When applied to prompts or display, only affect messages no more than N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts usable messages, i.e. not hidden or system."> <small data-i18n="[title]ext_regex_max_depth_desc" title="When applied to prompts or display, only affect messages no more than N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">
<span data-i18n="Max Depth">Max Depth</span> <span data-i18n="Max Depth">Max Depth</span>
<span class="fa-solid fa-circle-question note-link-span"></span> <span class="fa-solid fa-circle-question note-link-span"></span>
</small> </small>
<input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="999" placeholder="Unlimited" /> <input name="max_depth" class="text_pole textarea_compact" type="number" min="0" max="999" data-i18n="[placeholder]ext_regex_min_depth_placeholder" placeholder="Unlimited" />
</div> </div>
</div> </div>
</div> </div>
<div class="flex1 wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart"> <div class="flex1 wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
<small>Other Options</small> <small data-i18n="ext_regex_other_options">Other Options</small>
<label class="checkbox flex-container"> <label class="checkbox flex-container">
<input type="checkbox" name="disabled" /> <input type="checkbox" name="disabled" />
<span data-i18n="Disabled">Disabled</span> <span data-i18n="Disabled">Disabled</span>
</label> </label>
<label class="checkbox flex-container"> <label class="checkbox flex-container" title="Chat history won't change, only the message rendered in the UI.">
<input type="checkbox" name="only_format_display" /> <input type="checkbox" name="only_format_display" />
<span data-i18n="Only Format Display">Only Format Display</span> <span data-i18n="Only Format Display">Only Format Display</span>
</label> </label>
<label class="checkbox flex-container" title="Chat history won't change, only the prompt as the request is sent (on generation)"> <label class="checkbox flex-container" data-i18n="[title]ext_regex_only_format_prompt_desc" title="Chat history won't change, only the prompt as the request is sent (on generation).">
<input type="checkbox" name="only_format_prompt"/> <input type="checkbox" name="only_format_prompt"/>
<span> <span>
<span data-i18n="Only Format Prompt (?)">Only Format Prompt</span> <span data-i18n="Only Format Prompt (?)">Only Format Prompt</span>
@ -134,7 +141,7 @@
<input type="checkbox" name="run_on_edit" /> <input type="checkbox" name="run_on_edit" />
<span data-i18n="Run On Edit">Run On Edit</span> <span data-i18n="Run On Edit">Run On Edit</span>
</label> </label>
<label class="checkbox flex-container" title="Substitute {{macros}} in Find Regex before running it"> <label class="checkbox flex-container" data-i18n="[title]ext_regex_substitute_regex_desc" title="Substitute &lcub;&lcub;macros&rcub;&rcub; in Find Regex before running it">
<input type="checkbox" name="substitute_regex" /> <input type="checkbox" name="substitute_regex" />
<span> <span>
<span data-i18n="Substitute Regex">Substitute Regex</span> <span data-i18n="Substitute Regex">Substitute Regex</span>

View File

@ -17,6 +17,8 @@ const regex_placement = {
USER_INPUT: 1, USER_INPUT: 1,
AI_OUTPUT: 2, AI_OUTPUT: 2,
SLASH_COMMAND: 3, SLASH_COMMAND: 3,
// 4 - sendAs (legacy)
WORLD_INFO: 5,
}; };
/** /**

View File

@ -4,16 +4,16 @@
<div class="flex-container flexnowrap"> <div class="flex-container flexnowrap">
<label class="checkbox flex-container" for="regex_disable"> <label class="checkbox flex-container" for="regex_disable">
<input type="checkbox" name="regex_disable" class="disable_regex" /> <input type="checkbox" name="regex_disable" class="disable_regex" />
<span class="regex-toggle-on fa-solid fa-toggle-on" title="Disable script"></span> <span class="regex-toggle-on fa-solid fa-toggle-on" data-i18n="[title]ext_regex_disable_script" title="Disable script"></span>
<span class="regex-toggle-off fa-solid fa-toggle-off" title="Enable script"></span> <span class="regex-toggle-off fa-solid fa-toggle-off" data-i18n="[title]ext_regex_enable_script" title="Enable script"></span>
</label> </label>
<div class="edit_existing_regex menu_button" title="Edit script"> <div class="edit_existing_regex menu_button" data-i18n="[title]ext_regex_edit_script" title="Edit script">
<i class="fa-solid fa-pencil"></i> <i class="fa-solid fa-pencil"></i>
</div> </div>
<div class="export_regex menu_button" title="Export script"> <div class="export_regex menu_button" data-i18n="[title]ext_regex_export_script" title="Export script">
<i class="fa-solid fa-file-export"></i> <i class="fa-solid fa-file-export"></i>
</div> </div>
<div class="delete_regex menu_button" title="Delete script"> <div class="delete_regex menu_button" data-i18n="[title]ext_regex_delete_script" title="Delete script">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash"></i>
</div> </div>
</div> </div>

View File

@ -26,6 +26,7 @@ import { SECRET_KEYS, secret_state } from '../../secrets.js';
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js'; import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
import { getMultimodalCaption } from '../shared.js'; import { getMultimodalCaption } from '../shared.js';
import { registerSlashCommand } from '../../slash-commands.js'; import { registerSlashCommand } from '../../slash-commands.js';
import { resolveVariable } from '../../variables.js';
export { MODULE_NAME }; export { MODULE_NAME };
// Wraps a string into monospace font-face span // Wraps a string into monospace font-face span
@ -589,15 +590,17 @@ async function expandPrompt(prompt) {
* Modifies prompt based on auto-expansion and user inputs. * Modifies prompt based on auto-expansion and user inputs.
* @param {string} prompt Prompt to refine * @param {string} prompt Prompt to refine
* @param {boolean} allowExpand Whether to allow auto-expansion * @param {boolean} allowExpand Whether to allow auto-expansion
* @param {boolean} isNegative Whether the prompt is a negative one
* @returns {Promise<string>} Refined prompt * @returns {Promise<string>} Refined prompt
*/ */
async function refinePrompt(prompt, allowExpand) { async function refinePrompt(prompt, allowExpand, isNegative = false) {
if (allowExpand && extension_settings.sd.expand) { if (allowExpand && extension_settings.sd.expand) {
prompt = await expandPrompt(prompt); prompt = await expandPrompt(prompt);
} }
if (extension_settings.sd.refine_mode) { if (extension_settings.sd.refine_mode) {
const refinedPrompt = await callPopup('<h3>Review and edit the prompt:</h3>Press "Cancel" to abort the image generation.', 'input', prompt.trim(), { rows: 5, okButton: 'Generate' }); const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';
const refinedPrompt = await callPopup(text + 'Press "Cancel" to abort the image generation.', 'input', prompt.trim(), { rows: 5, okButton: 'Continue' });
if (refinedPrompt) { if (refinedPrompt) {
return refinedPrompt; return refinedPrompt;
@ -1967,9 +1970,9 @@ async function generatePicture(args, trigger, message, callback) {
eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath }); eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath });
if (typeof callbackOriginal === 'function') { if (typeof callbackOriginal === 'function') {
callbackOriginal(prompt, imagePath, generationType); callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix);
} else { } else {
sendMessage(prompt, imagePath, generationType); sendMessage(prompt, imagePath, generationType, negativePromptPrefix);
} }
}; };
} }
@ -1978,6 +1981,7 @@ async function generatePicture(args, trigger, message, callback) {
callback = () => { }; callback = () => { };
} }
const negativePromptPrefix = resolveVariable(args?.negative) || '';
const dimensions = setTypeSpecificDimensions(generationType); const dimensions = setTypeSpecificDimensions(generationType);
let imagePath = ''; let imagePath = '';
@ -1988,7 +1992,7 @@ async function generatePicture(args, trigger, message, callback) {
context.deactivateSendButtons(); context.deactivateSendButtons();
hideSwipeButtons(); hideSwipeButtons();
imagePath = await sendGenerationRequest(generationType, prompt, characterName, callback); imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback);
} catch (err) { } catch (err) {
console.trace(err); console.trace(err);
throw new Error('SD prompt text generation failed.'); throw new Error('SD prompt text generation failed.');
@ -2183,17 +2187,26 @@ async function generatePrompt(quietPrompt) {
return processedReply; return processedReply;
} }
async function sendGenerationRequest(generationType, prompt, characterName = null, callback) { /**
* Sends a request to image generation endpoint and processes the result.
* @param {number} generationType Type of image generation
* @param {string} prompt Prompt to be used for image generation
* @param {string} additionalNegativePrefix Additional negative prompt to be used for image generation
* @param {string} [characterName] Name of the character
* @param {function} [callback] Callback function to be called after image generation
* @returns
*/
async function sendGenerationRequest(generationType, prompt, additionalNegativePrefix, characterName = null, callback) {
const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL]; const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL];
const prefix = noCharPrefix.includes(generationType) const prefix = noCharPrefix.includes(generationType)
? extension_settings.sd.prompt_prefix ? extension_settings.sd.prompt_prefix
: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()); : combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix());
const negativePrefix = noCharPrefix.includes(generationType)
? extension_settings.sd.negative_prompt
: combinePrefixes(extension_settings.sd.negative_prompt, getCharacterNegativePrefix());
const prefixedPrompt = substituteParams(combinePrefixes(prefix, prompt, '{prompt}')); const prefixedPrompt = substituteParams(combinePrefixes(prefix, prompt, '{prompt}'));
const negativePrompt = substituteParams(combinePrefixes(additionalNegativePrefix, negativePrefix));
const negativePrompt = substituteParams(noCharPrefix.includes(generationType)
? extension_settings.sd.negative_prompt
: combinePrefixes(extension_settings.sd.negative_prompt, getCharacterNegativePrefix()));
let result = { format: '', data: '' }; let result = { format: '', data: '' };
const currentChatId = getCurrentChatId(); const currentChatId = getCurrentChatId();
@ -2249,7 +2262,7 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul
const filename = `${characterName}_${humanizedDateTime()}`; const filename = `${characterName}_${humanizedDateTime()}`;
const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format); const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format);
callback ? callback(prompt, base64Image, generationType) : sendMessage(prompt, base64Image, generationType); callback ? callback(prompt, base64Image, generationType, additionalNegativePrefix) : sendMessage(prompt, base64Image, generationType, additionalNegativePrefix);
return base64Image; return base64Image;
} }
@ -2722,6 +2735,9 @@ async function onComfyOpenWorkflowEditorClick() {
$('#sd_comfy_workflow_editor_placeholder_list_custom').append(el); $('#sd_comfy_workflow_editor_placeholder_list_custom').append(el);
el.find('.sd_comfy_workflow_editor_custom_find').val(placeholder.find); el.find('.sd_comfy_workflow_editor_custom_find').val(placeholder.find);
el.find('.sd_comfy_workflow_editor_custom_find').on('input', function () { el.find('.sd_comfy_workflow_editor_custom_find').on('input', function () {
if (!(this instanceof HTMLInputElement)) {
return;
}
placeholder.find = this.value; placeholder.find = this.value;
el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`); el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`);
el.attr('data-placeholder', `${this.value}`); el.attr('data-placeholder', `${this.value}`);
@ -2730,6 +2746,9 @@ async function onComfyOpenWorkflowEditorClick() {
}); });
el.find('.sd_comfy_workflow_editor_custom_replace').val(placeholder.replace); el.find('.sd_comfy_workflow_editor_custom_replace').val(placeholder.replace);
el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function () { el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function () {
if (!(this instanceof HTMLInputElement)) {
return;
}
placeholder.replace = this.value; placeholder.replace = this.value;
saveSettingsDebounced(); saveSettingsDebounced();
}); });
@ -2819,7 +2838,14 @@ async function onComfyDeleteWorkflowClick() {
onComfyWorkflowChange(); onComfyWorkflowChange();
} }
async function sendMessage(prompt, image, generationType) { /**
* Sends a chat message with the generated image.
* @param {string} prompt Prompt used for the image generation
* @param {string} image Base64 encoded image
* @param {number} generationType Generation type of the image
* @param {string} additionalNegativePrefix Additional negative prompt used for the image generation
*/
async function sendMessage(prompt, image, generationType, additionalNegativePrefix) {
const context = getContext(); const context = getContext();
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`; const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
const message = { const message = {
@ -2832,6 +2858,7 @@ async function sendMessage(prompt, image, generationType) {
image: image, image: image,
title: prompt, title: prompt,
generationType: generationType, generationType: generationType,
negative: additionalNegativePrefix,
}, },
}; };
context.chat.push(message); context.chat.push(message);
@ -2952,6 +2979,7 @@ async function sdMessageButton(e) {
const characterFileName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString(); const characterFileName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString();
const messageText = message?.mes; const messageText = message?.mes;
const hasSavedImage = message?.extra?.image && message?.extra?.title; const hasSavedImage = message?.extra?.image && message?.extra?.title;
const hasSavedNegative = message?.extra?.negative;
if ($icon.hasClass(busyClass)) { if ($icon.hasClass(busyClass)) {
console.log('Previous image is still being generated...'); console.log('Previous image is still being generated...');
@ -2963,13 +2991,14 @@ async function sdMessageButton(e) {
try { try {
setBusyIcon(true); setBusyIcon(true);
if (hasSavedImage) { if (hasSavedImage) {
const prompt = await refinePrompt(message.extra.title, false); const prompt = await refinePrompt(message.extra.title, false, false);
const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, false, true) : '';
message.extra.title = prompt; message.extra.title = prompt;
const generationType = message?.extra?.generationType ?? generationMode.FREE; const generationType = message?.extra?.generationType ?? generationMode.FREE;
console.log('Regenerating an image, using existing prompt:', prompt); console.log('Regenerating an image, using existing prompt:', prompt);
dimensions = setTypeSpecificDimensions(generationType); dimensions = setTypeSpecificDimensions(generationType);
await sendGenerationRequest(generationType, prompt, characterFileName, saveGeneratedImage); await sendGenerationRequest(generationType, prompt, negative, characterFileName, saveGeneratedImage);
} }
else { else {
console.log('doing /sd raw last'); console.log('doing /sd raw last');
@ -2987,7 +3016,7 @@ async function sdMessageButton(e) {
} }
} }
function saveGeneratedImage(prompt, image, generationType) { function saveGeneratedImage(prompt, image, generationType, negative) {
// Some message sources may not create the extra object // Some message sources may not create the extra object
if (typeof message.extra !== 'object') { if (typeof message.extra !== 'object') {
message.extra = {}; message.extra = {};
@ -2998,6 +3027,7 @@ async function sdMessageButton(e) {
message.extra.image = image; message.extra.image = image;
message.extra.title = prompt; message.extra.title = prompt;
message.extra.generationType = generationType; message.extra.generationType = generationType;
message.extra.negative = negative;
appendMediaToMessage(message, $mes); appendMediaToMessage(message, $mes);
context.saveChat(); context.saveChat();

View File

@ -107,12 +107,12 @@ export function applyLocale(root = document) {
const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
if (attributeMatch) { // attribute-tagged key if (attributeMatch) { // attribute-tagged key
const localizedValue = localeData?.[attributeMatch[2]]; const localizedValue = localeData?.[attributeMatch[2]];
if (localizedValue) { if (localizedValue || localizedValue == '') {
$(this).attr(attributeMatch[1], localizedValue); $(this).attr(attributeMatch[1], localizedValue);
} }
} else { // No attribute tag, treat as 'text' } else { // No attribute tag, treat as 'text'
const localizedValue = localeData?.[key]; const localizedValue = localeData?.[key];
if (localizedValue) { if (localizedValue || localizedValue == '') {
$(this).text(localizedValue); $(this).text(localizedValue);
} }
} }

View File

@ -4786,6 +4786,19 @@ $(document).ready(async function () {
}); });
} }
$('#openrouter_providers_chat').on('change', function () {
const selectedProviders = $(this).val();
// Not a multiple select?
if (!Array.isArray(selectedProviders)) {
return;
}
oai_settings.openrouter_providers = selectedProviders;
saveSettingsDebounced();
});
$('#api_button_openai').on('click', onConnectButtonClick); $('#api_button_openai').on('click', onConnectButtonClick);
$('#openai_reverse_proxy').on('input', onReverseProxyInput); $('#openai_reverse_proxy').on('input', onReverseProxyInput);
$('#model_openai_select').on('change', onModelChange); $('#model_openai_select').on('change', onModelChange);

View File

@ -1,8 +1,7 @@
import { isMobile } from './RossAscends-mods.js'; import { isMobile } from './RossAscends-mods.js';
import { amount_gen, callPopup, eventSource, event_types, getRequestHeaders, max_context, saveSettingsDebounced, setGenerationParamsFromPreset } from '../script.js'; import { amount_gen, callPopup, eventSource, event_types, getRequestHeaders, max_context, setGenerationParamsFromPreset } from '../script.js';
import { textgenerationwebui_settings as textgen_settings, textgen_types } from './textgen-settings.js'; import { textgenerationwebui_settings as textgen_settings, textgen_types } from './textgen-settings.js';
import { tokenizers } from './tokenizers.js'; import { tokenizers } from './tokenizers.js';
import { oai_settings } from './openai.js';
let mancerModels = []; let mancerModels = [];
let togetherModels = []; let togetherModels = [];
@ -515,25 +514,6 @@ jQuery(function () {
})); }));
} }
providersSelect.on('change', function () {
const selectedProviders = $(this).val();
// Not a multiple select?
if (!Array.isArray(selectedProviders)) {
return;
}
if ($(this).is('#openrouter_providers_text')) {
textgen_settings.openrouter_providers = selectedProviders;
}
if ($(this).is('#openrouter_providers_chat')) {
oai_settings.openrouter_providers = selectedProviders;
}
saveSettingsDebounced();
});
if (!isMobile()) { if (!isMobile()) {
$('#mancer_model').select2({ $('#mancer_model').select2({
placeholder: 'Select a model', placeholder: 'Select a model',

View File

@ -710,6 +710,19 @@ jQuery(function () {
} }
$('#textgen_logit_bias_new_entry').on('click', () => createNewLogitBiasEntry(settings.logit_bias, BIAS_KEY)); $('#textgen_logit_bias_new_entry').on('click', () => createNewLogitBiasEntry(settings.logit_bias, BIAS_KEY));
$('#openrouter_providers_text').on('change', function () {
const selectedProviders = $(this).val();
// Not a multiple select?
if (!Array.isArray(selectedProviders)) {
return;
}
settings.openrouter_providers = selectedProviders;
saveSettingsDebounced();
});
}); });
function showTypeSpecificControls(type) { function showTypeSpecificControls(type) {

View File

@ -10,6 +10,7 @@ import { power_user } from './power-user.js';
import { getTagKeyForEntity } from './tags.js'; import { getTagKeyForEntity } from './tags.js';
import { resolveVariable } from './variables.js'; import { resolveVariable } from './variables.js';
import { debounce_timeout } from './constants.js'; import { debounce_timeout } from './constants.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
export { export {
world_info, world_info,
@ -340,6 +341,13 @@ const world_info_position = {
ANTop: 2, ANTop: 2,
ANBottom: 3, ANBottom: 3,
atDepth: 4, atDepth: 4,
EMTop: 5,
EMBottom: 6,
};
export const wi_anchor_position = {
before: 0,
after: 1,
}; };
const worldInfoCache = {}; const worldInfoCache = {};
@ -349,7 +357,7 @@ const worldInfoCache = {};
* @param {string[]} chat The chat messages to scan. * @param {string[]} chat The chat messages to scan.
* @param {number} maxContext The maximum context size of the generation. * @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun If true, the function will not emit any events. * @param {boolean} isDryRun If true, the function will not emit any events.
* @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoDepth: any[]}} WIPromptResult * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
* @returns {Promise<WIPromptResult>} The world info string and depth. * @returns {Promise<WIPromptResult>} The world info string and depth.
*/ */
async function getWorldInfoPrompt(chat, maxContext, isDryRun) { async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
@ -369,7 +377,8 @@ async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
worldInfoString, worldInfoString,
worldInfoBefore, worldInfoBefore,
worldInfoAfter, worldInfoAfter,
worldInfoDepth: activatedWorldInfo.WIDepthEntries, worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
}; };
} }
@ -1686,13 +1695,24 @@ function getWorldEntry(name, data, entry) {
groupWeightInput.data('uid', entry.uid); groupWeightInput.data('uid', entry.uid);
groupWeightInput.on('input', function () { groupWeightInput.on('input', function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); let value = Number($(this).val());
const min = Number($(this).attr('min'));
const max = Number($(this).attr('max'));
data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 0; // Clamp the value
if (value < min) {
value = min;
$(this).val(min);
} else if (value > max) {
value = max;
$(this).val(max);
}
data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight); setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
saveWorldInfo(name, data); saveWorldInfo(name, data);
}); });
groupWeightInput.val(entry.groupWeight).trigger('input'); groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
// probability // probability
if (entry.probability === undefined) { if (entry.probability === undefined) {
@ -2474,7 +2494,7 @@ export async function getSortedEntries() {
* Performs a scan on the chat and returns the world info activated. * Performs a scan on the chat and returns the world info activated.
* @param {string[]} chat The chat messages to scan. * @param {string[]} chat The chat messages to scan.
* @param {number} maxContext The maximum context size of the generation. * @param {number} maxContext The maximum context size of the generation.
* @typedef {{ worldInfoBefore: string, worldInfoAfter: string, WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
* @returns {Promise<WIActivated>} The world info activated. * @returns {Promise<WIActivated>} The world info activated.
*/ */
async function checkWorldInfo(chat, maxContext) { async function checkWorldInfo(chat, maxContext) {
@ -2512,7 +2532,7 @@ async function checkWorldInfo(chat, maxContext) {
const sortedEntries = await getSortedEntries(); const sortedEntries = await getSortedEntries();
if (sortedEntries.length === 0) { if (sortedEntries.length === 0) {
return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], allActivatedEntries: new Set() }; return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
} }
while (needsToScan) { while (needsToScan) {
@ -2711,33 +2731,48 @@ async function checkWorldInfo(chat, maxContext) {
// Forward-sorted list of entries for joining // Forward-sorted list of entries for joining
const WIBeforeEntries = []; const WIBeforeEntries = [];
const WIAfterEntries = []; const WIAfterEntries = [];
const EMEntries = [];
const ANTopEntries = []; const ANTopEntries = [];
const ANBottomEntries = []; const ANBottomEntries = [];
const WIDepthEntries = []; const WIDepthEntries = [];
// Appends from insertion order 999 to 1. Use unshift for this purpose // Appends from insertion order 999 to 1. Use unshift for this purpose
// TODO (kingbri): Change to use WI Anchor positioning instead of separate top/bottom arrays
[...allActivatedEntries].sort(sortFn).forEach((entry) => { [...allActivatedEntries].sort(sortFn).forEach((entry) => {
const regexDepth = entry.position === world_info_position.atDepth ? (entry.depth ?? DEFAULT_DEPTH) : null;
const content = getRegexedString(entry.content, regex_placement.WORLD_INFO, { depth: regexDepth, isMarkdown: false, isPrompt: true });
switch (entry.position) { switch (entry.position) {
case world_info_position.before: case world_info_position.before:
WIBeforeEntries.unshift(substituteParams(entry.content)); WIBeforeEntries.unshift(substituteParams(content));
break; break;
case world_info_position.after: case world_info_position.after:
WIAfterEntries.unshift(substituteParams(entry.content)); WIAfterEntries.unshift(substituteParams(content));
break;
case world_info_position.EMTop:
EMEntries.unshift(
{ position: wi_anchor_position.before, content: content },
);
break;
case world_info_position.EMBottom:
EMEntries.unshift(
{ position: wi_anchor_position.after, content: content },
);
break; break;
case world_info_position.ANTop: case world_info_position.ANTop:
ANTopEntries.unshift(entry.content); ANTopEntries.unshift(content);
break; break;
case world_info_position.ANBottom: case world_info_position.ANBottom:
ANBottomEntries.unshift(entry.content); ANBottomEntries.unshift(content);
break; break;
case world_info_position.atDepth: { case world_info_position.atDepth: {
const existingDepthIndex = WIDepthEntries.findIndex((e) => e.depth === (entry.depth ?? DEFAULT_DEPTH) && e.role === (entry.role ?? extension_prompt_roles.SYSTEM)); const existingDepthIndex = WIDepthEntries.findIndex((e) => e.depth === (entry.depth ?? DEFAULT_DEPTH) && e.role === (entry.role ?? extension_prompt_roles.SYSTEM));
if (existingDepthIndex !== -1) { if (existingDepthIndex !== -1) {
WIDepthEntries[existingDepthIndex].entries.unshift(entry.content); WIDepthEntries[existingDepthIndex].entries.unshift(content);
} else { } else {
WIDepthEntries.push({ WIDepthEntries.push({
depth: entry.depth, depth: entry.depth,
entries: [entry.content], entries: [content],
role: entry.role ?? extension_prompt_roles.SYSTEM, role: entry.role ?? extension_prompt_roles.SYSTEM,
}); });
} }
@ -2759,7 +2794,7 @@ async function checkWorldInfo(chat, maxContext) {
buffer.cleanExternalActivations(); buffer.cleanExternalActivations();
return { worldInfoBefore, worldInfoAfter, WIDepthEntries, allActivatedEntries }; return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries };
} }
/** /**
@ -3332,6 +3367,10 @@ jQuery(() => {
}); });
$('#world_import_file').on('change', async function (e) { $('#world_import_file').on('change', async function (e) {
if (!(e.target instanceof HTMLInputElement)) {
return;
}
const file = e.target.files[0]; const file = e.target.files[0];
await importWorldInfo(file); await importWorldInfo(file);

View File

@ -283,6 +283,7 @@ app.use('/api/users', require('./src/endpoints/users-public').router);
// Everything below this line requires authentication // Everything below this line requires authentication
app.use(userModule.requireLoginMiddleware); app.use(userModule.requireLoginMiddleware);
app.get('/api/ping', (_, response) => response.sendStatus(204));
// File uploads // File uploads
app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar'));