mirror of
https://github.com/vladmandic/automatic
synced 2026-08-29 16:41:01 +02:00
17b7176e0d
A single handler bound to the AR dropdown, width and height (in the shared create_resolution_inputs and the resize section) wrote both sliders on every change. With a ratio selected it locked one axis and snapped it back on each edit, the two sliders looped, and the math ran server-side one round-trip per keystroke, echoing a value back into the field being typed in and yanking it. Move aspect-ratio linking to the browser (ui/resolutionLock.ts): a debounced edit writes only the partner axis, never the field being edited, and commits immediately on blur, enter, or slider release. Keep the kanvas notify on notifyKanvasResize wired to the resize sliders' gradio .change, so it still fires on programmatic size updates (detect, paste, swap) that client-side listeners miss. Drop the per-change AR wiring, res_apply, and the resolutionChange* helpers.
868 lines
30 KiB
TypeScript
868 lines
30 KiB
TypeScript
import { randomId, requestProgress } from './progressBar';
|
|
import { gradioApp, onAfterUiUpdate } from './script';
|
|
import { log, debug, error } from './logger';
|
|
import { timer } from './timers';
|
|
import { authFetch } from './authWrap';
|
|
import { getENActiveTab, markSelectedCards } from './extraNetworks';
|
|
|
|
window.opts = {};
|
|
window.localization = {};
|
|
window.titles = {};
|
|
let tabSelected = '';
|
|
let txt2img_textarea;
|
|
let img2img_textarea;
|
|
let fontSizeApplyRaf = 0;
|
|
let pendingFontSize: number | null = null;
|
|
let appliedFontSize: number | null = null;
|
|
let cachedGradioRoot: any = null;
|
|
const wait_time = 800;
|
|
const token_timeouts = {};
|
|
let uiLoaded = false;
|
|
let promptsInitialized = false;
|
|
window.args_to_array = Array.from; // Compatibility with e.g. extensions that may expect this to be around
|
|
|
|
function rememberGallerySelection(name) {
|
|
// dummy
|
|
}
|
|
|
|
export function set_theme(theme) {
|
|
const gradioURL = window.location.href;
|
|
if (!gradioURL.includes('?__theme=')) window.location.replace(`${gradioURL}?__theme=${theme}`);
|
|
}
|
|
|
|
function update_token_counter(button_id) {
|
|
if (token_timeouts[button_id]) clearTimeout(token_timeouts[button_id]);
|
|
token_timeouts[button_id] = setTimeout(() => gradioApp().getElementById(button_id)?.click(), wait_time);
|
|
}
|
|
|
|
function clip_gallery_urls(gallery) {
|
|
const files = gallery.map((v) => v.data);
|
|
navigator.clipboard.writeText(JSON.stringify(files)).then(
|
|
() => log('clipboard:', files),
|
|
(err) => error(`clipboard: ${files} ${err}`),
|
|
);
|
|
}
|
|
|
|
function isVisible(el) {
|
|
if (!el) return false;
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.width === 0 && rect.height === 0) return false;
|
|
return (rect.top >= 0) && (rect.left >= 0) && (rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)) && (rect.right <= (window.innerWidth || document.documentElement.clientWidth));
|
|
}
|
|
|
|
export function all_gallery_buttons() {
|
|
let allGalleryButtons = gradioApp().querySelectorAll('[style="display: block;"].tabitem div[id$=_gallery].gradio-gallery .thumbnails > .thumbnail-item.thumbnail-small');
|
|
if (allGalleryButtons.length === 0) allGalleryButtons = gradioApp().querySelectorAll('.gradio-gallery .thumbnails > .thumbnail-item.thumbnail-small');
|
|
const visibleGalleryButtons = [];
|
|
allGalleryButtons.forEach((elem) => {
|
|
if (elem.parentElement.offsetParent) visibleGalleryButtons.push(elem);
|
|
});
|
|
return visibleGalleryButtons;
|
|
}
|
|
|
|
export function selected_gallery_button() {
|
|
let allCurrentButtons = gradioApp().querySelectorAll('[style="display: block;"].tabitem div[id$=_gallery].gradio-gallery .thumbnail-item.thumbnail-small.selected');
|
|
if (allCurrentButtons.length === 0) allCurrentButtons = gradioApp().querySelectorAll('.gradio-gallery .thumbnails > .thumbnail-item.thumbnail-small.selected');
|
|
let visibleCurrentButton = null;
|
|
allCurrentButtons.forEach((elem) => {
|
|
if (elem.parentElement.offsetParent) visibleCurrentButton = elem;
|
|
});
|
|
return visibleCurrentButton;
|
|
}
|
|
|
|
function selected_gallery_index() {
|
|
const buttons = all_gallery_buttons();
|
|
const button = selected_gallery_button();
|
|
let result = -1;
|
|
buttons.forEach((v, i) => { if (v === button) { result = i; } });
|
|
if (result === -1 && gradioApp().getElementById('tab-gallery-search')?.checkVisibility()) {
|
|
const gallerySelection = window.getGallerySelection();
|
|
if (Number.isInteger(gallerySelection.index)) result = gallerySelection.index;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function selected_gallery_files(tabname) {
|
|
let allImages = [];
|
|
let allThumbnails;
|
|
if (tabname && tabname !== 'gallery') allThumbnails = gradioApp().querySelectorAll('div[id$=_gallery].gradio-gallery .thumbnail-item.thumbnail-small');
|
|
else allThumbnails = gradioApp().querySelectorAll('.gradio-gallery .thumbnails > .thumbnail-item.thumbnail-small');
|
|
try {
|
|
allImages = Array.from<any>(allThumbnails).map((v) => v.querySelector('img'));
|
|
if (tabname && tabname !== 'gallery') allImages = allImages.filter((img) => isVisible(img));
|
|
allImages = allImages.map((img) => {
|
|
let fn = img.src;
|
|
if (fn.includes('file=')) fn = fn.split('file=')[1];
|
|
return decodeURI(fn);
|
|
});
|
|
} catch (err) {
|
|
error(`selected_gallery_files: ${err}`);
|
|
}
|
|
let selectedIndex = -1;
|
|
if (tabname && tabname !== 'gallery') selectedIndex = selected_gallery_index();
|
|
return [allImages, selectedIndex];
|
|
}
|
|
|
|
function extract_image_from_gallery(gallery) {
|
|
if (gallery.length === 0) return [null];
|
|
if (gallery.length === 1) return [gallery[0]];
|
|
let index = selected_gallery_index();
|
|
if (index < 0 || index >= gallery.length) index = 0;
|
|
return [gallery[index]];
|
|
}
|
|
|
|
function send_to_kanvas(gallery) {
|
|
const [image] = extract_image_from_gallery(gallery);
|
|
log('sendToKanvas', image);
|
|
if (window.loadFromURL && image && image.data) window.loadFromURL(image.data);
|
|
}
|
|
|
|
export async function setTheme(val, old) {
|
|
if (!old || val === old) return;
|
|
old = old.replace('modern/', '');
|
|
val = val.replace('modern/', '');
|
|
const links = Array.from<any>(document.getElementsByTagName('link')).filter((l) => l.href.includes(old));
|
|
if (links.length === 0) {
|
|
log('setTheme: current theme not matched', old);
|
|
return;
|
|
}
|
|
for (const link of links) {
|
|
const href = link.href.replace(old, val);
|
|
const res = await fetch(href);
|
|
if (res.ok) {
|
|
log('setTheme', old, val);
|
|
link.href = link.href.replace(old, val);
|
|
} else {
|
|
log('setTheme: CSS not found', val);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function setFontSize(val, old) {
|
|
const size = Number(val || opts.font_size);
|
|
if (!Number.isFinite(size)) return;
|
|
if (size === old || size === appliedFontSize || size === pendingFontSize) return;
|
|
pendingFontSize = size;
|
|
if (fontSizeApplyRaf) return;
|
|
|
|
fontSizeApplyRaf = requestAnimationFrame(() => {
|
|
const t0 = performance.now();
|
|
fontSizeApplyRaf = 0;
|
|
const nextSize = pendingFontSize;
|
|
pendingFontSize = null;
|
|
if (!Number.isFinite(nextSize) || nextSize === appliedFontSize) return;
|
|
|
|
cachedGradioRoot = cachedGradioRoot || gradioApp();
|
|
const rootStyle = cachedGradioRoot.style;
|
|
document.documentElement.style.setProperty('--font-size', `${nextSize}px`);
|
|
rootStyle.setProperty('--font-size', `${nextSize}px`);
|
|
rootStyle.setProperty('--text-xxs', `${nextSize - 3}px`);
|
|
rootStyle.setProperty('--text-xs', `${nextSize - 2}px`);
|
|
rootStyle.setProperty('--text-sm', `${nextSize - 1}px`);
|
|
rootStyle.setProperty('--text-md', `${nextSize}px`);
|
|
rootStyle.setProperty('--text-lg', `${nextSize + 1}px`);
|
|
rootStyle.setProperty('--text-xl', `${nextSize + 2}px`);
|
|
rootStyle.setProperty('--text-xxl', `${nextSize + 3}px`);
|
|
appliedFontSize = nextSize;
|
|
const t1 = performance.now();
|
|
log('setFontSize', nextSize, `time=${Math.round(t1 - t0)}`);
|
|
timer('setFontSize', t1 - t0);
|
|
});
|
|
}
|
|
|
|
function switchToTab(tab) {
|
|
const tabs = Array.from<any>(gradioApp().querySelectorAll('#tabs > .tab-nav > button'));
|
|
const btn = tabs?.find((t) => t.innerText === tab);
|
|
log('switchToTab', tab);
|
|
if (btn) btn.click();
|
|
}
|
|
|
|
function switch_to_txt2img(...args) {
|
|
switchToTab('Text');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_img2img_tab(no) {
|
|
switchToTab('Image');
|
|
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[no].click();
|
|
}
|
|
|
|
function switch_to_img2img(...args) {
|
|
switchToTab('Image');
|
|
switch_to_img2img_tab(0);
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_inpaint(...args) {
|
|
switchToTab('Image');
|
|
switch_to_img2img_tab(1);
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_sketch(...args) {
|
|
switchToTab('Image');
|
|
switch_to_img2img_tab(2);
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_composite(...args) {
|
|
switchToTab('Image');
|
|
switch_to_img2img_tab(3);
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_extras(...args) {
|
|
switchToTab('Process');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_control(...args) {
|
|
switchToTab('Control');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_video(...args) {
|
|
switchToTab('Video');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function switch_to_caption(...args) {
|
|
switchToTab('Caption');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
export function get_tab_index(tabId) {
|
|
let res = 0;
|
|
gradioApp().getElementById(tabId)?.querySelector('div').querySelectorAll('button')
|
|
.forEach((button, i) => {
|
|
if (button.className.indexOf('selected') !== -1) res = i;
|
|
});
|
|
return res;
|
|
}
|
|
|
|
function create_tab_index_args(tabId, args) {
|
|
const res = Array.from<any>(args);
|
|
res[0] = get_tab_index(tabId);
|
|
return res;
|
|
}
|
|
|
|
function get_img2img_tab_index(...args) {
|
|
const res = Array.from<any>(arguments);
|
|
res.splice(-2);
|
|
res[0] = get_tab_index('mode_img2img');
|
|
return res;
|
|
}
|
|
|
|
export function create_submit_args(args) {
|
|
const res = Array.from<any>(args);
|
|
if (Array.isArray(res[res.length - 3])) res[res.length - 3] = null;
|
|
return res;
|
|
}
|
|
|
|
function getCaptionActiveTab(...args) {
|
|
const res = create_tab_index_args('mode_caption', args);
|
|
return res;
|
|
}
|
|
|
|
function showSubmitButtons(tabname, show) {}
|
|
|
|
function clearGallery(tabname) {
|
|
const gallery = gradioApp().getElementById(`${tabname}_gallery`);
|
|
gallery.classList.remove('logo');
|
|
// gallery.style.height = window.innerHeight - gallery.getBoundingClientRect().top - 200 + 'px'
|
|
const footer = gradioApp().getElementById(`${tabname}_footer`);
|
|
footer.style.display = 'flex';
|
|
}
|
|
|
|
export function submit_txt2img(...args) {
|
|
log('submitTxt');
|
|
clearGallery('txt2img');
|
|
const id = randomId();
|
|
requestProgress(id, null, gradioApp().getElementById('txt2img_gallery'));
|
|
const res = create_submit_args(args);
|
|
res[0] = id;
|
|
res[1] = window.submit_state;
|
|
window.submit_state = '';
|
|
return res;
|
|
}
|
|
|
|
function submit_img2img(...args) {
|
|
log('submitImg');
|
|
clearGallery('img2img');
|
|
const id = randomId();
|
|
requestProgress(id, null, gradioApp().getElementById('img2img_gallery'));
|
|
const res = create_submit_args(args);
|
|
res[0] = id;
|
|
res[1] = window.submit_state;
|
|
res[2] = get_tab_index('mode_img2img');
|
|
window.submit_state = '';
|
|
return res;
|
|
}
|
|
|
|
function submit_control(...args) {
|
|
log('submitControl');
|
|
clearGallery('control');
|
|
const id = randomId();
|
|
requestProgress(id, null, gradioApp().getElementById('control_gallery'));
|
|
const res = create_submit_args(args);
|
|
res[0] = id;
|
|
res[1] = window.submit_state;
|
|
|
|
const tabs = Array.from<any>(gradioApp().querySelectorAll('#control-tabs > .tab-nav > button'));
|
|
const tabIdx = tabs.findIndex((btn) => btn.classList.contains('selected'));
|
|
const tabNames = ['ControlNet', 'T2I Adapter', 'XS', 'Lite', 'Reference'];
|
|
const selectedTab = tabNames[tabIdx] || 'ControlNet';
|
|
res[2] = selectedTab.toLowerCase();
|
|
window.submit_state = '';
|
|
return res;
|
|
}
|
|
|
|
function submit_video(...args) {
|
|
log('submitVideo');
|
|
clearGallery('video');
|
|
const id = randomId();
|
|
requestProgress(id, null, gradioApp().getElementById('video_gallery'));
|
|
const res = create_submit_args(args);
|
|
res[0] = id;
|
|
res[1] = window.submit_state;
|
|
window.submit_state = '';
|
|
return res;
|
|
}
|
|
|
|
function submit_framepack(...args) {
|
|
const id = randomId();
|
|
log('submitFramepack', id);
|
|
requestProgress(id, null, null);
|
|
window.submit_state = '';
|
|
args[0] = id;
|
|
return args;
|
|
}
|
|
|
|
function submit_ltx(...args) {
|
|
const id = randomId();
|
|
log('submitFramepack', id);
|
|
requestProgress(id, null, null);
|
|
window.submit_state = '';
|
|
args[0] = id;
|
|
return args;
|
|
}
|
|
|
|
function submit_video_wrapper(...args) {
|
|
const modernEl = gradioApp().querySelector('.video_output.fade-in');
|
|
let id = modernEl ? modernEl.id : args[0];
|
|
id = id.replace('video-selector-', '');
|
|
log('submitVideoWrapper', id);
|
|
const btn = gradioApp().getElementById(`${id}_generate_btn`);
|
|
if (btn) btn.click();
|
|
}
|
|
|
|
function submit_postprocessing(...args) {
|
|
log('SubmitExtras');
|
|
clearGallery('extras');
|
|
return args;
|
|
}
|
|
|
|
window.submit_state = '';
|
|
|
|
function modelmerger(...args) {
|
|
const id = randomId();
|
|
const res = create_submit_args(args);
|
|
res[0] = id;
|
|
return res;
|
|
}
|
|
|
|
function clearPrompts(prompt, negative_prompt) {
|
|
prompt = '';
|
|
negative_prompt = '';
|
|
return [prompt, negative_prompt];
|
|
}
|
|
|
|
const promptTokenCountUpdateFuncs: Record<string, () => void> = {};
|
|
const registeredPromptTextareas = new WeakSet<HTMLTextAreaElement>();
|
|
const registeredPromptIds = new Set<string>();
|
|
const pendingCounterPlacement = new Set<string>();
|
|
const promptRegistrationConfig: [string, string, string][] = [
|
|
['txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button'],
|
|
['txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button'],
|
|
['img2img_prompt', 'img2img_token_counter', 'img2img_token_button'],
|
|
['img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button'],
|
|
['control_prompt', 'control_token_counter', 'control_token_button'],
|
|
['control_neg_prompt', 'control_negative_token_counter', 'control_negative_token_button'],
|
|
];
|
|
let promptRegistrationRaf = 0;
|
|
let promptRegistrationCursor = 0;
|
|
let promptRegistrationInProgress = false;
|
|
|
|
function scheduleIdleUI(task: () => void) {
|
|
if (typeof window.requestIdleCallback === 'function') {
|
|
window.requestIdleCallback(task, { timeout: 500 });
|
|
} else {
|
|
setTimeout(task, 0);
|
|
}
|
|
}
|
|
|
|
function recalculatePromptTokens(name) {
|
|
if (promptTokenCountUpdateFuncs[name]) {
|
|
promptTokenCountUpdateFuncs[name]();
|
|
}
|
|
}
|
|
|
|
function recalculate_prompts_txt2img(...args) {
|
|
recalculatePromptTokens('txt2img_prompt');
|
|
recalculatePromptTokens('txt2img_neg_prompt');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function recalculate_prompts_img2img(...args) {
|
|
recalculatePromptTokens('img2img_prompt');
|
|
recalculatePromptTokens('img2img_neg_prompt');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function recalculate_prompts_inpaint(...args) {
|
|
recalculatePromptTokens('img2img_prompt');
|
|
recalculatePromptTokens('img2img_neg_prompt');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
function recalculate_prompts_control(...args) {
|
|
recalculatePromptTokens('control_prompt');
|
|
recalculatePromptTokens('control_neg_prompt');
|
|
return Array.from<any>(arguments);
|
|
}
|
|
|
|
export function registerDragDrop() {
|
|
const qs = gradioApp().getElementById('quicksettings');
|
|
if (!qs) return;
|
|
qs.addEventListener('dragover', (evt) => {
|
|
evt.preventDefault();
|
|
evt.dataTransfer.dropEffect = 'copy';
|
|
});
|
|
qs.addEventListener('drop', (evt) => {
|
|
evt.preventDefault();
|
|
evt.dataTransfer.dropEffect = 'copy';
|
|
for (const f of evt.dataTransfer.files) {
|
|
log('QuickSettingsDrop', f);
|
|
}
|
|
});
|
|
}
|
|
|
|
function sortUIElements() {
|
|
// sort top-level tabs
|
|
const currSelected = gradioApp()?.querySelector('.tab-nav > .selected')?.innerText;
|
|
if (currSelected === tabSelected || !opts.ui_tab_reorder) return;
|
|
tabSelected = currSelected;
|
|
const tabs = gradioApp().getElementById('tabs')?.children[0];
|
|
if (!tabs) return;
|
|
let tabsOrder = opts.ui_tab_reorder?.split(',').map((el) => el.trim().toLowerCase()) || [];
|
|
for (const el of Array.from<any>(tabs.children)) {
|
|
const elIndex = tabsOrder.indexOf(el.innerText.toLowerCase());
|
|
if (elIndex > -1) el.style.order = elIndex - 50; // default is 0 so setting to negative values
|
|
}
|
|
// sort always-on scripts
|
|
const find = (el, ordered) => {
|
|
for (const i in ordered) {
|
|
if (el.innerText.toLowerCase().startsWith(ordered[i])) return i;
|
|
}
|
|
return 99;
|
|
};
|
|
|
|
tabsOrder = opts.ui_scripts_reorder?.split(',').map((el) => el.trim().toLowerCase()) || [];
|
|
|
|
const scriptsTxt = gradioApp().getElementById('scripts_alwayson_txt2img').children;
|
|
for (const el of Array.from<any>(scriptsTxt)) el.style.order = find(el, tabsOrder);
|
|
|
|
const scriptsImg = gradioApp().getElementById('scripts_alwayson_img2img').children;
|
|
for (const el of Array.from<any>(scriptsImg)) el.style.order = find(el, tabsOrder);
|
|
log('sortUIElements');
|
|
}
|
|
|
|
function registerTextareaCallback() {
|
|
// sortUIElements();
|
|
if (promptsInitialized) return;
|
|
if (promptRegistrationInProgress) return;
|
|
const t0 = performance.now();
|
|
|
|
const app = gradioApp();
|
|
if (!app) return;
|
|
|
|
const registerTextarea = (id, id_counter, id_button) => {
|
|
const prompt = app.getElementById(id);
|
|
const counter = app.getElementById(id_counter);
|
|
const localTextarea = prompt?.querySelector('label > textarea');
|
|
if (!prompt || !counter || !localTextarea || !prompt.parentElement) return false;
|
|
|
|
const promptParent = prompt.parentElement;
|
|
const needsCounterPlacement = counter.parentElement !== promptParent || counter.nextElementSibling !== prompt;
|
|
if (needsCounterPlacement && !pendingCounterPlacement.has(id)) {
|
|
pendingCounterPlacement.add(id);
|
|
scheduleIdleUI(() => {
|
|
pendingCounterPlacement.delete(id);
|
|
const currentPrompt = app.getElementById(id);
|
|
const currentCounter = app.getElementById(id_counter);
|
|
if (!currentPrompt || !currentCounter || !currentPrompt.parentElement) return;
|
|
const currentParent = currentPrompt.parentElement;
|
|
if (currentCounter.parentElement !== currentParent || currentCounter.nextElementSibling !== currentPrompt) {
|
|
currentParent.insertBefore(currentCounter, currentPrompt);
|
|
}
|
|
if (currentParent.style.position !== 'relative') {
|
|
currentParent.style.position = 'relative';
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!promptTokenCountUpdateFuncs[id]) promptTokenCountUpdateFuncs[id] = () => { update_token_counter(id_button); };
|
|
if (!registeredPromptTextareas.has(localTextarea)) {
|
|
localTextarea.addEventListener('input', promptTokenCountUpdateFuncs[id]);
|
|
registeredPromptTextareas.add(localTextarea);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const runPromptRegistrationStep = () => {
|
|
promptRegistrationRaf = 0;
|
|
const total = promptRegistrationConfig.length;
|
|
let cfg: [string, string, string] | null = null;
|
|
|
|
// Process one prompt registration per frame to avoid long blocking work.
|
|
for (let attempts = 0; attempts < total; attempts += 1) {
|
|
const nextCfg = promptRegistrationConfig[promptRegistrationCursor];
|
|
promptRegistrationCursor = (promptRegistrationCursor + 1) % total;
|
|
const [id] = nextCfg;
|
|
if (!registeredPromptIds.has(id)) {
|
|
cfg = nextCfg;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cfg) {
|
|
const [id] = cfg;
|
|
if (registerTextarea(...cfg)) {
|
|
registeredPromptIds.add(id);
|
|
} else {
|
|
// Prompt not available yet, retry on next onAfterUiUpdate callback.
|
|
promptRegistrationInProgress = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
promptsInitialized = registeredPromptIds.size === total;
|
|
if (promptsInitialized) {
|
|
promptRegistrationInProgress = false;
|
|
const t1 = performance.now();
|
|
log('initPrompts', { count: registeredPromptIds.size, time: Math.round(t1 - t0) });
|
|
timer('initPrompts', t1 - t0);
|
|
return;
|
|
}
|
|
|
|
promptRegistrationRaf = requestAnimationFrame(runPromptRegistrationStep);
|
|
};
|
|
|
|
promptRegistrationInProgress = true;
|
|
promptRegistrationRaf = requestAnimationFrame(runPromptRegistrationStep);
|
|
}
|
|
|
|
onAfterUiUpdate(registerTextareaCallback);
|
|
|
|
function update_txt2img_tokens(...args) {
|
|
update_token_counter('txt2img_token_button');
|
|
if (args.length === 2) return args[0];
|
|
return args;
|
|
}
|
|
|
|
function update_img2img_tokens(...args) {
|
|
update_token_counter('img2img_token_button');
|
|
if (args.length === 2) return args[0];
|
|
return args;
|
|
}
|
|
|
|
function getTranslation(...args) {
|
|
return null;
|
|
}
|
|
|
|
function monitorServerStatus() {
|
|
document.open();
|
|
document.write(`
|
|
<html>
|
|
<head><title>SD.Next</title></head>
|
|
<body style="background: #222222; font-size: 1rem; font-family:monospace; margin-top:20%; color:lightgray; text-align:center">
|
|
<h1>Waiting for server...</h1>
|
|
<script>
|
|
function monitorServerStatus() {
|
|
fetch('${window.api}/progress?skip_current_image=true')
|
|
.then((res) => {
|
|
console.log('monitorServerStatus', res);
|
|
if (res.ok) location.reload();
|
|
else setTimeout(monitorServerStatus, 1000);
|
|
})
|
|
.catch((err) => {
|
|
console.error('monitorServerStatus', err);
|
|
setTimeout(monitorServerStatus, 1000);
|
|
})
|
|
}
|
|
window.onload = () => monitorServerStatus();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`);
|
|
document.close();
|
|
}
|
|
|
|
const delay = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
|
|
export async function restartReload(initial = true) {
|
|
document.body.style = 'background: #222222; font-size: 1rem; font-family:monospace; margin-top:20%; color:lightgray; text-align:center';
|
|
document.body.innerHTML = '<h1>Server shutdown in progress...</h1>';
|
|
if (initial) await delay(10000);
|
|
try {
|
|
const res = await authFetch(`${window.api}/progress?skip_current_image=true`);
|
|
console.log('restartReload', res);
|
|
if (res?.ok) {
|
|
document.body.innerHTML = '<h1>Server restart in progress...</h1>';
|
|
setTimeout(() => location.reload(), 10000);
|
|
} else {
|
|
setTimeout(() => restartReload(false), 2500);
|
|
}
|
|
} catch {
|
|
setTimeout(() => restartReload(false), 2500);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
export function updateInput(target) {
|
|
const e = new Event('input', { bubbles: true });
|
|
Object.defineProperty(e, 'target', { value: target });
|
|
target.dispatchEvent(e);
|
|
}
|
|
|
|
let desiredCheckpointName = null;
|
|
export function selectCheckpoint(name) {
|
|
desiredCheckpointName = name;
|
|
const tabName = getENActiveTab();
|
|
const btnModel = gradioApp().getElementById(`${tabName}_extra_model`);
|
|
const isRefiner = btnModel && btnModel.classList.contains('toolbutton-selected');
|
|
if (isRefiner) gradioApp().getElementById('change_refiner').click();
|
|
else gradioApp().getElementById('change_checkpoint').click();
|
|
log(`selectCheckpoint ${isRefiner ? 'refiner' : 'model'}: ${desiredCheckpointName}`);
|
|
markSelectedCards([desiredCheckpointName], 'model');
|
|
setTimeout(requestProgress, 250);
|
|
}
|
|
|
|
let desiredVAEName = null;
|
|
export function selectVAE(name) {
|
|
desiredVAEName = name;
|
|
gradioApp().getElementById('change_vae').click();
|
|
log(`selectVAE: ${desiredVAEName}`);
|
|
markSelectedCards([desiredVAEName], 'vae');
|
|
}
|
|
|
|
let desiredUNetName = null;
|
|
|
|
function consumeDesiredCheckpointName(v) {
|
|
const res = desiredCheckpointName;
|
|
desiredCheckpointName = null;
|
|
return [res || v, null];
|
|
}
|
|
|
|
function consumeDesiredVAEName(v) {
|
|
const res = desiredVAEName;
|
|
desiredVAEName = null;
|
|
return [res || v, null];
|
|
}
|
|
|
|
function consumeDesiredUNetName(v) {
|
|
const res = desiredUNetName;
|
|
desiredUNetName = null;
|
|
return [res || v, null];
|
|
}
|
|
|
|
function getDesiredCheckpointName() {
|
|
return desiredCheckpointName;
|
|
}
|
|
|
|
export function selectUNet(name) {
|
|
desiredUNetName = name;
|
|
gradioApp().getElementById('change_unet').click();
|
|
log(`selectUNet: ${desiredUNetName}`);
|
|
markSelectedCards([desiredUNetName], 'unet');
|
|
}
|
|
|
|
export function selectReference(name) {
|
|
log(`selectReference: ${name}`);
|
|
desiredCheckpointName = name;
|
|
gradioApp().getElementById('change_reference').click();
|
|
markSelectedCards([desiredCheckpointName], 'model');
|
|
setTimeout(requestProgress, 250);
|
|
}
|
|
|
|
function currentImageResolutionimg2img(_a, _b, scaleBy) {
|
|
const img = gradioApp().querySelector('#mode_img2img > div[style="display: block;"] img');
|
|
return img ? [img.naturalWidth, img.naturalHeight, scaleBy] : [0, 0, scaleBy];
|
|
}
|
|
|
|
function currentImageResolutioncontrol(_a, _b, scaleBy) {
|
|
if (window.kanvas) {
|
|
const active = window.kanvas.stages?.getActiveStage();
|
|
return [active?.width || 0, active?.height || 0, scaleBy];
|
|
}
|
|
const img = gradioApp().querySelector('#control-tab-input > div[style="display: block;"] img');
|
|
return img ? [img.naturalWidth, img.naturalHeight, scaleBy] : [0, 0, scaleBy];
|
|
}
|
|
|
|
function updateImg2imgResizeToTextAfterChangingImage() {
|
|
const el = gradioApp().getElementById('img2img_update_resize_to');
|
|
if (el) setTimeout(() => gradioApp().getElementById('img2img_update_resize_to').click(), 500);
|
|
return [];
|
|
}
|
|
|
|
function createThemeElement(): HTMLImageElement {
|
|
const el = document.createElement('img');
|
|
el.id = 'theme-preview';
|
|
el.className = 'theme-preview';
|
|
el.onclick = () => { el.style.display = 'none'; };
|
|
document.body.appendChild(el);
|
|
return el;
|
|
}
|
|
|
|
export async function toggleCompact(val, old) {
|
|
if (val === old) return;
|
|
log('toggleCompact', val, old);
|
|
if (val) {
|
|
gradioApp().style.setProperty('--layout-gap', 'var(--spacing-md)');
|
|
gradioApp().querySelectorAll('input[type=range]').forEach((el) => el.classList.add('hidden'));
|
|
gradioApp().querySelectorAll('div .form').forEach((el) => el.classList.add('form-compact'));
|
|
gradioApp().querySelectorAll('.small-accordion .label-wrap').forEach((el) => el.classList.add('accordion-compact'));
|
|
} else {
|
|
gradioApp().style.setProperty('--layout-gap', 'var(--spacing-xxl)');
|
|
gradioApp().querySelectorAll('input[type=range]').forEach((el) => el.classList.remove('hidden'));
|
|
gradioApp().querySelectorAll('div .form').forEach((el) => el.classList.remove('form-compact'));
|
|
gradioApp().querySelectorAll('.small-accordion .label-wrap').forEach((el) => el.classList.remove('accordion-compact'));
|
|
}
|
|
}
|
|
|
|
function previewTheme() {
|
|
let name = gradioApp().getElementById('setting_gradio_theme').querySelectorAll('input')?.[0].value || '';
|
|
fetch(`${window.subpath}/file=data/themes.json`)
|
|
.then((res) => {
|
|
res.json()
|
|
.then((themes) => {
|
|
const theme = Array.isArray(themes) ? themes.find((t) => t.id === name) : null;
|
|
if (theme) {
|
|
window.open(theme.subdomain, '_blank');
|
|
} else {
|
|
const el = document.getElementById('theme-preview') as HTMLImageElement | null || createThemeElement();
|
|
el.style.display = el.style.display === 'block' ? 'none' : 'block';
|
|
name = name.replace('/', '-');
|
|
el.src = `/file=ui/css/${name}.jpg`;
|
|
}
|
|
})
|
|
.catch((e) => error(`previewTheme: ${e}`));
|
|
})
|
|
.catch((e) => error(`previewTheme: ${e}`));
|
|
}
|
|
|
|
async function browseFolder() {
|
|
const picker = window.showDirectoryPicker?.();
|
|
if (!picker) return null;
|
|
const f = await picker;
|
|
if (f && f.kind === 'directory') return f.name;
|
|
return null;
|
|
}
|
|
|
|
let kanvasNotifyTimer: ReturnType<typeof setTimeout> | undefined;
|
|
// Notify kanvas to resize its stage when the resize-panel width/height change. Wired through gradio's
|
|
// .change so it also fires on programmatic updates (detect-size, paste params, swap, send-to) that the
|
|
// client-side resolutionLock input listeners never see. Writes nothing back, so it cannot loop.
|
|
export function notifyKanvasResize(width: number, height: number) {
|
|
if (window.resizeStage) {
|
|
const w = Number(width);
|
|
const h = Number(height);
|
|
clearTimeout(kanvasNotifyTimer);
|
|
kanvasNotifyTimer = setTimeout(() => window.resizeStage?.(w, h), 250);
|
|
}
|
|
}
|
|
|
|
export async function reconnectUI() {
|
|
const t0 = performance.now();
|
|
const gallery = gradioApp().getElementById('txt2img_gallery');
|
|
const task_id = localStorage.getItem('task');
|
|
const api_logo = Array.from<any>(gradioApp().querySelectorAll('img')).filter((el) => el.src.endsWith('api-logo.svg'));
|
|
if (api_logo.length > 0) api_logo[0].remove();
|
|
if (task_id) {
|
|
debug('task check:', task_id);
|
|
requestProgress(task_id, null, gallery, null, null, true);
|
|
}
|
|
uiLoaded = true;
|
|
|
|
const sd_model = gradioApp().getElementById('setting_sd_model_checkpoint');
|
|
let loadingStarted = 0;
|
|
let loadingMonitor: ReturnType<typeof setInterval> | null = null;
|
|
|
|
const sd_model_callback = () => {
|
|
const loading = sd_model.querySelector('.eta-bar');
|
|
if (!loading) {
|
|
loadingStarted = 0;
|
|
clearInterval(loadingMonitor);
|
|
} else if (loadingStarted === 0) {
|
|
loadingStarted = Date.now();
|
|
loadingMonitor = setInterval(() => {
|
|
const elapsed = Date.now() - loadingStarted;
|
|
if (elapsed > 3000 && loading) loading.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
};
|
|
const sd_model_observer = new MutationObserver(sd_model_callback);
|
|
sd_model_observer.observe(sd_model, { attributes: true, childList: true, subtree: true });
|
|
const t1 = performance.now();
|
|
log('reconnectUI', Math.round(t1 - t0));
|
|
timer('reconnectUI', t1 - t0);
|
|
}
|
|
|
|
window.restartReload = restartReload;
|
|
window.updateInput = updateInput;
|
|
window.notifyKanvasResize = notifyKanvasResize;
|
|
window.clip_gallery_urls = clip_gallery_urls;
|
|
window.extract_image_from_gallery = extract_image_from_gallery;
|
|
window.getCaptionActiveTab = getCaptionActiveTab;
|
|
window.get_img2img_tab_index = get_img2img_tab_index;
|
|
window.modelmerger = modelmerger;
|
|
window.selected_gallery_index = selected_gallery_index;
|
|
window.selected_gallery_files = selected_gallery_files;
|
|
window.send_to_kanvas = send_to_kanvas;
|
|
window.submit_control = submit_control;
|
|
window.submit_framepack = submit_framepack;
|
|
window.submit_img2img = submit_img2img;
|
|
window.submit_ltx = submit_ltx;
|
|
window.submit_postprocessing = submit_postprocessing;
|
|
window.submit = submit_txt2img; // compatibility alias
|
|
window.submit_txt2img = submit_txt2img;
|
|
window.submit_video = submit_video;
|
|
window.submit_video_wrapper = submit_video_wrapper;
|
|
window.switch_to_txt2img = switch_to_txt2img;
|
|
window.switch_to_img2img_tab = switch_to_img2img_tab;
|
|
window.switch_to_img2img = switch_to_img2img;
|
|
window.switch_to_inpaint = switch_to_inpaint;
|
|
window.switch_to_sketch = switch_to_sketch;
|
|
window.switch_to_composite = switch_to_composite;
|
|
window.switch_to_extras = switch_to_extras;
|
|
window.switch_to_control = switch_to_control;
|
|
window.switch_to_video = switch_to_video;
|
|
window.switch_to_caption = switch_to_caption;
|
|
window.recalculate_prompts_txt2img = recalculate_prompts_txt2img;
|
|
window.recalculate_prompts_img2img = recalculate_prompts_img2img;
|
|
window.recalculate_prompts_inpaint = recalculate_prompts_inpaint;
|
|
window.recalculate_prompts_control = recalculate_prompts_control;
|
|
window.selectCheckpoint = selectCheckpoint;
|
|
window.selectVAE = selectVAE;
|
|
window.selectUNet = selectUNet;
|
|
window.selectReference = selectReference;
|
|
window.consumeDesiredCheckpointName = consumeDesiredCheckpointName;
|
|
window.consumeDesiredVAEName = consumeDesiredVAEName;
|
|
window.consumeDesiredUNetName = consumeDesiredUNetName;
|
|
window.getDesiredCheckpointName = getDesiredCheckpointName;
|
|
window.currentImageResolutionimg2img = currentImageResolutionimg2img;
|
|
window.currentImageResolutioncontrol = currentImageResolutioncontrol;
|
|
window.updateImg2imgResizeToTextAfterChangingImage = updateImg2imgResizeToTextAfterChangingImage;
|
|
window.create_submit_args = create_submit_args;
|
|
window.set_theme = set_theme;
|