import { chat_metadata, substituteParams, this_chid, eventSource, event_types, saveSettingsDebounced, } 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: 250, }, 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: 250, }, async function () { await delay(50); $('#cfgConfig').removeClass('resizing') }); setTimeout(function () { $('#cfgConfig').hide(); }, 250); } //duplicate options menu close handler from script.js //because this listener takes priority $('#options').stop().fadeOut(250); } 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: 200, easing: 'ease-in-out', }); setTimeout(function () { $('#cfgConfig').hide() }, 200); }); $('#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 }; }