495 lines
17 KiB
JavaScript
495 lines
17 KiB
JavaScript
window.state = window.state || {};
|
|
state = window.state;
|
|
let selectingQueue = -1;
|
|
|
|
state.utils = {
|
|
triggerEvent: function triggerEvent(element, event) {
|
|
if (! element) {
|
|
state.logging.warn('Element not found');
|
|
return;
|
|
}
|
|
element.dispatchEvent(new Event(event.trim()));
|
|
return element;
|
|
},
|
|
triggerMouseEvent: function triggerMouseEvent(element, event) {
|
|
if (! element) {
|
|
state.logging.warn('Element not found');
|
|
return;
|
|
}
|
|
event = event || 'click';
|
|
element.dispatchEvent(new MouseEvent(event, {
|
|
view: window,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
}));
|
|
return element;
|
|
},
|
|
clickToggleMenu: function openToggleMenu(element) {
|
|
if (! element) {
|
|
state.logging.warn('Toggle button not found');
|
|
return;
|
|
}
|
|
element.click();
|
|
return element;
|
|
},
|
|
setValue: function setValue(element, value, event) {
|
|
switch (element.type) {
|
|
case 'checkbox':
|
|
element.checked = value === 'true';
|
|
this.triggerEvent(element, event);
|
|
break;
|
|
case 'radio':
|
|
if (element.value === value) {
|
|
element.checked = true;
|
|
this.triggerEvent(element, event);
|
|
} else {
|
|
element.checked = false;
|
|
}
|
|
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:
|
|
element.value = value;
|
|
// 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) {
|
|
const observer = new MutationObserver((mutationsList, observer) => {
|
|
for (const mutation of mutationsList) {
|
|
if (mutation.type === 'childList') {
|
|
func(targetNode);
|
|
}
|
|
}
|
|
});
|
|
observer.observe(targetNode, {
|
|
childList: true,
|
|
characterData: true,
|
|
subtree: true
|
|
});
|
|
},
|
|
handleSelect: function handleSelect(select, id, store) {
|
|
try {
|
|
let value = store.get(id);
|
|
|
|
if (value) {
|
|
selectingQueue += 1;
|
|
setTimeout(() => {
|
|
let input = select.querySelector('input');
|
|
state.utils.triggerMouseEvent(input, 'focus');
|
|
|
|
setTimeout(() => {
|
|
let items = Array.from(select.querySelectorAll('ul li'));
|
|
items.forEach(li => {
|
|
if (li.lastChild.wholeText.trim() === value) {
|
|
state.utils.triggerMouseEvent(li, 'mousedown');
|
|
return false;
|
|
}
|
|
});
|
|
state.utils.triggerMouseEvent(input, 'blur');
|
|
selectingQueue -= 1;
|
|
}, 100);
|
|
}, selectingQueue * 200)
|
|
}
|
|
|
|
setTimeout(() => {
|
|
state.utils.onContentChange(select, function (el) {
|
|
// Use compatibility helper to get dropdown value
|
|
var value = state.utils.getDropdownValue(el);
|
|
if (value) {
|
|
store.set(id, 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);
|
|
} catch (error) {
|
|
console.error('[state]: Error:', error);
|
|
}
|
|
},
|
|
handleMultipleSelect: function handleMultipleSelect(select, id, store) {
|
|
try {
|
|
let value = store.get(id);
|
|
|
|
if (value) {
|
|
|
|
value = value.split(',').reverse();
|
|
|
|
if (value.length) {
|
|
|
|
let input = select.querySelector('input');
|
|
|
|
let selectOption = function () {
|
|
|
|
if (! value.length) {
|
|
state.utils.triggerMouseEvent(input, 'blur');
|
|
return;
|
|
}
|
|
|
|
let option = value.pop();
|
|
state.utils.triggerMouseEvent(input, 'focus');
|
|
|
|
setTimeout(() => {
|
|
let items = Array.from(select.querySelectorAll('ul li'));
|
|
items.forEach(li => {
|
|
if (li.lastChild.wholeText.trim() === option) {
|
|
state.utils.triggerMouseEvent(li, 'mousedown');
|
|
return false;
|
|
}
|
|
});
|
|
setTimeout(selectOption, 100);
|
|
}, 100);
|
|
}
|
|
selectOption();
|
|
}
|
|
}
|
|
state.utils.onContentChange(select, function (el) {
|
|
// Use compatibility helper to get multi-select values
|
|
const selected = state.utils.getMultiSelectValues(el);
|
|
store.set(id, selected);
|
|
});
|
|
} catch (error) {
|
|
console.error('[state]: Error:', error);
|
|
}
|
|
},
|
|
txtToId: function txtToId(txt) {
|
|
return txt.split(' ').join('-').toLowerCase();
|
|
},
|
|
callXTimes: function callXTimes(func, times) {
|
|
let called = 0;
|
|
return function() {
|
|
if (called < times) {
|
|
called++;
|
|
return func.apply(this);
|
|
}
|
|
}
|
|
},
|
|
saveFile: function saveJSON(fileName ,data) {
|
|
const json = JSON.stringify(data, null, 4);
|
|
const blob = new Blob([json], {type: 'application/json'});
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = fileName + '.json';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.parentNode.removeChild(link);
|
|
},
|
|
debounce: function debounce(func, delay) {
|
|
let lastCallTime = 0;
|
|
return function() {
|
|
const currentCallTime = new Date().getTime();
|
|
if (currentCallTime - lastCallTime > delay) {
|
|
lastCallTime = currentCallTime;
|
|
func.apply(this, arguments);
|
|
}
|
|
}
|
|
},
|
|
onNextUiUpdates: function (func) {
|
|
// brute force this to to ensure that the method is called after next few updates
|
|
onUiUpdate(this.callXTimes(function () { setTimeout(func, 5); }, 150));
|
|
}
|
|
};
|
|
|
|
state.utils.html = {
|
|
setStyle: function setStyle(elements, style) {
|
|
if (elements instanceof NodeList) {
|
|
elements = Array.from(elements);
|
|
} else if (elements instanceof Node){
|
|
elements = [elements];
|
|
} else {
|
|
return;
|
|
}
|
|
elements.forEach(element => {
|
|
for (let key in style) {
|
|
if (style.hasOwnProperty(key)) {
|
|
element.style[key] = style[key];
|
|
}
|
|
}
|
|
});
|
|
},
|
|
create: function create(type, props, style) {
|
|
const element = document.createElement(type);
|
|
if (props) {
|
|
for (let key in props) {
|
|
if (props.hasOwnProperty(key)) {
|
|
element[key] = props[key];
|
|
}
|
|
}
|
|
}
|
|
if (style) {
|
|
this.setStyle(element, style);
|
|
}
|
|
return element;
|
|
},
|
|
createButton: function createButton(text, onclick) {
|
|
const btn = document.createElement('button');
|
|
btn.innerHTML = text;
|
|
btn.onclick = onclick || function () {};
|
|
// Support both old Gradio 3.x and new Gradio 4.x button classes
|
|
btn.className = state.utils.getButtonClass();
|
|
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;
|
|
};
|