Spaces:
Paused
Paused
| 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 === undefined) { | |
| toastr.warning('Select a character before trying to configure CFG', '', { timeOut: 2000 }); | |
| return; | |
| } | |
| //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); | |
| } | |
| 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 based on the guidance scale. | |
| * @param {{type: number, value: number}} guidanceScale The CFG guidance scale | |
| * @param {boolean} isNegative Whether to get the negative prompt | |
| * @param {boolean} quiet Whether to suppress console output | |
| * @returns {{value: string, depth: number}} The CFG prompt and insertion depth | |
| */ | |
| export function getCfgPrompt(guidanceScale, isNegative, quiet = false) { | |
| 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; | |
| !quiet && console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedCfgPrompt}`); | |
| return { | |
| value: combinedCfgPrompt, | |
| depth: insertionDepth, | |
| }; | |
| } | |