From ac319dbd30882a8d6e3212bfc8b8a6b4dcd965ef Mon Sep 17 00:00:00 2001 From: kingbri Date: Sun, 20 Aug 2023 02:19:25 -0400 Subject: [PATCH] CFG: Add positive prompt support and fixes Positive prompts are the opposite of negative prompts. This helps make the mixing process more accurate by keeping the negative differences as close as possible to the positive ones by including this prompt. In addition, fix prompt insertion order at a depth of 0 by hijacking the same function used for Author's Note as a zero depth anchor. Signed-off-by: kingbri --- public/script.js | 55 ++++++---- public/scripts/extensions/cfg/index.js | 117 +++++++++++++++------- public/scripts/extensions/cfg/util.js | 56 +++++++---- public/scripts/extensions/cfg/window.html | 39 +++++--- 4 files changed, 180 insertions(+), 87 deletions(-) diff --git a/public/script.js b/public/script.js index c22628ff5..7567a7c02 100644 --- a/public/script.js +++ b/public/script.js @@ -2720,6 +2720,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, is_send_press = true; } + console.log(cycleGenerationPromt) generatedPromtCache += cycleGenerationPromt; if (generatedPromtCache.length == 0 || type === 'continue') { if (main_api === 'openai') { @@ -2871,17 +2872,28 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, setPromtString(); } + // Fetches the combined prompt for both negative and positive prompts const cfgGuidanceScale = getGuidanceScale(); function getCombinedPrompt(isNegative) { - if (isNegative && cfgGuidanceScale !== 1) { - const negativePrompt = getCfgPrompt(cfgGuidanceScale); - if (negativePrompt && negativePrompt?.value) { - // TODO: kingbri: use the insertion depth method instead of splicing - mesSend.splice(mesSend.length - negativePrompt.depth, 0, `${negativePrompt.value}\n`); + // Use a negative mesSend if present + let negativeMesSend = []; + let cfgPrompt = {}; + if (cfgGuidanceScale && cfgGuidanceScale?.value !== 1) { + cfgPrompt = getCfgPrompt(cfgGuidanceScale, isNegative); + } + + if (cfgPrompt && cfgPrompt?.value && cfgPrompt?.depth !== 0) { + const cfgPromptValue = `${cfgPrompt.value}\n` + // TODO: kingbri: use the insertion depth method instead of splicing + if (isNegative) { + negativeMesSend = [...mesSend]; + negativeMesSend.splice(mesSend.length - cfgPrompt.depth, 0, cfgPromptValue); + } else { + mesSend.splice(mesSend.length - cfgPrompt.depth, 0, cfgPromptValue); } } - let mesSendString = mesSend.join(''); + let mesSendString = isNegative ? negativeMesSend.join('') : mesSend.join(''); // add chat preamble mesSendString = addChatsPreamble(mesSendString); @@ -2889,12 +2901,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, // add a custom dingus (if defined) mesSendString = addChatsSeparator(mesSendString); - if (zeroDepthAnchor && zeroDepthAnchor.length) { - if (!isMultigenEnabled() || tokens_already_generated == 0) { - combinedPrompt = appendZeroDepthAnchor(force_name2, zeroDepthAnchor, combinedPrompt); - } - } - let combinedPrompt = storyString + afterScenarioAnchor + @@ -2902,6 +2908,19 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, mesSendString + generatedPromtCache; + if (zeroDepthAnchor && zeroDepthAnchor.length) { + if (!isMultigenEnabled() || tokens_already_generated == 0) { + combinedPrompt = appendZeroDepthAnchor(force_name2, zeroDepthAnchor, combinedPrompt); + } + } + + // Append zero-depth anchor for CFG + if (cfgPrompt && cfgPrompt?.value && cfgPrompt?.depth === 0) { + if (!isMultigenEnabled() || tokens_already_generated == 0) { + combinedPrompt = appendZeroDepthAnchor(force_name2, cfgPrompt.value, combinedPrompt); + } + } + combinedPrompt = combinedPrompt.replace(/\r/gm, ''); if (power_user.collapse_newlines) { @@ -2911,15 +2930,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, return combinedPrompt; } - let mesSendString = mesSend.join(''); - // add chat preamble - mesSendString = addChatsPreamble(mesSendString); - - // add a custom dingus (if defined) - mesSendString = addChatsSeparator(mesSendString); - - let finalPromt = getCombinedPrompt(false); + // Get the negative prompt first since it has the unmodified mesSend array let negativePrompt = getCombinedPrompt(true); + let finalPromt = getCombinedPrompt(false); const cfgValues = { guidanceScale: cfgGuidanceScale?.value, negativePrompt: negativePrompt @@ -3018,7 +3031,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, storyString: storyString, afterScenarioAnchor: afterScenarioAnchor, examplesString: examplesString, - mesSendString: mesSendString, + mesSendString: mesSend.join(''), generatedPromtCache: generatedPromtCache, promptBias: promptBias, finalPromt: finalPromt, diff --git a/public/scripts/extensions/cfg/index.js b/public/scripts/extensions/cfg/index.js index e38a255e0..c850ca6d6 100644 --- a/public/scripts/extensions/cfg/index.js +++ b/public/scripts/extensions/cfg/index.js @@ -23,7 +23,8 @@ const defaultSettings = { }; const settingType = { guidance_scale: 0, - negative_prompt: 1 + negative_prompt: 1, + positive_prompt: 2 } // Used for character and chat CFG values @@ -36,19 +37,19 @@ function setCharCfg(tempValue, setting) { const avatarName = getCharaFilename(); // Assign temp object - let tempCharaCfg; + let tempCharaCfg = { + name: avatarName + }; + switch(setting) { case settingType.guidance_scale: - tempCharaCfg = { - "name": avatarName, - "guidance_scale": Number(tempValue) - } + tempCharaCfg["guidance_scale"] = Number(tempValue); break; case settingType.negative_prompt: - tempCharaCfg = { - "name": avatarName, - "negative_prompt": tempValue - } + tempCharaCfg["negative_prompt"] = tempValue; + break; + case settingType.positive_prompt: + tempCharaCfg["positive_prompt"] = tempValue; break; default: return false; @@ -66,7 +67,11 @@ function setCharCfg(tempValue, setting) { 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) { + 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) { @@ -95,6 +100,9 @@ function setChatCfg(tempValue, setting) { 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; } @@ -174,31 +182,32 @@ function loadSettings() { $('#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] ?? ''); + $('#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.negative_combine]?.length > 0) { - chat_metadata[metadataKeys.negative_combine].forEach((element) => { - $(`input[name="cfg_negative_combine"][value="${element}"]`) + 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 negativeSeparatorDisplay = []; - const negativeSeparator = chat_metadata[metadataKeys.negative_separator]; - if (negativeSeparator) { - negativeSeparatorDisplay.push(negativeSeparator); - if (!negativeSeparator.startsWith(`"`)) { - negativeSeparatorDisplay.unshift(`"`); + let promptSeparatorDisplay = []; + const promptSeparator = chat_metadata[metadataKeys.prompt_separator]; + if (promptSeparator) { + promptSeparatorDisplay.push(promptSeparator); + if (!promptSeparator.startsWith(`"`)) { + promptSeparatorDisplay.unshift(`"`); } - if (!negativeSeparator.endsWith(`"`)) { - negativeSeparatorDisplay.push(`"`); + if (!promptSeparator.endsWith(`"`)) { + promptSeparatorDisplay.push(`"`); } } - $('#cfg_negative_separator').val(negativeSeparatorDisplay.length === 0 ? '' : negativeSeparatorDisplay.join('')); + $('#cfg_prompt_separator').val(promptSeparatorDisplay.length === 0 ? '' : promptSeparatorDisplay.join('')); - $('#cfg_negative_insertion_depth').val(chat_metadata[metadataKeys.negative_insertion_depth] ?? 1); + $('#cfg_prompt_insertion_depth').val(chat_metadata[metadataKeys.prompt_insertion_depth] ?? 1); // Set character CFG if it exists if (!selected_group) { @@ -206,6 +215,7 @@ function loadSettings() { $('#chara_cfg_guidance_scale').val(charaCfg?.guidance_scale ?? 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 ?? ''); + $('#chara_cfg_positive_prompt').val(charaCfg?.positive_prompt ?? ''); } } @@ -222,26 +232,50 @@ async function initialLoadSettings() { $('#global_cfg_guidance_scale').val(extension_settings.cfg.global.guidance_scale); $('#global_cfg_guidance_scale_counter').text(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 performSave = false; + 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']; - performSave = true; + performSettingsSave = true; } if (power_user.negative_prompt) { extension_settings.cfg.global.negative_prompt = power_user.negative_prompt; delete power_user['negative_prompt']; - performSave = true; + performSettingsSave = true; } - if (performSave) { + 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 @@ -273,6 +307,10 @@ jQuery(async () => { setChatCfg($(this).val(), settingType.negative_prompt); }); + windowHtml.find('#chat_cfg_positive_prompt').on('input', function() { + setChatCfg($(this).val(), settingType.positive_prompt); + }); + windowHtml.find('#chara_cfg_guidance_scale').on('input', function() { const value = $(this).val(); const success = setCharCfg(value, settingType.guidance_scale); @@ -285,6 +323,10 @@ jQuery(async () => { setCharCfg($(this).val(), settingType.negative_prompt); }); + windowHtml.find('#chara_cfg_positive_prompt').on('input', function() { + setCharCfg($(this).val(), settingType.positive_prompt); + }); + 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)); @@ -296,24 +338,29 @@ jQuery(async () => { saveSettingsDebounced(); }); - windowHtml.find(`input[name="cfg_negative_combine"]`).on('input', function() { - const values = windowHtml.find(`input[name="cfg_negative_combine"]`) + windowHtml.find('#global_cfg_positive_prompt').on('input', function() { + extension_settings.cfg.global.positive_prompt = $(this).val(); + saveSettingsDebounced(); + }); + + windowHtml.find(`input[name="cfg_prompt_combine"]`).on('input', function() { + const values = windowHtml.find(`input[name="cfg_prompt_combine"]`) .filter(":checked") .map(function() { return parseInt($(this).val()) }) .get() .filter((e) => e !== NaN) || []; - chat_metadata[metadataKeys.negative_combine] = values; + chat_metadata[metadataKeys.prompt_combine] = values; saveMetadataDebounced(); }); - windowHtml.find(`#cfg_negative_insertion_depth`).on('input', function() { - chat_metadata[metadataKeys.negative_insertion_depth] = Number($(this).val()); + windowHtml.find(`#cfg_prompt_insertion_depth`).on('input', function() { + chat_metadata[metadataKeys.prompt_insertion_depth] = Number($(this).val()); saveMetadataDebounced(); }); - windowHtml.find(`#cfg_negative_separator`).on('input', function() { - chat_metadata[metadataKeys.negative_separator] = $(this).val(); + windowHtml.find(`#cfg_prompt_separator`).on('input', function() { + chat_metadata[metadataKeys.prompt_separator] = $(this).val(); saveMetadataDebounced(); }); diff --git a/public/scripts/extensions/cfg/util.js b/public/scripts/extensions/cfg/util.js index d37d54725..108c22646 100644 --- a/public/scripts/extensions/cfg/util.js +++ b/public/scripts/extensions/cfg/util.js @@ -11,14 +11,15 @@ export const cfgType = { export const metadataKeys = { guidance_scale: "cfg_guidance_scale", negative_prompt: "cfg_negative_prompt", - negative_combine: "cfg_negative_combine", + positive_prompt: "cfg_positive_prompt", + prompt_combine: "cfg_prompt_combine", groupchat_individual_chars: "cfg_groupchat_individual_chars", - negative_insertion_depth: "cfg_negative_insertion_depth", - negative_separator: "cfg_negative_separator" + 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 negative prompt since it won't be used anyways +// If the guidance scale is 1, ignore the CFG prompt(s) since it won't be used anyways export function getGuidanceScale() { const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid)); const chatGuidanceScale = chat_metadata[metadataKeys.guidance_scale]; @@ -44,32 +45,49 @@ export function getGuidanceScale() { }; } -// Gets the CFG prompt. Currently only gets the negative prompt -export function getCfgPrompt(guidanceScale) { - let splitNegativePrompt = []; +// Gets the CFG prompt +export function getCfgPrompt(guidanceScale, isNegative) { + let splitCfgPrompt = []; - const chatNegativeCombine = chat_metadata[metadataKeys.negative_combine] ?? []; - if (guidanceScale.type === cfgType.chat || chatNegativeCombine.includes(cfgType.chat)) { - splitNegativePrompt.unshift(substituteParams(chat_metadata[metadataKeys.negative_prompt])?.trim()); + 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] + ) + ?.trim() + ); } const charaCfg = extension_settings.cfg.chara?.find((e) => e.name === getCharaFilename(this_chid)); - if (guidanceScale.type === cfgType.chara || chatNegativeCombine.includes(cfgType.chara)) { - splitNegativePrompt.unshift(substituteParams(charaCfg.negative_prompt)?.trim()) + if (guidanceScale.type === cfgType.chara || cfgPromptCombine.includes(cfgType.chara)) { + splitCfgPrompt.unshift( + substituteParams( + isNegative ? charaCfg.negative_prompt : charaCfg.positive_prompt + ) + ?.trim() + ); } - if (guidanceScale.type === cfgType.global || chatNegativeCombine.includes(cfgType.global)) { - splitNegativePrompt.unshift(substituteParams(extension_settings.cfg.global.negative_prompt)?.trim()); + console.log(guidanceScale.type); + console.log(cfgPromptCombine); + 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 + ) + ?.trim() + ); } // This line is a bit hacky with a JSON.stringify and JSON.parse. Fix this if possible. - const negativeSeparator = JSON.parse(chat_metadata[metadataKeys.negative_separator] || JSON.stringify("\n")) ?? "\n"; - const combinedNegatives = splitNegativePrompt.filter((e) => e.length > 0).join(negativeSeparator); - const insertionDepth = chat_metadata[metadataKeys.negative_insertion_depth] ?? 1; - console.log(`Setting CFG with guidance scale: ${guidanceScale.value}, negatives: ${combinedNegatives}`); + const customSeparator = JSON.parse(chat_metadata[metadataKeys.prompt_separator] || JSON.stringify("\n")) ?? "\n"; + 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: combinedNegatives, + value: combinedCfgPrompt, depth: insertionDepth }; } diff --git a/public/scripts/extensions/cfg/window.html b/public/scripts/extensions/cfg/window.html index 5e0d21f7c..889d5b0bb 100644 --- a/public/scripts/extensions/cfg/window.html +++ b/public/scripts/extensions/cfg/window.html @@ -14,7 +14,7 @@ Unique to this chat.
- + + +
@@ -86,7 +96,7 @@
Will be used as the default CFG options for every chat unless overridden.
- + + +
-
+

- Negative Cascading + CFG Prompt Cascading
- Combine negative prompts from other boxes. + Combine positive/negative prompts from other boxes.
For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.

-