Forge support - initial commi
parent
3660174122
commit
e1578a252c
14
CLAUDE.md
14
CLAUDE.md
|
|
@ -6,6 +6,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
|
|
||||||
This is a Stable Diffusion WebUI extension that preserves UI parameters (inputs, sliders, checkboxes, etc.) after page reload. It uses localStorage for persistence and supports multiple SD extensions (ControlNet, ADetailer, Dynamic Prompting, Multidiffusion/Tiled VAE).
|
This is a Stable Diffusion WebUI extension that preserves UI parameters (inputs, sliders, checkboxes, etc.) after page reload. It uses localStorage for persistence and supports multiple SD extensions (ControlNet, ADetailer, Dynamic Prompting, Multidiffusion/Tiled VAE).
|
||||||
|
|
||||||
|
**Compatible with:**
|
||||||
|
- AUTOMATIC1111 Stable Diffusion WebUI
|
||||||
|
- Stable Diffusion WebUI Forge
|
||||||
|
- Gradio 3.x and 4.x
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
**No build system** - This is a pure extension with vanilla JavaScript and Python. Files are loaded directly by the parent Stable Diffusion WebUI.
|
**No build system** - This is a pure extension with vanilla JavaScript and Python. Files are loaded directly by the parent Stable Diffusion WebUI.
|
||||||
|
|
@ -33,6 +38,15 @@ state.ext.*.js → Extension-specific handlers (ControlNet, ADetailer, et
|
||||||
- All code lives under `window.state` namespace (`state.core`, `state.store`, `state.utils`, `state.extensions`)
|
- All code lives under `window.state` namespace (`state.core`, `state.store`, `state.utils`, `state.extensions`)
|
||||||
- Extension plugins register via `state.extensions[name] = { init: function() {} }`
|
- Extension plugins register via `state.extensions[name] = { init: function() {} }`
|
||||||
|
|
||||||
|
**Gradio Compatibility Utilities** (`state.utils`):
|
||||||
|
- `state.utils.gradio.detectVersion()` - Detects Gradio 3.x vs 4.x
|
||||||
|
- `state.utils.getButtonClass()` - Returns appropriate button CSS classes
|
||||||
|
- `state.utils.findDropdowns(container)` - Finds dropdowns with fallback selectors
|
||||||
|
- `state.utils.findAccordions(container)` - Finds accordions with fallback selectors
|
||||||
|
- `state.utils.getDropdownValue(select)` - Gets dropdown value (works with both Gradio versions)
|
||||||
|
- `state.utils.getMultiSelectValues(select)` - Gets multi-select values
|
||||||
|
- `state.utils.isAccordionOpen(accordion)` - Checks accordion state with multiple methods
|
||||||
|
|
||||||
### Backend (Python)
|
### Backend (Python)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ state.core = (function () {
|
||||||
'hires_resize_x': 'hr_resize_x',
|
'hires_resize_x': 'hr_resize_x',
|
||||||
'hires_resize_y': 'hr_resize_y',
|
'hires_resize_y': 'hr_resize_y',
|
||||||
'hires_denoising_strength': 'denoising_strength',
|
'hires_denoising_strength': 'denoising_strength',
|
||||||
|
'hires_cfg_scale': 'hr_cfg',
|
||||||
'refiner_switch': 'switch_at',
|
'refiner_switch': 'switch_at',
|
||||||
'upscaler_2_visibility': 'extras_upscaler_2_visibility',
|
'upscaler_2_visibility': 'extras_upscaler_2_visibility',
|
||||||
'upscaler_scale_by_resize': 'extras_upscaling_resize',
|
'upscaler_scale_by_resize': 'extras_upscaling_resize',
|
||||||
|
|
@ -225,13 +226,47 @@ state.core = (function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get tab buttons with multiple fallback selectors for compatibility
|
||||||
|
function getTabButtons() {
|
||||||
|
var root = gradioApp();
|
||||||
|
// Try multiple selectors for compatibility with different Gradio versions
|
||||||
|
var tabs = root.querySelectorAll('#tabs > div:first-child button');
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = root.querySelectorAll('#tabs .tab-nav button');
|
||||||
|
}
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = root.querySelectorAll('#tabs > .tabs > .tab-nav button');
|
||||||
|
}
|
||||||
|
if (!tabs.length) {
|
||||||
|
// Gradio 4.x may have different structure
|
||||||
|
tabs = root.querySelectorAll('#tabs button[role="tab"]');
|
||||||
|
}
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = root.querySelectorAll('#tabs > div > button');
|
||||||
|
}
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get selected tab button with fallback
|
||||||
|
function getSelectedTabButton() {
|
||||||
|
var root = gradioApp();
|
||||||
|
var selected = root.querySelector('#tabs .tab-nav button.selected');
|
||||||
|
if (!selected) {
|
||||||
|
selected = root.querySelector('#tabs button.selected');
|
||||||
|
}
|
||||||
|
if (!selected) {
|
||||||
|
selected = root.querySelector('#tabs button[aria-selected="true"]');
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
function restoreTabs(config) {
|
function restoreTabs(config) {
|
||||||
|
|
||||||
if (! config.hasSetting('tabs')) {
|
if (! config.hasSetting('tabs')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button');
|
const tabs = getTabButtons();
|
||||||
const value = store.get('tab');
|
const value = store.get('tab');
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|
@ -244,20 +279,23 @@ state.core = (function () {
|
||||||
}
|
}
|
||||||
// Use this when onUiTabChange is fixed
|
// Use this when onUiTabChange is fixed
|
||||||
// onUiTabChange(function () {
|
// onUiTabChange(function () {
|
||||||
// store.set('tab', gradioApp().querySelector('#tabs .tab-nav button.selected').textContent);
|
// store.set('tab', getSelectedTabButton()?.textContent);
|
||||||
// });
|
// });
|
||||||
bindTabClickEvents();
|
bindTabClickEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindTabClickEvents() {
|
function bindTabClickEvents() {
|
||||||
Array.from(gradioApp().querySelectorAll('#tabs .tab-nav button')).forEach(tab => {
|
Array.from(getTabButtons()).forEach(tab => {
|
||||||
tab.removeEventListener('click', storeTab);
|
tab.removeEventListener('click', storeTab);
|
||||||
tab.addEventListener('click', storeTab);
|
tab.addEventListener('click', storeTab);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeTab() {
|
function storeTab() {
|
||||||
store.set('tab', gradioApp().querySelector('#tabs .tab-nav button.selected').textContent);
|
var selected = getSelectedTabButton();
|
||||||
|
if (selected) {
|
||||||
|
store.set('tab', selected.textContent);
|
||||||
|
}
|
||||||
bindTabClickEvents(); // dirty hack here...
|
bindTabClickEvents(); // dirty hack here...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,6 +326,12 @@ state.core = (function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert to array for easier handling
|
||||||
|
elements = Array.from(elements);
|
||||||
|
|
||||||
|
// For sliders with both number input and range, prefer number input for storage
|
||||||
|
let primaryElement = elements.find(el => el.type === 'number') || elements[0];
|
||||||
|
|
||||||
let forEach = function (action) {
|
let forEach = function (action) {
|
||||||
events.forEach(function(event) {
|
events.forEach(function(event) {
|
||||||
elements.forEach(function (element) {
|
elements.forEach(function (element) {
|
||||||
|
|
@ -296,6 +340,7 @@ state.core = (function () {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Attach event listeners to all elements
|
||||||
forEach(function (event) {
|
forEach(function (event) {
|
||||||
this.addEventListener(event, function () {
|
this.addEventListener(event, function () {
|
||||||
let value = this.value;
|
let value = this.value;
|
||||||
|
|
@ -306,17 +351,23 @@ state.core = (function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
TABS.forEach(tab => {
|
// Special handling for seed buttons
|
||||||
const seedInput = gradioApp().querySelector(`#${tab}_seed input`);
|
if (id.indexOf('seed') > -1) {
|
||||||
['random_seed', 'reuse_seed'].forEach(id => {
|
TABS.forEach(tab => {
|
||||||
const btn = gradioApp().querySelector(`#${tab}_${id}`);
|
const seedInput = gradioApp().querySelector(`#${tab}_seed input[type="number"]`);
|
||||||
btn.addEventListener('click', () => {
|
if (!seedInput) return;
|
||||||
setTimeout(() => {
|
['random_seed', 'reuse_seed'].forEach(btnId => {
|
||||||
state.utils.triggerEvent(seedInput, 'change');
|
const btn = gradioApp().querySelector(`#${tab}_${btnId}`);
|
||||||
}, 100);
|
if (btn) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
state.utils.triggerEvent(seedInput, 'change');
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
|
|
||||||
|
|
@ -324,9 +375,67 @@ state.core = (function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(function (event) {
|
// Delay restoration to ensure UI and updateInput are ready
|
||||||
state.utils.setValue(this, value, event);
|
// Use longer delay to ensure Gradio components are fully initialized
|
||||||
});
|
setTimeout(function() {
|
||||||
|
// Check if updateInput is available (it should be by now)
|
||||||
|
if (typeof updateInput !== 'function') {
|
||||||
|
state.logging.warn('updateInput not available yet, retrying...');
|
||||||
|
setTimeout(function() {
|
||||||
|
restoreInputValue(id, elements, value);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
restoreInputValue(id, elements, value);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreInputValue(id, elements, value) {
|
||||||
|
state.logging.log(`Attempting to restore ${id} with value: ${value}`);
|
||||||
|
|
||||||
|
// Check element types
|
||||||
|
let numberInput = elements.find(el => el.type === 'number');
|
||||||
|
let rangeInput = elements.find(el => el.type === 'range');
|
||||||
|
let textareaInput = elements.find(el => el.tagName === 'TEXTAREA');
|
||||||
|
let textInput = elements.find(el => el.type === 'text');
|
||||||
|
|
||||||
|
state.logging.log(`Found elements for ${id}: number=${!!numberInput}, range=${!!rangeInput}, textarea=${!!textareaInput}, text=${!!textInput}`);
|
||||||
|
|
||||||
|
// For sliders (have both number and range), use special handler
|
||||||
|
if (numberInput && rangeInput) {
|
||||||
|
// Get the container element
|
||||||
|
let container = numberInput.closest('.gradio-slider, [class*="slider"]') ||
|
||||||
|
numberInput.parentElement.parentElement;
|
||||||
|
state.logging.log(`Updating slider container for ${id}`, container);
|
||||||
|
state.utils.updateGradioSlider(container, value);
|
||||||
|
|
||||||
|
// Verify the value was set
|
||||||
|
setTimeout(function() {
|
||||||
|
state.logging.log(`After restore - ${id} number value: ${numberInput.value}, range value: ${rangeInput.value}`);
|
||||||
|
}, 100);
|
||||||
|
} else if (numberInput) {
|
||||||
|
// Just number input
|
||||||
|
state.utils.setValue(numberInput, value, 'input');
|
||||||
|
state.logging.log(`Restored number ${id}: ${value}`);
|
||||||
|
} else if (textareaInput) {
|
||||||
|
// For textareas (prompts)
|
||||||
|
state.utils.setValue(textareaInput, value, 'input');
|
||||||
|
state.logging.log(`Restored textarea ${id}: ${value}`);
|
||||||
|
} else if (textInput) {
|
||||||
|
// For text inputs
|
||||||
|
state.utils.setValue(textInput, value, 'input');
|
||||||
|
state.logging.log(`Restored text ${id}: ${value}`);
|
||||||
|
} else if (rangeInput) {
|
||||||
|
// Fallback to range input
|
||||||
|
state.utils.setValue(rangeInput, value, 'input');
|
||||||
|
state.logging.log(`Restored range ${id}: ${value}`);
|
||||||
|
} else {
|
||||||
|
// For any other elements, update all
|
||||||
|
state.logging.log(`Restoring ${id} to all ${elements.length} elements`);
|
||||||
|
elements.forEach(function (element) {
|
||||||
|
state.utils.setValue(element, value, 'input');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSavedSelects(id, duplicateIds) {
|
function handleSavedSelects(id, duplicateIds) {
|
||||||
|
|
@ -353,15 +462,29 @@ state.core = (function () {
|
||||||
|
|
||||||
let selector = duplicateIds ? `[id="${id}"]` : `#${id}`;
|
let selector = duplicateIds ? `[id="${id}"]` : `#${id}`;
|
||||||
|
|
||||||
elements = gradioApp().querySelectorAll(`.input-accordion${selector}>.label-wrap`);
|
// Try multiple selector patterns for compatibility
|
||||||
|
var elements = gradioApp().querySelectorAll(`.input-accordion${selector}>.label-wrap`);
|
||||||
|
if (!elements.length) {
|
||||||
|
elements = gradioApp().querySelectorAll(`.input-accordion${selector} .label-wrap`);
|
||||||
|
}
|
||||||
|
if (!elements.length) {
|
||||||
|
elements = gradioApp().querySelectorAll(`${selector} > .label-wrap`);
|
||||||
|
}
|
||||||
|
if (!elements.length) {
|
||||||
|
elements = gradioApp().querySelectorAll(`${selector}.input-accordion > .label-wrap`);
|
||||||
|
}
|
||||||
|
|
||||||
elements.forEach(function (element) {
|
elements.forEach(function (element) {
|
||||||
if (store.get(id) === 'true') {
|
if (store.get(id) === 'true') {
|
||||||
state.utils.clickToggleMenu(element);
|
state.utils.clickToggleMenu(element);
|
||||||
}
|
}
|
||||||
element.addEventListener('click', function () {
|
element.addEventListener('click', function () {
|
||||||
let classList = Array.from(this.parentNode.classList);
|
var parent = this.parentNode;
|
||||||
store.set(id, classList.indexOf('input-accordion-open') > -1);
|
// Check for open state using multiple methods for compatibility
|
||||||
|
var isOpen = parent.classList.contains('input-accordion-open') ||
|
||||||
|
this.classList.contains('open') ||
|
||||||
|
state.utils.isAccordionOpen(parent);
|
||||||
|
store.set(id, isOpen);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@ state.extensions['adetailer'] = (function () {
|
||||||
let cnTabs = [];
|
let cnTabs = [];
|
||||||
|
|
||||||
function bindTabEvents() {
|
function bindTabEvents() {
|
||||||
const tabs = container.querySelectorAll('.tabs > div > button');
|
// Try multiple selectors for compatibility
|
||||||
|
let tabs = container.querySelectorAll('.tabs > div > button');
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = container.querySelectorAll('.tabs .tab-nav button');
|
||||||
|
}
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = container.querySelectorAll('button[role="tab"]');
|
||||||
|
}
|
||||||
tabs.forEach(tab => { // dirty hack here
|
tabs.forEach(tab => { // dirty hack here
|
||||||
tab.removeEventListener('click', onTabClick);
|
tab.removeEventListener('click', onTabClick);
|
||||||
tab.addEventListener('click', onTabClick);
|
tab.addEventListener('click', onTabClick);
|
||||||
|
|
@ -36,6 +43,8 @@ state.extensions['adetailer'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCheckbox(checkbox, id) {
|
function handleCheckbox(checkbox, id) {
|
||||||
|
if (!checkbox) return;
|
||||||
|
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
if (value) {
|
if (value) {
|
||||||
state.utils.setValue(checkbox, value, 'change');
|
state.utils.setValue(checkbox, value, 'change');
|
||||||
|
|
@ -81,7 +90,8 @@ state.extensions['adetailer'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelects(container, container_idx) {
|
function handleSelects(container, container_idx) {
|
||||||
let selects = container.querySelectorAll('.gradio-dropdown')
|
// Use compatibility helper to find dropdowns
|
||||||
|
let selects = state.utils.findDropdowns(container);
|
||||||
selects.forEach(function (select, idx) {
|
selects.forEach(function (select, idx) {
|
||||||
state.utils.handleSelect(select, `ad-tab-${container_idx}-select-${idx}`, store);
|
state.utils.handleSelect(select, `ad-tab-${container_idx}-select-${idx}`, store);
|
||||||
});
|
});
|
||||||
|
|
@ -107,28 +117,49 @@ state.extensions['adetailer'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDropdown(dropdown, id) {
|
function handleDropdown(dropdown, id) {
|
||||||
|
if (!dropdown) return;
|
||||||
|
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
|
|
||||||
if (value && value === 'true') {
|
if (value && value === 'true') {
|
||||||
state.utils.triggerEvent(dropdown, 'click');
|
state.utils.triggerEvent(dropdown, 'click');
|
||||||
}
|
}
|
||||||
dropdown.addEventListener('click', function () {
|
dropdown.addEventListener('click', function () {
|
||||||
let span = this.querySelector('.transition, .icon');
|
// Use multiple methods to check open state for compatibility
|
||||||
store.set(id, span.style.transform !== 'rotate(90deg)');
|
let isOpen = this.classList.contains('open') ||
|
||||||
|
this.parentNode.classList.contains('open') ||
|
||||||
|
state.utils.isAccordionOpen(this.parentNode);
|
||||||
|
store.set(id, isOpen);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDropdowns(container, container_idx) {
|
function handleDropdowns(container, container_idx) {
|
||||||
let dropdowns = container.querySelectorAll('.gradio-accordion .label-wrap');
|
// Use compatibility helper to find accordions
|
||||||
dropdowns.forEach(function (dropdown, idx) {
|
let accordions = state.utils.findAccordions(container);
|
||||||
handleDropdown(dropdown, `ad-tab-${container_idx}-dropdown-${idx}`);
|
accordions.forEach(function (accordion, idx) {
|
||||||
|
let labelWrap = accordion.querySelector('.label-wrap');
|
||||||
|
if (labelWrap) {
|
||||||
|
handleDropdown(labelWrap, `ad-tab-${container_idx}-dropdown-${idx}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
handleDropdown(container.querySelector('#script_txt2img_adetailer_ad_main_accordion > .label-wrap'), 'ad-dropdown-main');
|
// Try multiple selectors for the main accordion
|
||||||
handleCheckbox(container.querySelector('#script_txt2img_adetailer_ad_enable > label > input'), 'ad-checkbox-enable');
|
let mainAccordion = container.querySelector('#script_txt2img_adetailer_ad_main_accordion > .label-wrap');
|
||||||
|
if (!mainAccordion) {
|
||||||
|
mainAccordion = container.querySelector('.label-wrap');
|
||||||
|
}
|
||||||
|
handleDropdown(mainAccordion, 'ad-dropdown-main');
|
||||||
|
|
||||||
|
// Try multiple selectors for the enable checkbox
|
||||||
|
let enableCheckbox = container.querySelector('#script_txt2img_adetailer_ad_enable > label > input');
|
||||||
|
if (!enableCheckbox) {
|
||||||
|
enableCheckbox = container.querySelector('input[type="checkbox"]');
|
||||||
|
}
|
||||||
|
handleCheckbox(enableCheckbox, 'ad-checkbox-enable');
|
||||||
|
|
||||||
cnTabs.forEach(({ container, container_idx }) => {
|
cnTabs.forEach(({ container, container_idx }) => {
|
||||||
handleTabs(container, container_idx);
|
handleTabs(container, container_idx);
|
||||||
handleTextboxes(container, container_idx);
|
handleTextboxes(container, container_idx);
|
||||||
|
|
@ -143,14 +174,30 @@ state.extensions['adetailer'] = (function () {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
|
// Try multiple selectors for ADetailer container (Forge vs A1111 compatibility)
|
||||||
container = gradioApp().getElementById('script_txt2img_adetailer_ad_main_accordion');
|
container = gradioApp().getElementById('script_txt2img_adetailer_ad_main_accordion');
|
||||||
store = new state.Store('ext-adetailerr');
|
if (!container) {
|
||||||
|
container = gradioApp().querySelector('[id*="adetailer"]');
|
||||||
|
}
|
||||||
|
if (!container) {
|
||||||
|
container = gradioApp().querySelector('[id*="ADetailer"]');
|
||||||
|
}
|
||||||
|
|
||||||
if (! container) {
|
store = new state.Store('ext-adetailer');
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
state.logging.log('ADetailer extension not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try multiple selectors for tabs
|
||||||
let tabs = container.querySelectorAll('.tabitem');
|
let tabs = container.querySelectorAll('.tabitem');
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = container.querySelectorAll('[id*="tabitem"]');
|
||||||
|
}
|
||||||
|
if (!tabs.length) {
|
||||||
|
tabs = container.querySelectorAll('.tabs > div[role="tabpanel"]');
|
||||||
|
}
|
||||||
|
|
||||||
if (tabs.length) {
|
if (tabs.length) {
|
||||||
cnTabs = [];
|
cnTabs = [];
|
||||||
|
|
@ -162,7 +209,8 @@ state.extensions['adetailer'] = (function () {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
cnTabs = [{
|
cnTabs = [{
|
||||||
container: container
|
container: container,
|
||||||
|
container_idx: 0
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,17 @@ function ControlNetTabContext(tabName, container) {
|
||||||
this.tabElements = [];
|
this.tabElements = [];
|
||||||
this.cnTabs = [];
|
this.cnTabs = [];
|
||||||
|
|
||||||
|
// Try multiple selectors for compatibility with different Gradio/Forge versions
|
||||||
let tabs = this.container.querySelectorAll(':scope > div > div > .tabs > .tabitem');
|
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) {
|
if (tabs.length) {
|
||||||
tabs.forEach((tabContainer, i) => {
|
tabs.forEach((tabContainer, i) => {
|
||||||
|
|
@ -21,10 +31,10 @@ function ControlNetTabContext(tabName, container) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.cnTabs.push[{
|
this.cnTabs.push({
|
||||||
container: container,
|
container: container,
|
||||||
store: new state.Store(`ext-control-net-${this.tabName}-${i}`)
|
store: new state.Store(`ext-control-net-${this.tabName}-0`)
|
||||||
}];
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +48,11 @@ state.extensions['control-net'] = (function () {
|
||||||
|
|
||||||
contexts.forEach(context => {
|
contexts.forEach(context => {
|
||||||
|
|
||||||
const elements = context.container.querySelectorAll(`:scope > .label-wrap`)
|
// 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 => {
|
elements.forEach(element => {
|
||||||
if (context.store.get(id) === 'true') {
|
if (context.store.get(id) === 'true') {
|
||||||
|
|
@ -46,8 +60,11 @@ state.extensions['control-net'] = (function () {
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
element.addEventListener('click', function () {
|
element.addEventListener('click', function () {
|
||||||
let classList = Array.from(this.classList);
|
// Check for open state using multiple methods for compatibility
|
||||||
context.store.set(id, classList.indexOf('open') > -1);
|
let isOpen = this.classList.contains('open') ||
|
||||||
|
this.parentNode.classList.contains('open') ||
|
||||||
|
state.utils.isAccordionOpen(this.parentNode);
|
||||||
|
context.store.set(id, isOpen);
|
||||||
load();
|
load();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -56,7 +73,18 @@ state.extensions['control-net'] = (function () {
|
||||||
|
|
||||||
function bindTabEvents() {
|
function bindTabEvents() {
|
||||||
contexts.forEach(context => {
|
contexts.forEach(context => {
|
||||||
const tabs = context.container.querySelectorAll(':scope > div > div > .tabs > div > button');
|
// 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() {
|
function onTabClick() {
|
||||||
context.store.set('tab', this.textContent);
|
context.store.set('tab', this.textContent);
|
||||||
bindTabEvents();
|
bindTabEvents();
|
||||||
|
|
@ -92,12 +120,84 @@ state.extensions['control-net'] = (function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
function handleCheckboxes() {
|
||||||
handleContext((container, store) => {
|
handleContext((container, store) => {
|
||||||
let checkboxes = container.querySelectorAll('input[type="checkbox"]');
|
let checkboxes = container.querySelectorAll('input[type="checkbox"]');
|
||||||
checkboxes.forEach(function (checkbox) {
|
checkboxes.forEach(function (checkbox, idx) {
|
||||||
let label = checkbox.nextElementSibling;
|
let labelText = getCheckboxLabel(checkbox);
|
||||||
let id = state.utils.txtToId(label.textContent);
|
// Use index-based fallback if no label found
|
||||||
|
let id = labelText ? state.utils.txtToId(labelText) : `checkbox-${idx}`;
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
if (value) {
|
if (value) {
|
||||||
state.utils.setValue(checkbox, value, 'change');
|
state.utils.setValue(checkbox, value, 'change');
|
||||||
|
|
@ -111,8 +211,12 @@ state.extensions['control-net'] = (function () {
|
||||||
|
|
||||||
function handleSelects() {
|
function handleSelects() {
|
||||||
handleContext((container, store) => {
|
handleContext((container, store) => {
|
||||||
container.querySelectorAll('.gradio-dropdown').forEach(select => {
|
// Use compatibility helper to find dropdowns
|
||||||
let id = state.utils.txtToId(select.querySelector('label').firstChild.textContent);
|
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);
|
let value = store.get(id);
|
||||||
state.utils.handleSelect(select, id, store);
|
state.utils.handleSelect(select, id, store);
|
||||||
if (id === 'preprocessor' && value && value.toLowerCase() !== 'none') {
|
if (id === 'preprocessor' && value && value.toLowerCase() !== 'none') {
|
||||||
|
|
@ -122,12 +226,64 @@ state.extensions['control-net'] = (function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
function handleSliders() {
|
||||||
handleContext((container, store) => {
|
handleContext((container, store) => {
|
||||||
let sliders = container.querySelectorAll('input[type="range"]');
|
let sliders = container.querySelectorAll('input[type="range"]');
|
||||||
sliders.forEach(function (slider) {
|
sliders.forEach(function (slider, idx) {
|
||||||
let label = slider.previousElementSibling.querySelector('label span');
|
let labelText = getSliderLabel(slider);
|
||||||
let id = state.utils.txtToId(label.textContent);
|
// Use index-based fallback if no label found
|
||||||
|
let id = labelText ? state.utils.txtToId(labelText) : `slider-${idx}`;
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
if (value) {
|
if (value) {
|
||||||
state.utils.setValue(slider, value, 'change');
|
state.utils.setValue(slider, value, 'change');
|
||||||
|
|
@ -142,10 +298,11 @@ state.extensions['control-net'] = (function () {
|
||||||
function handleRadioButtons() {
|
function handleRadioButtons() {
|
||||||
handleContext((container, store) => {
|
handleContext((container, store) => {
|
||||||
let fieldsets = container.querySelectorAll('fieldset');
|
let fieldsets = container.querySelectorAll('fieldset');
|
||||||
fieldsets.forEach(function (fieldset) {
|
fieldsets.forEach(function (fieldset, idx) {
|
||||||
let label = fieldset.firstChild.nextElementSibling;
|
|
||||||
let radios = fieldset.querySelectorAll('input[type="radio"]');
|
let radios = fieldset.querySelectorAll('input[type="radio"]');
|
||||||
let id = state.utils.txtToId(label.textContent);
|
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);
|
let value = store.get(id);
|
||||||
if (value) {
|
if (value) {
|
||||||
radios.forEach(function (radio) {
|
radios.forEach(function (radio) {
|
||||||
|
|
@ -164,9 +321,10 @@ state.extensions['control-net'] = (function () {
|
||||||
function handleTextareas() {
|
function handleTextareas() {
|
||||||
handleContext((container, store) => {
|
handleContext((container, store) => {
|
||||||
let textareas = container.querySelectorAll('textarea');
|
let textareas = container.querySelectorAll('textarea');
|
||||||
textareas.forEach(function (textarea) {
|
textareas.forEach(function (textarea, idx) {
|
||||||
let label = textarea.previousElementSibling;
|
let labelText = getTextareaLabel(textarea);
|
||||||
let id = state.utils.txtToId(label.textContent);
|
// Use index-based fallback if no label found
|
||||||
|
let id = labelText ? state.utils.txtToId(labelText) : `textarea-${idx}`;
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
if (value) {
|
if (value) {
|
||||||
state.utils.setValue(textarea, value, 'change');
|
state.utils.setValue(textarea, value, 'change');
|
||||||
|
|
@ -191,14 +349,32 @@ state.extensions['control-net'] = (function () {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
|
// Try multiple selectors for ControlNet container (Forge vs A1111 compatibility)
|
||||||
let elements = gradioApp().querySelectorAll('#controlnet');
|
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) {
|
if (!elements.length) {
|
||||||
|
state.logging.log('ControlNet extension not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contexts[0] = new ControlNetTabContext('txt2img', elements[0]);
|
// Handle both single container and separate txt2img/img2img containers
|
||||||
contexts[1] = new ControlNetTabContext('img2img', elements[1]);
|
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();
|
handleToggle();
|
||||||
load();
|
load();
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ state.extensions['dynamic prompting'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelects() {
|
function handleSelects() {
|
||||||
let selects = container.querySelectorAll('.gradio-dropdown')
|
// Use compatibility helper to find dropdowns
|
||||||
|
let selects = state.utils.findDropdowns(container);
|
||||||
selects.forEach(function (select, idx) {
|
selects.forEach(function (select, idx) {
|
||||||
state.utils.handleSelect(select, `dp-select-${idx}`, store);
|
state.utils.handleSelect(select, `dp-select-${idx}`, store);
|
||||||
});
|
});
|
||||||
|
|
@ -76,17 +77,24 @@ state.extensions['dynamic prompting'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDropdowns() {
|
function handleDropdowns() {
|
||||||
let dropdowns = container.querySelectorAll('.gradio-accordion .label-wrap');
|
// Use compatibility helper to find accordions
|
||||||
dropdowns.forEach(function (dropdown, idx) {
|
let accordions = state.utils.findAccordions(container);
|
||||||
|
accordions.forEach(function (accordion, idx) {
|
||||||
|
let labelWrap = accordion.querySelector('.label-wrap');
|
||||||
|
if (!labelWrap) return;
|
||||||
|
|
||||||
let id = `dp-dropdown-${idx}`;
|
let id = `dp-dropdown-${idx}`;
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
|
|
||||||
if (value && value === 'true') {
|
if (value && value === 'true') {
|
||||||
state.utils.triggerEvent(dropdown, 'click');
|
state.utils.triggerEvent(labelWrap, 'click');
|
||||||
}
|
}
|
||||||
dropdown.addEventListener('click', function () {
|
labelWrap.addEventListener('click', function () {
|
||||||
let span = this.querySelector('.transition, .icon');
|
// Use multiple methods to check open state for compatibility
|
||||||
store.set(id, span.style.transform !== 'rotate(90deg)');
|
let isOpen = this.classList.contains('open') ||
|
||||||
|
this.parentNode.classList.contains('open') ||
|
||||||
|
state.utils.isAccordionOpen(this.parentNode);
|
||||||
|
store.set(id, isOpen);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -104,10 +112,22 @@ state.extensions['dynamic prompting'] = (function () {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
|
// Try multiple selectors for Dynamic Prompting container (Forge vs A1111 compatibility)
|
||||||
container = gradioApp().getElementById('sddp-dynamic-prompting');
|
container = gradioApp().getElementById('sddp-dynamic-prompting');
|
||||||
|
if (!container) {
|
||||||
|
container = gradioApp().querySelector('[id*="dynamic-prompting"]');
|
||||||
|
}
|
||||||
|
if (!container) {
|
||||||
|
container = gradioApp().querySelector('[id*="dynamicprompts"]');
|
||||||
|
}
|
||||||
|
if (!container) {
|
||||||
|
container = gradioApp().querySelector('[id*="dynamic_prompts"]');
|
||||||
|
}
|
||||||
|
|
||||||
store = new state.Store('ext-dynamic-prompting');
|
store = new state.Store('ext-dynamic-prompting');
|
||||||
|
|
||||||
if (! container) {
|
if (!container) {
|
||||||
|
state.logging.log('Dynamic Prompting extension not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ state.extensions['multidiffusion'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelects(container, name) {
|
function handleSelects(container, name) {
|
||||||
let selects = container.querySelectorAll('.gradio-dropdown')
|
// Use compatibility helper to find dropdowns
|
||||||
|
let selects = state.utils.findDropdowns(container);
|
||||||
selects.forEach(function (select, idx) {
|
selects.forEach(function (select, idx) {
|
||||||
state.utils.handleSelect(select, `md-${name}-select-${idx}`, store);
|
state.utils.handleSelect(select, `md-${name}-select-${idx}`, store);
|
||||||
});
|
});
|
||||||
|
|
@ -76,17 +77,24 @@ state.extensions['multidiffusion'] = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDropdowns(container, name) {
|
function handleDropdowns(container, name) {
|
||||||
let dropdowns = container.querySelectorAll('.gradio-accordion .label-wrap');
|
// Use compatibility helper to find accordions
|
||||||
dropdowns.forEach(function (dropdown, idx) {
|
let accordions = state.utils.findAccordions(container);
|
||||||
|
accordions.forEach(function (accordion, idx) {
|
||||||
|
let labelWrap = accordion.querySelector('.label-wrap');
|
||||||
|
if (!labelWrap) return;
|
||||||
|
|
||||||
let id = `md-${name}-dropdown-${idx}`;
|
let id = `md-${name}-dropdown-${idx}`;
|
||||||
let value = store.get(id);
|
let value = store.get(id);
|
||||||
|
|
||||||
if (value && value === 'true') {
|
if (value && value === 'true') {
|
||||||
state.utils.triggerEvent(dropdown, 'click');
|
state.utils.triggerEvent(labelWrap, 'click');
|
||||||
}
|
}
|
||||||
dropdown.addEventListener('click', function () {
|
labelWrap.addEventListener('click', function () {
|
||||||
let span = this.querySelector('.transition, .icon');
|
// Use multiple methods to check open state for compatibility
|
||||||
store.set(id, span.style.transform !== 'rotate(90deg)');
|
let isOpen = this.classList.contains('open') ||
|
||||||
|
this.parentNode.classList.contains('open') ||
|
||||||
|
state.utils.isAccordionOpen(this.parentNode);
|
||||||
|
store.set(id, isOpen);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -106,20 +114,47 @@ state.extensions['multidiffusion'] = (function () {
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
|
// Try to find Tiled Diffusion/VAE containers by text content
|
||||||
let spanTags = gradioApp().getElementsByTagName("span");
|
let spanTags = gradioApp().getElementsByTagName("span");
|
||||||
for (var i = 0; i < spanTags.length; i++) {
|
for (var i = 0; i < spanTags.length; i++) {
|
||||||
if (spanTags[i].textContent == 'Tiled Diffusion') {
|
let text = spanTags[i].textContent.trim();
|
||||||
containers.push({container: spanTags[i].parentElement.parentElement,name: 'diffusion'});
|
if (text === 'Tiled Diffusion' || text === 'Tiled Diffusion (Multidiffusion)') {
|
||||||
|
let parent = spanTags[i].parentElement;
|
||||||
|
// Navigate up to find the accordion container
|
||||||
|
while (parent && !parent.classList.contains('gradio-accordion') && !parent.classList.contains('accordion') && !parent.id) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
containers.push({container: parent, name: 'diffusion'});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (spanTags[i].textContent == 'Tiled VAE') {
|
if (text === 'Tiled VAE') {
|
||||||
containers.push({container: spanTags[i].parentElement.parentElement,name: 'vae'});
|
let parent = spanTags[i].parentElement;
|
||||||
break;
|
while (parent && !parent.classList.contains('gradio-accordion') && !parent.classList.contains('accordion') && !parent.id) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
containers.push({container: parent, name: 'vae'});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// Also try by ID for Forge compatibility
|
||||||
|
if (!containers.length) {
|
||||||
|
let tiledDiff = gradioApp().querySelector('[id*="tiled_diffusion"], [id*="multidiffusion"]');
|
||||||
|
if (tiledDiff) {
|
||||||
|
containers.push({container: tiledDiff, name: 'diffusion'});
|
||||||
|
}
|
||||||
|
let tiledVae = gradioApp().querySelector('[id*="tiled_vae"]');
|
||||||
|
if (tiledVae) {
|
||||||
|
containers.push({container: tiledVae, name: 'vae'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
store = new state.Store('ext-multidiffusion');
|
store = new state.Store('ext-multidiffusion');
|
||||||
|
|
||||||
if (! containers.length) {
|
if (!containers.length) {
|
||||||
|
state.logging.log('Multidiffusion/Tiled VAE extension not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,45 @@ state.logging = {
|
||||||
|
|
||||||
name: 'state',
|
name: 'state',
|
||||||
|
|
||||||
log: function (message) {
|
// Set to true to enable debug logging
|
||||||
console.log(`[${this.name}]: `, message);
|
DEBUG: false,
|
||||||
|
|
||||||
|
log: function (message, data) {
|
||||||
|
if (!this.DEBUG) return;
|
||||||
|
if (data !== undefined) {
|
||||||
|
console.log(`[${this.name}]: `, message, data);
|
||||||
|
} else {
|
||||||
|
console.log(`[${this.name}]: `, message);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
error: function (message) {
|
error: function (message, data) {
|
||||||
console.error(`[${this.name}]: `, message);
|
// Errors are always shown
|
||||||
|
if (data !== undefined) {
|
||||||
|
console.error(`[${this.name}]: `, message, data);
|
||||||
|
} else {
|
||||||
|
console.error(`[${this.name}]: `, message);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
warn: function (message) {
|
warn: function (message, data) {
|
||||||
console.warn(`[${this.name}]: `, message);
|
if (!this.DEBUG) return;
|
||||||
|
if (data !== undefined) {
|
||||||
|
console.warn(`[${this.name}]: `, message, data);
|
||||||
|
} else {
|
||||||
|
console.warn(`[${this.name}]: `, message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Call this in browser console to enable debugging: state.logging.enable()
|
||||||
|
enable: function() {
|
||||||
|
this.DEBUG = true;
|
||||||
|
console.log(`[${this.name}]: Debug logging enabled`);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Call this in browser console to disable debugging: state.logging.disable()
|
||||||
|
disable: function() {
|
||||||
|
this.DEBUG = false;
|
||||||
|
console.log(`[${this.name}]: Debug logging disabled`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,106 @@ state.utils = {
|
||||||
element.checked = false;
|
element.checked = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'number':
|
||||||
|
case 'range':
|
||||||
|
// For sliders and number inputs, use Forge pattern
|
||||||
|
element.value = value;
|
||||||
|
if (typeof updateInput === 'function') {
|
||||||
|
updateInput(element);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'textarea':
|
||||||
|
// Textareas (prompts) - use Forge pattern
|
||||||
|
element.value = value;
|
||||||
|
if (typeof updateInput === 'function') {
|
||||||
|
updateInput(element);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
element.value = value;
|
element.value = value;
|
||||||
this.triggerEvent(element, event);
|
// For text inputs and other types, use Forge pattern if available
|
||||||
|
if (typeof updateInput === 'function' && (element.tagName === 'TEXTAREA' || element.tagName === 'INPUT')) {
|
||||||
|
updateInput(element);
|
||||||
|
} else {
|
||||||
|
this.triggerEvent(element, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Update input using Gradio's expected event format (works with Forge and new Gradio)
|
||||||
|
updateGradioInput: function updateGradioInput(target) {
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
// Use the global updateInput if available (Forge/A1111)
|
||||||
|
if (typeof updateInput === 'function') {
|
||||||
|
updateInput(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also dispatch events manually to ensure Svelte/Gradio components update
|
||||||
|
// Input event with bubbles
|
||||||
|
let inputEvent = new Event("input", { bubbles: true, cancelable: true });
|
||||||
|
Object.defineProperty(inputEvent, "target", { value: target });
|
||||||
|
target.dispatchEvent(inputEvent);
|
||||||
|
|
||||||
|
// Change event
|
||||||
|
let changeEvent = new Event("change", { bubbles: true, cancelable: true });
|
||||||
|
target.dispatchEvent(changeEvent);
|
||||||
|
|
||||||
|
// For Svelte components, also try InputEvent
|
||||||
|
try {
|
||||||
|
let inputEvent2 = new InputEvent("input", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
inputType: "insertText",
|
||||||
|
data: target.value
|
||||||
|
});
|
||||||
|
target.dispatchEvent(inputEvent2);
|
||||||
|
} catch (e) {
|
||||||
|
// InputEvent might not be supported in all browsers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set value using native setter to bypass framework reactivity issues
|
||||||
|
setNativeValue: function setNativeValue(element, value) {
|
||||||
|
// Get the native value setter
|
||||||
|
const valueSetter = Object.getOwnPropertyDescriptor(element.__proto__, 'value')?.set ||
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set ||
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
|
||||||
|
|
||||||
|
if (valueSetter) {
|
||||||
|
valueSetter.call(element, value);
|
||||||
|
} else {
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update a Gradio slider component - matches exact Forge pattern
|
||||||
|
updateGradioSlider: function updateGradioSlider(container, value) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
let numberInput = container.querySelector('input[type=number]');
|
||||||
|
let rangeInput = container.querySelector('input[type=range]');
|
||||||
|
|
||||||
|
// Use exact Forge pattern: set .value then call updateInput()
|
||||||
|
if (numberInput) {
|
||||||
|
numberInput.value = value;
|
||||||
|
if (typeof updateInput === 'function') {
|
||||||
|
updateInput(numberInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update range for visual sync
|
||||||
|
if (rangeInput) {
|
||||||
|
rangeInput.value = value;
|
||||||
|
if (typeof updateInput === 'function') {
|
||||||
|
updateInput(rangeInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update visual slider fill
|
||||||
|
let min = parseFloat(rangeInput.min) || 0;
|
||||||
|
let max = parseFloat(rangeInput.max) || 100;
|
||||||
|
let val = parseFloat(value) || 0;
|
||||||
|
let percentage = ((val - min) / (max - min)) * 100;
|
||||||
|
rangeInput.style.backgroundSize = percentage + '% 100%';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onContentChange: function onContentChange(targetNode, func) {
|
onContentChange: function onContentChange(targetNode, func) {
|
||||||
|
|
@ -91,17 +188,22 @@ state.utils = {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
state.utils.onContentChange(select, function (el) {
|
state.utils.onContentChange(select, function (el) {
|
||||||
let selected = el.querySelector('span.single-select');
|
// Use compatibility helper to get dropdown value
|
||||||
if (selected) {
|
var value = state.utils.getDropdownValue(el);
|
||||||
store.set(id, selected.textContent);
|
if (value) {
|
||||||
} else {
|
store.set(id, value);
|
||||||
// new gradio version...
|
|
||||||
let input = select.querySelector('input');
|
|
||||||
if (input) {
|
|
||||||
store.set(id, input.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Also listen to input events directly for Gradio 4.x
|
||||||
|
let input = select.querySelector('input');
|
||||||
|
if (input) {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
if (this.value) {
|
||||||
|
store.set(id, this.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}, 150);
|
}, 150);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[state]: Error:', error);
|
console.error('[state]: Error:', error);
|
||||||
|
|
@ -144,7 +246,8 @@ state.utils = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.utils.onContentChange(select, function (el) {
|
state.utils.onContentChange(select, function (el) {
|
||||||
const selected = Array.from(el.querySelectorAll('.token > span')).map(item => item.textContent);
|
// Use compatibility helper to get multi-select values
|
||||||
|
const selected = state.utils.getMultiSelectValues(el);
|
||||||
store.set(id, selected);
|
store.set(id, selected);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -225,7 +328,167 @@ state.utils.html = {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.innerHTML = text;
|
btn.innerHTML = text;
|
||||||
btn.onclick = onclick || function () {};
|
btn.onclick = onclick || function () {};
|
||||||
btn.className = 'gr-button gr-button-lg gr-button-primary';
|
// Support both old Gradio 3.x and new Gradio 4.x button classes
|
||||||
|
btn.className = state.utils.getButtonClass();
|
||||||
return btn;
|
return btn;
|
||||||
|
},
|
||||||
|
// Get the appropriate button class based on Gradio version
|
||||||
|
getButtonClass: function() {
|
||||||
|
return state.utils.getButtonClass();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Gradio version detection and compatibility helpers
|
||||||
|
state.utils.gradio = {
|
||||||
|
_version: null,
|
||||||
|
_detected: false,
|
||||||
|
|
||||||
|
// Detect Gradio version based on available DOM elements/classes
|
||||||
|
detectVersion: function() {
|
||||||
|
if (this._detected) return this._version;
|
||||||
|
|
||||||
|
var root = gradioApp();
|
||||||
|
|
||||||
|
// Check for Gradio 4.x indicators
|
||||||
|
if (root.querySelector('.gradio-container-4-')) {
|
||||||
|
this._version = 4;
|
||||||
|
} else if (root.querySelector('[class*="gradio-container-4"]')) {
|
||||||
|
this._version = 4;
|
||||||
|
} else if (root.querySelector('.svelte-')) {
|
||||||
|
// Gradio 4.x uses svelte classes
|
||||||
|
this._version = 4;
|
||||||
|
} else {
|
||||||
|
// Default to 3.x for older versions
|
||||||
|
this._version = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._detected = true;
|
||||||
|
state.logging.log('Detected Gradio version: ' + this._version + '.x');
|
||||||
|
return this._version;
|
||||||
|
},
|
||||||
|
|
||||||
|
isVersion4: function() {
|
||||||
|
return this.detectVersion() >= 4;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get button class with fallback support
|
||||||
|
state.utils.getButtonClass = function() {
|
||||||
|
var root = gradioApp();
|
||||||
|
// Try to find an existing button and copy its class
|
||||||
|
var existingBtn = root.querySelector('#quicksettings button');
|
||||||
|
if (existingBtn && existingBtn.className) {
|
||||||
|
return existingBtn.className;
|
||||||
|
}
|
||||||
|
// Fallback class names - try both old and new
|
||||||
|
if (state.utils.gradio.isVersion4()) {
|
||||||
|
return 'lg primary gradio-button svelte-cmf5ev';
|
||||||
|
}
|
||||||
|
return 'gr-button gr-button-lg gr-button-primary';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find dropdown elements with fallback selectors
|
||||||
|
state.utils.findDropdowns = function(container) {
|
||||||
|
container = container || gradioApp();
|
||||||
|
// Try multiple selectors for compatibility
|
||||||
|
var dropdowns = container.querySelectorAll('.gradio-dropdown');
|
||||||
|
if (!dropdowns.length) {
|
||||||
|
dropdowns = container.querySelectorAll('[data-testid="dropdown"]');
|
||||||
|
}
|
||||||
|
if (!dropdowns.length) {
|
||||||
|
dropdowns = container.querySelectorAll('.dropdown');
|
||||||
|
}
|
||||||
|
return dropdowns;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find accordion elements with fallback selectors
|
||||||
|
state.utils.findAccordions = function(container) {
|
||||||
|
container = container || gradioApp();
|
||||||
|
var accordions = container.querySelectorAll('.gradio-accordion');
|
||||||
|
if (!accordions.length) {
|
||||||
|
accordions = container.querySelectorAll('.accordion');
|
||||||
|
}
|
||||||
|
if (!accordions.length) {
|
||||||
|
accordions = container.querySelectorAll('[data-testid="accordion"]');
|
||||||
|
}
|
||||||
|
return accordions;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get selected value from dropdown with version compatibility
|
||||||
|
state.utils.getDropdownValue = function(select) {
|
||||||
|
if (!select) return null;
|
||||||
|
|
||||||
|
// Try new Gradio 4.x input method first
|
||||||
|
var input = select.querySelector('input');
|
||||||
|
if (input && input.value) {
|
||||||
|
return input.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try old Gradio 3.x span method
|
||||||
|
var selected = select.querySelector('span.single-select');
|
||||||
|
if (selected) {
|
||||||
|
return selected.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try other common patterns
|
||||||
|
var selectedOption = select.querySelector('.selected');
|
||||||
|
if (selectedOption) {
|
||||||
|
return selectedOption.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get multi-select values with version compatibility
|
||||||
|
state.utils.getMultiSelectValues = function(select) {
|
||||||
|
if (!select) return [];
|
||||||
|
|
||||||
|
// Try token pattern (common in both versions)
|
||||||
|
var tokens = select.querySelectorAll('.token > span, .token span:first-child');
|
||||||
|
if (tokens.length) {
|
||||||
|
return Array.from(tokens).map(item => item.textContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try secondary-wrap pattern (Gradio 4.x)
|
||||||
|
var secondary = select.querySelectorAll('.secondary-wrap .token');
|
||||||
|
if (secondary.length) {
|
||||||
|
return Array.from(secondary).map(item => item.textContent.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try pill/tag pattern
|
||||||
|
var pills = select.querySelectorAll('.pill, .tag, [data-value]');
|
||||||
|
if (pills.length) {
|
||||||
|
return Array.from(pills).map(item => item.textContent || item.dataset.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if accordion is open with version compatibility
|
||||||
|
state.utils.isAccordionOpen = function(accordion) {
|
||||||
|
if (!accordion) return false;
|
||||||
|
|
||||||
|
var labelWrap = accordion.querySelector('.label-wrap');
|
||||||
|
if (labelWrap) {
|
||||||
|
// Check for 'open' class (Forge/A1111 style)
|
||||||
|
if (labelWrap.classList.contains('open')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for input-accordion-open class
|
||||||
|
if (accordion.classList.contains('input-accordion-open')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check icon rotation (older pattern)
|
||||||
|
var icon = accordion.querySelector('.transition, .icon');
|
||||||
|
if (icon) {
|
||||||
|
var transform = icon.style.transform || window.getComputedStyle(icon).transform;
|
||||||
|
if (transform && transform.indexOf('90') === -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from fastapi import FastAPI, Body, HTTPException, Request, Response
|
from fastapi import FastAPI
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import modules.shared as shared
|
import modules.shared as shared
|
||||||
|
|
@ -7,6 +7,11 @@ import modules.script_callbacks as script_callbacks
|
||||||
|
|
||||||
|
|
||||||
class StateApi():
|
class StateApi():
|
||||||
|
"""
|
||||||
|
API endpoint for the State extension.
|
||||||
|
Provides configuration data to the frontend JavaScript.
|
||||||
|
Compatible with both AUTOMATIC1111 and Forge WebUI.
|
||||||
|
"""
|
||||||
|
|
||||||
BASE_PATH = '/state'
|
BASE_PATH = '/state'
|
||||||
|
|
||||||
|
|
@ -21,11 +26,32 @@ class StateApi():
|
||||||
self.add_api_route('/config.json', self.get_config, methods=['GET'])
|
self.add_api_route('/config.json', self.get_config, methods=['GET'])
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self):
|
||||||
return FileResponse(shared.cmd_opts.ui_settings_file)
|
"""
|
||||||
|
Return the UI settings file containing state configuration.
|
||||||
|
Works with both A1111 and Forge which may have different settings locations.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Try standard location first (works for both A1111 and Forge)
|
||||||
|
settings_file = getattr(shared.cmd_opts, 'ui_settings_file', None)
|
||||||
|
if settings_file:
|
||||||
|
return FileResponse(settings_file)
|
||||||
|
|
||||||
|
# Fallback: try to get settings from shared.opts
|
||||||
|
config = {
|
||||||
|
'state': getattr(shared.opts, 'state', []),
|
||||||
|
'state_txt2img': getattr(shared.opts, 'state_txt2img', []),
|
||||||
|
'state_img2img': getattr(shared.opts, 'state_img2img', []),
|
||||||
|
'state_extensions': getattr(shared.opts, 'state_extensions', []),
|
||||||
|
'state_ui': getattr(shared.opts, 'state_ui', []),
|
||||||
|
}
|
||||||
|
return JSONResponse(content=config)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[State] Error loading config: {e}")
|
||||||
|
return JSONResponse(content={})
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = StateApi()
|
api = StateApi()
|
||||||
script_callbacks.on_app_started(api.start)
|
script_callbacks.on_app_started(api.start)
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
print(f"[State] Error initializing API: {e}")
|
||||||
|
|
@ -1,18 +1,29 @@
|
||||||
|
"""
|
||||||
|
State extension settings for Stable Diffusion WebUI.
|
||||||
|
Compatible with both AUTOMATIC1111 and Forge WebUI.
|
||||||
|
"""
|
||||||
|
|
||||||
import gradio as gr
|
import gradio as gr
|
||||||
import modules.shared as shared
|
import modules.shared as shared
|
||||||
from modules import scripts
|
from modules import scripts
|
||||||
|
|
||||||
|
|
||||||
def on_ui_settings():
|
def on_ui_settings():
|
||||||
|
"""
|
||||||
|
Register the State extension settings in the WebUI settings page.
|
||||||
|
Uses CheckboxGroup for compatibility with both Gradio 3.x and 4.x.
|
||||||
|
"""
|
||||||
|
|
||||||
section = ("state", "State")
|
section = ("state", "State")
|
||||||
|
|
||||||
|
# Main elements (tabs)
|
||||||
shared.opts.add_option("state", shared.OptionInfo([], "Saved main elements", gr.CheckboxGroup, lambda: {
|
shared.opts.add_option("state", shared.OptionInfo([], "Saved main elements", gr.CheckboxGroup, lambda: {
|
||||||
"choices": [
|
"choices": [
|
||||||
"tabs"
|
"tabs"
|
||||||
]
|
]
|
||||||
}, section=section))
|
}, section=section))
|
||||||
|
|
||||||
|
# txt2img elements - includes Forge-specific options
|
||||||
shared.opts.add_option("state_txt2img", shared.OptionInfo([], "Saved elements from txt2img", gr.CheckboxGroup, lambda: {
|
shared.opts.add_option("state_txt2img", shared.OptionInfo([], "Saved elements from txt2img", gr.CheckboxGroup, lambda: {
|
||||||
"choices": [
|
"choices": [
|
||||||
"prompt",
|
"prompt",
|
||||||
|
|
@ -34,6 +45,7 @@ def on_ui_settings():
|
||||||
"hires_resize_x",
|
"hires_resize_x",
|
||||||
"hires_resize_y",
|
"hires_resize_y",
|
||||||
"hires_denoising_strength",
|
"hires_denoising_strength",
|
||||||
|
"hires_cfg_scale",
|
||||||
"refiner",
|
"refiner",
|
||||||
"refiner_checkpoint",
|
"refiner_checkpoint",
|
||||||
"refiner_switch",
|
"refiner_switch",
|
||||||
|
|
@ -50,6 +62,7 @@ def on_ui_settings():
|
||||||
]
|
]
|
||||||
}, section=section))
|
}, section=section))
|
||||||
|
|
||||||
|
# img2img elements
|
||||||
shared.opts.add_option("state_img2img", shared.OptionInfo([], "Saved elements from img2img", gr.CheckboxGroup, lambda: {
|
shared.opts.add_option("state_img2img", shared.OptionInfo([], "Saved elements from img2img", gr.CheckboxGroup, lambda: {
|
||||||
"choices": [
|
"choices": [
|
||||||
"prompt",
|
"prompt",
|
||||||
|
|
@ -82,6 +95,7 @@ def on_ui_settings():
|
||||||
]
|
]
|
||||||
}, section=section))
|
}, section=section))
|
||||||
|
|
||||||
|
# Extension support - includes Forge built-in extensions
|
||||||
shared.opts.add_option("state_extensions", shared.OptionInfo([], "Saved elements from extensions", gr.CheckboxGroup, lambda: {
|
shared.opts.add_option("state_extensions", shared.OptionInfo([], "Saved elements from extensions", gr.CheckboxGroup, lambda: {
|
||||||
"choices": [
|
"choices": [
|
||||||
"control-net",
|
"control-net",
|
||||||
|
|
@ -91,6 +105,7 @@ def on_ui_settings():
|
||||||
]
|
]
|
||||||
}, section=section))
|
}, section=section))
|
||||||
|
|
||||||
|
# UI buttons configuration
|
||||||
shared.opts.add_option("state_ui", shared.OptionInfo([
|
shared.opts.add_option("state_ui", shared.OptionInfo([
|
||||||
"Reset Button",
|
"Reset Button",
|
||||||
"Import Button",
|
"Import Button",
|
||||||
|
|
@ -103,4 +118,8 @@ def on_ui_settings():
|
||||||
],
|
],
|
||||||
}, section=section))
|
}, section=section))
|
||||||
|
|
||||||
scripts.script_callbacks.on_ui_settings(on_ui_settings)
|
|
||||||
|
try:
|
||||||
|
scripts.script_callbacks.on_ui_settings(on_ui_settings)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[State] Error registering settings: {e}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue