mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2024-12-15 18:59:07 +01:00
494 lines
17 KiB
JavaScript
494 lines
17 KiB
JavaScript
import {
|
|
chat_metadata,
|
|
substituteParams,
|
|
this_chid,
|
|
eventSource,
|
|
event_types,
|
|
saveSettingsDebounced,
|
|
animation_duration,
|
|
} from '../script.js';
|
|
import { extension_settings, saveMetadataDebounced } from './extensions.js';
|
|
import { selected_group } from './group-chats.js';
|
|
import { getCharaFilename, delay } from './utils.js';
|
|
import { power_user } from './power-user.js';
|
|
|
|
const extensionName = 'cfg';
|
|
const defaultSettings = {
|
|
global: {
|
|
'guidance_scale': 1,
|
|
'negative_prompt': '',
|
|
},
|
|
chara: [],
|
|
};
|
|
const settingType = {
|
|
guidance_scale: 0,
|
|
negative_prompt: 1,
|
|
positive_prompt: 2,
|
|
};
|
|
|
|
// Used for character and chat CFG values
|
|
function updateSettings() {
|
|
saveSettingsDebounced();
|
|
loadSettings();
|
|
}
|
|
|
|
function setCharCfg(tempValue, setting) {
|
|
const avatarName = getCharaFilename();
|
|
|
|
// Assign temp object
|
|
let tempCharaCfg = {
|
|
name: avatarName,
|
|
};
|
|
|
|
switch(setting) {
|
|
case settingType.guidance_scale:
|
|
tempCharaCfg['guidance_scale'] = Number(tempValue);
|
|
break;
|
|
case settingType.negative_prompt:
|
|
tempCharaCfg['negative_prompt'] = tempValue;
|
|
break;
|
|
case settingType.positive_prompt:
|
|
tempCharaCfg['positive_prompt'] = tempValue;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
let existingCharaCfgIndex;
|
|
let existingCharaCfg;
|
|
|
|
if (extension_settings.cfg.chara) {
|
|
existingCharaCfgIndex = extension_settings.cfg.chara.findIndex((e) => e.name === avatarName);
|
|
existingCharaCfg = extension_settings.cfg.chara[existingCharaCfgIndex];
|
|
}
|
|
|
|
if (extension_settings.cfg.chara && existingCharaCfg) {
|
|
const tempAssign = Object.assign(existingCharaCfg, tempCharaCfg);
|
|
|
|
// If both values are default, remove the entry
|
|
if (!existingCharaCfg.useChara &&
|
|
(tempAssign.guidance_scale ?? 1.00) === 1.00 &&
|
|
(tempAssign.negative_prompt?.length ?? 0) === 0 &&
|
|
(tempAssign.positive_prompt?.length ?? 0) === 0)
|
|
{
|
|
extension_settings.cfg.chara.splice(existingCharaCfgIndex, 1);
|
|
}
|
|
} else if (avatarName && tempValue.length > 0) {
|
|
if (!extension_settings.cfg.chara) {
|
|
extension_settings.cfg.chara = [];
|
|
}
|
|
|
|
extension_settings.cfg.chara.push(tempCharaCfg);
|
|
} else {
|
|
console.debug('Character CFG error: No avatar name key could be found.');
|
|
|
|
// Don't save settings if something went wrong
|
|
return false;
|
|
}
|
|
|
|
updateSettings();
|
|
|
|
return true;
|
|
}
|
|
|
|
function setChatCfg(tempValue, setting) {
|
|
switch(setting) {
|
|
case settingType.guidance_scale:
|
|
chat_metadata[metadataKeys.guidance_scale] = tempValue;
|
|
break;
|
|
case settingType.negative_prompt:
|
|
chat_metadata[metadataKeys.negative_prompt] = tempValue;
|
|
break;
|
|
case settingType.positive_prompt:
|
|
chat_metadata[metadataKeys.positive_prompt] = tempValue;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
saveMetadataDebounced();
|
|
|
|
return true;
|
|
}
|
|
|
|
// TODO: Only change CFG when character is selected
|
|
function onCfgMenuItemClick() {
|
|
if (selected_group || this_chid) {
|
|
//show CFG config if it's hidden
|
|
if ($('#cfgConfig').css('display') !== 'flex') {
|
|
$('#cfgConfig').addClass('resizing');
|
|
$('#cfgConfig').css('display', 'flex');
|
|
$('#cfgConfig').css('opacity', 0.0);
|
|
$('#cfgConfig').transition({
|
|
opacity: 1.0,
|
|
duration: animation_duration,
|
|
}, async function () {
|
|
await delay(50);
|
|
$('#cfgConfig').removeClass('resizing');
|
|
});
|
|
|
|
//auto-open the main AN inline drawer
|
|
if ($('#CFGBlockToggle')
|
|
.siblings('.inline-drawer-content')
|
|
.css('display') !== 'block') {
|
|
$('#floatingPrompt').addClass('resizing');
|
|
$('#CFGBlockToggle').click();
|
|
}
|
|
} else {
|
|
//hide AN if it's already displayed
|
|
$('#cfgConfig').addClass('resizing');
|
|
$('#cfgConfig').transition({
|
|
opacity: 0.0,
|
|
duration: animation_duration,
|
|
},
|
|
async function () {
|
|
await delay(50);
|
|
$('#cfgConfig').removeClass('resizing');
|
|
});
|
|
setTimeout(function () {
|
|
$('#cfgConfig').hide();
|
|
}, animation_duration);
|
|
|
|
}
|
|
//duplicate options menu close handler from script.js
|
|
//because this listener takes priority
|
|
$('#options').stop().fadeOut(animation_duration);
|
|
} else {
|
|
toastr.warning('Select a character before trying to configure CFG', '', { timeOut: 2000 });
|
|
}
|
|
}
|
|
|
|
async function onChatChanged() {
|
|
loadSettings();
|
|
await modifyCharaHtml();
|
|
}
|
|
|
|
// Rearrange the panel if a group chat is present
|
|
async function modifyCharaHtml() {
|
|
if (selected_group) {
|
|
$('#chara_cfg_container').hide();
|
|
$('#groupchat_cfg_use_chara_container').show();
|
|
} else {
|
|
$('#chara_cfg_container').show();
|
|
$('#groupchat_cfg_use_chara_container').hide();
|
|
// TODO: Remove chat checkbox here
|
|
}
|
|
}
|
|
|
|
// Reloads chat-specific settings
|
|
function loadSettings() {
|
|
// Set chat CFG if it exists
|
|
$('#chat_cfg_guidance_scale').val(chat_metadata[metadataKeys.guidance_scale] ?? 1.0.toFixed(2));
|
|
$('#chat_cfg_guidance_scale_counter').val(chat_metadata[metadataKeys.guidance_scale]?.toFixed(2) ?? 1.0.toFixed(2));
|
|
$('#chat_cfg_negative_prompt').val(chat_metadata[metadataKeys.negative_prompt] ?? '');
|
|
$('#chat_cfg_positive_prompt').val(chat_metadata[metadataKeys.positive_prompt] ?? '');
|
|
$('#groupchat_cfg_use_chara').prop('checked', chat_metadata[metadataKeys.groupchat_individual_chars] ?? false);
|
|
if (chat_metadata[metadataKeys.prompt_combine]?.length > 0) {
|
|
chat_metadata[metadataKeys.prompt_combine].forEach((element) => {
|
|
$(`input[name="cfg_prompt_combine"][value="${element}"]`)
|
|
.prop('checked', true);
|
|
});
|
|
}
|
|
|
|
// Display the negative separator in quotes if not quoted already
|
|
let promptSeparatorDisplay = [];
|
|
const promptSeparator = chat_metadata[metadataKeys.prompt_separator];
|
|
if (promptSeparator) {
|
|
promptSeparatorDisplay.push(promptSeparator);
|
|
if (!promptSeparator.startsWith('"')) {
|
|
promptSeparatorDisplay.unshift('"');
|
|
}
|
|
|
|
if (!promptSeparator.endsWith('"')) {
|
|
promptSeparatorDisplay.push('"');
|
|
}
|
|
}
|
|
|
|
$('#cfg_prompt_separator').val(promptSeparatorDisplay.length === 0 ? '' : promptSeparatorDisplay.join(''));
|
|
|
|
$('#cfg_prompt_insertion_depth').val(chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1);
|
|
|
|
// Set character CFG if it exists
|
|
if (!selected_group) {
|
|
const charaCfg = extension_settings.cfg.chara.find((e) => e.name === getCharaFilename());
|
|
$('#chara_cfg_guidance_scale').val(charaCfg?.guidance_scale ?? 1.00);
|
|
$('#chara_cfg_guidance_scale_counter').val(charaCfg?.guidance_scale?.toFixed(2) ?? 1.0.toFixed(2));
|
|
$('#chara_cfg_negative_prompt').val(charaCfg?.negative_prompt ?? '');
|
|
$('#chara_cfg_positive_prompt').val(charaCfg?.positive_prompt ?? '');
|
|
}
|
|
}
|
|
|
|
// Load initial extension settings
|
|
async function initialLoadSettings() {
|
|
// Create the settings if they don't exist
|
|
extension_settings[extensionName] = extension_settings[extensionName] || {};
|
|
if (Object.keys(extension_settings[extensionName]).length === 0) {
|
|
Object.assign(extension_settings[extensionName], defaultSettings);
|
|
saveSettingsDebounced();
|
|
}
|
|
|
|
// Set global CFG values on load
|
|
$('#global_cfg_guidance_scale').val(extension_settings.cfg.global.guidance_scale);
|
|
$('#global_cfg_guidance_scale_counter').val(extension_settings.cfg.global.guidance_scale.toFixed(2));
|
|
$('#global_cfg_negative_prompt').val(extension_settings.cfg.global.negative_prompt);
|
|
$('#global_cfg_positive_prompt').val(extension_settings.cfg.global.positive_prompt);
|
|
}
|
|
|
|
function migrateSettings() {
|
|
let performSettingsSave = false;
|
|
let performMetaSave = false;
|
|
|
|
if (power_user.guidance_scale) {
|
|
extension_settings.cfg.global.guidance_scale = power_user.guidance_scale;
|
|
delete power_user['guidance_scale'];
|
|
performSettingsSave = true;
|
|
}
|
|
|
|
if (power_user.negative_prompt) {
|
|
extension_settings.cfg.global.negative_prompt = power_user.negative_prompt;
|
|
delete power_user['negative_prompt'];
|
|
performSettingsSave = true;
|
|
}
|
|
|
|
if (chat_metadata['cfg_negative_combine']) {
|
|
chat_metadata[metadataKeys.prompt_combine] = chat_metadata['cfg_negative_combine'];
|
|
chat_metadata['cfg_negative_combine'] = undefined;
|
|
performMetaSave = true;
|
|
}
|
|
|
|
if (chat_metadata['cfg_negative_insertion_depth']) {
|
|
chat_metadata[metadataKeys.prompt_insertion_depth] = chat_metadata['cfg_negative_insertion_depth'];
|
|
chat_metadata['cfg_negative_insertion_depth'] = undefined;
|
|
performMetaSave = true;
|
|
}
|
|
|
|
if (chat_metadata['cfg_negative_separator']) {
|
|
chat_metadata[metadataKeys.prompt_separator] = chat_metadata['cfg_negative_separator'];
|
|
chat_metadata['cfg_negative_separator'] = undefined;
|
|
performMetaSave = true;
|
|
}
|
|
|
|
if (performSettingsSave) {
|
|
saveSettingsDebounced();
|
|
}
|
|
|
|
if (performMetaSave) {
|
|
saveMetadataDebounced();
|
|
}
|
|
}
|
|
|
|
// This function is called when the extension is loaded
|
|
export function initCfg() {
|
|
$('#CFGClose').on('click', function () {
|
|
$('#cfgConfig').transition({
|
|
opacity: 0,
|
|
duration: animation_duration,
|
|
easing: 'ease-in-out',
|
|
});
|
|
setTimeout(function () { $('#cfgConfig').hide(); }, animation_duration);
|
|
});
|
|
|
|
$('#chat_cfg_guidance_scale').on('input', function() {
|
|
const numberValue = Number($(this).val());
|
|
const success = setChatCfg(numberValue, settingType.guidance_scale);
|
|
if (success) {
|
|
$('#chat_cfg_guidance_scale_counter').val(numberValue.toFixed(2));
|
|
}
|
|
});
|
|
|
|
$('#chat_cfg_negative_prompt').on('input', function() {
|
|
setChatCfg($(this).val(), settingType.negative_prompt);
|
|
});
|
|
|
|
$('#chat_cfg_positive_prompt').on('input', function() {
|
|
setChatCfg($(this).val(), settingType.positive_prompt);
|
|
});
|
|
|
|
$('#chara_cfg_guidance_scale').on('input', function() {
|
|
const value = $(this).val();
|
|
const success = setCharCfg(value, settingType.guidance_scale);
|
|
if (success) {
|
|
$('#chara_cfg_guidance_scale_counter').val(Number(value).toFixed(2));
|
|
}
|
|
});
|
|
|
|
$('#chara_cfg_negative_prompt').on('input', function() {
|
|
setCharCfg($(this).val(), settingType.negative_prompt);
|
|
});
|
|
|
|
$('#chara_cfg_positive_prompt').on('input', function() {
|
|
setCharCfg($(this).val(), settingType.positive_prompt);
|
|
});
|
|
|
|
$('#global_cfg_guidance_scale').on('input', function() {
|
|
extension_settings.cfg.global.guidance_scale = Number($(this).val());
|
|
$('#global_cfg_guidance_scale_counter').val(extension_settings.cfg.global.guidance_scale.toFixed(2));
|
|
saveSettingsDebounced();
|
|
});
|
|
|
|
$('#global_cfg_negative_prompt').on('input', function() {
|
|
extension_settings.cfg.global.negative_prompt = $(this).val();
|
|
saveSettingsDebounced();
|
|
});
|
|
|
|
$('#global_cfg_positive_prompt').on('input', function() {
|
|
extension_settings.cfg.global.positive_prompt = $(this).val();
|
|
saveSettingsDebounced();
|
|
});
|
|
|
|
$('input[name="cfg_prompt_combine"]').on('input', function() {
|
|
const values = $('#cfgConfig').find('input[name="cfg_prompt_combine"]')
|
|
.filter(':checked')
|
|
.map(function() { return Number($(this).val()); })
|
|
.get()
|
|
.filter((e) => !Number.isNaN(e)) || [];
|
|
|
|
chat_metadata[metadataKeys.prompt_combine] = values;
|
|
saveMetadataDebounced();
|
|
});
|
|
|
|
$('#cfg_prompt_insertion_depth').on('input', function() {
|
|
chat_metadata[metadataKeys.prompt_insertion_depth] = Number($(this).val());
|
|
saveMetadataDebounced();
|
|
});
|
|
|
|
$('#cfg_prompt_separator').on('input', function() {
|
|
chat_metadata[metadataKeys.prompt_separator] = $(this).val();
|
|
saveMetadataDebounced();
|
|
});
|
|
|
|
$('#groupchat_cfg_use_chara').on('input', function() {
|
|
const checked = !!$(this).prop('checked');
|
|
chat_metadata[metadataKeys.groupchat_individual_chars] = checked;
|
|
|
|
if (checked) {
|
|
toastr.info('You can edit character CFG values in their respective character chats.');
|
|
}
|
|
|
|
saveMetadataDebounced();
|
|
});
|
|
|
|
initialLoadSettings();
|
|
|
|
if (extension_settings.cfg) {
|
|
migrateSettings();
|
|
}
|
|
|
|
$('#option_toggle_CFG').on('click', onCfgMenuItemClick);
|
|
|
|
// Hook events
|
|
eventSource.on(event_types.CHAT_CHANGED, async () => {
|
|
await onChatChanged();
|
|
});
|
|
}
|
|
|
|
export const cfgType = {
|
|
chat: 0,
|
|
chara: 1,
|
|
global: 2,
|
|
};
|
|
|
|
export const metadataKeys = {
|
|
guidance_scale: 'cfg_guidance_scale',
|
|
negative_prompt: 'cfg_negative_prompt',
|
|
positive_prompt: 'cfg_positive_prompt',
|
|
prompt_combine: 'cfg_prompt_combine',
|
|
groupchat_individual_chars: 'cfg_groupchat_individual_chars',
|
|
prompt_insertion_depth: 'cfg_prompt_insertion_depth',
|
|
prompt_separator: 'cfg_prompt_separator',
|
|
};
|
|
|
|
// Gets the CFG guidance scale
|
|
// If the guidance scale is 1, ignore the CFG prompt(s) since it won't be used anyways
|
|
export function getGuidanceScale() {
|
|
if (!extension_settings.cfg) {
|
|
console.warn('CFG extension is not enabled. Skipping CFG guidance.');
|
|
return;
|
|
}
|
|
|
|
const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid));
|
|
const chatGuidanceScale = chat_metadata[metadataKeys.guidance_scale];
|
|
const groupchatCharOverride = chat_metadata[metadataKeys.groupchat_individual_chars] ?? false;
|
|
|
|
if (chatGuidanceScale && chatGuidanceScale !== 1 && !groupchatCharOverride) {
|
|
return {
|
|
type: cfgType.chat,
|
|
value: chatGuidanceScale,
|
|
};
|
|
}
|
|
|
|
if ((!selected_group && charaCfg || groupchatCharOverride) && charaCfg?.guidance_scale !== 1) {
|
|
return {
|
|
type: cfgType.chara,
|
|
value: charaCfg.guidance_scale,
|
|
};
|
|
}
|
|
|
|
if (extension_settings.cfg.global && extension_settings.cfg.global?.guidance_scale !== 1) {
|
|
return {
|
|
type: cfgType.global,
|
|
value: extension_settings.cfg.global.guidance_scale,
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the CFG prompt separator.
|
|
* @returns {string} The CFG prompt separator
|
|
*/
|
|
function getCustomSeparator() {
|
|
const defaultSeparator = '\n';
|
|
|
|
try {
|
|
if (chat_metadata[metadataKeys.prompt_separator]) {
|
|
return JSON.parse(chat_metadata[metadataKeys.prompt_separator]);
|
|
}
|
|
|
|
return defaultSeparator;
|
|
} catch {
|
|
console.warn('Invalid JSON detected for prompt separator. Using default separator.');
|
|
return defaultSeparator;
|
|
}
|
|
}
|
|
|
|
// Gets the CFG prompt
|
|
export function getCfgPrompt(guidanceScale, isNegative) {
|
|
let splitCfgPrompt = [];
|
|
|
|
const cfgPromptCombine = chat_metadata[metadataKeys.prompt_combine] ?? [];
|
|
if (guidanceScale.type === cfgType.chat || cfgPromptCombine.includes(cfgType.chat)) {
|
|
splitCfgPrompt.unshift(
|
|
substituteParams(
|
|
chat_metadata[isNegative ? metadataKeys.negative_prompt : metadataKeys.positive_prompt],
|
|
),
|
|
);
|
|
}
|
|
|
|
const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid));
|
|
if (guidanceScale.type === cfgType.chara || cfgPromptCombine.includes(cfgType.chara)) {
|
|
splitCfgPrompt.unshift(
|
|
substituteParams(
|
|
isNegative ? charaCfg.negative_prompt : charaCfg.positive_prompt,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (guidanceScale.type === cfgType.global || cfgPromptCombine.includes(cfgType.global)) {
|
|
splitCfgPrompt.unshift(
|
|
substituteParams(
|
|
isNegative ? extension_settings.cfg.global.negative_prompt : extension_settings.cfg.global.positive_prompt,
|
|
),
|
|
);
|
|
}
|
|
|
|
const customSeparator = getCustomSeparator();
|
|
const combinedCfgPrompt = splitCfgPrompt.filter((e) => e.length > 0).join(customSeparator);
|
|
const insertionDepth = chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1;
|
|
console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedCfgPrompt}`);
|
|
|
|
return {
|
|
value: combinedCfgPrompt,
|
|
depth: insertionDepth,
|
|
};
|
|
}
|