function isJsonString(str) { if (typeof str !== 'string' || str.trim() === '') { return false; } try { const result = JSON.parse(str); return typeof result === 'object' && result !== null; } catch (e) { return false; } } // Copied and adapted from https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/82a973c04367123ae98bd9abdf80d9eda9b910e2/javascript/extraNetworks.js#L582 function requestGet(url, data, handler, errorHandler) { if (typeof url !== 'string' || url.trim() === '') { console.error("requestGet: Invalid or empty URL provided."); if (typeof errorHandler === 'function') errorHandler(new Error("Invalid URL")); return; } const requestData = (data && typeof data === 'object') ? data : {}; const successHandler = typeof handler === 'function' ? handler : () => { }; const failureHandler = typeof errorHandler === 'function' ? errorHandler : (err) => { console.error("requestGet failed:", err); }; let args = ''; try { args = Object.keys(requestData).map(function (k) { const key = encodeURIComponent(k); const value = encodeURIComponent(requestData[k]); return `${key}=${value}`; }).join('&'); } catch (e) { console.error("requestGet: Failed to serialize data object.", e); failureHandler(e); return; } const fullUrl = args ? `${url}?${args}` : url; const xhr = new XMLHttpRequest(); xhr.open("GET", fullUrl, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { try { const responseJson = isJsonString(xhr.responseText) ? JSON.parse(xhr.responseText) : {}; successHandler(responseJson); } catch (error) { console.error("requestGet: Error parsing JSON response.", error); failureHandler(error); } } else { failureHandler(new Error(`Request failed with status ${xhr.status}: ${xhr.statusText}`)); } } }; xhr.onerror = function () { failureHandler(new Error("Network error occurred.")); }; xhr.send(); } // copied and adapted from https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/82a973c04367123ae98bd9abdf80d9eda9b910e2/javascript/ui.js#L348 function restart_ui() { const overlay = document.createElement('div'); overlay.innerHTML = '

Reloading...

'; overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(20, 20, 20, 0.9)'; overlay.style.zIndex = '10000'; document.body.appendChild(overlay); const requestPing = function () { requestGet( window.location.href, {}, function (data) { window.location.reload(); }, function (error) { console.log("Ping failed, retrying..."); setTimeout(requestPing, 500); } ); }; setTimeout(requestPing, 2000); } /** * Moves all child elements from one or more source containers to their corresponding target containers. * By convention, the target ID for a source 'my-source-id' is expected to be 'my-source-id_target'. * After moving, the source containers are removed from the DOM by default. * * @param {string | string[]} source_ids - A single source element ID or an array of IDs. * @param {object} [options] - Optional settings. * @param {boolean} [options.remove_sources=true] - If true, removes the source elements after their content is moved. */ function reparent_flyout(source_ids, options = { remove_sources: true }) { // Ensure the input is always an array to simplify the logic. const sourceArray = Array.isArray(source_ids) ? source_ids : [source_ids]; // Iterate over each provided source ID. sourceArray.forEach(source_id => { // Find the source element. const source = document.getElementById(source_id); // If the source element doesn't exist, we can't do anything for this ID. if (!source) { console.warn(`WARNING: Source element with ID '${source_id}' not found. Skipping.`); return; // 'return' inside a forEach acts like 'continue' in a for loop. } // Derive the target ID from the source ID const target_id = `${source_id}_target`; const target = document.getElementById(target_id); // If the corresponding target element doesn't exist, log an error. if (!target) { console.error(`ERROR: Reparenting for '#${source_id}' failed. Corresponding target '#${target_id}' not found.`); return; } // Move each child node from the source to the target. // The `while (source.firstChild)` loop is efficient because `appendChild` // automatically removes the child from its original parent. while (source.firstChild) { target.appendChild(source.firstChild); } // Remove the now-empty source container if the option is enabled. if (options.remove_sources) { source.remove(); } console.log(`SUCCESS: Content from '#${source_id}' has been reparented to '#${target_id}'.`); }); } function position_flyout(anchorId, target_id) { if (!anchorId) { return; } const anchorElem = document.getElementById(anchorId); const flyoutElem = document.getElementById(target_id); if (anchorElem && flyoutElem) { //console.log("JS: Positioning flyout relative to:", anchorId); const anchorRect = anchorElem.getBoundingClientRect(); const flyoutWidth = flyoutElem.offsetWidth; const flyoutHeight = flyoutElem.offsetHeight; let topPosition = anchorRect.top + (anchorRect.height / 2) - (flyoutHeight / 2); let leftPosition = anchorRect.left + (anchorRect.width / 2) - (flyoutWidth / 2); const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; if (leftPosition < 8) leftPosition = 8; if (topPosition < 8) topPosition = 8; if (leftPosition + flyoutWidth > windowWidth) leftPosition = windowWidth - flyoutWidth - 8; if (topPosition + flyoutHeight > windowHeight) topPosition = windowHeight - flyoutHeight - 8; flyoutElem.style.top = `${topPosition}px`; flyoutElem.style.left = `${leftPosition}px`; } } // This is the new main function called by Gradio's .then() event function update_flyout_from_state(jsonData) { //console.log("JS: update_flyout_from_state() called with data:", jsonData); if (!jsonData) return; const state = JSON.parse(jsonData); const { isVisible, anchorId, targetId } = state; const flyout = document.getElementById(targetId); if (!flyout) { console.error("ERROR: Cannot update UI. Flyout container not found."); return; } //console.log("JS: Parsed state:", { isVisible, anchorId }); if (isVisible) { flyout.style.display = 'flex'; position_flyout(anchorId, targetId); } else { flyout.style.display = 'none'; } }