Merge pull request #3000 from error-usernotavailable/staging ADetailer functionality for stable-diffusion extension

cb987706eba8303e7d11ea0f8e129b73b0618c85

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +99 -32Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+57 -32
@@ -18,7 +18,7 @@ import {
1818} from '../../../script.js';
1919import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
2020import { selected_group } from '../../group-chats.js';
2121import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce, isFalseBoolean, deepMerge } from '../../utils.js';
2222import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
2323import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
2424import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
@@ -219,6 +219,7 @@ const defaultSettings = {
219219 // Automatic1111/Horde exclusives
220220 restore_faces: false,
221221 enable_hr: false,
222+ adetailer_face: false,
222223
223224 // Horde settings
224225 horde: false,
@@ -435,6 +436,7 @@ async function loadSettings() {
435436 $('#sd_horde_sanitize').prop('checked', extension_settings.sd.horde_sanitize);
436437 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
437438 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
439+ $('#sd_adetailer_face').prop('checked', extension_settings.sd.adetailer_face);
438440 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
439441 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
440442 $('#sd_auto_url').val(extension_settings.sd.auto_url);
@@ -853,6 +855,11 @@ function onSamplerChange() {
853855 saveSettingsDebounced();
854856}
855857
858+function onADetailerFaceChange() {
859+ extension_settings.sd.adetailer_face = !!$('#sd_adetailer_face').prop('checked');
860+ saveSettingsDebounced();
861+}
862+
856863const resolutionOptions = {
857864 sd_res_512x512: { width: 512, height: 512, name: '512x512 (1:1, icons, profile pictures)' },
858865 sd_res_600x600: { width: 600, height: 600, name: '600x600 (1:1, icons, profile pictures)' },
@@ -2920,41 +2927,58 @@ async function generateHordeImage(prompt, negativePrompt, signal) {
29202927 */
29212928async function generateAutoImage(prompt, negativePrompt, signal) {
29222929 const isValidVae = extension_settings.sd.vae && !['N/A', placeholderVae].includes(extension_settings.sd.vae);
2930+ let payload = {
2931+ ...getSdRequestBody(),
2932+ prompt: prompt,
2933+ negative_prompt: negativePrompt,
2934+ sampler_name: extension_settings.sd.sampler,
2935+ scheduler: extension_settings.sd.scheduler,
2936+ steps: extension_settings.sd.steps,
2937+ cfg_scale: extension_settings.sd.scale,
2938+ width: extension_settings.sd.width,
2939+ height: extension_settings.sd.height,
2940+ restore_faces: !!extension_settings.sd.restore_faces,
2941+ enable_hr: !!extension_settings.sd.enable_hr,
2942+ hr_upscaler: extension_settings.sd.hr_upscaler,
2943+ hr_scale: extension_settings.sd.hr_scale,
2944+ denoising_strength: extension_settings.sd.denoising_strength,
2945+ hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
2946+ seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
2947+ override_settings: {
2948+ CLIP_stop_at_last_layers: extension_settings.sd.clip_skip,
2949+ sd_vae: isValidVae ? extension_settings.sd.vae : undefined,
2950+ },
2951+ override_settings_restore_afterwards: true,
2952+ clip_skip: extension_settings.sd.clip_skip, // For SD.Next
2953+ save_images: true,
2954+ send_images: true,
2955+ do_not_save_grid: false,
2956+ do_not_save_samples: false,
2957+ };
2958+
2959+ // Conditionally add the ADetailer if adetailer_face is enabled
2960+ if (extension_settings.sd.adetailer_face) {
2961+ payload = deepMerge(payload, {
2962+ alwayson_scripts: {
2963+ ADetailer: {
2964+ args: [
2965+ true, // ad_enable
2966+ true, // skip_img2img
2967+ {
2968+ 'ad_model': 'face_yolov8n.pt',
2969+ },
2970+ ],
2971+ },
2972+ },
2973+ });
2974+ }
2975+
2976+ // Make the fetch call with the payload
29232977 const result = await fetch('/api/sd/generate', {
29242978 method: 'POST',
29252979 headers: getRequestHeaders(),
29262980 signal: signal,
29272981 body: JSON.stringify({payload),
2928- ...getSdRequestBody(),
2929- prompt: prompt,
2930- negative_prompt: negativePrompt,
2931- sampler_name: extension_settings.sd.sampler,
2932- scheduler: extension_settings.sd.scheduler,
2933- steps: extension_settings.sd.steps,
2934- cfg_scale: extension_settings.sd.scale,
2935- width: extension_settings.sd.width,
2936- height: extension_settings.sd.height,
2937- restore_faces: !!extension_settings.sd.restore_faces,
2938- enable_hr: !!extension_settings.sd.enable_hr,
2939- hr_upscaler: extension_settings.sd.hr_upscaler,
2940- hr_scale: extension_settings.sd.hr_scale,
2941- denoising_strength: extension_settings.sd.denoising_strength,
2942- hr_second_pass_steps: extension_settings.sd.hr_second_pass_steps,
2943- seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
2944- // For AUTO1111
2945- override_settings: {
2946- CLIP_stop_at_last_layers: extension_settings.sd.clip_skip,
2947- sd_vae: isValidVae ? extension_settings.sd.vae : undefined,
2948- },
2949- override_settings_restore_afterwards: true,
2950- // For SD.Next
2951- clip_skip: extension_settings.sd.clip_skip,
2952- // Ensure generated img is saved to disk
2953- save_images: true,
2954- send_images: true,
2955- do_not_save_grid: false,
2956- do_not_save_samples: false,
2957- }),
29582982 });
29592983
29602984 if (result.ok) {
@@ -4186,6 +4210,7 @@ jQuery(async () => {
41864210 $('#sd_horde_sanitize').on('input', onHordeSanitizeInput);
41874211 $('#sd_restore_faces').on('input', onRestoreFacesInput);
41884212 $('#sd_enable_hr').on('input', onHighResFixInput);
4213+ $('#sd_adetailer_face').on('change', onADetailerFaceChange);
41894214 $('#sd_refine_mode').on('input', onRefineModeInput);
41904215 $('#sd_character_prompt').on('input', onCharacterPromptInput);
41914216 $('#sd_character_negative_prompt').on('input', onCharacterNegativePromptInput);
public/scripts/extensions/stable-diffusion/settings.html+10 -0
@@ -356,6 +356,16 @@
356356 </label>
357357 </div>
358358
359+ <div class="flex-container marginTopBot5" data-sd-source="auto,vlad">
360+ <label for="sd_adetailer_face" class="flex1 checkbox_label" data-i18n="[title]sd_adetailer_face" title="Use ADetailer with face model during the generation. The ADetailer extension must be installed on the backend.">
361+ <input id="sd_adetailer_face" type="checkbox" />
362+ <small data-i18n="Use ADetailer (Face)">Use ADetailer (Face)</small>
363+ </label>
364+ <div class="flex1">
365+ <!-- I will be useful later! -->
366+ </div>
367+ </div>
368+
359369 <div class="flex-container marginTopBot5" data-sd-source="novel">
360370 <label class="flex1 checkbox_label" data-i18n="[title]SMEA versions of samplers are modified to perform better at high resolution." title="SMEA versions of samplers are modified to perform better at high resolution.">
361371 <input id="sd_novel_sm" type="checkbox" />
public/scripts/utils.js+32 -0
@@ -23,6 +23,38 @@ export const navigation_option = {
2323 previous: -1000,
2424};
2525
26+/**
27+ * Determines if a value is an object.
28+ * @param {any} item The item to check.
29+ * @returns {boolean} True if the item is an object, false otherwise.
30+ */
31+function isObject(item) {
32+ return (item && typeof item === 'object' && !Array.isArray(item));
33+}
34+
35+/**
36+ * Merges properties of two objects. If the property is an object, it will be merged recursively.
37+ * @param {object} target The target object
38+ * @param {object} source The source object
39+ * @returns {object} Merged object
40+ */
41+export function deepMerge(target, source) {
42+ let output = Object.assign({}, target);
43+ if (isObject(target) && isObject(source)) {
44+ Object.keys(source).forEach(key => {
45+ if (isObject(source[key])) {
46+ if (!(key in target))
47+ Object.assign(output, { [key]: source[key] });
48+ else
49+ output[key] = deepMerge(target[key], source[key]);
50+ } else {
51+ Object.assign(output, { [key]: source[key] });
52+ }
53+ });
54+ }
55+ return output;
56+}
57+
2658export function escapeHtml(str) {
2759 return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2860}