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 <bdashore3@proton.me>
This commit is contained in:
kingbri 2023-08-12 16:17:35 -04:00
parent 5bb6c89868
commit 4a21ee0337
3 changed files with 114 additions and 57 deletions

View File

@ -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();
});

View File

@ -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];
// 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());
}
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 (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());
}
} else if (extension_settings.cfg.global?.guidance_scale !== 1) {
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
};
}

View File

@ -63,10 +63,6 @@
</label>
<textarea id="chara_cfg_negative_prompt" rows="2" class="text_pole textarea_compact" data-i18n="[placeholder]write short replies, write replies using past tense" placeholder="write short replies, write replies using past tense"></textarea>
</div>
<label class="checkbox_label" for="use_chara_cfg">
<input id="use_chara_cfg" type="checkbox" />
<span data-i18n="Use character CFG">Use character CFG</span>
</label>
</div>
</div>
<hr class="sysHR">
@ -99,5 +95,33 @@
</div>
</div>
</div>
<hr class="sysHR">
<div class="inline-drawer">
<div id="defaultANBlockToggle" class="inline-drawer-toggle inline-drawer-header">
<b>Negative Cascading</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<small>
Combine negative prompts from other boxes. For example, ticking the chat, global, and character boxes combine all negative prompts into a comma-separated string.
</small>
<br />
<label for="cfg_negative_combine">
<span data-i18n="Scale">Always Include</span>
</label>
<label class="checkbox_label">
<input type="checkbox" name="cfg_negative_combine" value="0" />
<span data-i18n="Only Format Display">Chat Negatives</span>
</label>
<label class="checkbox_label">
<input type="checkbox" name="cfg_negative_combine" value="1" />
<span data-i18n="Only Format Display">Character Negatives</span>
</label>
<label class="checkbox_label">
<input type="checkbox" name="cfg_negative_combine" value="2" />
<span data-i18n="Use character CFG">Global Negatives</span>
</label>
</div>
</div>
</div>
</div>