Add auto-select for instruct presets. Re-organize instruct mode code. Prepare for preset manager integration

This commit is contained in:
Cohee 2023-08-20 23:29:43 +03:00
parent 01cdcfa198
commit 6738ffe2a4
6 changed files with 289 additions and 199 deletions

View File

@ -2143,7 +2143,7 @@
</label>
<div class="flex-container">
<select id="instruct_presets" class="flex1 margin0"></select>
<div id="instruct_set_default" class="menu_button menu_button_icon margin0">
<div id="instruct_set_default" class="menu_button menu_button_icon margin0" title="Auto-select this preset on API connection.">
<i class="fa-solid fa-xs fa-fw fa-heart"></i>
<span data-i18n="Default">Default</span>
</div>

View File

@ -66,9 +66,6 @@ import {
power_user,
pygmalion_options,
tokenizers,
formatInstructModeChat,
formatInstructStoryString,
formatInstructModePrompt,
persona_description_positions,
loadMovingUIState,
getCustomStoppingStrings,
@ -165,6 +162,13 @@ import { deviceInfo } from "./scripts/RossAscends-mods.js";
import { registerPromptManagerMigration } from "./scripts/PromptManager.js";
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
import {
formatInstructModeChat,
formatInstructModePrompt,
getInstructStoppingSequences,
formatInstructStoryString,
autoSelectInstructPreset,
} from "./scripts/instruct-mode.js";
//exporting functions and vars for mods
export {
@ -330,7 +334,6 @@ let scrollLock = false;
const durationSaveEdit = 1000;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit);
const getStatusDebounced = debounce(() => getStatus(), 300_000);
const saveChatDebounced = debounce(() => saveChatConditional(), durationSaveEdit);
const system_message_types = {
@ -884,10 +887,6 @@ async function getStatus() {
const hordeStatus = await checkHordeStatus();
online_status = hordeStatus ? 'Connected' : 'no_connection';
resultCheckStatus();
if (online_status !== "no_connection") {
getStatusDebounced();
}
}
catch {
online_status = "no_connection";
@ -916,6 +915,10 @@ async function getStatus() {
if (online_status == undefined) {
online_status = "no_connection";
}
// Determine instruct mode preset
autoSelectInstructPreset(online_status);
if ((online_status.toLowerCase().indexOf("pygmalion") != -1 && power_user.pygmalion_formatting == pygmalion_options.AUTO)
|| (online_status !== "no_connection" && power_user.pygmalion_formatting == pygmalion_options.ENABLED)) {
is_pygmalion = true;
@ -937,9 +940,6 @@ async function getStatus() {
//console.log(online_status);
resultCheckStatus();
if (online_status !== "no_connection") {
getStatusDebounced();
}
},
error: function (jqXHR, exception) {
console.log(exception);
@ -1913,32 +1913,7 @@ function getStoppingStrings(isImpersonate, addSpace) {
}
}
function addInstructSequence(sequence) {
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
// But it's a problem for Metharme which doesn't use newlines to separate them.
const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
// Sequence must be a non-empty string
if (typeof sequence === 'string' && sequence.length > 0) {
// If sequence is just a whitespace or newline - we don't want to make it a stopping string
// User can always add it as a custom stop string if really needed
if (sequence.trim().length > 0) {
const wrappedSequence = wrap(sequence);
// Need to respect "insert macro" setting
const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
result.push(stopString);
}
}
}
if (power_user.instruct.enabled) {
const input_sequence = power_user.instruct.input_sequence;
const output_sequence = power_user.instruct.output_sequence;
const last_output_sequence = power_user.instruct.last_output_sequence;
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
}
result.push(...getInstructStoppingSequences());
if (power_user.custom_stopping_strings) {
const customStoppingStrings = getCustomStoppingStrings();

View File

@ -8,6 +8,8 @@ import {
import { SECRET_KEYS, writeSecret } from "./secrets.js";
import { delay } from "./utils.js";
import { deviceInfo } from "./RossAscends-mods.js";
import { power_user } from "./power-user.js";
import { autoSelectInstructPreset } from "./instruct-mode.js";
export {
horde_settings,
@ -226,19 +228,11 @@ async function showKudos() {
jQuery(function () {
$("#horde_model").on('mousedown change', async function (e) {
//desktop-only routine for multi-select without CTRL
/*if (deviceInfo.device.type === 'desktop') {
let hordeModelSelectScrollTop = null;
e.preventDefault();
const option = $(e.target);
const selectElement = $(this)[0];
hordeModelSelectScrollTop = selectElement.scrollTop;
option.prop('selected', !option.prop('selected'));
await delay(1);
selectElement.scrollTop = hordeModelSelectScrollTop;
}*/
horde_settings.models = $('#horde_model').val();
console.log('Updated Horde models', horde_settings.models);
// Try select instruct preset
autoSelectInstructPreset(horde_settings.models.join(' '));
});
$("#horde_auto_adjust_response_length").on("input", function () {

View File

@ -0,0 +1,253 @@
"use strict";
import { name1, name2, saveSettingsDebounced, substituteParams } from "../script.js";
import { selected_group } from "./group-chats.js";
import { power_user } from "./power-user.js";
export let instruct_presets = [];
/**
* Loads instruct mode settings from the given data object.
* @param {object} data Settings data object.
*/
export function loadInstructMode(data) {
if (data.instruct !== undefined) {
instruct_presets = data.instruct;
}
if (power_user.instruct.names_force_groups === undefined) {
power_user.instruct.names_force_groups = true;
}
const controls = [
{ id: "instruct_enabled", property: "enabled", isCheckbox: true },
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
{ id: "instruct_names", property: "names", isCheckbox: true },
{ id: "instruct_macro", property: "macro", isCheckbox: true },
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
{ id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
];
controls.forEach(control => {
const $element = $(`#${control.id}`);
if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]);
} else {
$element.val(power_user.instruct[control.property]);
}
$element.on('input', function () {
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
saveSettingsDebounced();
});
});
instruct_presets.forEach((preset) => {
const name = preset.name;
const option = document.createElement('option');
option.value = name;
option.innerText = name;
option.selected = name === power_user.instruct.preset;
$('#instruct_presets').append(option);
});
function highlightDefaultPreset() {
$('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
}
$('#instruct_set_default').on('click', function () {
power_user.default_instruct = power_user.instruct.preset;
$(this).addClass('default');
toastr.success(`Default instruct preset set to ${power_user.default_instruct}`);
saveSettingsDebounced();
});
highlightDefaultPreset();
$('#instruct_presets').on('change', function () {
const name = $(this).find(':selected').val();
const preset = instruct_presets.find(x => x.name === name);
if (!preset) {
return;
}
power_user.instruct.preset = name;
controls.forEach(control => {
if (preset[control.property] !== undefined) {
power_user.instruct[control.property] = preset[control.property];
const $element = $(`#${control.id}`);
if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
} else {
$element.val(power_user.instruct[control.property]).trigger('input');
}
}
});
highlightDefaultPreset();
});
}
/**
* Automatically select instruct preset based on model id.
* 1. Enables instruct mode if preset is found.
* 2. Otherwise, if default instruct preset is set, selects it.
* @param {string} modelId Model name reported by the API.
* @returns {boolean} True if instruct preset was activated by model id, false otherwise.
*/
export function autoSelectInstructPreset(modelId) {
for (const preset of instruct_presets) {
// If activation regex is set, check if it matches the model id
if (preset.activation_regex) {
try {
const modeState = power_user.instruct.enabled;
const regex = new RegExp(preset.activation_regex, 'i');
// If regex matches, select the preset and enable instruct mode
// Should not enable instruct mode if it's already enabled and the preset is the same
if (regex.test(modelId) && (power_user.instruct.preset !== preset.name || !modeState)) {
$('#instruct_presets').val(preset.name).trigger('change');
$('#instruct_enabled').prop('checked', true).trigger('input');
console.log(`Instruct mode: preset "${preset.name}" auto-selected`);
// If instruct mode was disabled, show a notification
if (!modeState) {
toastr.info(`Instruct mode enabled by preset "${preset.name}" for model "${modelId}"`);
}
return true;
}
} catch {
// If regex is invalid, ignore it
console.warn(`Invalid instruct activation regex in preset "${preset.name}"`);
}
}
}
if (power_user.instruct.enabled && power_user.default_instruct && power_user.instruct.preset !== power_user.default_instruct) {
if (instruct_presets.some(p => p.name === power_user.default_instruct)) {
console.log(`Instruct mode: default preset "${power_user.default_instruct}" selected`);
$('#instruct_presets').val(power_user.default_instruct).trigger('change');
}
}
return false;
}
/**
* Converts instruct mode sequences to an array of stopping strings.
* @returns {string[]} Array of instruct mode stopping strings.
*/
export function getInstructStoppingSequences() {
function addInstructSequence(sequence) {
// Cohee: oobabooga's textgen always appends newline before the sequence as a stopping string
// But it's a problem for Metharme which doesn't use newlines to separate them.
const wrap = (s) => power_user.instruct.wrap ? '\n' + s : s;
// Sequence must be a non-empty string
if (typeof sequence === 'string' && sequence.length > 0) {
// If sequence is just a whitespace or newline - we don't want to make it a stopping string
// User can always add it as a custom stop string if really needed
if (sequence.trim().length > 0) {
const wrappedSequence = wrap(sequence);
// Need to respect "insert macro" setting
const stopString = power_user.instruct.macro ? substituteParams(wrappedSequence) : wrappedSequence;
result.push(stopString);
}
}
}
const result = [];
if (power_user.instruct.enabled) {
const input_sequence = power_user.instruct.input_sequence;
const output_sequence = power_user.instruct.output_sequence;
const last_output_sequence = power_user.instruct.last_output_sequence;
const combined_sequence = `${input_sequence}\n${output_sequence}\n${last_output_sequence}`;
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
}
return result;
}
/**
* Formats instruct mode chat message.
* @param {string} name Character name.
* @param {string} mes Message text.
* @param {boolean} isUser Is the message from the user.
* @param {boolean} isNarrator Is the message from the narrator.
* @param {string} forceAvatar Force avatar string.
* @param {string} name1 User name.
* @param {string} name2 Character name.
* @returns {string} Formatted instruct mode chat message.
*/
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
let includeNames = isNarrator ? false : power_user.instruct.names;
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
includeNames = true;
}
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
if (power_user.instruct.macro) {
sequence = substituteParams(sequence, name1, name2);
}
const separator = power_user.instruct.wrap ? '\n' : '';
const separatorSequence = power_user.instruct.separator_sequence && !isUser
? power_user.instruct.separator_sequence
: separator;
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
const text = textArray.filter(x => x).join(separator);
return text;
}
export function formatInstructStoryString(story, systemPrompt) {
// If the character has a custom system prompt AND user has it preferred, use that instead of the default
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt;
const sequence = power_user.instruct.system_sequence || '';
const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || '';
const separator = power_user.instruct.wrap ? '\n' : '';
const textArray = [sequence, prompt + '\n' + story];
const text = textArray.filter(x => x).join(separator);
return text;
}
/**
* Formats instruct mode last prompt line.
* @param {string} name Character name.
* @param {boolean} isImpersonate Is generation in impersonation mode.
* @param {string} promptBias Prompt bias string.
* @param {string} name1 User name.
* @param {string} name2 Character name.
*/
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
if (power_user.instruct.macro) {
sequence = substituteParams(sequence, name1, name2);
}
const separator = power_user.instruct.wrap ? '\n' : '';
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
if (!isImpersonate && promptBias) {
text += (includeNames ? promptBias : (separator + promptBias));
}
return text.trimEnd() + (includeNames ? '' : separator);
}

View File

@ -12,8 +12,6 @@ import {
event_types,
getCurrentChatId,
printCharacters,
name1,
name2,
setCharacterId,
setEditedMessageId
} from "../script.js";
@ -21,8 +19,8 @@ import { isMobile, initMovingUI } from "./RossAscends-mods.js";
import {
groups,
resetSelectedGroup,
selected_group,
} from "./group-chats.js";
import { loadInstructMode } from "./instruct-mode.js";
import { registerSlashCommand } from "./slash-commands.js";
@ -206,7 +204,6 @@ let power_user = {
let themes = [];
let movingUIPresets = [];
let instruct_presets = [];
let context_presets = [];
const storage_keys = {
@ -666,9 +663,6 @@ function loadPowerUserSettings(settings, data) {
movingUIPresets = data.movingUIPresets;
}
if (data.instruct !== undefined) {
instruct_presets = data.instruct;
}
if (data.context !== undefined) {
context_presets = data.context;
@ -804,7 +798,7 @@ function loadPowerUserSettings(settings, data) {
$(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop("selected", true);
reloadMarkdownProcessor(power_user.render_formulas);
loadInstructMode();
loadInstructMode(data);
loadContextSettings();
loadMaxContextUnlocked();
switchWaifuMode();
@ -928,90 +922,6 @@ function loadContextSettings() {
});
}
function loadInstructMode() {
const controls = [
{ id: "instruct_enabled", property: "enabled", isCheckbox: true },
{ id: "instruct_wrap", property: "wrap", isCheckbox: true },
{ id: "instruct_system_prompt", property: "system_prompt", isCheckbox: false },
{ id: "instruct_system_sequence", property: "system_sequence", isCheckbox: false },
{ id: "instruct_separator_sequence", property: "separator_sequence", isCheckbox: false },
{ id: "instruct_input_sequence", property: "input_sequence", isCheckbox: false },
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
{ id: "instruct_names", property: "names", isCheckbox: true },
{ id: "instruct_macro", property: "macro", isCheckbox: true },
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
{ id: "instruct_last_output_sequence", property: "last_output_sequence", isCheckbox: false },
{ id: "instruct_activation_regex", property: "activation_regex", isCheckbox: false },
];
if (power_user.instruct.names_force_groups === undefined) {
power_user.instruct.names_force_groups = true;
}
controls.forEach(control => {
const $element = $(`#${control.id}`);
if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]);
} else {
$element.val(power_user.instruct[control.property]);
}
$element.on('input', function () {
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
saveSettingsDebounced();
});
});
instruct_presets.forEach((preset) => {
const name = preset.name;
const option = document.createElement('option');
option.value = name;
option.innerText = name;
option.selected = name === power_user.instruct.preset;
$('#instruct_presets').append(option);
});
function highlightDefaultPreset() {
$('#instruct_set_default').toggleClass('default', power_user.default_instruct === power_user.instruct.preset);
}
$('#instruct_set_default').on('click', function () {
power_user.default_instruct = power_user.instruct.preset;
$(this).addClass('default');
toastr.success(`Default instruct preset set to ${power_user.default_instruct}`);
saveSettingsDebounced();
});
highlightDefaultPreset();
$('#instruct_presets').on('change', function () {
const name = $(this).find(':selected').val();
const preset = instruct_presets.find(x => x.name === name);
if (!preset) {
return;
}
power_user.instruct.preset = name;
controls.forEach(control => {
if (preset[control.property] !== undefined) {
power_user.instruct[control.property] = preset[control.property];
const $element = $(`#${control.id}`);
if (control.isCheckbox) {
$element.prop('checked', power_user.instruct[control.property]).trigger('input');
} else {
$element.val(power_user.instruct[control.property]).trigger('input');
}
}
});
highlightDefaultPreset();
});
}
export function fuzzySearchCharacters(searchValue) {
const fuse = new Fuse(characters, {
keys: [
@ -1068,58 +978,6 @@ export function renderStoryString(params) {
}
}
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
let includeNames = isNarrator ? false : power_user.instruct.names;
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
includeNames = true;
}
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
if (power_user.instruct.macro) {
sequence = substituteParams(sequence, name1, name2);
}
const separator = power_user.instruct.wrap ? '\n' : '';
const separatorSequence = power_user.instruct.separator_sequence && !isUser
? power_user.instruct.separator_sequence
: separator;
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
const text = textArray.filter(x => x).join(separator);
return text;
}
export function formatInstructStoryString(story, systemPrompt) {
// If the character has a custom system prompt AND user has it preferred, use that instead of the default
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt;
const sequence = power_user.instruct.system_sequence || '';
const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || '';
const separator = power_user.instruct.wrap ? '\n' : '';
const textArray = [sequence, prompt + '\n' + story];
const text = textArray.filter(x => x).join(separator);
return text;
}
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
if (power_user.instruct.macro) {
sequence = substituteParams(sequence, name1, name2);
}
const separator = power_user.instruct.wrap ? '\n' : '';
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
if (!isImpersonate && promptBias) {
text += (includeNames ? promptBias : (separator + promptBias));
}
return text.trimEnd() + (includeNames ? '' : separator);
}
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
const compareFunc = (first, second) => {
if (power_user.sort_order == 'random') {

View File

@ -55,8 +55,10 @@ function autoSelectPreset() {
}
}
function getPresetManager() {
const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
function getPresetManager(apiId) {
if (!apiId) {
apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
}
if (!Object.keys(presetManagers).includes(apiId)) {
return null;
@ -289,9 +291,11 @@ jQuery(async () => {
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
registerPresetManagers();
$(document).on("click", "[data-preset-manager-update]", async function () {
const presetManager = getPresetManager();
const apiId = $(this).data("preset-manager-update");
const presetManager = getPresetManager(apiId);
if (!presetManager) {
console.warn(`Preset Manager not found for API: ${apiId}`);
return;
}
@ -299,9 +303,11 @@ jQuery(async () => {
});
$(document).on("click", "[data-preset-manager-new]", async function () {
const presetManager = getPresetManager();
const apiId = $(this).data("preset-manager-new");
const presetManager = getPresetManager(apiId);
if (!presetManager) {
console.warn(`Preset Manager not found for API: ${apiId}`);
return;
}
@ -309,9 +315,11 @@ jQuery(async () => {
});
$(document).on("click", "[data-preset-manager-export]", async function () {
const presetManager = getPresetManager();
const apiId = $(this).data("preset-manager-export");
const presetManager = getPresetManager(apiId);
if (!presetManager) {
console.warn(`Preset Manager not found for API: ${apiId}`);
return;
}
@ -348,9 +356,11 @@ jQuery(async () => {
});
$(document).on("click", "[data-preset-manager-delete]", async function () {
const presetManager = getPresetManager();
const apiId = $(this).data("preset-manager-delete");
const presetManager = getPresetManager(apiId);
if (!presetManager) {
console.warn(`Preset Manager not found for API: ${apiId}`);
return;
}