new extra networks

This commit is contained in:
Vladimir Mandic
2023-06-30 15:29:28 -04:00
parent a9fa014e31
commit 8000d1965d
18 changed files with 246 additions and 308 deletions
+16 -4
View File
@@ -1,13 +1,25 @@
# Change Log for SD.Next
## Update for 06/30/2023
A bit bigger upadate this time, but contained to specific areas...
- change in behavior
extensions no longer auto-update on startup
using `--upgrade` flag upgrades core app as well as all submodules and extensions
- live server log monitoring in ui
- **live server log monitoring** in ui
configurable via settings -> live preview
- auto-create thumbnails from preview images in extra networks in a background thread
this creates significant load time saving on subsequent restarts
- extensions sort by trending
- new **extra networks interface**
*note: if you're using a 3rd party ui extension for extra networks, it will likely need to be updated to work with new interface*
- display in front of main ui, inline with main ui or as a sidebar
- lazy load thumbnails
drastically reduces load times for large number of extra networks
- auto-create thumbnails from preview images in extra networks in a background thread
significant load time saving on subsequent restarts
- support for info files in addition to description files
- support for variable aspect-ratio thumbnails
- new folder view
- **extensions sort** by trending
- add requirements check for training
## Update for 06/26/2023
+1 -1
View File
@@ -52,4 +52,4 @@ Tech that can be integrated as part of the core workflow...
- port `p.all_hr_prompts`
- test `lyco_patch_lora`
- fix `lyco` logging
- save `ui-settings.json`
- delete thumb on preview replace
-14
View File
@@ -1,14 +0,0 @@
<div class='card' style={style} onclick={card_clicked}>
<div class='actions'>
<div class='additional'>
<ul>
<li><a href="#" title="set preview image with current selection" onclick={save_card_preview}>Replace preview</a></li>
<li><a href="#" title="read description" onclick={read_card_description}>Description</a> <a href="#" title="[replace]" onclick={save_card_description}>[Replace]</a></li>
<li><a href="#" title="read metadata" onclick={read_card_metadata}>Metadata</a></li>
</ul>
<span style="display:none" class='search_term'>{search_term}</span>
</div>
<span class='name'>{name}</span>
<span class='description'>{description}</span>
</div>
</div>
-13
View File
@@ -1,13 +0,0 @@
<div class='card' style={style} onclick={card_clicked}>
<div class='actions'>
<div class='additional'>
<ul>
<li><a href="#" title="set preview image with current selection" onclick={save_card_preview}>Replace preview</a></li>
<li><a href="#" title="read description" onclick={read_card_description}>Description</a> <a href="#" title="[replace]" onclick={save_card_description}>[Replace]</a></li>
</ul>
<span style="display:none" class='search_term'>{search_term}</span>
</div>
<span class='name'>{name}</span>
<span class='description'>{description}</span>
</div>
</div>
-8
View File
@@ -1,8 +0,0 @@
<div class='nocards'>
<h1>Nothing here. Add some content to the following directories:</h1>
<ul>
{dirs}
</ul>
</div>
-13
View File
@@ -1,13 +0,0 @@
<div>
<a href="/docs">API</a>
 • 
<a href="https://github.com/vladmandic/automatic">Github</a>
 • 
<a href="https://gradio.app">Gradio</a>
 • 
<a href="/" onclick="javascript:gradioApp().getElementById('settings_restart_gradio').click(); return false">Reload UI</a>
</div>
<br />
<div class="versions">
{versions}
</div>
+3 -6
View File
@@ -39,10 +39,6 @@ div.gradio-container.dark > div.w-full.flex.flex-col.min-h-screen > div { backgr
.dark fieldset span.text-gray-500, .dark .gr-block.gr-box span.text-gray-500, .dark label.block span { padding: 0; margin-top: -4px; }
.dark fieldset span.text-gray-500, .dark .gr-block.gr-box span.text-gray-500, .dark label.block span { border-radius: 0;}
.eta-bar { display: none !important }
.extra-networks .tab-nav .search { max-height: 2em; }
.extra-network-cards .card .actions .name { font-weight: 400; font-size: 1.2rem; }
.extra-network-cards .card { width: 18em; height: 12em; box-shadow: none; }
.extra-network-cards .card:hover { transform: scale(1.15); transition: all 0.2s ease; z-index: 99; box-shadow: none; }
svg.feather.feather-image, .feather .feather-image { display: none }
.gap-2 { padding-top: 8px; }
.gr-box > div > div > input.gr-text-input { right: 0; width: 4em; padding: 0; top: -12px; border: none; max-height: 20px; }
@@ -56,7 +52,7 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.tabs { background-color: black; }
.gradio-button.tool { border-radius: 0; }
.block.token-counter span { background-color: #222 !important; box-shadow: 2px 2px 2px #111; border: none !important; border-radius: 0; font-size: 0.8rem; }
.tab-nav { zoom: 130%; margin-bottom: 16px; border-bottom: 2px solid #CE6400 !important; padding-bottom: 2px; }
.tab-nav { zoom: 120%; margin-bottom: 10px; border-bottom: 2px solid #CE6400 !important; padding-bottom: 2px; }
.label-wrap { margin: 16px 0px 8px 0px; }
.gradio-slider input[type="number"] { width: 4.5em; font-size: 0.8rem; }
#tab_extensions table td, #tab_extensions table th { border: none; padding: 0.5em; }
@@ -68,6 +64,8 @@ svg.feather.feather-image, .feather .feather-image { display: none }
.progressDiv .progress { border-radius: 0 !important; background: var(--highlight-color); line-height: 3rem; height: 48px; }
.gallery-item { box-shadow: none !important; }
.performance { color: #888; }
.extra-networks { border-left: 2px solid #CE6400 !important; padding-left: 4px; }
.image-buttons { gap: 10px !important}
/* gradio elements overrides */
#div.gradio-container.dark { overflow-x: hidden; }
@@ -99,7 +97,6 @@ svg.feather.feather-image, .feather .feather-image { display: none }
#txt2img_subseed_strength { margin-top: 0; }
#txtimg_hr_finalres { max-width: 200px; }
#pnginfo_html2_info { margin-top: -18px; background-color: var(--input-background-fill); padding: var(--input-padding) }
#txt2img_extra_refresh, #txt2img_extra_close { height: 1.7em; }
#txt2img_tools > div > button, #img2img_tools > div > button { filter: hue-rotate(180deg) saturate(0.5); }
/* custom elements overrides */
+66 -12
View File
@@ -1,15 +1,20 @@
let globalPopup = null;
let globalPopupInner = null;
const activePromptTextarea = {};
function setupExtraNetworksForTab(tabname) {
gradioApp().querySelector(`#${tabname}_extra_tabs`).classList.add('extra-networks');
const tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`);
const search = gradioApp().querySelector(`#${tabname}_extra_search textarea`);
const refresh = gradioApp().getElementById(`${tabname}_extra_refresh`);
const descriptInput = gradioApp().getElementById(`${tabname}_description_input`);
const description = gradioApp().getElementById(`${tabname}_description`);
const close = gradioApp().getElementById(`${tabname}_extra_close`);
search.classList.add('search');
tabs.appendChild(search);
description.classList.add('description');
tabs.appendChild(refresh);
tabs.appendChild(close);
tabs.appendChild(descriptInput);
tabs.appendChild(search);
tabs.appendChild(description);
search.addEventListener('input', (evt) => {
searchTerm = search.value.toLowerCase();
gradioApp().querySelectorAll(`#${tabname}_extra_tabs div.card`).forEach((elem) => {
@@ -17,13 +22,47 @@ function setupExtraNetworksForTab(tabname) {
elem.style.display = text.indexOf(searchTerm) == -1 ? 'none' : '';
});
});
intersectionObserver = new IntersectionObserver((entries) => {
// if (entries[0].intersectionRatio <= 0) onHidden();
if (entries[0].intersectionRatio > 0) {
for (el of Array.from(gradioApp().querySelectorAll('.extra-network-cards'))) {
const rect = el.getBoundingClientRect();
if (rect.top > 0) {
const en = gradioApp().getElementById(`${tabname}_extra_networks`);
if (!en) return
if (window.opts.extra_networks_card_cover == 'cover') {
en.style.zIndex = 0;
en.style.position = 'absolute';
en.style.right = 'unset';
en.style.width = 'unset';
el.style.height = document.body.offsetHeight - el.getBoundingClientRect().top + 'px';
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset'
} if (window.opts.extra_networks_card_cover == 'sidebar') {
en.style.zIndex = 9999;
en.style.position = 'absolute';
en.style.right = '0';
en.style.width = '38vw';
el.style.height = gradioApp().getElementById(`${tabname}_settings`).offsetHeight - 90 + 'px';
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = '60vw'
} else {
en.style.zIndex = 0;
en.style.position = 'relative';
en.style.right = 'unset';
en.style.width = 'unset';
el.style.height = window.innerHeight - el.getBoundingClientRect().top + 'px';
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset'
}
}
}
}
});
intersectionObserver.observe(search); // monitor visibility of
}
const activePromptTextarea = {};
function setupExtraNetworks() {
setupExtraNetworksForTab('txt2img');
setupExtraNetworksForTab('img2img');
function registerPrompt(tabname, id) {
const textarea = gradioApp().querySelector(`#${id} > label > textarea`);
if (!activePromptTextarea[tabname]) activePromptTextarea[tabname] = textarea;
@@ -31,6 +70,7 @@ function setupExtraNetworks() {
activePromptTextarea[tabname] = textarea;
});
}
registerPrompt('txt2img', 'txt2img_prompt');
registerPrompt('txt2img', 'txt2img_neg_prompt');
registerPrompt('img2img', 'img2img_prompt');
@@ -93,9 +133,10 @@ function saveCardPreview(event, tabname, filename) {
}
function saveCardDescription(event, tabname, filename, descript) {
console.log('saveCardDescription', event, tabname, filename, descript);
const textarea = gradioApp().querySelector(`#${tabname}_description_filename > label > textarea`);
const button = gradioApp().getElementById(`${tabname}_save_description`);
const description = gradioApp().getElementById(`${tabname}_description_input`);
const description = gradioApp().getElementById(`${tabname}_description`);
textarea.value = filename;
description.value = descript;
updateInput(textarea);
@@ -105,13 +146,15 @@ function saveCardDescription(event, tabname, filename, descript) {
}
function readCardDescription(event, tabname, filename, descript, extraPage, cardName) {
console.log('readCardDescription', event, tabname, filename, descript, extraPage, cardName);
const textarea = gradioApp().querySelector(`#${tabname}_description_filename > label > textarea`);
const description_textarea = gradioApp().querySelector(`#${tabname}_description_input > label > textarea`);
const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`);
const button = gradioApp().getElementById(`${tabname}_read_description`);
textarea.value = filename;
description_textarea.value = descript;
description.value = descript;
console.log(textarea.value, description.value);
updateInput(textarea);
updateInput(description_textarea);
updateInput(description);
button.click();
event.stopPropagation();
event.preventDefault();
@@ -125,8 +168,6 @@ function extraNetworksSearchButton(tabs_id, event) {
updateInput(searchTextarea);
}
let globalPopup = null;
let globalPopupInner = null;
function popup(contents) {
if (!globalPopup) {
globalPopup = document.createElement('div');
@@ -150,7 +191,7 @@ function popup(contents) {
function readCardMetadata(event, extraPage, cardName) {
requestGet('./sd_extra_networks/metadata', { page: extraPage, item: cardName }, (data) => {
if (data && data.metadata) {
if (data?.metadata && (typeof(data?.metadata) === 'string')) {
elem = document.createElement('pre');
elem.classList.add('popup-metadata');
elem.textContent = data.metadata;
@@ -161,6 +202,19 @@ function readCardMetadata(event, extraPage, cardName) {
event.preventDefault();
}
function readCardInformation(event, extraPage, cardName) {
requestGet('./sd_extra_networks/info', { page: extraPage, item: cardName }, (data) => {
if (data?.info && (typeof(data?.info) === 'string')) {
elem = document.createElement('pre');
elem.classList.add('popup-metadata');
elem.textContent = data.info;
popup(elem);
}
}, () => {});
event.stopPropagation();
event.preventDefault();
}
function requestGet(url, data, handler, errorHandler) {
const xhr = new XMLHttpRequest();
const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
+6 -2
View File
@@ -1,11 +1,15 @@
let logMonitorEl = null;
let logMonitorStatus = true;
async function logMonitor() {
setTimeout(logMonitor, opts.logmonitor_refresh_period);
if (logMonitorStatus) setTimeout(logMonitor, opts.logmonitor_refresh_period);
if (logMonitorEl) logMonitorEl.parentElement.style.display = opts.logmonitor_show ? 'block' : 'none';
if (!opts.logmonitor_show) return;
const res = await fetch('/sdapi/v1/log?clear=True');
logMonitorStatus = false;
let res;
try { res = await fetch('/sdapi/v1/log?clear=True'); } catch {}
if (res?.ok) {
logMonitorStatus = true;
if (!logMonitorEl) logMonitorEl = document.getElementById('logMonitorData');
if (!logMonitorEl) return;
logMonitorEl.parentElement.style.display = 'block';
+31 -122
View File
@@ -1,6 +1,7 @@
:root, .dark{ --checkbox-label-gap: 0.25em 0.1em; --section-header-text-size: 12pt; --block-background-fill: transparent;}
a { font-weight: bold; cursor: pointer; }
div.gradio-container{ max-width: unset !important; }
div.gradio-container{ max-width: unset !important; padding: 8px !important; }
div.tabitem { padding: 0 !important; }
div.form{ border-width: 0; box-shadow: none; background: transparent; overflow: visible; gap: 0.5em; }
div.compact{ gap: 1em; }
div.gradio-html.min{ min-height: 0; }
@@ -33,6 +34,7 @@ div.gradio-html.min{ min-height: 0; }
.gradio-button.secondary-down:hover{ background: var(--button-secondary-background-fill-hover); color: var(--button-secondary-text-color-hover); }
.checkboxes-row{ margin-bottom: 0.5em; margin-left: 0em; }
.checkboxes-row > div{ flex: 0; white-space: nowrap; min-width: auto; }
button.custom-button{
border-radius: var(--button-large-radius);
padding: var(--button-large-padding);
@@ -141,30 +143,22 @@ div#extras_scale_to_tab div.form{
margin-bottom: 0.9em;
}
#img2img_unused_scale_by_slider{
#img2img_unused_scale_by_slider {
visibility: hidden;
width: 0.5em;
max-width: 0.5em;
min-width: 0.5em;
}
.extra-network-cards{
height: fit-content;
max-height: 50vh;
overflow-y: scroll;
overflow-x: hidden;
resize: vertical;
}
.inactive{
opacity: 0.5;
}
[id$=_column_batch]{
[id$=_column_batch] {
min-width: min(13.5em, 100%) !important;
}
div.dimensions-tools{
div.dimensions-tools {
min-width: 0 !important;
max-width: fit-content;
flex-direction: row;
@@ -344,38 +338,15 @@ div#extras_scale_to_tab div.form{
padding: 0 0.5em;
}
.livePreview{
position: absolute;
z-index: 300;
background-color: transparent;
width: -webkit-fill-available;
}
.dark .livePreview{
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
}
.livePreview img{
position: absolute;
object-fit: contain;
width: 100%;
height: 100%;
}
/* fullscreen popup (ie in Lora's (i) button) */
.popup-metadata{
color: black;
background: white;
display: inline-block;
padding: 1em;
white-space: pre-wrap;
}
.livePreview { position: absolute; z-index: 300; background-color: transparent; width: -webkit-fill-available; }
.livePreview img { position: absolute; object-fit: contain; width: 100%; height: 100%; }
.dark .livePreview { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); }
.popup-metadata { color: black; background: white; display: inline-block; padding: 1em; white-space: pre-wrap; font-size: 0.8em; opacity: 80%; }
.global-popup{
display: flex;
position: fixed;
z-index: 1001;
z-index: 10001;
left: 0;
top: 0;
width: 100%;
@@ -548,7 +519,6 @@ table.settings-value-table td{
/* extensions */
#tab_extensions table{ border-collapse: collapse; }
#tab_extensions table td, #tab_extensions table th { border: 1px solid #ccc; padding: 0.25em 0.5em; }
#tab_extensions table input[type="checkbox"] { margin-right: 0.5em; appearance: checkbox; }
@@ -563,87 +533,26 @@ table.settings-value-table td{
.extension-button { font-size: 95% !important; width: 6em; }
/* extra networks */
.extra-networks > div > [id *= '_extra_']{ margin: 0.3em; }
.extra-network-subdirs { padding: 0.2em 0.35em; }
.extra-network-subdirs button { margin: 0 0.15em; box-shadow: none; }
.extra-networks .tab-nav .search { display: inline-block; max-width: 16em; margin: 0.3em; align-self: center; width: 16em; }
#txt2img_extra_view, #img2img_extra_view { width: auto; }
.extra-network-cards .nocards, .extra-network-thumbs .nocards{ margin: 1.25em 0.5em 0.5em 0.5em; }
.extra-network-cards .nocards h1, .extra-network-thumbs .nocards h1{ font-size: 1.5em; margin-bottom: 1em; }
.extra-network-cards .nocards li, .extra-network-thumbs .nocards li{ margin-left: 0.5em; }
.extra-network-cards .card .metadata-button, .extra-network-thumbs .card .metadata-button{
display: none;
position: absolute;
right: 0;
color: white;
text-shadow: 2px 2px 3px black;
padding: 0.25em;
font-size: 22pt;
width: 1.5em;
}
.extra-network-cards .card:hover .metadata-button, .extra-network-thumbs .card:hover .metadata-button{ display: inline-block; }
.extra-network-thumbs { display: flex; flex-flow: row wrap; gap: 10px; }
.extra-network-cards .card .additional a:hover, .extra-network-thumbs .card .additional a:hover { color: darkorange }
.extra-network-thumbs .card {
display: inline-block;
height: 9em;
width: 9em;
cursor: pointer;
background-image: url('../html/card-no-preview.png');
background-size: cover;
background-position: center center;
position: relative;
}
.extra-network-cards .card .additional, .extra-network-thumbs .card .additional { white-space: nowrap; overflow: hidden; }
.extra-network-thumbs .card:hover .additional a { display: inline-block; }
.extra-network-thumbs .actions .name {
position: absolute;
bottom: 0;
font-size: 10px;
padding: 3px;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background: rgba(0,0,0,.5);
color: white;
}
.extra-network-thumbs .card:hover .actions .name { white-space: normal; word-break: break-all; }
.extra-network-cards .card{
display: inline-block;
margin: 0.5em;
width: 16em;
height: 24em;
box-shadow: 0 0 5px rgba(128, 128, 128, 0.5);
border-radius: 0.2em;
position: relative;
background-size: cover;
background-position: center;
overflow: hidden;
cursor: pointer;
background-image: url('../html/card-no-preview.png')
}
.extra-network-cards .card:hover { box-shadow: 0 0 2px 0.3em rgba(0, 128, 255, 0.35); }
.extra-network-cards .card .actions .additional, .extra-network-thumbs .card .actions .additional{ display: none; }
.extra-network-cards .card .actions{
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.5em;
background: rgba(0,0,0,0.5);
box-shadow: 0 0 0.25em 0.25em rgba(0,0,0,0.5);
text-shadow: 0 0 0.2em black;
}
.extra-network-cards .card .actions *{ color: white; }
.extra-network-cards .card .actions:hover { box-shadow: 0 0 0.75em 0.75em rgba(0,0,0,0.5) !important; }
.extra-network-cards .card .actions .name { font-size: 1.7em; font-weight: bold; line-break: anywhere; }
.extra-network-cards .card .actions .description { display: block; max-height: 3em; white-space: pre-wrap; line-height: 1.1; }
.extra-network-cards .card .actions .description:hover { max-height: none; }
.extra-network-cards .card .actions:hover .additional, .extra-network-thumbs .card:hover .additional{ display: block; }
.extra-network-cards .card ul{ margin: 0.25em 0 0.75em 0.25em; cursor: unset; }
.extra-network-cards .card ul a{ cursor: pointer; }
.extra-network-cards .card ul a:hover{ color: red; }
.extra-networks > div { margin: 0; border-bottom: none !important; }
.extra-networks .search { width: 150px; position: absolute; right: 0; height: unset !important; }
.extra-networks .description { margin-top: 8px; }
.extra-networks .tab-nav > button { margin-right: 0; height: auto; padding: 2px 4px 2px 4px; }
.extra-networks-tab { padding: 0 !important; }
.extra-network-subdirs { background: var(--input-background-fill); }
.extra-networks-page { display: flex }
.extra-networks .custom-button { min-width: 60px; width: 100%; background: none; justify-content: left; padding: 2px 8px 2px 8px; box-shadow: none; }
.extra-networks .custom-button:hover { background: var(--button-primary-background-fill) }
.extra-network-cards { display: flex; flex-wrap: wrap; height: 50vh; overflow-y: scroll; overflow-x: hidden; scroll-snap-type: y mandatory; }
.extra-network-cards .card { height: fit-content; margin: 0.5em; position: relative; scroll-snap-align: start; scroll-margin-top: 0; }
.extra-network-cards .card .overlay { position: absolute; bottom: 0; padding: 0.2em; z-index: 10; width: 100%; background: none; }
.extra-network-cards .card:hover .overlay { background: rgba(0, 0, 0, 0.40); }
.extra-network-cards .card .overlay .name { font-size: 1.2em; font-weight: bold; line-break: anywhere; text-shadow: 1px 1px black; }
.extra-network-cards .card .overlay .actions { font-size: 1.8em; display: none; text-align-last: justify; cursor: pointer; word-spacing: -6px; }
.extra-network-cards .card:hover .overlay .actions { display: block; }
.extra-network-cards .card .overlay .description { line-break: anywhere; display: none; }
.extra-network-cards .card:hover .overlay .description { display: block; }
.extra-network-cards .card .preview { box-shadow: var(--button-shadow); min-height: 30px; }
.extra-network-cards .card:hover .preview { box-shadow: none; }
/* controlnet */
.controlnet_control_type .controlnet_control_type_filter_group .wrap:last-of-type { display: grid; grid-auto-flow: row; grid-template-columns: repeat(4, minmax(0, 1fr)); }
+5 -33
View File
@@ -38,31 +38,18 @@ class ExtraNetwork:
def activate(self, p, params_list):
"""
Called by processing on every run. Whatever the extra network is meant to do should be activated here.
Passes arguments related to this extra network in params_list.
User passes arguments by specifying this in his prompt:
Called by processing on every run. Whatever the extra network is meant to do should be activated here. Passes arguments related to this extra network in params_list. User passes arguments by specifying this in his prompt:
<name:arg1:arg2:arg3>
Where name matches the name of this ExtraNetwork object, and arg1:arg2:arg3 are any natural number of text arguments
separated by colon.
Even if the user does not mention this ExtraNetwork in his prompt, the call will stil be made, with empty params_list -
in this case, all effects of this extra networks should be disabled.
Where name matches the name of this ExtraNetwork object, and arg1:arg2:arg3 are any natural number of text arguments separated by colon.
Even if the user does not mention this ExtraNetwork in his prompt, the call will stil be made, with empty params_list - in this case, all effects of this extra networks should be disabled.
Can be called multiple times before deactivate() - each new call should override the previous call completely.
For example, if this ExtraNetwork's name is 'hypernet' and user's prompt is:
> "1girl, <hypernet:agm:1.1> <extrasupernet:master:12:13:14> <hypernet:ray>"
params_list will be:
[
ExtraNetworkParams(items=["agm", "1.1"]),
ExtraNetworkParams(items=["ray"])
]
"""
raise NotImplementedError
@@ -70,20 +57,16 @@ class ExtraNetwork:
"""
Called at the end of processing for housekeeping. No need to do anything here.
"""
raise NotImplementedError
def activate(p, extra_network_data):
"""call activate for extra networks in extra_network_data in specified order, then call
activate for all remaining registered networks with an empty argument list"""
"""call activate for extra networks in extra_network_data in specified order, then call activate for all remaining registered networks with an empty argument list"""
for extra_network_name, extra_network_args in extra_network_data.items():
extra_network = extra_network_registry.get(extra_network_name, None)
if extra_network is None:
print(f"Skipping unknown extra network: {extra_network_name}")
continue
try:
extra_network.activate(p, extra_network_args)
except Exception as e:
@@ -93,7 +76,6 @@ def activate(p, extra_network_data):
args = extra_network_data.get(extra_network_name, None)
if args is not None:
continue
try:
extra_network.activate(p, [])
except Exception as e:
@@ -101,14 +83,11 @@ def activate(p, extra_network_data):
def deactivate(p, extra_network_data):
"""call deactivate for extra networks in extra_network_data in specified order, then call
deactivate for all remaining registered networks"""
"""call deactivate for extra networks in extra_network_data in specified order, then call deactivate for all remaining registered networks"""
for extra_network_name in extra_network_data:
extra_network = extra_network_registry.get(extra_network_name, None)
if extra_network is None:
continue
try:
extra_network.deactivate(p)
except Exception as e:
@@ -118,7 +97,6 @@ def deactivate(p, extra_network_data):
args = extra_network_data.get(extra_network_name, None)
if args is not None:
continue
try:
extra_network.deactivate(p)
except Exception as e:
@@ -134,13 +112,9 @@ def parse_prompt(prompt):
def found(m):
name = m.group(1)
args = m.group(2)
res[name].append(ExtraNetworkParams(items=args.split(":")))
return ""
prompt = re.sub(re_extra_net, found, prompt)
return prompt, res
@@ -150,10 +124,8 @@ def parse_prompts(prompts):
for prompt in prompts:
updated_prompt, parsed_extra_data = parse_prompt(prompt)
if extra_data is None:
extra_data = parsed_extra_data
res.append(updated_prompt)
return res, extra_data
+5 -3
View File
@@ -105,11 +105,13 @@ def create_buttons(tabs_list):
for tab in tabs_list:
name = tab
if name == 'txt2img':
name = 'text'
name = 'Text'
elif name == 'img2img':
name = 'image'
name = 'Image'
elif name == 'inpaint':
name = 'Inpaint'
elif name == 'extras':
name = 'process'
name = 'Process'
buttons[tab] = gr.Button(f"{name}", elem_id=f"{tab}_tab")
return buttons
+5 -4
View File
@@ -519,14 +519,15 @@ options_templates.update(options_section(('upscaling', "Upscaling"), {
}))
options_templates.update(options_section(('extra_networks', "Extra Networks"), {
"extra_networks_card_cover": OptionInfo("inline", "UI position", gr.Radio, lambda: {"choices": ["cover", "inline", "sidebar"]}),
"extra_networks_card_size": OptionInfo(200, "UI card size (px)", gr.Slider, {"minimum": 20, "maximum": 2000, "step": 1}),
"extra_networks_card_square": OptionInfo(False, "UI disable variable aspect ratio"),
"extra_networks_card_fit": OptionInfo("cover", "UI image contain method", gr.Radio, lambda: {"choices": ["contain", "cover", "fill"]}),
"lyco_patch_lora": OptionInfo(False, "Use LyCoris handler for all Lora types", gr.Checkbox),
"lora_disable": OptionInfo(False, "Disable built-in Lora handler", gr.Checkbox, { "visible": True }, onchange=lora_disable),
"lora_functional": OptionInfo(False, "Use Kohya method for handling multiple Loras", gr.Checkbox),
"extra_networks_default_view": OptionInfo("cards", "Default view for Extra Networks", gr.Dropdown, {"choices": ["cards", "thumbs"]}),
"extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
"extra_networks_card_width": OptionInfo(0, "Card width for Extra Networks (px)"),
"extra_networks_card_height": OptionInfo(0, "Card height for Extra Networks (px)"),
"extra_networks_add_text_separator": OptionInfo(" ", "Extra text to add before <...> when adding extra network to prompt", gr.Text, { "visible": False }),
"extra_networks_default_multiplier": OptionInfo(1.0, "Multiplier for extra networks", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}),
"sd_hypernetwork": OptionInfo("None", "Add hypernetwork to prompt", gr.Dropdown, lambda: {"choices": ["None"] + list(hypernetworks.keys())}, refresh=reload_hypernetworks),
}))
+4 -4
View File
@@ -329,13 +329,13 @@ def create_ui():
modules.scripts.scripts_current = modules.scripts.scripts_txt2img
modules.scripts.scripts_txt2img.initialize_scripts(is_img2img=False)
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _, _, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=False)
txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, submit, _interrogate, _deepbooru, txt2img_prompt_style_apply, txt2img_save_style, txt2img_paste, extra_networks_button, token_counter, token_button, negative_token_counter, negative_token_button = create_toprow(is_img2img=False)
dummy_component = gr.Label(visible=False)
txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False)
with FormRow(variant='compact', elem_id="txt2img_extra_networks", visible=False) as extra_networks_ui:
from modules import ui_extra_networks
extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, extra_networks_button, 'txt2img')
with gr.Row().style(equal_height=False):
with gr.Row().style(equal_height=False, elem_id="txt2img_interface"):
with gr.Column(variant='compact', elem_id="txt2img_settings"):
for category in ordered_ui_categories():
if category == "sampler":
@@ -504,14 +504,14 @@ def create_ui():
from modules import ui_extra_networks
extra_networks_ui_img2img = ui_extra_networks.create_ui(extra_networks_ui, extra_networks_button, 'img2img')
with FormRow().style(equal_height=False):
with FormRow().style(equal_height=False, elem_id="img2img_interface"):
with gr.Column(variant='compact', elem_id="img2img_settings"):
copy_image_buttons = []
copy_image_destinations = {}
def add_copy_image_controls(tab_name, elem):
with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"):
for title, name in zip(['image', 'sketch', 'inpaint', 'inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']):
for title, name in zip(['Image', 'Sketch', 'Inpaint', 'Inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']):
if name == tab_name:
gr.Button(title, interactive=False)
copy_image_destinations[name] = elem
+4 -4
View File
@@ -164,10 +164,10 @@ def create_output_panel(tabname, outdir):
with gr.Column():
with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"):
open_folder_button = gr.Button('show', visible=not shared.cmd_opts.hide_ui_dir_config)
save = gr.Button('save', elem_id=f'save_{tabname}')
save_zip = gr.Button('zip', elem_id=f'save_zip_{tabname}')
delete = gr.Button('delete', elem_id=f'delete_{tabname}')
open_folder_button = gr.Button('Show', visible=not shared.cmd_opts.hide_ui_dir_config)
save = gr.Button('Save', elem_id=f'save_{tabname}')
save_zip = gr.Button('Zip', elem_id=f'save_zip_{tabname}')
delete = gr.Button('Delete', elem_id=f'delete_{tabname}')
buttons = parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"])
open_folder_button.click(fn=lambda: open_folder(shared.opts.outdir_samples or outdir), inputs=[], outputs=[])
+98 -63
View File
@@ -26,6 +26,8 @@ def register_page(page):
def fetch_file(filename: str = ""):
from starlette.responses import FileResponse, JSONResponse
if filename.startswith('html/'):
return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
if not any(Path(x).absolute() in Path(filename).absolute().parents for x in allowed_dirs):
return JSONResponse({"error": f"File cannot be fetched: {filename}. Must be in one of directories registered by extra pages."})
if os.path.splitext(filename)[1].lower() not in (".png", ".jpg", ".webp"):
@@ -37,28 +39,52 @@ def get_metadata(page: str = "", item: str = ""):
from starlette.responses import JSONResponse
page = next(iter([x for x in extra_pages if x.name == page]), None)
if page is None:
return JSONResponse({})
metadata = page.metadata.get(item)
if metadata is None:
return JSONResponse({})
return JSONResponse({ 'metadata': 'none' })
metadata = page.metadata.get(item, 'none')
return JSONResponse({"metadata": metadata})
def get_info(page: str = "", item: str = ""):
from starlette.responses import JSONResponse
page = next(iter([x for x in extra_pages if x.name == page]), None)
if page is None:
return JSONResponse({ 'info': 'none' })
info = page.info.get(item, 'none')
return JSONResponse({"info": info})
def add_pages_to_demo(app):
app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"])
app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"])
app.add_api_route("/sd_extra_networks/info", get_info, methods=["GET"])
class ExtraNetworksPage:
def __init__(self, title):
self.title = title
self.name = title.lower()
self.card_long = shared.html("extra-networks-card-long.html")
self.card_short = shared.html("extra-networks-card-short.html")
self.allow_negative_prompt = False
self.metadata = {}
self.info = {}
self.items = []
self.missing_thumbs = []
self.card = '''
<div class='card' onclick={card_click}>
<div class='overlay'>
<span style="display:none" class='search_term'>{search_term}</span>
<div class='name'>{name}</div>
<div class='description'>{description}</div>
<div class='actions'>
<span title="Save current image as preview image" onclick={card_save_preview}>💙</span>
<span title="Read description" onclick={card_read_desc}>📖</span>
<span title="Save current description" onclick={card_save_desc}>🛅</span>
<span title="Read metadata" onclick={card_read_meta}>📘</span>
<span title="Read info" onclick={card_read_info}></span>
</div>
</div>
<img class='preview' src='{preview}' style='width: {width}px; height: {height}px; object-fit: {fit}' loading='lazy'></img>
</div>
''' # noqa: RUF001
def refresh(self):
pass
@@ -93,8 +119,11 @@ class ExtraNetworksPage:
return ""
def is_empty(self, folder):
files = [f for f in os.listdir(folder) if f.lower().endswith(".ckpt") or f.lower().endswith(".safetensors") or f.lower().endswith(".pt")]
return len(files) == 0
for f in os.listdir(folder):
_fn, ext = os.path.splitext(f)
if ext.lower() in ['.ckpt', '.safetensors', '.pt'] or os.path.isdir(os.path.join(folder, f)):
return False
return True
def create_thumb(self):
created = 0
@@ -115,9 +144,7 @@ class ExtraNetworksPage:
self.missing_thumbs.clear()
def create_html(self, tabname):
view = shared.opts.extra_networks_default_view
items_html = ''
self.metadata = {}
subdirs = {}
allowed_folders = [os.path.abspath(x) for x in self.allowed_directories_for_previews()]
for parentdir in [*set(allowed_folders)]:
@@ -137,28 +164,27 @@ class ExtraNetworksPage:
subdirs_html = "".join([f"""
<button class='lg secondary gradio-button custom-button{" search-all" if subdir=="" else ""}' onclick='extraNetworksSearchButton("{tabname}_extra_tabs", event)'>
{html.escape(subdir) if subdir!="" else "all"}
</button>""" for subdir in subdirs])
try:
self.items = list(self.list_items())
self.create_xyz_grid()
for item in self.items:
metadata = item.get("metadata")
if metadata:
self.metadata[item["name"]] = metadata
items_html += self.create_html_for_item(item, tabname)
if items_html == '':
dirs = "".join([f"<li>{x}</li>" for x in self.allowed_directories_for_previews()])
items_html = shared.html("extra-networks-no-cards.html").format(dirs=dirs)
self_name_id = self.name.replace(" ", "_")
res = f"""
<div id='{tabname}_{self_name_id}_subdirs' class='extra-network-subdirs extra-network-subdirs-{view}'>{subdirs_html}</div>
<div id='{tabname}_{self_name_id}_cards' class='extra-network-{view}'>{items_html}</div>
"""
threading.Thread(target=self.create_thumb).start()
return res
except Exception as e:
shared.log.error(f'Extra networks page error: {e}')
return ''
</button><br>""" for subdir in subdirs])
# try:
self.items = list(self.list_items())
self.create_xyz_grid()
for item in self.items:
self.metadata[item["name"]] = item.get("metadata", {})
self.info[item["name"]] = self.find_info(item['filename'])
items_html += self.create_html_for_item(item, tabname)
if items_html == '':
dirs = "".join([f"<li>{x}</li>" for x in self.allowed_directories_for_previews()])
items_html = f'<div class="nocards">No models found: ${dirs}</div>'
self_name_id = self.name.replace(" ", "_")
res = f"""
<div id='{tabname}_{self_name_id}_subdirs' class='extra-network-subdirs'>{subdirs_html}</div>
<div id='{tabname}_{self_name_id}_cards' class='extra-network-cards'>{items_html}</div>
"""
threading.Thread(target=self.create_thumb).start()
return res
# except Exception as e:
# shared.log.error(f'Extra networks page error: {e}')
# return ''
def list_items(self):
raise NotImplementedError
@@ -168,35 +194,28 @@ class ExtraNetworksPage:
def create_html_for_item(self, item, tabname):
preview = item.get("preview", None)
onclick = item.get("onclick", None)
if onclick is None:
onclick = '"' + html.escape(f"""return cardClicked({json.dumps(tabname)}, {item["prompt"]}, {"true" if self.allow_negative_prompt else "false"})""") + '"'
height = f"height: {shared.opts.extra_networks_card_height}px;" if shared.opts.extra_networks_card_height else ''
width = f"width: {shared.opts.extra_networks_card_width}px;" if shared.opts.extra_networks_card_width else ''
background_image = f"background-image: url(\"{html.escape(preview)}\");" if preview else ''
args = {
"style": f"'{height}{width}{background_image}'",
"preview": html.escape(preview),
"width": shared.opts.extra_networks_card_size,
"height": shared.opts.extra_networks_card_size if shared.opts.extra_networks_card_square else 'auto',
"fit": shared.opts.extra_networks_card_fit,
"prompt": item.get("prompt", None),
"tabname": json.dumps(tabname),
"local_preview": json.dumps(item["local_preview"]),
"name": item["name"],
"description": (item.get("description") or ""),
"card_clicked": onclick,
"save_card_description": '"' + html.escape(f"""return saveCardDescription(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"',
"save_card_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"',
"read_card_description": '"' + html.escape(f"""return readCardDescription(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])}, {json.dumps(item.get("description", ""))}, {json.dumps(self.name)}, {json.dumps(item["name"])})""") + '"',
"search_term": item.get("search_term", ""),
"read_card_metadata": '"' + html.escape(f"""return readCardMetadata(event, {json.dumps(self.name)}, {json.dumps(item["name"])})""") + '"',
"card_click": item.get("onclick", '"' + html.escape(f"""return cardClicked({json.dumps(tabname)}, {item.get("prompt", None)}, {"true" if self.allow_negative_prompt else "false"})""") + '"'),
"card_save_desc": '"' + html.escape(f"""return saveCardDescription(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"',
"card_save_preview": '"' + html.escape(f"""return saveCardPreview(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])})""") + '"',
"card_read_desc": '"' + html.escape(f"""return readCardDescription(event, {json.dumps(tabname)}, {json.dumps(item["local_preview"])}, {json.dumps(item.get("description", ""))}, {json.dumps(self.name)}, {json.dumps(item["name"])})""") + '"',
"card_read_meta": '"' + html.escape(f"""return readCardMetadata(event, {json.dumps(self.name)}, {json.dumps(item["name"])})""") + '"',
"card_read_info": '"' + html.escape(f"""return readCardInformation(event, {json.dumps(self.name)}, {json.dumps(item["name"])})""") + '"',
}
if item.get("metadata"):
return self.card_long.format(**args)
else:
return self.card_short.format(**args)
self.card.format(**args)
return self.card.format(**args)
def find_preview(self, path):
"""
Find a preview PNG for a given path (without extension) and call link_preview on it.
"""
preview_extensions = ["jpg", "jpeg", "png", "webp", "tiff", "jp2"]
for file in sum([[f'{path}.thumb.{ext}'] for ext in preview_extensions], []): # use thumbnail if exists
if os.path.isfile(file):
@@ -205,12 +224,9 @@ class ExtraNetworksPage:
if os.path.isfile(file):
self.missing_thumbs.append(file)
return self.link_preview(file)
return None
return self.link_preview('html/card-no-preview.png')
def find_description(self, path):
"""
Find and read a description file for a given path (without extension).
"""
for file in [f"{path}.txt", f"{path}.description.txt"]:
try:
with open(file, "r", encoding="utf-8", errors="replace") as f:
@@ -219,6 +235,16 @@ class ExtraNetworksPage:
pass
return None
def find_info(self, path):
for file in [f"{path}.info", f"{path}.civitai.info"]:
try:
with open(file, "r", encoding="utf-8", errors="replace") as f:
return f.read()
except OSError:
pass
return None
def initialize():
extra_pages.clear()
@@ -242,19 +268,21 @@ class ExtraNetworksUi:
self.button_save_description = None
self.button_read_description = None
self.description_target_filename = None
self.description_input = None
self.description = None
self.tabname = None
self.search = None
def pages_in_preferred_order(pages):
def sort_extra_pages(pages):
tab_order = [x.lower().strip() for x in shared.opts.ui_extra_networks_tab_reorder.split(",")]
def tab_name_score(name):
name = name.lower()
for i, possible_match in enumerate(tab_order):
if possible_match in name:
return i
return len(pages)
tab_scores = {page.name: (tab_name_score(page.name), original_index) for original_index, page in enumerate(pages)}
return sorted(pages, key=lambda x: tab_scores[x.name])
@@ -262,21 +290,23 @@ def pages_in_preferred_order(pages):
def create_ui(container, button, tabname):
ui = ExtraNetworksUi()
ui.pages = []
ui.stored_extra_pages = pages_in_preferred_order(extra_pages.copy())
ui.stored_extra_pages = sort_extra_pages(extra_pages)
ui.tabname = tabname
with gr.Tabs(elem_id=tabname+"_extra_tabs"):
ui.search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=True)
ui.description_input = gr.TextArea('', show_label=False, elem_id=tabname+"_description_input", placeholder="Save/Replace Extra Network Description...", lines=2)
button_refresh = ToolButton(refresh_symbol, elem_id=tabname+"_extra_refresh")
button_close = ToolButton(close_symbol, elem_id=tabname+"_extra_close")
ui.search = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=True, elem_classes="textbox")
ui.description = gr.TextArea('', show_label=False, elem_id=tabname+"_description", placeholder="Save/Replace Extra Network Description...", lines=2, elem_classes="textbox")
ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False)
ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False)
ui.button_save_description = gr.Button('Save description', elem_id=tabname+"_save_description", visible=False)
ui.button_read_description = gr.Button('Read description', elem_id=tabname+"_read_description", visible=False)
ui.description_target_filename = gr.Textbox('Description save filename', elem_id=tabname+"_description_filename", visible=False)
for page in ui.stored_extra_pages:
with gr.Tab(page.title, id=page.title.lower().replace(" ", "_")):
page_elem = gr.HTML(page.create_html(ui.tabname))
with gr.Tab(page.title, id=page.title.lower().replace(" ", "_"), elem_classes="extra-networks-tab"):
page_elem = gr.HTML(page.create_html(ui.tabname), elem_id=tabname+page.name+"_extra_page", elem_classes="extra-networks-page")
page_elem.change(fn=lambda: None, _js=f'() => refreshExtraNetworks("{tabname}")', inputs=[], outputs=[])
ui.pages.append(page_elem)
@@ -322,6 +352,11 @@ def setup_ui(ui, gallery):
break
assert is_allowed, f'writing to {filename} is not allowed'
image.save(filename)
fn, _ext = os.path.splitext(filename)
thumb = fn + '.thumb.jpg'
if os.path.exists(thumb):
shared.log.debug(f'Extra network delete thumbnail: {thumb}')
os.remove(thumb)
shared.log.info(f'Extra network save preview: {filename}')
return [page.create_html(ui.tabname) for page in ui.stored_extra_pages]
@@ -348,6 +383,6 @@ def setup_ui(ui, gallery):
ui.button_save_description.click(
fn=save_description,
_js="function(x,y){return [x,y]}",
inputs=[ui.description_target_filename, ui.description_input],
inputs=[ui.description_target_filename, ui.description],
outputs=[*ui.pages]
)