mirror of
https://github.com/lllyasviel/stable-diffusion-webui-forge.git
synced 2026-02-06 16:09:58 +00:00
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Give a badge on ControlNet Accordion indicating total number of active
|
||||
* Give a badge on ControlNet Accordion indicating total number of active
|
||||
* units.
|
||||
* Make active unit's tab name green.
|
||||
* Append control type to tab name.
|
||||
@@ -66,30 +66,39 @@
|
||||
constructor(tab, accordion) {
|
||||
this.tab = tab;
|
||||
this.accordion = accordion;
|
||||
this.isImg2Img = tab.querySelector('.cnet-unit-enabled').id.includes('img2img');
|
||||
this.isImg2Img = tab.querySelector('.cnet-mask-upload').id.includes('img2img');
|
||||
|
||||
this.enabledCheckbox = tab.querySelector('.cnet-unit-enabled input');
|
||||
this.enabledCheckbox = tab.querySelector('.input-accordion-checkbox');
|
||||
this.inputImage = tab.querySelector('.cnet-input-image-group .cnet-image input[type="file"]');
|
||||
this.inputImageContainer = tab.querySelector('.cnet-input-image-group .cnet-image');
|
||||
this.controlTypeRadios = tab.querySelectorAll('.controlnet_control_type_filter_group input[type="radio"]');
|
||||
this.resizeModeRadios = tab.querySelectorAll('.controlnet_resize_mode_radio input[type="radio"]');
|
||||
this.runPreprocessorButton = tab.querySelector('.cnet-run-preprocessor');
|
||||
|
||||
const tabs = tab.parentNode;
|
||||
this.tabNav = tabs.querySelector('.tab-nav');
|
||||
this.tabIndex = childIndex(tab) - 1; // -1 because tab-nav is also at the same level.
|
||||
this.tabs = tab.parentNode;
|
||||
this.tabIndex = childIndex(tab);
|
||||
|
||||
// By default the InputAccordion checkbox is linked with the state
|
||||
// of accordion's open/close state. To disable this link, we can
|
||||
// simulate click to check the checkbox and uncheck it.
|
||||
this.enabledCheckbox.click();
|
||||
this.enabledCheckbox.click();
|
||||
|
||||
this.attachEnabledButtonListener();
|
||||
this.attachControlTypeRadioListener();
|
||||
this.attachTabNavChangeObserver();
|
||||
this.attachImageUploadListener();
|
||||
this.attachImageStateChangeObserver();
|
||||
this.attachA1111SendInfoObserver();
|
||||
this.attachPresetDropdownObserver();
|
||||
}
|
||||
|
||||
getTabNavButton() {
|
||||
return this.tabNav.querySelector(`:nth-child(${this.tabIndex + 1})`);
|
||||
/**
|
||||
* Get the span that has text "Unit {X}".
|
||||
*/
|
||||
getUnitHeaderTextElement() {
|
||||
return this.tab.querySelector(
|
||||
`:nth-child(${this.tabIndex + 1}) span.svelte-s1r2yt`
|
||||
);
|
||||
}
|
||||
|
||||
getActiveControlType() {
|
||||
@@ -102,13 +111,13 @@
|
||||
}
|
||||
|
||||
updateActiveState() {
|
||||
const tabNavButton = this.getTabNavButton();
|
||||
if (!tabNavButton) return;
|
||||
const unitHeader = this.getUnitHeaderTextElement();
|
||||
if (!unitHeader) return;
|
||||
|
||||
if (this.enabledCheckbox.checked) {
|
||||
tabNavButton.classList.add('cnet-unit-active');
|
||||
unitHeader.classList.add('cnet-unit-active');
|
||||
} else {
|
||||
tabNavButton.classList.remove('cnet-unit-active');
|
||||
unitHeader.classList.remove('cnet-unit-active');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +153,11 @@
|
||||
* Add the active control type to tab displayed text.
|
||||
*/
|
||||
updateActiveControlType() {
|
||||
const tabNavButton = this.getTabNavButton();
|
||||
if (!tabNavButton) return;
|
||||
const unitHeader = this.getUnitHeaderTextElement();
|
||||
if (!unitHeader) return;
|
||||
|
||||
// Remove the control if exists
|
||||
const controlTypeSuffix = tabNavButton.querySelector('.control-type-suffix');
|
||||
const controlTypeSuffix = unitHeader.querySelector('.control-type-suffix');
|
||||
if (controlTypeSuffix) controlTypeSuffix.remove();
|
||||
|
||||
// Add new suffix.
|
||||
@@ -158,31 +167,7 @@
|
||||
const span = document.createElement('span');
|
||||
span.innerHTML = `[${controlType}]`;
|
||||
span.classList.add('control-type-suffix');
|
||||
tabNavButton.appendChild(span);
|
||||
}
|
||||
|
||||
/**
|
||||
* When 'Inpaint' control type is selected in img2img:
|
||||
* - Make image input disabled
|
||||
* - Clear existing image input
|
||||
*/
|
||||
updateImageInputState() {
|
||||
if (!this.isImg2Img) return;
|
||||
|
||||
const tabNavButton = this.getTabNavButton();
|
||||
if (!tabNavButton) return;
|
||||
|
||||
const controlType = this.getActiveControlType();
|
||||
if (controlType.toLowerCase() === 'inpaint') {
|
||||
this.inputImage.disabled = true;
|
||||
this.inputImage.parentNode.addEventListener('click', imageInputDisabledAlert);
|
||||
const removeButton = this.tab.querySelector(
|
||||
'.cnet-input-image-group .cnet-image button[aria-label="Remove Image"]');
|
||||
if (removeButton) removeButton.click();
|
||||
} else {
|
||||
this.inputImage.disabled = false;
|
||||
this.inputImage.parentNode.removeEventListener('click', imageInputDisabledAlert);
|
||||
}
|
||||
unitHeader.appendChild(span);
|
||||
}
|
||||
|
||||
attachEnabledButtonListener() {
|
||||
@@ -200,22 +185,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each time the active tab change, all tab nav buttons are cleared and
|
||||
* regenerated by gradio. So we need to reapply the active states on
|
||||
* them.
|
||||
*/
|
||||
attachTabNavChangeObserver() {
|
||||
new MutationObserver((mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'childList') {
|
||||
this.updateActiveState();
|
||||
this.updateActiveControlType();
|
||||
}
|
||||
}
|
||||
}).observe(this.tabNav, { childList: true });
|
||||
}
|
||||
|
||||
attachImageUploadListener() {
|
||||
// Automatically check `enable` checkbox when image is uploaded.
|
||||
this.inputImage.addEventListener('change', (event) => {
|
||||
@@ -303,7 +272,7 @@
|
||||
|
||||
gradioApp().querySelectorAll('#controlnet').forEach(accordion => {
|
||||
if (cnetAllAccordions.has(accordion)) return;
|
||||
accordion.querySelectorAll('.cnet-unit-tab')
|
||||
accordion.querySelectorAll('.input-accordion')
|
||||
.forEach(tab => new ControlNetUnitTab(tab, accordion));
|
||||
cnetAllAccordions.add(accordion);
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
const tabs = gradioApp().querySelectorAll('.cnet-unit-tab');
|
||||
const tabs = gradioApp().querySelectorAll('#controlnet .input-accordion');
|
||||
tabs.forEach(tab => {
|
||||
if (cnetOpenposeEditorRegisteredElements.has(tab)) return;
|
||||
cnetOpenposeEditorRegisteredElements.add(tab);
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
Copyright 2011 Jon Leighton
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
@@ -298,13 +298,13 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch detected maps from each ControlNet units.
|
||||
* Fetch detected maps from each ControlNet units.
|
||||
* Create a new photopea document.
|
||||
* Add those detected maps to the created document.
|
||||
*/
|
||||
async fetchFromControlNet(tabs) {
|
||||
if (tabs.length === 0) return;
|
||||
const isImg2Img = tabs[0].querySelector('.cnet-unit-enabled').id.includes('img2img');
|
||||
const isImg2Img = tabs[0].querySelector('.cnet-mask-upload').id.includes('img2img');
|
||||
const generationType = isImg2Img ? 'img2img' : 'txt2img';
|
||||
const width = gradioApp().querySelector(`#${generationType}_width input[type=number]`).value;
|
||||
const height = gradioApp().querySelector(`#${generationType}_height input[type=number]`).value;
|
||||
@@ -401,7 +401,7 @@
|
||||
}
|
||||
|
||||
const closeModalButton = accordion.querySelector('.cnet-photopea-edit .cnet-modal-close');
|
||||
const tabs = accordion.querySelectorAll('.cnet-unit-tab');
|
||||
const tabs = accordion.querySelectorAll('.controlnet .input-accordion');
|
||||
const photopeaIframe = accordion.querySelector('.photopea-iframe');
|
||||
const photopeaContext = new PhotopeaContext(photopeaIframe, tabs);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class A1111Context:
|
||||
)
|
||||
|
||||
@property
|
||||
def img2img_non_inpaint_tabs(self) -> List[gr.components.IOComponent]:
|
||||
def img2img_non_inpaint_tabs(self) -> Tuple[gr.components.IOComponent]:
|
||||
return (
|
||||
self.img2img_img2img_tab,
|
||||
self.img2img_img2img_sketch_tab,
|
||||
@@ -151,7 +151,8 @@ class ControlNetUiGroup(object):
|
||||
self,
|
||||
is_img2img: bool,
|
||||
default_unit: external_code.ControlNetUnit,
|
||||
photopea: Optional[Photopea],
|
||||
unit_enabled: gr.Checkbox,
|
||||
photopea: Optional[Photopea] = None,
|
||||
):
|
||||
# Whether callbacks have been registered.
|
||||
self.callbacks_registered: bool = False
|
||||
@@ -164,6 +165,8 @@ class ControlNetUiGroup(object):
|
||||
self.webcam_enabled = False
|
||||
self.webcam_mirrored = False
|
||||
|
||||
# Now the enabled checkbox is moved to display on InputAccordion.
|
||||
self.enabled = unit_enabled
|
||||
# Note: All gradio elements declared in `render` will be defined as member variable.
|
||||
# Update counter to trigger a force update of UiControlNetUnit.
|
||||
# This is useful when a field with no event subscriber available changes.
|
||||
@@ -190,7 +193,6 @@ class ControlNetUiGroup(object):
|
||||
self.webcam_enable = None
|
||||
self.webcam_mirror = None
|
||||
self.send_dimen_button = None
|
||||
self.enabled = None
|
||||
self.pixel_perfect = None
|
||||
self.preprocessor_preview = None
|
||||
self.mask_upload = None
|
||||
@@ -393,18 +395,6 @@ class ControlNetUiGroup(object):
|
||||
)
|
||||
|
||||
with FormRow(elem_classes=["controlnet_main_options"]):
|
||||
self.enabled = gr.Checkbox(
|
||||
label="Enable",
|
||||
value=self.default_unit.enabled,
|
||||
elem_id=f"{elem_id_tabname}_{tabname}_controlnet_enable_checkbox",
|
||||
elem_classes=["cnet-unit-enabled"],
|
||||
)
|
||||
# self.low_vram = gr.Checkbox(
|
||||
# label="Low VRAM",
|
||||
# value=self.default_unit.low_vram,
|
||||
# elem_id=f"{elem_id_tabname}_{tabname}_controlnet_low_vram_checkbox",
|
||||
# visible=False, # Not needed now
|
||||
# )
|
||||
self.pixel_perfect = gr.Checkbox(
|
||||
label="Pixel Perfect",
|
||||
value=self.default_unit.pixel_perfect,
|
||||
|
||||
@@ -5,7 +5,8 @@ import cv2
|
||||
import torch
|
||||
|
||||
import modules.scripts as scripts
|
||||
from modules import shared, script_callbacks, processing, masking, images
|
||||
from modules import shared, script_callbacks, masking, images
|
||||
from modules.ui_components import InputAccordion
|
||||
from modules.api.api import decode_base64_to_image
|
||||
import gradio as gr
|
||||
|
||||
@@ -58,33 +59,32 @@ class ControlNetForForgeOfficial(scripts.Script):
|
||||
def show(self, is_img2img):
|
||||
return scripts.AlwaysVisible
|
||||
|
||||
def uigroup(self, tabname: str, is_img2img: bool, elem_id_tabname: str, photopea: Optional[Photopea]) -> Tuple[ControlNetUiGroup, gr.State]:
|
||||
default_unit = UiControlNetUnit(enabled=False, module="None", model="None")
|
||||
group = ControlNetUiGroup(is_img2img, default_unit, photopea)
|
||||
return group, group.render(tabname, elem_id_tabname)
|
||||
|
||||
def ui(self, is_img2img):
|
||||
infotext = Infotext()
|
||||
ui_groups = []
|
||||
controls = []
|
||||
max_models = shared.opts.data.get("control_net_unit_count", 3)
|
||||
elem_id_tabname = ("img2img" if is_img2img else "txt2img") + "_controlnet"
|
||||
gen_type = "img2img" if is_img2img else "txt2img"
|
||||
elem_id_tabname = gen_type + "_controlnet"
|
||||
default_unit = UiControlNetUnit(enabled=False, module="None", model="None")
|
||||
with gr.Group(elem_id=elem_id_tabname):
|
||||
with gr.Accordion(f"ControlNet Integrated", open=False, elem_id="controlnet"):
|
||||
photopea = Photopea() if not shared.opts.data.get("controlnet_disable_photopea_edit", False) else None
|
||||
if max_models > 1:
|
||||
with gr.Tabs(elem_id=f"{elem_id_tabname}_tabs"):
|
||||
for i in range(max_models):
|
||||
with gr.Tab(f"ControlNet Unit {i}",
|
||||
elem_classes=['cnet-unit-tab']):
|
||||
group, state = self.uigroup(f"ControlNet-{i}", is_img2img, elem_id_tabname, photopea)
|
||||
ui_groups.append(group)
|
||||
controls.append(state)
|
||||
else:
|
||||
with gr.Column():
|
||||
group, state = self.uigroup(f"ControlNet", is_img2img, elem_id_tabname, photopea)
|
||||
ui_groups.append(group)
|
||||
controls.append(state)
|
||||
with gr.Accordion(f"ControlNet Integrated", open=False, elem_id="controlnet",
|
||||
elem_classes=["controlnet"]):
|
||||
photopea = (
|
||||
Photopea()
|
||||
if not shared.opts.data.get("controlnet_disable_photopea_edit", False)
|
||||
else None
|
||||
)
|
||||
with gr.Row(elem_id=elem_id_tabname + "_accordions", elem_classes="accordions"):
|
||||
for i in range(max_models):
|
||||
with InputAccordion(
|
||||
value=False,
|
||||
label=f"ControlNet Unit {i}",
|
||||
elem_classes=["cnet-unit-enabled"],
|
||||
) as enable_unit:
|
||||
group = ControlNetUiGroup(is_img2img, default_unit, enable_unit, photopea)
|
||||
ui_groups.append(group)
|
||||
controls.append(group.render(f"ControlNet-{i}", elem_id_tabname))
|
||||
|
||||
for i, ui_group in enumerate(ui_groups):
|
||||
infotext.register_unit(i, ui_group)
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
/* InputAccordion alignment */
|
||||
/* Flex container */
|
||||
.controlnet .svelte-vt1mxs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
/* Adjusts the space between items */
|
||||
}
|
||||
|
||||
.controlnet .input-accordion {
|
||||
flex: 1 1 calc(50% - 10px);
|
||||
/* Adjusts for the gap, default 2 columns */
|
||||
/* Additional styling for items */
|
||||
}
|
||||
|
||||
/* Media query for screens smaller than a specific width */
|
||||
@media (max-width: 600px) {
|
||||
|
||||
/* Adjust the threshold as needed */
|
||||
.controlnet .input-accordion {
|
||||
flex: 1 1 100%;
|
||||
/* Changes to 1 column when window width is ≤ 600px */
|
||||
}
|
||||
}
|
||||
|
||||
.cnet-modal {
|
||||
display: none;
|
||||
/* Hidden by default */
|
||||
@@ -179,4 +205,4 @@
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--background-fill-primary);
|
||||
color: var(--block-label-text-color);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user