diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b06a61f..41d131795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/TODO.md b/TODO.md index fa4d6e5f9..32ea90da2 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/extensions-builtin/sd-webui-agent-scheduler b/extensions-builtin/sd-webui-agent-scheduler index 9180642eb..8198489fd 160000 --- a/extensions-builtin/sd-webui-agent-scheduler +++ b/extensions-builtin/sd-webui-agent-scheduler @@ -1 +1 @@ -Subproject commit 9180642eb103706912fbfe7d46eb1e0fde1cfc78 +Subproject commit 8198489fd42883d3af119e219c320ba4853802c8 diff --git a/html/extra-networks-card-long.html b/html/extra-networks-card-long.html deleted file mode 100644 index e2e01a15c..000000000 --- a/html/extra-networks-card-long.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
-
- - -
- {name} - {description} -
-
diff --git a/html/extra-networks-card-short.html b/html/extra-networks-card-short.html deleted file mode 100644 index 3dc9e46a2..000000000 --- a/html/extra-networks-card-short.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-
- - -
- {name} - {description} -
-
diff --git a/html/extra-networks-no-cards.html b/html/extra-networks-no-cards.html deleted file mode 100644 index 389358d6c..000000000 --- a/html/extra-networks-no-cards.html +++ /dev/null @@ -1,8 +0,0 @@ -
-

Nothing here. Add some content to the following directories:

- - -
- diff --git a/html/footer.html b/html/footer.html deleted file mode 100644 index 3632621e9..000000000 --- a/html/footer.html +++ /dev/null @@ -1,13 +0,0 @@ -
- API -  •  - Github -  •  - Gradio -  •  - Reload UI -
-
-
-{versions} -
diff --git a/javascript/black-orange.css b/javascript/black-orange.css index bfc019f5f..79464fb6b 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -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 */ diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 34d699bbf..254bbac7d 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -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('&'); diff --git a/javascript/logMonitor.js b/javascript/logMonitor.js index f8718f0db..dce29c014 100644 --- a/javascript/logMonitor.js +++ b/javascript/logMonitor.js @@ -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'; diff --git a/javascript/style.css b/javascript/style.css index 3f0035073..fc1097f12 100644 --- a/javascript/style.css +++ b/javascript/style.css @@ -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)); } diff --git a/modules/extra_networks.py b/modules/extra_networks.py index 3a80fbe26..3793290ee 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -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: - - 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, " - 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 diff --git a/modules/generation_parameters_copypaste.py b/modules/generation_parameters_copypaste.py index 2150c3d96..bccc7c0fe 100644 --- a/modules/generation_parameters_copypaste.py +++ b/modules/generation_parameters_copypaste.py @@ -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 diff --git a/modules/lycoris b/modules/lycoris index 4c2001111..b3776a3ab 160000 --- a/modules/lycoris +++ b/modules/lycoris @@ -1 +1 @@ -Subproject commit 4c200111133938d037c39dcc7d8481e4c1eecd20 +Subproject commit b3776a3ab9a61e8aaa9ea71f3b2073b0f2135c4b diff --git a/modules/shared.py b/modules/shared.py index b467638ec..11a711001 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -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), })) diff --git a/modules/ui.py b/modules/ui.py index d90c12d1a..3edab8f2b 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -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 diff --git a/modules/ui_common.py b/modules/ui_common.py index a724e92df..ccd76024b 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -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=[]) diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 164b8f4d3..ded4f809f 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -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 = ''' +
+
+ +
{name}
+
{description}
+
+ 💙 + 📖 + 🛅 + 📘 + ℹ️ +
+
+ +
+ ''' # 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""" """ 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"
  • {x}
  • " 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""" -
    {subdirs_html}
    -
    {items_html}
    - """ - threading.Thread(target=self.create_thumb).start() - return res - except Exception as e: - shared.log.error(f'Extra networks page error: {e}') - return '' +
    """ 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"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) + items_html = f'
    No models found: ${dirs}
    ' + self_name_id = self.name.replace(" ", "_") + res = f""" +
    {subdirs_html}
    +
    {items_html}
    + """ + 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] )