Spaces:
Runtime error
Runtime error
const activePromptTextarea = {}; | |
let sortVal = 0; | |
// helpers | |
const requestGet = (url, data, handler) => { | |
const xhr = new XMLHttpRequest(); | |
const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&'); | |
xhr.open('GET', `${url}?${args}`, true); | |
xhr.onreadystatechange = () => { | |
if (xhr.readyState === 4) { | |
if (xhr.status === 200) handler(JSON.parse(xhr.responseText)); | |
else console.error(`Request: url=${url} status=${xhr.status} err`); | |
} | |
}; | |
xhr.send(JSON.stringify(data)); | |
}; | |
const getENActiveTab = () => { | |
if (gradioApp().getElementById('tab_txt2img').style.display === 'block') return 'txt2img'; | |
if (gradioApp().getElementById('tab_img2img').style.display === 'block') return 'img2img'; | |
if (gradioApp().getElementById('tab_control').style.display === 'block') return 'control'; | |
return ''; | |
}; | |
const getENActivePage = () => { | |
const tabname = getENActiveTab(); | |
const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`); | |
const pageName = page ? page.innerText : ''; | |
const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`); | |
if (btnApply) btnApply.style.display = pageName === 'Style' ? 'inline-flex' : 'none'; | |
return pageName; | |
}; | |
const setENState = (state) => { | |
if (!state) return; | |
state.tab = getENActiveTab(); | |
state.page = getENActivePage(); | |
// log('setENState', state); | |
const el = gradioApp().querySelector(`#${state.tab}_extra_state > label > textarea`); | |
el.value = JSON.stringify(state); | |
updateInput(el); | |
}; | |
// methods | |
function showCardDetails(event) { | |
console.log('showCardDetails', event); | |
const tabname = getENActiveTab(); | |
const btn = gradioApp().getElementById(`${tabname}_extra_details_btn`); | |
btn.click(); | |
event.stopPropagation(); | |
event.preventDefault(); | |
} | |
function getCardDetails(...args) { | |
const el = event?.target?.parentElement?.parentElement; | |
if (el?.classList?.contains('card')) setENState({ op: 'getCardDetails', item: el.dataset.name }); | |
else setENState({ op: 'getCardDetails', item: null }); | |
return [...args]; | |
} | |
function readCardTags(el, tags) { | |
const replaceOutsideBrackets = (input, target, replacement) => input.split(/(<[^>]*>|\{[^}]*\})/g).map((part, i) => { | |
if (i % 2 === 0) return part.split(target).join(replacement); // Only replace in the parts that are not inside brackets (which are at even indices) | |
return part; | |
}).join(''); | |
const clickTag = (e, tag) => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
const textarea = activePromptTextarea[getENActiveTab()]; | |
let new_prompt = textarea.value; | |
new_prompt = replaceOutsideBrackets(new_prompt, ` ${tag}`, ''); // try to remove tag | |
new_prompt = replaceOutsideBrackets(new_prompt, `${tag} `, ''); | |
if (new_prompt === textarea.value) new_prompt += ` ${tag}`; // if not removed, then append it | |
textarea.value = new_prompt; | |
updateInput(textarea); | |
}; | |
if (tags.length === 0) return; | |
const cardTags = tags.split('|'); | |
if (!cardTags || cardTags.length === 0) return; | |
const tagsEl = el.getElementsByClassName('tags')[0]; | |
if (!tagsEl?.children || tagsEl.children.length > 0) return; | |
for (const tag of cardTags) { | |
const span = document.createElement('span'); | |
span.classList.add('tag'); | |
span.textContent = tag; | |
span.onclick = (e) => clickTag(e, tag); | |
tagsEl.appendChild(span); | |
} | |
} | |
function readCardDescription(page, item) { | |
requestGet('/sd_extra_networks/description', { page, item }, (data) => { | |
const tabname = getENActiveTab(); | |
const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`); | |
description.value = data?.description?.trim() || ''; | |
// description.focus(); | |
updateInput(description); | |
setENState({ op: 'readCardDescription', page, item }); | |
}); | |
} | |
function getCardsForActivePage() { | |
const pagename = getENActivePage(); | |
if (!pagename) return []; | |
const allCards = Array.from(gradioApp().querySelectorAll('.extra-network-cards > .card')); | |
const cards = allCards.filter((el) => el.dataset.page.toLowerCase().includes(pagename.toLowerCase())); | |
log('getCardsForActivePage', pagename, cards.length); | |
return allCards; | |
} | |
async function filterExtraNetworksForTab(searchTerm) { | |
let found = 0; | |
let items = 0; | |
const t0 = performance.now(); | |
const pagename = getENActivePage(); | |
if (!pagename) return; | |
const allPages = Array.from(gradioApp().querySelectorAll('.extra-network-cards')); | |
const pages = allPages.filter((el) => el.id.toLowerCase().includes(pagename.toLowerCase())); | |
for (const pg of pages) { | |
const cards = Array.from(pg.querySelectorAll('.card') || []); | |
// We will always have as many items as cards | |
items += cards.length; | |
// Reset the results to show all cards if the search term is empty | |
if (searchTerm === '') { | |
cards.forEach((elem) => { | |
elem.style.display = ''; | |
}); | |
} else { | |
// Do not account for case or whitespace | |
searchTerm = searchTerm.toLowerCase().trim(); | |
// If the searchTerm starts with "r#", then we are using regex search | |
if (searchTerm.startsWith('r#')) { | |
searchTerm = searchTerm.substring(2); | |
// Insensitive regex search based on the searchTerm | |
// The regex can be invalid -> then it will error out of this function, so the timing log will be missing, instead the error will be logged to console | |
const re = new RegExp(searchTerm, 'i'); | |
cards.forEach((elem) => { | |
// Construct the search text, which is the concatenation of all data elements with a prefix to make it unique | |
// This combined text allows to exclude search terms for example by using negative lookahead | |
if (re.test(`filename: ${elem.dataset.filename}|name: ${elem.dataset.name}|tags: ${elem.dataset.tags}`)) { | |
elem.style.display = ''; | |
found += 1; | |
} else { | |
elem.style.display = 'none'; | |
} | |
}); | |
} else { | |
// If we are not using regex search, we still use an extended syntax to allow for searching for multiple keywords, or also excluding keywords | |
// Keywords are separated by |, and keywords that should be excluded are prefixed with - | |
const searchList = searchTerm.split('|').filter((s) => s !== '' && !s.startsWith('-')).map((s) => s.trim()); | |
const excludeList = searchTerm.split('|').filter((s) => s !== '' && s.trim().startsWith('-')).map((s) => s.trim().substring(1).trim()); | |
// In addition, both the searchList, and exclude List can be separated by &, which means that all keywords in the searchList must be present, and none of the excludeList | |
// So we construct an array of arrays, which we will then use to filter the cards | |
const searchListAll = searchList.map((s) => s.split('&').map((t) => t.trim())); | |
const excludeListAll = excludeList.map((s) => s.split('&').map((t) => t.trim())); | |
cards.forEach((elem) => { | |
let text = ''; | |
if (elem.dataset.filename) text += `${elem.dataset.filename} `; | |
if (elem.dataset.name) text += `${elem.dataset.name} `; | |
if (elem.dataset.tags) text += `${elem.dataset.tags} `; | |
text = text.toLowerCase().replace('models--', 'diffusers').replaceAll('\\', '/'); | |
if ( | |
// In searchListAll we have a list of lists, in the sublist, every keyword must be present | |
// In the top level list, at least one sublist must be present | |
// In excludeListAll we have a list of lists, in the sublist, the keywords may not appear together | |
// In the top level list, none of the sublists must be present | |
searchListAll.some((sl) => sl.every((st) => text.includes(st))) && !excludeListAll.some((el) => el.every((et) => text.includes(et))) | |
) { | |
elem.style.display = ''; | |
found += 1; | |
} else { | |
elem.style.display = 'none'; | |
} | |
}); | |
} | |
} | |
} | |
const t1 = performance.now(); | |
if (searchTerm !== '') log(`filterExtraNetworks: text=${searchTerm} items=${items} match=${found} time=${Math.round(1000 * (t1 - t0)) / 1000000}`); | |
else log(`filterExtraNetworks: text=all items=${items} time=${Math.round(1000 * (t1 - t0)) / 1000000}`); | |
} | |
function tryToRemoveExtraNetworkFromPrompt(textarea, text) { | |
const re_extranet = /<([^:]+:[^:]+):[\d\.]+>/; | |
const re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g; | |
let m = text.match(re_extranet); | |
let replaced = false; | |
let newTextareaText; | |
if (m) { | |
const partToSearch = m[1]; | |
newTextareaText = textarea.value.replaceAll(re_extranet_g, (found) => { | |
m = found.match(re_extranet); | |
if (m[1] === partToSearch) { | |
replaced = true; | |
return ''; | |
} | |
return found; | |
}); | |
} else { | |
newTextareaText = textarea.value.replaceAll(new RegExp(text, 'g'), (found) => { | |
if (found === text) { | |
replaced = true; | |
return ''; | |
} | |
return found; | |
}); | |
} | |
if (replaced) { | |
textarea.value = newTextareaText; | |
return true; | |
} | |
return false; | |
} | |
function sortExtraNetworks() { | |
const sortDesc = ['Name [A-Z]', 'Name [Z-A]', 'Date [Newest]', 'Date [Oldest]', 'Size [Largest]', 'Size [Smallest]']; | |
const pagename = getENActivePage(); | |
if (!pagename) return 'sort error: unknown page'; | |
const allPages = Array.from(gradioApp().querySelectorAll('.extra-network-cards')); | |
const pages = allPages.filter((el) => el.id.toLowerCase().includes(pagename.toLowerCase())); | |
let num = 0; | |
for (const pg of pages) { | |
const cards = Array.from(pg.querySelectorAll('.card') || []); | |
num = cards.length; | |
if (num === 0) return 'sort: no cards'; | |
cards.sort((a, b) => { // eslint-disable-line no-loop-func | |
switch (sortVal) { | |
case 0: return a.dataset.name ? a.dataset.name.localeCompare(b.dataset.name) : 0; | |
case 1: return b.dataset.name ? b.dataset.name.localeCompare(a.dataset.name) : 0; | |
case 2: return a.dataset.mtime && !isNaN(a.dataset.mtime) ? parseFloat(b.dataset.mtime) - parseFloat(a.dataset.mtime) : 0; | |
case 3: return b.dataset.mtime && !isNaN(b.dataset.mtime) ? parseFloat(a.dataset.mtime) - parseFloat(b.dataset.mtime) : 0; | |
case 4: return a.dataset.size && !isNaN(a.dataset.size) ? parseFloat(b.dataset.size) - parseFloat(a.dataset.size) : 0; | |
case 5: return b.dataset.size && !isNaN(b.dataset.size) ? parseFloat(a.dataset.size) - parseFloat(b.dataset.size) : 0; | |
} | |
return 0; | |
}); | |
for (const card of cards) pg.appendChild(card); | |
} | |
const desc = sortDesc[sortVal]; | |
sortVal = (sortVal + 1) % sortDesc.length; | |
log('sortExtraNetworks', pagename, num, desc); | |
return `sort page ${pagename} cards ${num} by ${desc}`; | |
} | |
function refreshENInput(tabname) { | |
log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value); | |
gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input')); | |
} | |
function cardClicked(textToAdd, allowNegativePrompt) { | |
const tabname = getENActiveTab(); | |
const textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector(`#${tabname}_prompt > label > textarea`); | |
if (textarea.value.indexOf(textToAdd) !== -1) textarea.value = textarea.value.replace(textToAdd, ''); | |
else textarea.value += textToAdd; | |
updateInput(textarea); | |
} | |
function extraNetworksSearchButton(event) { | |
const tabname = getENActiveTab(); | |
const searchTextarea = gradioApp().querySelector(`#${tabname}_extra_search textarea`); | |
const button = event.target; | |
if (button.classList.contains('search-all')) { | |
searchTextarea.value = ''; | |
} else { | |
searchTextarea.value = `${button.textContent.trim()}/`; | |
} | |
updateInput(searchTextarea); | |
} | |
let desiredStyle = ''; | |
function selectStyle(name) { | |
desiredStyle = name; | |
const tabname = getENActiveTab(); | |
const button = gradioApp().querySelector(`#${tabname}_styles_select`); | |
button.click(); | |
} | |
function applyStyles(styles) { | |
let newStyles = []; | |
if (styles) newStyles = Array.isArray(styles) ? styles : [styles]; | |
const index = newStyles.indexOf(desiredStyle); | |
if (index > -1) newStyles.splice(index, 1); | |
else newStyles.push(desiredStyle); | |
return newStyles.join('|'); | |
} | |
function quickApplyStyle() { | |
const tabname = getENActiveTab(); | |
const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`); | |
if (btnApply) btnApply.click(); | |
} | |
function quickSaveStyle() { | |
const tabname = getENActiveTab(); | |
const btnSave = gradioApp().getElementById(`${tabname}_extra_quicksave`); | |
if (btnSave) btnSave.click(); | |
} | |
let enDirty = false; | |
function closeDetailsEN(...args) { | |
// log('closeDetailsEN'); | |
enDirty = true; | |
const tabname = getENActiveTab(); | |
const btnClose = gradioApp().getElementById(`${tabname}_extra_details_close`); | |
if (btnClose) setTimeout(() => btnClose.click(), 100); | |
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); | |
if (btnRefresh && enDirty) setTimeout(() => btnRefresh.click(), 100); | |
return [...args]; | |
} | |
function refeshDetailsEN(args) { | |
log(`refeshDetailsEN: ${enDirty}`); | |
const tabname = getENActiveTab(); | |
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); | |
if (btnRefresh && enDirty) setTimeout(() => btnRefresh.click(), 100); | |
enDirty = false; | |
return args; | |
} | |
// refresh on en show | |
function refreshENpage() { | |
if (getCardsForActivePage().length === 0) { | |
log('refreshENpage'); | |
const tabname = getENActiveTab(); | |
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); | |
if (btnRefresh) btnRefresh.click(); | |
} | |
} | |
// init | |
function setupExtraNetworksForTab(tabname) { | |
let tabs = gradioApp().querySelector(`#${tabname}_extra_tabs`); | |
if (tabs) tabs.classList.add('extra-networks'); | |
const en = gradioApp().getElementById(`${tabname}_extra_networks`); | |
tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`); | |
if (!tabs) return; | |
// buttons | |
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`); | |
const btnScan = gradioApp().getElementById(`${tabname}_extra_scan`); | |
const btnSave = gradioApp().getElementById(`${tabname}_extra_save`); | |
const btnClose = gradioApp().getElementById(`${tabname}_extra_close`); | |
const btnSort = gradioApp().getElementById(`${tabname}_extra_sort`); | |
const btnView = gradioApp().getElementById(`${tabname}_extra_view`); | |
const btnModel = gradioApp().getElementById(`${tabname}_extra_model`); | |
const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`); | |
const buttons = document.createElement('span'); | |
buttons.classList.add('buttons'); | |
if (btnRefresh) buttons.appendChild(btnRefresh); | |
if (btnModel) buttons.appendChild(btnModel); | |
if (btnApply) buttons.appendChild(btnApply); | |
if (btnScan) buttons.appendChild(btnScan); | |
if (btnSave) buttons.appendChild(btnSave); | |
if (btnSort) buttons.appendChild(btnSort); | |
if (btnView) buttons.appendChild(btnView); | |
if (btnClose) buttons.appendChild(btnClose); | |
btnModel.onclick = () => btnModel.classList.toggle('toolbutton-selected'); | |
tabs.appendChild(buttons); | |
// details | |
const detailsImg = gradioApp().getElementById(`${tabname}_extra_details_img`); | |
const detailsClose = gradioApp().getElementById(`${tabname}_extra_details_close`); | |
if (detailsImg && detailsClose) { | |
detailsImg.title = 'Close details'; | |
detailsImg.onclick = () => detailsClose.click(); | |
} | |
// search and description | |
const div = document.createElement('div'); | |
div.classList.add('second-line'); | |
tabs.appendChild(div); | |
const txtSearch = gradioApp().querySelector(`#${tabname}_extra_search`); | |
const txtSearchValue = gradioApp().querySelector(`#${tabname}_extra_search textarea`); | |
const txtDescription = gradioApp().getElementById(`${tabname}_description`); | |
txtSearch.classList.add('search'); | |
txtDescription.classList.add('description'); | |
div.appendChild(txtSearch); | |
div.appendChild(txtDescription); | |
let searchTimer = null; | |
txtSearchValue.addEventListener('input', (evt) => { | |
if (searchTimer) clearTimeout(searchTimer); | |
searchTimer = setTimeout(async () => { | |
await filterExtraNetworksForTab(txtSearchValue.value.toLowerCase()); | |
searchTimer = null; | |
}, 100); | |
}); | |
// card hover | |
let hoverTimer = null; | |
let previousCard = null; | |
gradioApp().getElementById(`${tabname}_extra_tabs`).onmouseover = (e) => { | |
const el = e.target.closest('.card'); // bubble-up to card | |
if (!el || (el.title === previousCard)) return; | |
if (!hoverTimer) { | |
hoverTimer = setTimeout(() => { | |
readCardDescription(el.dataset.page, el.dataset.name); | |
readCardTags(el, el.dataset.tags); | |
previousCard = el.title; | |
}, 300); | |
} | |
el.onmouseout = () => { | |
clearTimeout(hoverTimer); | |
hoverTimer = null; | |
}; | |
}; | |
// en style | |
if (!en) return; | |
const intersectionObserver = new IntersectionObserver((entries) => { | |
for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) { | |
el.style.height = `${window.opts.extra_networks_height}vh`; | |
el.parentElement.style.width = '-webkit-fill-available'; | |
} | |
if (entries[0].intersectionRatio > 0) { | |
refreshENpage(); | |
if (window.opts.extra_networks_card_cover === 'cover') { | |
en.style.transition = ''; | |
en.style.zIndex = 100; | |
en.style.top = '13em'; | |
en.style.position = 'absolute'; | |
en.style.right = 'unset'; | |
en.style.width = 'unset'; | |
en.style.height = 'unset'; | |
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset'; | |
} else if (window.opts.extra_networks_card_cover === 'sidebar') { | |
en.style.zIndex = 100; | |
en.style.position = 'absolute'; | |
en.style.right = '0'; | |
en.style.top = '13em'; | |
en.style.height = '-webkit-fill-available'; | |
en.style.transition = 'width 0.3s ease'; | |
en.style.width = `${window.opts.extra_networks_sidebar_width}vw`; | |
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = `${100 - 2 - window.opts.extra_networks_sidebar_width}vw`; | |
} else { | |
en.style.transition = ''; | |
en.style.zIndex = 0; | |
en.style.top = 0; | |
en.style.position = 'relative'; | |
en.style.right = 'unset'; | |
en.style.width = 'unset'; | |
en.style.height = 'unset'; | |
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset'; | |
} | |
} else { | |
if (window.opts.extra_networks_card_cover === 'sidebar') en.style.width = 0; | |
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset'; | |
} | |
}); | |
intersectionObserver.observe(en); // monitor visibility of | |
} | |
async function setupExtraNetworks() { | |
setupExtraNetworksForTab('txt2img'); | |
setupExtraNetworksForTab('img2img'); | |
setupExtraNetworksForTab('control'); | |
function registerPrompt(tabname, id) { | |
const textarea = gradioApp().querySelector(`#${id} > label > textarea`); | |
if (!textarea) return; | |
if (!activePromptTextarea[tabname]) activePromptTextarea[tabname] = textarea; | |
textarea.addEventListener('focus', () => { activePromptTextarea[tabname] = textarea; }); | |
} | |
registerPrompt('txt2img', 'txt2img_prompt'); | |
registerPrompt('txt2img', 'txt2img_neg_prompt'); | |
registerPrompt('img2img', 'img2img_prompt'); | |
registerPrompt('img2img', 'img2img_neg_prompt'); | |
registerPrompt('control', 'control_prompt'); | |
registerPrompt('control', 'control_neg_prompt'); | |
log('initExtraNetworks'); | |
} | |
onUiLoaded(setupExtraNetworks); | |