From 4a21ee03374e330b90711862315d2ba604afb681 Mon Sep 17 00:00:00 2001 From: kingbri Date: Sat, 12 Aug 2023 16:17:35 -0400 Subject: [PATCH] CFG: Add ability to combine negative prompts This allows for flexibility with global, character, and chat negative prompts. Combining prompts is very useful for users who want to maintain a set of global negatives and then add extra layers on top of that. The ordering is chat -> character -> global tags due to the specificity of each. The guidance scale follows the cascade of chat -> character -> global due to being one number that is set when CFG is fired. If the guidance scale is 1, nothing happens. Signed-off-by: kingbri --- public/scripts/extensions/cfg/index.js | 64 ++++++++++--------- public/scripts/extensions/cfg/util.js | 75 +++++++++++++++++------ public/scripts/extensions/cfg/window.html | 32 ++++++++-- 3 files changed, 114 insertions(+), 57 deletions(-) diff --git a/public/scripts/extensions/cfg/index.js b/public/scripts/extensions/cfg/index.js index 05e9ebb86..94192af0c 100644 --- a/public/scripts/extensions/cfg/index.js +++ b/public/scripts/extensions/cfg/index.js @@ -7,22 +7,23 @@ import { } from "../../../script.js"; import { selected_group } from "../../group-chats.js"; import { extension_settings, saveMetadataDebounced } from "../../extensions.js"; -import { getCharaFilename, delay } from "../../utils.js"; +import { getCharaFilename, delay, debounce } from "../../utils.js"; import { power_user } from "../../power-user.js"; +import { metadataKeys } from "./util.js"; // Keep track of where your extension is located, name should match repo name const extensionName = "cfg"; const extensionFolderPath = `scripts/extensions/${extensionName}`; const defaultSettings = { - "global": { + global: { "guidance_scale": 1, "negative_prompt": '' }, - "chara": [] + chara: [] }; const settingType = { - "guidance_scale": 0, - "negative_prompt": 1 + guidance_scale: 0, + negative_prompt: 1 } // Used for character and chat CFG values @@ -64,7 +65,7 @@ function setCharCfg(tempValue, setting) { if (extension_settings.cfg.chara && existingCharaCfg) { const tempAssign = Object.assign(existingCharaCfg, tempCharaCfg); - // if both values are default, remove the entry + // If both values are default, remove the entry if (!existingCharaCfg.useChara && (tempAssign.guidance_scale ?? 1.00) === 1.00 && (tempAssign.negative_prompt?.length ?? 0) === 0) { extension_settings.cfg.chara.splice(existingCharaCfgIndex, 1); } @@ -73,8 +74,6 @@ function setCharCfg(tempValue, setting) { extension_settings.cfg.chara = [] } - Object.assign(tempCharaCfg, { useChara: false }) - extension_settings.cfg.chara.push(tempCharaCfg); } else { console.log("Character CFG error: No avatar name key could be found."); @@ -88,28 +87,13 @@ function setCharCfg(tempValue, setting) { return true; } -function setCharCfgCheckbox() { - const value = !!$(this).prop('checked'); - const charaCfgIndex = extension_settings.cfg.chara.findIndex((e) => e.name === getCharaFilename()); - const charaCfg = extension_settings.cfg.chara[charaCfgIndex]; - if (charaCfg) { - if (!value && (charaCfg.guidance_scale ?? 1.00) === 1.00 && (charaCfg.negative_prompt?.length ?? 0) === 0) { - extension_settings.cfg.chara.splice(charaCfgIndex, 1); - } else { - charaCfg.useChara = value; - } - - updateSettings(); - } -} - function setChatCfg(tempValue, setting) { switch(setting) { case settingType.guidance_scale: - chat_metadata['guidance_scale'] = tempValue; + chat_metadata[metadataKeys.guidance_scale] = tempValue; break; case settingType.negative_prompt: - chat_metadata['negative_prompt'] = tempValue; + chat_metadata[metadataKeys.negative_prompt] = tempValue; break; default: return false; @@ -173,18 +157,24 @@ function onChatChanged() { } // Reloads chat-specific settings +// TODO: Fix race condition bug where deleted chara CFG still loads previous prompts function loadSettings() { // Set chat CFG if it exists - $('#chat_cfg_guidance_scale').val(chat_metadata['guidance_scale'] ?? 1.00); - $('#chat_cfg_guidance_scale_counter').text(chat_metadata['guidance_scale']?.toFixed(2) ?? 1.00); - $('#chat_cfg_negative_prompt').val(chat_metadata['negative_prompt'] ?? ''); + $('#chat_cfg_guidance_scale').val(chat_metadata[metadataKeys.guidance_scale] ?? 1.0.toFixed(2)); + $('#chat_cfg_guidance_scale_counter').text(chat_metadata[metadataKeys.guidance_scale]?.toFixed(2) ?? 1.0.toFixed(2)); + $('#chat_cfg_negative_prompt').val(chat_metadata[metadataKeys.negative_prompt] ?? ''); + if (chat_metadata[metadataKeys.negative_combine]?.length > 0) { + chat_metadata[metadataKeys.negative_combine].forEach((element) => { + $(`input[name="cfg_negative_combine"][value="${element}"]`) + .prop("checked", true); + }); + } // Set character CFG if it exists 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').text(charaCfg?.guidance_scale?.toFixed(2) ?? 1.00); + $('#chara_cfg_guidance_scale_counter').text(charaCfg?.guidance_scale?.toFixed(2) ?? 1.0.toFixed(2)); $('#chara_cfg_negative_prompt').val(charaCfg?.negative_prompt ?? ''); - $('#use_chara_cfg').prop('checked', charaCfg?.useChara ?? false); } // Load initial extension settings @@ -251,6 +241,17 @@ jQuery(async () => { setChatCfg($(this).val(), settingType.negative_prompt); }); + windowHtml.find(`input[name="cfg_negative_combine"]`).on('input', function() { + const values = windowHtml.find(`input[name="cfg_negative_combine"]`) + .filter(":checked") + .map(function() { return parseInt($(this).val()) }) + .get() + .filter((e) => e !== NaN) || []; + + chat_metadata[metadataKeys.negative_combine] = values; + saveMetadataDebounced(); + }); + windowHtml.find('#chara_cfg_guidance_scale').on('input', function() { const value = $(this).val(); const success = setCharCfg(value, settingType.guidance_scale); @@ -263,12 +264,9 @@ jQuery(async () => { setCharCfg($(this).val(), settingType.negative_prompt); }); - windowHtml.find('#use_chara_cfg').on('input', setCharCfgCheckbox); - windowHtml.find('#global_cfg_guidance_scale').on('input', function() { extension_settings.cfg.global.guidance_scale = Number($(this).val()); $('#global_cfg_guidance_scale_counter').text(extension_settings.cfg.global.guidance_scale.toFixed(2)); - console.log(extension_settings.cfg.global.guidance_scale) saveSettingsDebounced(); }); diff --git a/public/scripts/extensions/cfg/util.js b/public/scripts/extensions/cfg/util.js index 91997ba39..dba090965 100644 --- a/public/scripts/extensions/cfg/util.js +++ b/public/scripts/extensions/cfg/util.js @@ -2,32 +2,67 @@ import { chat_metadata } from "../../../script.js"; import { extension_settings } from "../../extensions.js" import { getCharaFilename } from "../../utils.js"; +export const cfgType = { + chat: 0, + chara: 1, + global: 2 +} +export const metadataKeys = { + guidance_scale: "cfg_guidance_scale", + negative_prompt: "cfg_negative_prompt", + negative_combine: "cfg_negative_combine" +} + +// TODO: Add groupchat support and fetch the CFG values for the current character + // Gets the CFG value from hierarchy of chat -> character -> global // Returns undefined values which should be handled in the respective backend APIs -// If the guidance scale is 1, ignore the CFG negative prompt since it won't be used anyways - -// TODO: Add the ability to combine negative prompts if specified. Proposed, chat + global and chat + chara -// TODO: Add groupchat support and fetch the CFG values for the current character export function getCfg() { - if (chat_metadata['guidance_scale'] !== 1) { - return { - guidanceScale: chat_metadata['guidance_scale'], - negativePrompt: chat_metadata['negative_prompt'] - } - } + let splitNegativePrompt = []; + const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename()); + const guidanceScale = getGuidanceScale(charaCfg); + const chatNegativeCombine = chat_metadata[metadataKeys.negative_combine]; - const charaCfg = extension_settings.cfg.chara.find((e) => e.name === getCharaFilename()); - if (charaCfg && charaCfg?.useChara) { - if (charaCfg.guidance_scale !== 1) { - return { - guidanceScale: charaCfg.guidance_scale, - negativePrompt: charaCfg.negative_prompt - } + // If there's a guidance scale, continue. Otherwise assume undefined + if (guidanceScale?.value && guidanceScale?.value !== 1) { + if (guidanceScale.type === cfgType.chat || chatNegativeCombine.includes(cfgType.chat)) { + splitNegativePrompt.push(chat_metadata[metadataKeys.negative_prompt]?.trim()); } - } else if (extension_settings.cfg.global?.guidance_scale !== 1) { + + if (guidanceScale.type === cfgType.chara || chatNegativeCombine.includes(cfgType.chara)) { + splitNegativePrompt.push(charaCfg.negative_prompt?.trim()) + } + + if (guidanceScale.type === cfgType.global || chatNegativeCombine.includes(cfgType.global)) { + splitNegativePrompt.push(extension_settings.cfg.global.negative_prompt?.trim()); + } + return { - guidanceScale: extension_settings.cfg.global.guidance_scale, - negativePrompt: extension_settings.cfg.global.negative_prompt + guidanceScale: guidanceScale.value, + negativePrompt: splitNegativePrompt.filter((e) => e.length > 0).join(", ") } } } + +// If the guidance scale is 1, ignore the CFG negative prompt since it won't be used anyways +function getGuidanceScale(charaCfg) { + const chatGuidanceScale = chat_metadata[metadataKeys.guidance_scale]; + if (chatGuidanceScale && chatGuidanceScale !== 1) { + return { + type: cfgType.chat, + value: chatGuidanceScale + }; + } + + if (charaCfg && charaCfg.guidance_scale !== 1) { + return { + type: cfgType.chara, + value: charaCfg.guidance_scale + }; + } + + return { + type: cfgType.global, + value: extension_settings.cfg.global.guidance_scale + }; +} diff --git a/public/scripts/extensions/cfg/window.html b/public/scripts/extensions/cfg/window.html index 92225f355..7945b58cc 100644 --- a/public/scripts/extensions/cfg/window.html +++ b/public/scripts/extensions/cfg/window.html @@ -63,10 +63,6 @@ -
@@ -99,5 +95,33 @@ +
+
+
+ Negative Cascading +
+
+
+ + Combine negative prompts from other boxes. For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string. + +
+ + + + +
+