385 lines
14 KiB
JavaScript
385 lines
14 KiB
JavaScript
window.state = window.state || {};
|
|
window.state.extensions = window.state.extensions || {};
|
|
state = window.state;
|
|
|
|
|
|
function ControlNetTabContext(tabName, container) {
|
|
|
|
this.tabName = tabName;
|
|
this.container = container;
|
|
this.store = new state.Store(`ext-control-net-${this.tabName}`);
|
|
this.tabElements = [];
|
|
this.cnTabs = [];
|
|
|
|
// Try multiple selectors for compatibility with different Gradio/Forge versions
|
|
let tabs = this.container.querySelectorAll(':scope > div > div > .tabs > .tabitem');
|
|
if (!tabs.length) {
|
|
tabs = this.container.querySelectorAll('.tabitem');
|
|
}
|
|
if (!tabs.length) {
|
|
tabs = this.container.querySelectorAll('[id*="tabitem"]');
|
|
}
|
|
if (!tabs.length) {
|
|
tabs = this.container.querySelectorAll('.tabs > div[role="tabpanel"]');
|
|
}
|
|
|
|
if (tabs.length) {
|
|
tabs.forEach((tabContainer, i) => {
|
|
this.cnTabs.push({
|
|
container: tabContainer,
|
|
store: new state.Store(`ext-control-net-${this.tabName}-${i}`)
|
|
});
|
|
});
|
|
} else {
|
|
this.cnTabs.push({
|
|
container: container,
|
|
store: new state.Store(`ext-control-net-${this.tabName}-0`)
|
|
});
|
|
}
|
|
}
|
|
|
|
state.extensions['control-net'] = (function () {
|
|
|
|
let contexts = [];
|
|
|
|
function handleToggle() {
|
|
|
|
const id = 'toggled';
|
|
|
|
contexts.forEach(context => {
|
|
|
|
// Try multiple selectors for compatibility
|
|
let elements = context.container.querySelectorAll(`:scope > .label-wrap`);
|
|
if (!elements.length) {
|
|
elements = context.container.querySelectorAll('.label-wrap');
|
|
}
|
|
|
|
elements.forEach(element => {
|
|
if (context.store.get(id) === 'true') {
|
|
state.utils.clickToggleMenu(element);
|
|
load();
|
|
}
|
|
element.addEventListener('click', function () {
|
|
// Check for open state using multiple methods for compatibility
|
|
let isOpen = this.classList.contains('open') ||
|
|
this.parentNode.classList.contains('open') ||
|
|
state.utils.isAccordionOpen(this.parentNode);
|
|
context.store.set(id, isOpen);
|
|
load();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function bindTabEvents() {
|
|
contexts.forEach(context => {
|
|
// Try multiple selectors for compatibility
|
|
let tabs = context.container.querySelectorAll(':scope > div > div > .tabs > div > button');
|
|
if (!tabs.length) {
|
|
tabs = context.container.querySelectorAll('.tabs .tab-nav button');
|
|
}
|
|
if (!tabs.length) {
|
|
tabs = context.container.querySelectorAll('.tabs > div > button');
|
|
}
|
|
if (!tabs.length) {
|
|
tabs = context.container.querySelectorAll('button[role="tab"]');
|
|
}
|
|
|
|
function onTabClick() {
|
|
context.store.set('tab', this.textContent);
|
|
bindTabEvents();
|
|
}
|
|
tabs.forEach(tab => { // dirty hack here
|
|
tab.removeEventListener('click', onTabClick);
|
|
tab.addEventListener('click', onTabClick);
|
|
});
|
|
context.tabElements = tabs;
|
|
});
|
|
}
|
|
|
|
function handleTabs() {
|
|
bindTabEvents();
|
|
contexts.forEach(context => {
|
|
let value = context.store.get('tab');
|
|
if (value) {
|
|
for (var i = 0; i < context.tabElements.length; i++) {
|
|
if (context.tabElements[i].textContent === value) {
|
|
state.utils.triggerEvent(context.tabElements[i], 'click');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function handleContext(action) {
|
|
contexts.forEach(context => {
|
|
context.cnTabs.forEach(({ container, store }) => {
|
|
action(container, store);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Helper to find checkbox label text
|
|
function getCheckboxLabel(checkbox) {
|
|
// Try nextElementSibling (common pattern)
|
|
if (checkbox.nextElementSibling && checkbox.nextElementSibling.textContent) {
|
|
return checkbox.nextElementSibling.textContent;
|
|
}
|
|
|
|
// Try parent label
|
|
let parentLabel = checkbox.closest('label');
|
|
if (parentLabel) {
|
|
// Get text excluding the checkbox itself
|
|
let clone = parentLabel.cloneNode(true);
|
|
let input = clone.querySelector('input');
|
|
if (input) input.remove();
|
|
if (clone.textContent && clone.textContent.trim()) {
|
|
return clone.textContent.trim();
|
|
}
|
|
}
|
|
|
|
// Try aria-label or title
|
|
if (checkbox.getAttribute('aria-label')) return checkbox.getAttribute('aria-label');
|
|
if (checkbox.title) return checkbox.title;
|
|
|
|
return null;
|
|
}
|
|
|
|
// Helper to find select/dropdown label text
|
|
function getSelectLabel(select) {
|
|
// Try label inside the select container
|
|
let label = select.querySelector('label');
|
|
if (label) {
|
|
if (label.firstChild && label.firstChild.textContent) {
|
|
return label.firstChild.textContent;
|
|
}
|
|
if (label.textContent) return label.textContent;
|
|
}
|
|
|
|
// Try span with label class
|
|
let span = select.querySelector('span[data-testid="block-label"], span[class*="label"]');
|
|
if (span && span.textContent) return span.textContent;
|
|
|
|
// Try previous sibling
|
|
if (select.previousElementSibling) {
|
|
let prevLabel = select.previousElementSibling.querySelector('label, span');
|
|
if (prevLabel && prevLabel.textContent) return prevLabel.textContent;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Helper to find textarea label text
|
|
function getTextareaLabel(textarea) {
|
|
// Try previousElementSibling
|
|
if (textarea.previousElementSibling && textarea.previousElementSibling.textContent) {
|
|
return textarea.previousElementSibling.textContent;
|
|
}
|
|
|
|
// Try parent container for label
|
|
let parent = textarea.closest('.gradio-textbox, [class*="textbox"]');
|
|
if (parent) {
|
|
let label = parent.querySelector('label, span[data-testid="block-label"]');
|
|
if (label && label.textContent) return label.textContent;
|
|
}
|
|
|
|
// Try aria-label or placeholder
|
|
if (textarea.getAttribute('aria-label')) return textarea.getAttribute('aria-label');
|
|
if (textarea.placeholder) return textarea.placeholder;
|
|
|
|
return null;
|
|
}
|
|
|
|
function handleCheckboxes() {
|
|
handleContext((container, store) => {
|
|
let checkboxes = container.querySelectorAll('input[type="checkbox"]');
|
|
checkboxes.forEach(function (checkbox, idx) {
|
|
let labelText = getCheckboxLabel(checkbox);
|
|
// Use index-based fallback if no label found
|
|
let id = labelText ? state.utils.txtToId(labelText) : `checkbox-${idx}`;
|
|
let value = store.get(id);
|
|
if (value) {
|
|
state.utils.setValue(checkbox, value, 'change');
|
|
}
|
|
checkbox.addEventListener('change', function () {
|
|
store.set(id, this.checked);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function handleSelects() {
|
|
handleContext((container, store) => {
|
|
// Use compatibility helper to find dropdowns
|
|
let dropdowns = state.utils.findDropdowns(container);
|
|
dropdowns.forEach(function (select, idx) {
|
|
let labelText = getSelectLabel(select);
|
|
// Use index-based fallback if no label found
|
|
let id = labelText ? state.utils.txtToId(labelText) : `select-${idx}`;
|
|
let value = store.get(id);
|
|
state.utils.handleSelect(select, id, store);
|
|
if (id === 'preprocessor' && value && value.toLowerCase() !== 'none') {
|
|
state.utils.onNextUiUpdates(handleSliders); // update new sliders if needed
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Helper to find slider label text with multiple fallback methods
|
|
function getSliderLabel(slider) {
|
|
// Try previousElementSibling first (old structure)
|
|
if (slider.previousElementSibling) {
|
|
let label = slider.previousElementSibling.querySelector('label span');
|
|
if (label && label.textContent) return label.textContent;
|
|
|
|
// Try just the label
|
|
label = slider.previousElementSibling.querySelector('label');
|
|
if (label && label.textContent) return label.textContent;
|
|
|
|
// Try span directly
|
|
label = slider.previousElementSibling.querySelector('span');
|
|
if (label && label.textContent) return label.textContent;
|
|
}
|
|
|
|
// Try parent container for label (Forge/Gradio 4.x structure)
|
|
let parent = slider.closest('.gradio-slider, .slider, [class*="slider"]');
|
|
if (parent) {
|
|
let label = parent.querySelector('label span, label, span[data-testid="block-label"]');
|
|
if (label && label.textContent) return label.textContent;
|
|
}
|
|
|
|
// Try looking for label in parent's previous sibling
|
|
if (slider.parentElement && slider.parentElement.previousElementSibling) {
|
|
let label = slider.parentElement.previousElementSibling.querySelector('span, label');
|
|
if (label && label.textContent) return label.textContent;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Helper to find fieldset/radio label text
|
|
function getFieldsetLabel(fieldset) {
|
|
// Try firstChild.nextElementSibling (old structure)
|
|
if (fieldset.firstChild && fieldset.firstChild.nextElementSibling) {
|
|
let label = fieldset.firstChild.nextElementSibling;
|
|
if (label && label.textContent) return label.textContent;
|
|
}
|
|
|
|
// Try legend element
|
|
let legend = fieldset.querySelector('legend');
|
|
if (legend && legend.textContent) return legend.textContent;
|
|
|
|
// Try label or span
|
|
let label = fieldset.querySelector('label, span[class*="label"]');
|
|
if (label && label.textContent) return label.textContent;
|
|
|
|
return null;
|
|
}
|
|
|
|
function handleSliders() {
|
|
handleContext((container, store) => {
|
|
let sliders = container.querySelectorAll('input[type="range"]');
|
|
sliders.forEach(function (slider, idx) {
|
|
let labelText = getSliderLabel(slider);
|
|
// Use index-based fallback if no label found
|
|
let id = labelText ? state.utils.txtToId(labelText) : `slider-${idx}`;
|
|
let value = store.get(id);
|
|
if (value) {
|
|
state.utils.setValue(slider, value, 'change');
|
|
}
|
|
slider.addEventListener('change', function () {
|
|
store.set(id, this.value);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function handleRadioButtons() {
|
|
handleContext((container, store) => {
|
|
let fieldsets = container.querySelectorAll('fieldset');
|
|
fieldsets.forEach(function (fieldset, idx) {
|
|
let radios = fieldset.querySelectorAll('input[type="radio"]');
|
|
let labelText = getFieldsetLabel(fieldset);
|
|
// Use index-based fallback if no label found
|
|
let id = labelText ? state.utils.txtToId(labelText) : `radio-${idx}`;
|
|
let value = store.get(id);
|
|
if (value) {
|
|
radios.forEach(function (radio) {
|
|
state.utils.setValue(radio, value, 'change');
|
|
});
|
|
}
|
|
radios.forEach(function (radio) {
|
|
radio.addEventListener('change', function () {
|
|
store.set(id, this.value);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function handleTextareas() {
|
|
handleContext((container, store) => {
|
|
let textareas = container.querySelectorAll('textarea');
|
|
textareas.forEach(function (textarea, idx) {
|
|
let labelText = getTextareaLabel(textarea);
|
|
// Use index-based fallback if no label found
|
|
let id = labelText ? state.utils.txtToId(labelText) : `textarea-${idx}`;
|
|
let value = store.get(id);
|
|
if (value) {
|
|
state.utils.setValue(textarea, value, 'change');
|
|
}
|
|
textarea.addEventListener('change', function () {
|
|
store.set(id, this.value);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function load() {
|
|
setTimeout(function () {
|
|
handleTabs();
|
|
handleCheckboxes();
|
|
handleSelects();
|
|
handleSliders();
|
|
handleRadioButtons();
|
|
handleTextareas();
|
|
}, 500);
|
|
}
|
|
|
|
function init() {
|
|
|
|
// Try multiple selectors for ControlNet container (Forge vs A1111 compatibility)
|
|
let elements = gradioApp().querySelectorAll('#controlnet');
|
|
if (!elements.length) {
|
|
elements = gradioApp().querySelectorAll('[id*="controlnet"]');
|
|
}
|
|
if (!elements.length) {
|
|
elements = gradioApp().querySelectorAll('#txt2img_controlnet, #img2img_controlnet');
|
|
}
|
|
// Forge built-in ControlNet uses different IDs
|
|
if (!elements.length) {
|
|
elements = gradioApp().querySelectorAll('[id*="forge_controlnet"], [id*="sd_forge_controlnet"]');
|
|
}
|
|
|
|
if (!elements.length) {
|
|
state.logging.log('ControlNet extension not found');
|
|
return;
|
|
}
|
|
|
|
// Handle both single container and separate txt2img/img2img containers
|
|
if (elements.length >= 2) {
|
|
contexts[0] = new ControlNetTabContext('txt2img', elements[0]);
|
|
contexts[1] = new ControlNetTabContext('img2img', elements[1]);
|
|
} else if (elements.length === 1) {
|
|
// Single container mode
|
|
contexts[0] = new ControlNetTabContext('main', elements[0]);
|
|
}
|
|
|
|
handleToggle();
|
|
load();
|
|
}
|
|
|
|
return { init };
|
|
}());
|